Merge branch 'migration/net11'

# Conflicts:
#	docs/architecting-and-developing-containerized-and-microservice-based-net-applications-ebook-early-draft.pdf
#	src/Services/Ordering/Ordering.API/Startup.cs
This commit is contained in:
etomas 2017-02-14 16:16:42 +01:00
commit decb87e0c6
43 changed files with 434 additions and 270 deletions

View File

@ -47,7 +47,7 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "UnitTest", "test\Services\U
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{24CD3B53-141E-4A07-9B0D-796641E1CF78}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Identity", "Identity", "{24CD3B53-141E-4A07-9B0D-796641E1CF78}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Identity.API", "src\Services\Identity\eShopOnContainers.Identity\Identity.API.xproj", "{A579E108-5445-403D-A407-339AC4D1611B}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Identity.API", "src\Services\Identity\Identity.API\Identity.API.xproj", "{A579E108-5445-403D-A407-339AC4D1611B}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Authorization;
namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{ {
//NOTE: Right now this is a very chunky API, as the app evolves it is possible we would //TODO NOTE: Right now this is a very chunky API, as the app evolves it is possible we would
//want to make the actions more fine graned, add basket item as an action for example. //want to make the actions more fine graned, add basket item as an action for example.
//If this is the case we should also investigate changing the serialization format used for Redis, //If this is the case we should also investigate changing the serialization format used for Redis,
//using a HashSet instead of a simple string. //using a HashSet instead of a simple string.

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.0.1 FROM microsoft/aspnetcore:1.1
ENTRYPOINT ["dotnet", "Basket.API.dll"] ENTRYPOINT ["dotnet", "Basket.API.dll"]
ARG source=. ARG source=.
WORKDIR /app WORKDIR /app

View File

@ -18,7 +18,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Model
private ConnectionMultiplexer _redis; private ConnectionMultiplexer _redis;
public RedisBasketRepository(IOptions<BasketSettings> options, ILoggerFactory loggerFactory) public RedisBasketRepository(IOptionsSnapshot<BasketSettings> options, ILoggerFactory loggerFactory)
{ {
_settings = options.Value; _settings = options.Value;
_logger = loggerFactory.CreateLogger<RedisBasketRepository>(); _logger = loggerFactory.CreateLogger<RedisBasketRepository>();

View File

@ -44,7 +44,7 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API
//that cost to startup instead of having the first request pay the //that cost to startup instead of having the first request pay the
//penalty. //penalty.
services.AddSingleton<ConnectionMultiplexer>((sp) => { services.AddSingleton<ConnectionMultiplexer>((sp) => {
var config = sp.GetRequiredService<IOptions<BasketSettings>>().Value; var config = sp.GetRequiredService<IOptionsSnapshot<BasketSettings>>().Value;
var ips = Dns.GetHostAddressesAsync(config.ConnectionString).Result; var ips = Dns.GetHostAddressesAsync(config.ConnectionString).Result;
return ConnectionMultiplexer.Connect(ips.First().ToString()); return ConnectionMultiplexer.Connect(ips.First().ToString());
}); });

View File

@ -1,20 +1,20 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"System.Threading": "4.0.11", "System.Threading": "4.3.0",
"Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0", "Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"StackExchange.Redis": "1.1.608", "StackExchange.Redis": "1.1.608",
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"IdentityServer4.AccessTokenValidation": "1.0.1-rc3", "IdentityServer4.AccessTokenValidation": "1.0.1-rc3",
@ -24,10 +24,11 @@
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final" "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final"
}, },
"frameworks": { "frameworks": {
"netcoreapp1.0": { "netcoreapp1.1": {
"imports": [ "imports": [
"dotnet5.6", "netstandard1.6.1",
"portable-net45+win8" "dnxcore50",
"portable-net451+win8"
] ]
} }
}, },

View File

@ -1,27 +1,27 @@
 using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Microsoft.eShopOnContainers.Services.Catalog.API.Model;
using Microsoft.eShopOnContainers.Services.Catalog.API.ViewModel;
using Microsoft.Extensions.Options;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
{ {
using Extensions.Options;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure;
using Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewModel;
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
public class CatalogController : ControllerBase public class CatalogController : ControllerBase
{ {
private readonly CatalogContext _context; private readonly CatalogContext _context;
private readonly IOptions<Settings> _settings; private readonly IOptionsSnapshot<Settings> _settings;
public CatalogController(CatalogContext context, IOptions<Settings> settings) public CatalogController(CatalogContext context, IOptionsSnapshot<Settings> settings)
{ {
_context = context; _context = context;
_settings = settings; _settings = settings;
((DbContext)context).ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
} }
// GET api/v1/[controller]/items/[?pageSize=3&pageIndex=10] // GET api/v1/[controller]/items/[?pageSize=3&pageIndex=10]
@ -30,7 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
public async Task<IActionResult> Items(int pageSize = 10, int pageIndex = 0) public async Task<IActionResult> Items(int pageSize = 10, int pageIndex = 0)
{ {
var totalItems = await _context.CatalogItems var totalItems = await _context.CatalogItems
.LongCountAsync(); .LongCountAsync();
var itemsOnPage = await _context.CatalogItems var itemsOnPage = await _context.CatalogItems
.OrderBy(c=>c.Name) .OrderBy(c=>c.Name)

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.0.1 FROM microsoft/aspnetcore:1.1
WORKDIR /app WORKDIR /app
EXPOSE 80 EXPOSE 80
COPY . /app COPY . /app

View File

@ -38,8 +38,6 @@
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
services.AddSingleton<IConfiguration>(Configuration);
services.AddDbContext<CatalogContext>(c => services.AddDbContext<CatalogContext>(c =>
{ {
c.UseSqlServer(Configuration["ConnectionString"]); c.UseSqlServer(Configuration["ConnectionString"]);

View File

@ -1,47 +1,39 @@
{ {
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.1",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.0.1",
"Microsoft.AspNetCore.Diagnostics": "1.0.0",
"Microsoft.AspNetCore.Diagnostics.Abstractions": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0",
"Microsoft.EntityFrameworkCore": "1.0.1",
"Microsoft.EntityFrameworkCore.Relational": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.EntityFrameworkCore.Design": "1.0.0-preview2-final",
"Swashbuckle": "6.0.0-beta902"
},
"tools": {
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
}
},
"buildOptions": { "buildOptions": {
"emitEntryPoint": true, "emitEntryPoint": true,
"preserveCompilationContext": true, "preserveCompilationContext": true,
"debugType": "portable" "debugType": "portable"
}, },
"runtimeOptions": { "dependencies": {
"configProperties": { "Microsoft.NETCore.App": {
"System.GC.Server": true "version": "1.1.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Diagnostics.Abstractions": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.EntityFrameworkCore": "1.1.0",
"Microsoft.EntityFrameworkCore.Relational": "1.1.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
"Microsoft.EntityFrameworkCore.Design": "1.1.0",
"Swashbuckle": "6.0.0-beta902"
},
"frameworks": {
"netcoreapp1.1": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
]
} }
}, },
"publishOptions": { "publishOptions": {
@ -56,6 +48,14 @@
"Dockerfile" "Dockerfile"
] ]
}, },
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"scripts": {}, "scripts": {},
"tools": {
"Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final"
},
"userSecretsId": "aspnet-Catalog.API-20161122013618" "userSecretsId": "aspnet-Catalog.API-20161122013618"
} }

View File

@ -15,10 +15,10 @@ namespace IdentityServer4.Quickstart.UI.Controllers
public class HomeController : Controller public class HomeController : Controller
{ {
private readonly IIdentityServerInteractionService _interaction; private readonly IIdentityServerInteractionService _interaction;
private readonly IOptions<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IRedirectService _redirectSvc; private readonly IRedirectService _redirectSvc;
public HomeController(IIdentityServerInteractionService interaction, IOptions<AppSettings> settings,IRedirectService redirectSvc) public HomeController(IIdentityServerInteractionService interaction, IOptionsSnapshot<AppSettings> settings,IRedirectService redirectSvc)
{ {
_interaction = interaction; _interaction = interaction;
_settings = settings; _settings = settings;

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.0.1 FROM microsoft/aspnetcore:1.1
ENTRYPOINT ["dotnet", "Identity.API.dll"] ENTRYPOINT ["dotnet", "Identity.API.dll"]
ARG source=. ARG source=.
WORKDIR /app WORKDIR /app

View File

@ -3,59 +3,55 @@
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.1", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.0.0", "Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "1.1.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0", "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.0",
"Microsoft.AspNetCore.Razor.Tools": { "Microsoft.AspNetCore.Routing": "1.1.0",
"version": "1.0.0-preview2-final", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"type": "build" "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
}, "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.AspNetCore.Routing": "1.0.1", "Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.1",
"Microsoft.AspNetCore.StaticFiles": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer.Design": { "Microsoft.EntityFrameworkCore.SqlServer.Design": {
"version": "1.0.1", "version": "1.1.0",
"type": "build" "type": "build"
}, },
"Microsoft.EntityFrameworkCore.Tools": { "Microsoft.EntityFrameworkCore.Tools": {
"version": "1.0.0-preview2-final", "version": "1.1.0-preview4-final",
"type": "build" "type": "build"
}, },
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.1.0",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": { "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final", "version": "1.1.0-preview4-final",
"type": "build" "type": "build"
}, },
"Microsoft.VisualStudio.Web.CodeGenerators.Mvc": { "Microsoft.VisualStudio.Web.CodeGenerators.Mvc": {
"version": "1.0.0-preview2-final", "version": "1.1.0-preview4-final",
"type": "build" "type": "build"
}, },
"IdentityServer4.AspNetIdentity": "1.0.0-rc3", "IdentityServer4.AspNetIdentity": "1.0.0-rc3",
"IdentityServer4.EntityFramework": "1.0.0-rc3" "IdentityServer4.EntityFramework": "1.0.0-rc3",
}, },
"tools": { "tools": {
"BundlerMinifier.Core": "2.0.238", "BundlerMinifier.Core": "2.0.238",
"Microsoft.AspNetCore.Razor.Tools": "1.0.0-preview2-final", "Microsoft.AspNetCore.Razor.Tools": "1.1.0-preview4-final",
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final", "Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final", "Microsoft.EntityFrameworkCore.Tools": "1.1.0-preview4-final",
"Microsoft.Extensions.SecretManager.Tools": "1.0.0-preview2-final", "Microsoft.Extensions.SecretManager.Tools": "1.1.0-preview4-final",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": { "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final", "version": "1.1.0-preview4-final",
"imports": [ "imports": [
"portable-net45+win8" "portable-net45+win8"
] ]

View File

@ -31,7 +31,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands
public int CardTypeId { get; set; } public int CardTypeId { get; set; }
public string BuyerFullName { get; set; } public string BuyerIdentityGuid { get; set; }
public IEnumerable<OrderItemDTO> OrderItems => _orderItems; public IEnumerable<OrderItemDTO> OrderItems => _orderItems;

View File

@ -12,6 +12,7 @@
private readonly IBuyerRepository _buyerRepository; private readonly IBuyerRepository _buyerRepository;
private readonly IOrderRepository _orderRepository; private readonly IOrderRepository _orderRepository;
// Using DI to inject infrastructure persistence Repositories
public CreateOrderCommandHandler(IBuyerRepository buyerRepository, IOrderRepository orderRepository) public CreateOrderCommandHandler(IBuyerRepository buyerRepository, IOrderRepository orderRepository)
{ {
if (buyerRepository == null) if (buyerRepository == null)
@ -30,13 +31,16 @@
public async Task<bool> Handle(CreateOrderCommand message) public async Task<bool> Handle(CreateOrderCommand message)
{ {
//find buyer/payment or add a new one buyer/payment // Add/Update the Buyer AggregateRoot
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate
var buyer = await _buyerRepository.FindAsync(message.BuyerFullName); var buyer = await _buyerRepository.FindAsync(message.BuyerIdentityGuid);
if (buyer == null) if (buyer == null)
{ {
buyer = new Buyer(message.BuyerFullName); buyer = new Buyer(message.BuyerIdentityGuid);
} }
var payment = buyer.AddPaymentMethod(message.CardTypeId, var payment = buyer.AddPaymentMethod(message.CardTypeId,
@ -51,7 +55,10 @@
await _buyerRepository.UnitOfWork await _buyerRepository.UnitOfWork
.SaveChangesAsync(); .SaveChangesAsync();
//create order for buyer and payment method // Create the Order AggregateRoot
// DDD patterns comment: Add child entities and value-objects through the Order Aggregate-Root
// methods and constructor so validations, invariants and business logic
// make sure that consistency is preserved across the whole aggregate
var order = new Order(buyer.Id, payment.Id, new Address(message.Street, message.City, message.State, message.Country, message.ZipCode)); var order = new Order(buyer.Id, payment.Id, new Address(message.Street, message.City, message.State, message.Country, message.ZipCode));
@ -66,6 +73,7 @@
.SaveChangesAsync(); .SaveChangesAsync();
return result > 0; return result > 0;
} }
} }
} }

View File

@ -13,9 +13,9 @@
{ {
private string _connectionString = string.Empty; private string _connectionString = string.Empty;
public OrderQueries(IConfiguration configuration) public OrderQueries(string constr)
{ {
_connectionString = configuration["ConnectionString"]; _connectionString = constr;
} }
@ -28,8 +28,9 @@
var result = await connection.QueryAsync<dynamic>( var result = await connection.QueryAsync<dynamic>(
@"select o.[Id] as ordernumber,o.OrderDate as date, os.Name as status, @"select o.[Id] as ordernumber,o.OrderDate as date, os.Name as status,
oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl, oi.ProductName as productname, oi.Units as units, oi.UnitPrice as unitprice, oi.PictureUrl as pictureurl,
o.Street as street, o.City as city, o.Country as country, o.State as state, o.ZipCode as zipcode a.Street as street, a.City as city, a.Country as country, a.State as state, a.ZipCode as zipcode
FROM ordering.Orders o FROM ordering.Orders o
INNER JOIN ordering.Address a ON o.AddressId = a.Id
LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid LEFT JOIN ordering.Orderitems oi ON o.Id = oi.orderid
LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id LEFT JOIN ordering.orderstatus os on o.OrderStatusId = os.Id
WHERE o.Id=@id" WHERE o.Id=@id"

View File

@ -11,7 +11,7 @@ using System.Threading.Tasks;
namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
{ {
[Route("api/v1/[controller]")] [Route("api/v1/[controller]")]
[Authorize] //[Authorize]
public class OrdersController : Controller public class OrdersController : Controller
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
@ -42,17 +42,17 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Controllers
[Route("new")] [Route("new")]
[HttpPost] [HttpPost]
public async Task<IActionResult> AddOrder([FromBody]CreateOrderCommand createOrderCommand) public async Task<IActionResult> CreateOrder([FromBody]CreateOrderCommand createOrderCommand)
{ {
if (createOrderCommand.CardTypeId == 0) if (createOrderCommand.CardTypeId == 0)
{ {
createOrderCommand.CardTypeId = 1; createOrderCommand.CardTypeId = 1;
} }
createOrderCommand.BuyerFullName = _identityService.GetUserIdentity(); createOrderCommand.BuyerIdentityGuid = _identityService.GetUserIdentity();
var added = await _mediator.SendAsync(createOrderCommand); var result = await _mediator.SendAsync(createOrderCommand);
if (added) if (result)
{ {
return Ok(); return Ok();
} }

View File

@ -10,9 +10,19 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Autof
public class ApplicationModule public class ApplicationModule
:Autofac.Module :Autofac.Module
{ {
public string QueriesConnectionString { get; }
public ApplicationModule(string qconstr)
{
QueriesConnectionString = qconstr;
}
protected override void Load(ContainerBuilder builder) protected override void Load(ContainerBuilder builder)
{ {
builder.RegisterType<OrderQueries>()
builder.Register(c => new OrderQueries(QueriesConnectionString))
.As<IOrderQueries>() .As<IOrderQueries>()
.InstancePerLifetimeScope(); .InstancePerLifetimeScope();

View File

@ -8,7 +8,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure;
namespace Ordering.API.Migrations namespace Ordering.API.Migrations
{ {
[DbContext(typeof(OrderingContext))] [DbContext(typeof(OrderingContext))]
[Migration("20170127103457_Initial")] [Migration("20170208181933_Initial")]
partial class Initial partial class Initial
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -29,13 +29,13 @@ namespace Ordering.API.Migrations
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("FullName") b.Property<string>("IdentityGuid")
.IsRequired() .IsRequired()
.HasMaxLength(200); .HasMaxLength(200);
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("FullName") b.HasIndex("IdentityGuid")
.IsUnique(); .IsUnique();
b.ToTable("buyers","ordering"); b.ToTable("buyers","ordering");
@ -90,6 +90,26 @@ namespace Ordering.API.Migrations
b.ToTable("paymentmethods","ordering"); b.ToTable("paymentmethods","ordering");
}); });
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -98,31 +118,20 @@ namespace Ordering.API.Migrations
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int>("BuyerId"); b.Property<int>("BuyerId");
b.Property<string>("City")
.IsRequired();
b.Property<string>("Country")
.IsRequired();
b.Property<DateTime>("OrderDate"); b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId"); b.Property<int>("OrderStatusId");
b.Property<int>("PaymentMethodId"); b.Property<int>("PaymentMethodId");
b.Property<string>("State")
.IsRequired();
b.Property<string>("Street")
.IsRequired();
b.Property<string>("ZipCode")
.IsRequired();
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AddressId");
b.HasIndex("BuyerId"); b.HasIndex("BuyerId");
b.HasIndex("OrderStatusId"); b.HasIndex("OrderStatusId");
@ -190,6 +199,10 @@ namespace Ordering.API.Migrations
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{ {
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
.WithMany()
.HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer") b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
.WithMany() .WithMany()
.HasForeignKey("BuyerId") .HasForeignKey("BuyerId")

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Ordering.API.Migrations namespace Ordering.API.Migrations
{ {
@ -36,7 +37,7 @@ namespace Ordering.API.Migrations
columns: table => new columns: table => new
{ {
Id = table.Column<int>(nullable: false), Id = table.Column<int>(nullable: false),
FullName = table.Column<string>(maxLength: 200, nullable: false) IdentityGuid = table.Column<string>(maxLength: 200, nullable: false)
}, },
constraints: table => constraints: table =>
{ {
@ -56,6 +57,24 @@ namespace Ordering.API.Migrations
table.PrimaryKey("PK_cardtypes", x => x.Id); table.PrimaryKey("PK_cardtypes", x => x.Id);
}); });
migrationBuilder.CreateTable(
name: "address",
schema: "ordering",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
City = table.Column<string>(nullable: true),
Country = table.Column<string>(nullable: true),
State = table.Column<string>(nullable: true),
Street = table.Column<string>(nullable: true),
ZipCode = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_address", x => x.Id);
});
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "orderstatus", name: "orderstatus",
schema: "ordering", schema: "ordering",
@ -107,19 +126,22 @@ namespace Ordering.API.Migrations
columns: table => new columns: table => new
{ {
Id = table.Column<int>(nullable: false), Id = table.Column<int>(nullable: false),
AddressId = table.Column<int>(nullable: true),
BuyerId = table.Column<int>(nullable: false), BuyerId = table.Column<int>(nullable: false),
City = table.Column<string>(nullable: false),
Country = table.Column<string>(nullable: false),
OrderDate = table.Column<DateTime>(nullable: false), OrderDate = table.Column<DateTime>(nullable: false),
OrderStatusId = table.Column<int>(nullable: false), OrderStatusId = table.Column<int>(nullable: false),
PaymentMethodId = table.Column<int>(nullable: false), PaymentMethodId = table.Column<int>(nullable: false)
State = table.Column<string>(nullable: false),
Street = table.Column<string>(nullable: false),
ZipCode = table.Column<string>(nullable: false)
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_orders", x => x.Id); table.PrimaryKey("PK_orders", x => x.Id);
table.ForeignKey(
name: "FK_orders_address_AddressId",
column: x => x.AddressId,
principalSchema: "ordering",
principalTable: "address",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey( table.ForeignKey(
name: "FK_orders_buyers_BuyerId", name: "FK_orders_buyers_BuyerId",
column: x => x.BuyerId, column: x => x.BuyerId,
@ -170,10 +192,10 @@ namespace Ordering.API.Migrations
}); });
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_buyers_FullName", name: "IX_buyers_IdentityGuid",
schema: "ordering", schema: "ordering",
table: "buyers", table: "buyers",
column: "FullName", column: "IdentityGuid",
unique: true); unique: true);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
@ -188,6 +210,12 @@ namespace Ordering.API.Migrations
table: "paymentmethods", table: "paymentmethods",
column: "CardTypeId"); column: "CardTypeId");
migrationBuilder.CreateIndex(
name: "IX_orders_AddressId",
schema: "ordering",
table: "orders",
column: "AddressId");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_orders_BuyerId", name: "IX_orders_BuyerId",
schema: "ordering", schema: "ordering",
@ -223,6 +251,10 @@ namespace Ordering.API.Migrations
name: "orders", name: "orders",
schema: "ordering"); schema: "ordering");
migrationBuilder.DropTable(
name: "address",
schema: "ordering");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "orderstatus", name: "orderstatus",
schema: "ordering"); schema: "ordering");

View File

@ -28,13 +28,13 @@ namespace Ordering.API.Migrations
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<string>("FullName") b.Property<string>("IdentityGuid")
.IsRequired() .IsRequired()
.HasMaxLength(200); .HasMaxLength(200);
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("FullName") b.HasIndex("IdentityGuid")
.IsUnique(); .IsUnique();
b.ToTable("buyers","ordering"); b.ToTable("buyers","ordering");
@ -89,6 +89,26 @@ namespace Ordering.API.Migrations
b.ToTable("paymentmethods","ordering"); b.ToTable("paymentmethods","ordering");
}); });
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("City");
b.Property<string>("Country");
b.Property<string>("State");
b.Property<string>("Street");
b.Property<string>("ZipCode");
b.HasKey("Id");
b.ToTable("address","ordering");
});
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{ {
b.Property<int>("Id") b.Property<int>("Id")
@ -97,31 +117,20 @@ namespace Ordering.API.Migrations
.HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering") .HasAnnotation("SqlServer:HiLoSequenceSchema", "ordering")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo); .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.SequenceHiLo);
b.Property<int?>("AddressId");
b.Property<int>("BuyerId"); b.Property<int>("BuyerId");
b.Property<string>("City")
.IsRequired();
b.Property<string>("Country")
.IsRequired();
b.Property<DateTime>("OrderDate"); b.Property<DateTime>("OrderDate");
b.Property<int>("OrderStatusId"); b.Property<int>("OrderStatusId");
b.Property<int>("PaymentMethodId"); b.Property<int>("PaymentMethodId");
b.Property<string>("State")
.IsRequired();
b.Property<string>("Street")
.IsRequired();
b.Property<string>("ZipCode")
.IsRequired();
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("AddressId");
b.HasIndex("BuyerId"); b.HasIndex("BuyerId");
b.HasIndex("OrderStatusId"); b.HasIndex("OrderStatusId");
@ -189,6 +198,10 @@ namespace Ordering.API.Migrations
modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b => modelBuilder.Entity("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Order", b =>
{ {
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate.Address", "Address")
.WithMany()
.HasForeignKey("AddressId");
b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer") b.HasOne("Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.BuyerAggregate.Buyer", "Buyer")
.WithMany() .WithMany()
.HasForeignKey("BuyerId") .HasForeignKey("BuyerId")

View File

@ -81,7 +81,6 @@
// Add application services. // Add application services.
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>(); services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IConfiguration>(this.Configuration);
services.AddTransient<IIdentityService,IdentityService>(); services.AddTransient<IIdentityService,IdentityService>();
services.AddOptions(); services.AddOptions();
@ -92,7 +91,7 @@
container.Populate(services); container.Populate(services);
container.RegisterModule(new MediatorModule()); container.RegisterModule(new MediatorModule());
container.RegisterModule(new ApplicationModule()); container.RegisterModule(new ApplicationModule(Configuration["ConnectionString"] ));
return new AutofacServiceProvider(container.Build()); return new AutofacServiceProvider(container.Build());
} }

View File

@ -8,11 +8,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
public class Buyer public class Buyer
: Entity, IAggregateRoot : Entity, IAggregateRoot
{ {
public string FullName { get; private set; } public string IdentityGuid { get; private set; }
private HashSet<PaymentMethod> _paymentMethods; private List<PaymentMethod> _paymentMethods;
public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods?.ToList().AsEnumerable(); public IEnumerable<PaymentMethod> PaymentMethods => _paymentMethods?.AsReadOnly();
protected Buyer() { } protected Buyer() { }
@ -23,9 +23,9 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
throw new ArgumentNullException(nameof(identity)); throw new ArgumentNullException(nameof(identity));
} }
FullName = identity; IdentityGuid = identity;
_paymentMethods = new HashSet<PaymentMethod>(); _paymentMethods = new List<PaymentMethod>();
} }
public PaymentMethod AddPaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration) public PaymentMethod AddPaymentMethod(int cardTypeId, string alias, string cardNumber, string securityNumber, string cardHolderName, DateTime expiration)

View File

@ -7,7 +7,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.B
//This is just the RepositoryContracts or Interface defined at the Domain Layer //This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Buyer Aggregate //as requisite for the Buyer Aggregate
public interface IBuyerRepository public interface IBuyerRepository
:IRepository :IAggregateRepository
{ {
Buyer Add(Buyer buyer); Buyer Add(Buyer buyer);

View File

@ -1,18 +1,21 @@
using System; using Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork;
using System;
using System.Collections.Generic;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.OrderAggregate
{ {
public class Address public class Address
:ValueObject
{ {
public String Street { get; } public String Street { get; private set; }
public String City { get; } public String City { get; private set; }
public String State { get; } public String State { get; private set; }
public String Country { get; } public String Country { get; private set; }
public String ZipCode { get; } public String ZipCode { get; private set; }
public Address(string street, string city, string state, string country, string zipcode) public Address(string street, string city, string state, string country, string zipcode)
{ {
@ -22,5 +25,14 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
Country = country; Country = country;
ZipCode = zipcode; ZipCode = zipcode;
} }
protected override IEnumerable<object> GetAtomicValues()
{
yield return Street;
yield return City;
yield return State;
yield return Country;
yield return ZipCode;
}
} }
} }

View File

@ -5,7 +5,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
//This is just the RepositoryContracts or Interface defined at the Domain Layer //This is just the RepositoryContracts or Interface defined at the Domain Layer
//as requisite for the Order Aggregate //as requisite for the Order Aggregate
public interface IOrderRepository public interface IOrderRepository
:IRepository :IAggregateRepository
{ {
Order Add(Order order); Order Add(Order order);
} }

View File

@ -9,44 +9,51 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public class Order public class Order
: Entity : Entity
{ {
private string _street; // DDD Patterns comment
private string _city; // Using private fields, allowed since EF Core 1.1, is a much better encapsulation
private string _state; // aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private string _country;
private string _zipCode;
private DateTime _orderDate; private DateTime _orderDate;
public Address Address { get; private set; }
public Buyer Buyer { get; private set; } public Buyer Buyer { get; private set; }
int _buyerId; private int _buyerId;
public OrderStatus OrderStatus { get; private set; } public OrderStatus OrderStatus { get; private set; }
int _orderStatusId; private int _orderStatusId;
HashSet<OrderItem> _orderItems; // DDD Patterns comment
public IEnumerable<OrderItem> OrderItems => _orderItems.ToList().AsEnumerable(); // Using a private collection field, better for DDD Aggregate's encapsulation
// so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
// but only through the method OrderAggrergateRoot.AddOrderItem() which includes behaviour.
private readonly List<OrderItem> _orderItems;
public IEnumerable<OrderItem> OrderItems => _orderItems.AsReadOnly();
// Using List<>.AsReadOnly()
// This will create a read only wrapper around the private list so is protected against "external updates".
// It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
//https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx
public PaymentMethod PaymentMethod { get; private set; } public PaymentMethod PaymentMethod { get; private set; }
int _paymentMethodId; private int _paymentMethodId;
protected Order() { } protected Order() { }
public Order(int buyerId, int paymentMethodId, Address address) public Order(int buyerId, int paymentMethodId, Address address)
{ {
_orderItems = new List<OrderItem>();
_buyerId = buyerId; _buyerId = buyerId;
_paymentMethodId = paymentMethodId; _paymentMethodId = paymentMethodId;
_orderStatusId = OrderStatus.InProcess.Id; _orderStatusId = OrderStatus.InProcess.Id;
_orderDate = DateTime.UtcNow; _orderDate = DateTime.UtcNow;
_street = address.Street;
_city = address.City;
_state = address.State;
_country = address.Country;
_zipCode = address.ZipCode;
_orderItems = new HashSet<OrderItem>(); Address = address;
} }
// DDD Patterns comment
// This Order AggregateRoot's method "AddOrderitem()" should be the only way to add Items to the Order,
// so any behavior (discounts, etc.) and validations are controlled by the AggregateRoot
// in order to maintain consistency between the whole Aggregate.
public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1) public void AddOrderItem(int productId, string productName, decimal unitPrice, decimal discount, string pictureUrl, int units = 1)
{ {
var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId) var existingOrderForProduct = _orderItems.Where(o => o.ProductId == productId)

View File

@ -6,16 +6,18 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.AggregatesModel.O
public class OrderItem public class OrderItem
: Entity : Entity
{ {
private string _productName; // DDD Patterns comment
private string _pictureUrl; // Using private fields, allowed since EF Core 1.1, is a much better encapsulation
private int _orderId; // aligned with DDD Aggregates and Domain Entities (Instead of properties and property collections)
private string _productName;
private string _pictureUrl;
private int _orderId;
private decimal _unitPrice; private decimal _unitPrice;
private decimal _discount; private decimal _discount;
private int _units; private int _units;
public int ProductId { get; private set; } public int ProductId { get; private set; }
protected OrderItem() { } protected OrderItem() { }
public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1) public OrderItem(int productId, string productName, decimal unitPrice, decimal discount, string PictureUrl, int units = 1)

View File

@ -1,6 +1,6 @@
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.Seedwork
{ {
public interface IRepository public interface IAggregateRepository
{ {
IUnitOfWork UnitOfWork { get; } IUnitOfWork UnitOfWork { get; }
} }

View File

@ -0,0 +1,63 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.eShopOnContainers.Services.Ordering.Domain.SeedWork
{
public abstract class ValueObject
{
protected static bool EqualOperator(ValueObject left, ValueObject right)
{
if (ReferenceEquals(left, null) ^ ReferenceEquals(right, null))
{
return false;
}
return ReferenceEquals(left, null) || left.Equals(right);
}
protected static bool NotEqualOperator(ValueObject left, ValueObject right)
{
return !(EqualOperator(left, right));
}
protected abstract IEnumerable<object> GetAtomicValues();
public override bool Equals(object obj)
{
if (obj == null || obj.GetType() != GetType())
{
return false;
}
ValueObject other = (ValueObject)obj;
IEnumerator<object> thisValues = GetAtomicValues().GetEnumerator();
IEnumerator<object> otherValues = other.GetAtomicValues().GetEnumerator();
while (thisValues.MoveNext() && otherValues.MoveNext())
{
if (ReferenceEquals(thisValues.Current, null) ^ ReferenceEquals(otherValues.Current, null))
{
return false;
}
if (thisValues.Current != null && !thisValues.Current.Equals(otherValues.Current))
{
return false;
}
}
return !thisValues.MoveNext() && !otherValues.MoveNext();
}
public override int GetHashCode()
{
return GetAtomicValues()
.Select(x => x != null ? x.GetHashCode() : 0)
.Aggregate((x, y) => x ^ y);
}
public ValueObject GetCopy()
{
return this.MemberwiseClone() as ValueObject;
}
}
}

View File

@ -30,6 +30,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
protected override void OnModelCreating(ModelBuilder modelBuilder) protected override void OnModelCreating(ModelBuilder modelBuilder)
{ {
modelBuilder.Entity<Address>(ConfigureAddress);
modelBuilder.Entity<Buyer>(ConfigureBuyer); modelBuilder.Entity<Buyer>(ConfigureBuyer);
modelBuilder.Entity<PaymentMethod>(ConfigurePayment); modelBuilder.Entity<PaymentMethod>(ConfigurePayment);
modelBuilder.Entity<Order>(ConfigureOrder); modelBuilder.Entity<Order>(ConfigureOrder);
@ -38,6 +39,16 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
modelBuilder.Entity<OrderStatus>(ConfigureOrderStatus); modelBuilder.Entity<OrderStatus>(ConfigureOrderStatus);
} }
void ConfigureAddress(EntityTypeBuilder<Address> addressConfiguration)
{
addressConfiguration.ToTable("address", DEFAULT_SCHEMA);
addressConfiguration.Property<int>("Id")
.IsRequired();
addressConfiguration.HasKey("Id");
}
void ConfigureBuyer(EntityTypeBuilder<Buyer> buyerConfiguration) void ConfigureBuyer(EntityTypeBuilder<Buyer> buyerConfiguration)
{ {
buyerConfiguration.ToTable("buyers", DEFAULT_SCHEMA); buyerConfiguration.ToTable("buyers", DEFAULT_SCHEMA);
@ -47,11 +58,11 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
buyerConfiguration.Property(b => b.Id) buyerConfiguration.Property(b => b.Id)
.ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA); .ForSqlServerUseSequenceHiLo("buyerseq", DEFAULT_SCHEMA);
buyerConfiguration.Property(b=>b.FullName) buyerConfiguration.Property(b=>b.IdentityGuid)
.HasMaxLength(200) .HasMaxLength(200)
.IsRequired(); .IsRequired();
buyerConfiguration.HasIndex("FullName") buyerConfiguration.HasIndex("IdentityGuid")
.IsUnique(true); .IsUnique(true);
buyerConfiguration.HasMany(b => b.PaymentMethods) buyerConfiguration.HasMany(b => b.PaymentMethods)
@ -109,17 +120,13 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure
.ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA); .ForSqlServerUseSequenceHiLo("orderseq", DEFAULT_SCHEMA);
orderConfiguration.Property<DateTime>("OrderDate").IsRequired(); orderConfiguration.Property<DateTime>("OrderDate").IsRequired();
orderConfiguration.Property<string>("Street").IsRequired();
orderConfiguration.Property<string>("State").IsRequired();
orderConfiguration.Property<string>("City").IsRequired();
orderConfiguration.Property<string>("ZipCode").IsRequired();
orderConfiguration.Property<string>("Country").IsRequired();
orderConfiguration.Property<int>("BuyerId").IsRequired(); orderConfiguration.Property<int>("BuyerId").IsRequired();
orderConfiguration.Property<int>("OrderStatusId").IsRequired(); orderConfiguration.Property<int>("OrderStatusId").IsRequired();
orderConfiguration.Property<int>("PaymentMethodId").IsRequired(); orderConfiguration.Property<int>("PaymentMethodId").IsRequired();
var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems)); var navigation = orderConfiguration.Metadata.FindNavigation(nameof(Order.OrderItems));
// DDD Patterns comment:
//Set as Field (New since EF 1.1) to access the OrderItem collection property through its field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field); navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
orderConfiguration.HasOne(o => o.PaymentMethod) orderConfiguration.HasOne(o => o.PaymentMethod)

View File

@ -49,7 +49,7 @@ namespace Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Repositor
{ {
var buyer = await _context.Buyers var buyer = await _context.Buyers
.Include(b => b.PaymentMethods) .Include(b => b.PaymentMethods)
.Where(b => b.FullName == identity) .Where(b => b.IdentityGuid == identity)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
return buyer; return buyer;

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.0.1 FROM microsoft/aspnetcore:1.1
ENTRYPOINT ["dotnet", "WebMVC.dll"] ENTRYPOINT ["dotnet", "WebMVC.dll"]
ARG source=. ARG source=.
WORKDIR /app WORKDIR /app

View File

@ -14,12 +14,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class BasketService : IBasketService public class BasketService : IBasketService
{ {
private readonly IOptions<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
private HttpClient _apiClient; private HttpClient _apiClient;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
private IHttpContextAccessor _httpContextAccesor; private IHttpContextAccessor _httpContextAccesor;
public BasketService(IOptions<AppSettings> settings, IHttpContextAccessor httpContextAccesor) public BasketService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor)
{ {
_settings = settings; _settings = settings;
_remoteServiceBaseUrl = _settings.Value.BasketUrl; _remoteServiceBaseUrl = _settings.Value.BasketUrl;

View File

@ -15,11 +15,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public class CatalogService : ICatalogService public class CatalogService : ICatalogService
{ {
private readonly IOptions<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
private HttpClient _apiClient; private HttpClient _apiClient;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
public CatalogService(IOptions<AppSettings> settings, ILoggerFactory loggerFactory) { public CatalogService(IOptionsSnapshot<AppSettings> settings, ILoggerFactory loggerFactory) {
_settings = settings; _settings = settings;
_remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/"; _remoteServiceBaseUrl = $"{_settings.Value.CatalogUrl}/api/v1/catalog/";

View File

@ -15,10 +15,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
private HttpClient _apiClient; private HttpClient _apiClient;
private readonly string _remoteServiceBaseUrl; private readonly string _remoteServiceBaseUrl;
private readonly IOptions<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
private readonly IHttpContextAccessor _httpContextAccesor; private readonly IHttpContextAccessor _httpContextAccesor;
public OrderingService(IOptions<AppSettings> settings, IHttpContextAccessor httpContextAccesor) public OrderingService(IOptionsSnapshot<AppSettings> settings, IHttpContextAccessor httpContextAccesor)
{ {
_remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders"; _remoteServiceBaseUrl = $"{settings.Value.OrderingUrl}/api/v1/orders";
_settings = settings; _settings = settings;

View File

@ -25,7 +25,7 @@
<section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section> <section class="esh-orders-item col-xs-4">@Html.DisplayFor(modelItem => item.Date)</section>
<section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section> <section class="esh-orders-item col-xs-2">$ @Html.DisplayFor(modelItem => item.Total)</section>
<section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section> <section class="esh-orders-item col-xs-2">@Html.DisplayFor(modelItem => item.Status)</section>
<section class="esh-orders-item esh-orders-item--hover col-xs-2"> <section class="esh-orders-item col-xs-2">
<a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a> <a class="esh-orders-link" asp-controller="Order" asp-action="Detail" asp-route-orderId="@item.OrderNumber">Detail</a>
</section> </section>
</article> </article>

View File

@ -2,27 +2,27 @@
"userSecretsId": "aspnet-Microsoft.eShopOnContainers-946ae052-8305-4a99-965b-ec8636ddbae3", "userSecretsId": "aspnet-Microsoft.eShopOnContainers-946ae052-8305-4a99-965b-ec8636ddbae3",
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.0", "Microsoft.AspNetCore.Mvc": "1.1.1",
"Microsoft.AspNetCore.Razor.Tools": { "Microsoft.AspNetCore.Razor.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.0.0", "Microsoft.VisualStudio.Web.BrowserLink.Loader": "14.1.0",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": { "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
@ -32,10 +32,11 @@
"type": "build" "type": "build"
}, },
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"System.IdentityModel.Tokens.Jwt": "5.0.0", "System.IdentityModel.Tokens.Jwt": "5.1.0",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.0", "Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.1.0",
"Microsoft.AspNetCore.Authentication.JwtBearer": "1.0.0", "Microsoft.AspNetCore.Authentication.JwtBearer": "1.1.0",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.0.0" "Microsoft.AspNetCore.Identity.EntityFrameworkCore": "1.1.0",
"Microsoft.Extensions.Options": "1.1.0"
}, },
"tools": { "tools": {
"BundlerMinifier.Core": "2.0.238", "BundlerMinifier.Core": "2.0.238",

View File

@ -16,7 +16,7 @@
<section class="esh-orders-item col-xs-4">{{order.date | date:'short'}}</section> <section class="esh-orders-item col-xs-4">{{order.date | date:'short'}}</section>
<section class="esh-orders-item col-xs-2">$ {{order.total}}</section> <section class="esh-orders-item col-xs-2">$ {{order.total}}</section>
<section class="esh-orders-item col-xs-2">{{order.status}}</section> <section class="esh-orders-item col-xs-2">{{order.status}}</section>
<section class="esh-orders-item esh-orders-item--hover col-xs-2"> <section class="esh-orders-item col-xs-2">
<a class="esh-orders-link" routerLink="/orders/{{order.ordernumber}}">Detail</a> <a class="esh-orders-link" routerLink="/orders/{{order.ordernumber}}">Detail</a>
</section> </section>
</article> </article>

View File

@ -1,4 +1,4 @@
FROM microsoft/aspnetcore:1.0.1 FROM microsoft/aspnetcore:1.1
ENTRYPOINT ["dotnet", "eShopOnContainers.WebSPA.dll"] ENTRYPOINT ["dotnet", "eShopOnContainers.WebSPA.dll"]
ARG source=. ARG source=.
WORKDIR /app WORKDIR /app

View File

@ -11,9 +11,9 @@ namespace eShopConContainers.WebSPA.Server.Controllers
public class HomeController : Controller public class HomeController : Controller
{ {
private readonly IHostingEnvironment _env; private readonly IHostingEnvironment _env;
private readonly IOptions<AppSettings> _settings; private readonly IOptionsSnapshot<AppSettings> _settings;
public HomeController(IHostingEnvironment env, IOptions<AppSettings> settings) public HomeController(IHostingEnvironment env, IOptionsSnapshot<AppSettings> settings)
{ {
_env = env; _env = env;
_settings = settings; _settings = settings;

View File

@ -2,16 +2,16 @@
"userSecretsId": "aspnetcorespa-c23d27a4-eb88-4b18-9b77-2a93f3b15119", "userSecretsId": "aspnetcorespa-c23d27a4-eb88-4b18-9b77-2a93f3b15119",
"dependencies": { "dependencies": {
"Microsoft.NETCore.App": { "Microsoft.NETCore.App": {
"version": "1.0.0", "version": "1.1.0",
"type": "platform" "type": "platform"
}, },
"Microsoft.Extensions.Configuration.UserSecrets": "1.0.0", "Microsoft.Extensions.Configuration.UserSecrets": "1.1.0",
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.0", "Microsoft.AspNetCore.Authentication.Cookies": "1.1.0",
"Microsoft.AspNetCore.Diagnostics": "1.0.0", "Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.Mvc": "1.0.1", "Microsoft.AspNetCore.Mvc": "1.1.1",
"Microsoft.AspNetCore.Cors": "1.0.0", "Microsoft.AspNetCore.Cors": "1.1.0",
"Microsoft.AspNetCore.Antiforgery": "1.0.1", "Microsoft.AspNetCore.Antiforgery": "1.1.0",
"Microsoft.AspNetCore.Authorization": "1.0.0", "Microsoft.AspNetCore.Authorization": "1.1.0",
"Newtonsoft.Json": "9.0.1", "Newtonsoft.Json": "9.0.1",
"Webpack": "3.0.0", "Webpack": "3.0.0",
"Microsoft.AspNetCore.AngularServices": "1.0.0-beta-000014", "Microsoft.AspNetCore.AngularServices": "1.0.0-beta-000014",
@ -19,15 +19,15 @@
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0", "Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0", "Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
"Microsoft.AspNetCore.StaticFiles": "1.0.0", "Microsoft.AspNetCore.StaticFiles": "1.1.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0", "Microsoft.Extensions.Configuration.EnvironmentVariables": "1.1.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0", "Microsoft.Extensions.Configuration.Json": "1.1.0",
"Microsoft.Extensions.Logging": "1.0.0", "Microsoft.Extensions.Logging": "1.1.0",
"Microsoft.Extensions.Logging.Console": "1.0.0", "Microsoft.Extensions.Logging.Console": "1.1.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0", "Microsoft.Extensions.Logging.Debug": "1.1.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0", "Microsoft.Extensions.Options.ConfigurationExtensions": "1.1.0",
"Microsoft.VisualStudio.Web.CodeGeneration.Tools": { "Microsoft.VisualStudio.Web.CodeGeneration.Tools": {
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
@ -36,7 +36,8 @@
"version": "1.0.0-preview2-final", "version": "1.0.0-preview2-final",
"type": "build" "type": "build"
}, },
"Microsoft.AspNetCore.Http.Abstractions": "1.0.0" "Microsoft.AspNetCore.Http.Abstractions": "1.1.0",
"Microsoft.Extensions.Options": "1.1.0"
}, },
"tools": { "tools": {
"Microsoft.DotNet.Watcher.Tools": { "Microsoft.DotNet.Watcher.Tools": {

View File

@ -25,7 +25,7 @@ namespace UnitTest.Ordering.Application
public async Task Handle_returns_true_when_order_is_persisted_succesfully() public async Task Handle_returns_true_when_order_is_persisted_succesfully()
{ {
// Arrange // Arrange
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(FakeOrderRequestWithBuyer().BuyerFullName)) _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(FakeOrderRequestWithBuyer().BuyerIdentityGuid))
.Returns(Task.FromResult<Buyer>(FakeBuyer())); .Returns(Task.FromResult<Buyer>(FakeBuyer()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
@ -48,7 +48,7 @@ namespace UnitTest.Ordering.Application
[Fact] [Fact]
public async Task Handle_return_false_if_order_is_not_persisted() public async Task Handle_return_false_if_order_is_not_persisted()
{ {
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(FakeOrderRequestWithBuyer().BuyerFullName)) _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.FindAsync(FakeOrderRequestWithBuyer().BuyerIdentityGuid))
.Returns(Task.FromResult<Buyer>(FakeBuyer())); .Returns(Task.FromResult<Buyer>(FakeBuyer()));
_buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken))) _buyerRepositoryMock.Setup(buyerRepo => buyerRepo.UnitOfWork.SaveChangesAsync(default(CancellationToken)))
@ -80,7 +80,7 @@ namespace UnitTest.Ordering.Application
{ {
return new CreateOrderCommand return new CreateOrderCommand
{ {
BuyerFullName = "1234", BuyerIdentityGuid = "1234",
CardNumber = "1234", CardNumber = "1234",
CardExpiration = DateTime.Now.AddYears(1), CardExpiration = DateTime.Now.AddYears(1),
CardSecurityNumber = "123", CardSecurityNumber = "123",