Browse Source

Merge

pull/133/head
Ramón Tomás 8 years ago
parent
commit
feafb4ba51
17 changed files with 192 additions and 177 deletions
  1. +1
    -3
      eShopOnContainers-ServicesAndWebApps.sln
  2. +44
    -6
      src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs
  3. +3
    -8
      src/Web/WebMVC/Controllers/AccountController.cs
  4. +3
    -8
      src/Web/WebMVC/Controllers/CatalogController.cs
  5. +3
    -9
      src/Web/WebMVC/Extensions/HttpClientExtensions.cs
  6. +1
    -3
      src/Web/WebMVC/Extensions/SessionExtensions.cs
  7. +11
    -10
      src/Web/WebMVC/Services/BasketService.cs
  8. +10
    -5
      src/Web/WebMVC/Services/CatalogService.cs
  9. +24
    -19
      src/Web/WebMVC/Services/IdentityParser.cs
  10. +7
    -19
      src/Web/WebMVC/Services/Utilities/HttpApiClient.cs
  11. +58
    -60
      src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs
  12. +1
    -4
      src/Web/WebMVC/ViewComponents/Cart.cs
  13. +3
    -8
      src/Web/WebMVC/ViewComponents/CartList.cs
  14. +13
    -4
      src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs
  15. +5
    -5
      src/Web/WebMVC/ViewModels/Basket.cs
  16. +1
    -1
      src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs
  17. +4
    -5
      src/Web/WebMVC/ViewModels/Order.cs

+ 1
- 3
eShopOnContainers-ServicesAndWebApps.sln View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26228.0
VisualStudioVersion = 15.0.26228.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{932D8224-11F6-4D07-B109-DA28AD288A63}"
EndProject
@ -64,8 +64,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IntegrationTests", "test\Se
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FunctionalTests", "test\Services\FunctionalTests\FunctionalTests.csproj", "{CFE2FACB-4538-4B99-8A10-306F3882952D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{47857844-D05A-4C37-BFB2-AF19B7EC418D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{DB0EFB20-B024-4E5E-A75C-52143C131D25}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EventBus", "EventBus", "{807BB76E-B2BB-47A2-A57B-3D1B20FF5E7F}"


+ 44
- 6
src/Services/Catalog/Catalog.API/Controllers/CatalogController.cs View File

@ -130,20 +130,21 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
return Ok(items);
}
[Route("edit")]
[HttpPost]
public async Task<IActionResult> Post([FromBody]CatalogItem value)
public async Task<IActionResult> EditProduct([FromBody]CatalogItem product)
{
var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == value.Id);
var item = await _context.CatalogItems.SingleOrDefaultAsync(i => i.Id == product.Id);
if (item == null)
{
return NotFound();
}
if (item.Price != value.Price)
if (item.Price != product.Price)
{
var oldPrice = item.Price;
item.Price = value.Price;
item.Price = product.Price;
_context.CatalogItems.Update(item);
var @event = new ProductPriceChangedIntegrationEvent(item.Id, item.Price, oldPrice);
@ -158,10 +159,47 @@ namespace Microsoft.eShopOnContainers.Services.Catalog.API.Controllers
eventLogEntry.State = EventStateEnum.Published;
_context.IntegrationEventLog.Update(eventLogEntry);
await _context.SaveChangesAsync();
}
}
return Ok();
}
}
[Route("create")]
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody]CatalogItem product)
{
_context.CatalogItems.Add(
new CatalogItem
{
CatalogBrandId = product.CatalogBrandId,
CatalogTypeId = product.CatalogTypeId,
Description = product.Description,
Name = product.Name,
PictureUri = product.PictureUri,
Price = product.Price
});
await _context.SaveChangesAsync();
return Ok();
}
[Route("{id}")]
[HttpDelete]
public async Task<IActionResult> DeleteProduct(int id)
{
var product = _context.CatalogItems.SingleOrDefault(x => x.Id == id);
if (product == null)
{
return NotFound();
}
_context.CatalogItems.Remove(product);
await _context.SaveChangesAsync();
return Ok();
}
private List<CatalogItem> ComposePicUri(List<CatalogItem> items) {
var baseUri = _settings.Value.ExternalCatalogBaseUrl;


+ 3
- 8
src/Web/WebMVC/Controllers/AccountController.cs View File

@ -11,16 +11,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
public class AccountController : Controller
{
private readonly IIdentityParser<ApplicationUser> _identityParser;
public AccountController(IIdentityParser<ApplicationUser> identityParser)
{
public AccountController(IIdentityParser<ApplicationUser> identityParser) =>
_identityParser = identityParser;
}
public ActionResult Index()
{
return View();
}
public ActionResult Index() => View();
[Authorize]
public IActionResult SignIn(string returnUrl)
{


+ 3
- 8
src/Web/WebMVC/Controllers/CatalogController.cs View File

@ -14,10 +14,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{
private ICatalogService _catalogSvc;
public CatalogController(ICatalogService catalogSvc)
{
public CatalogController(ICatalogService catalogSvc) =>
_catalogSvc = catalogSvc;
}
public async Task<IActionResult> Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page)
{
@ -35,7 +33,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
ActualPage = page ?? 0,
ItemsPerPage = catalog.Data.Count,
TotalItems = catalog.Count,
TotalPages = int.Parse(Math.Ceiling(((decimal)catalog.Count / itemsPage)).ToString())
TotalPages = (int)Math.Ceiling(((decimal)catalog.Count / itemsPage))
}
};
@ -45,10 +43,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
return View(vm);
}
public IActionResult Error()
{
return View();
}
public IActionResult Error() => View();
}
}

+ 3
- 9
src/Web/WebMVC/Extensions/HttpClientExtensions.cs View File

@ -11,20 +11,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Extensions
{
public static class HttpClientExtensions
{
public static void SetBasicAuthentication(this HttpClient client, string userName, string password)
{
public static void SetBasicAuthentication(this HttpClient client, string userName, string password) =>
client.DefaultRequestHeaders.Authorization = new BasicAuthenticationHeaderValue(userName, password);
}
public static void SetToken(this HttpClient client, string scheme, string token)
{
public static void SetToken(this HttpClient client, string scheme, string token) =>
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, token);
}
public static void SetBearerToken(this HttpClient client, string token)
{
public static void SetBearerToken(this HttpClient client, string token) =>
client.SetToken(JwtConstants.TokenType, token);
}
}
public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue


+ 1
- 3
src/Web/WebMVC/Extensions/SessionExtensions.cs View File

@ -8,10 +8,8 @@ using System.Threading.Tasks;
public static class SessionExtensions
{
public static void SetObject(this ISession session, string key, object value)
{
public static void SetObject(this ISession session, string key, object value) =>
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetObject<T>(this ISession session, string key)
{


+ 11
- 10
src/Web/WebMVC/Services/BasketService.cs View File

@ -33,16 +33,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}";
var dataString = await _apiClient.GetStringAsync(basketUrl);
var response = JsonConvert.DeserializeObject<Basket>(dataString);
if (response == null)
{
response = new Basket()
// Use the ?? Null conditional operator to simplify the initialization of response
var response = JsonConvert.DeserializeObject<Basket>(dataString) ??
new Basket()
{
BuyerId = user.Id
};
}
return response;
}
@ -67,9 +65,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
basket.Items.ForEach(x =>
{
var quantity = quantities.Where(y => y.Key == x.Id).FirstOrDefault();
if (quantities.Where(y => y.Key == x.Id).Count() > 0)
x.Quantity = quantity.Value;
// Simplify this logic by using the
// new out variable initializer.
if (quantities.TryGetValue(x.Id, out var quantity))
{
x.Quantity = quantity;
}
});
return basket;
@ -119,7 +120,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
var token = await context.Authentication.GetTokenAsync("access_token");
_apiClient.Inst.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id.ToString()}";
var basketUrl = $"{_remoteServiceBaseUrl}/{user.Id}";
var response = await _apiClient.DeleteAsync(basketUrl);
//CCE: response status code...


+ 10
- 5
src/Web/WebMVC/Services/CatalogService.cs View File

@ -61,8 +61,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
JArray brands = JArray.Parse(dataString);
foreach (JObject brand in brands.Children<JObject>())
{
dynamic item = brand;
items.Add(new SelectListItem() { Value = item.id, Text = item.brand });
items.Add(new SelectListItem()
{
Value = brand.Value<string>("id"),
Text = brand.Value<string>("brand")
});
}
return items;
@ -79,10 +82,12 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
JArray brands = JArray.Parse(dataString);
foreach (JObject brand in brands.Children<JObject>())
{
dynamic item = brand;
items.Add(new SelectListItem() { Value = item.id, Text = item.type });
items.Add(new SelectListItem()
{
Value = brand.Value<string>("id"),
Text = brand.Value<string>("type")
});
}
return items;
}
}


+ 24
- 19
src/Web/WebMVC/Services/IdentityParser.cs View File

@ -12,26 +12,31 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{
public ApplicationUser Parse(IPrincipal principal)
{
var user = new ApplicationUser();
var claims = (ClaimsPrincipal)principal;
// Pattern matching 'is' expression
// assigns "claims" if "principal" is a "ClaimsPrincipal"
if (principal is ClaimsPrincipal claims)
{
return new ApplicationUser
{
user.CardHolderName = (claims.Claims.Where(x => x.Type == "card_holder").Count() > 0) ? claims.Claims.First(x => x.Type == "card_holder").Value : "";
user.CardNumber = (claims.Claims.Where(x => x.Type == "card_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_number").Value : "";
user.Expiration = (claims.Claims.Where(x => x.Type == "card_expiration").Count() > 0) ? claims.Claims.First(x => x.Type == "card_expiration").Value : "";
user.CardType = (claims.Claims.Where(x => x.Type == "missing").Count() > 0) ? int.Parse(claims.Claims.First(x => x.Type == "missing").Value) : 0;
user.City = (claims.Claims.Where(x => x.Type == "address_city").Count() > 0) ? claims.Claims.First(x => x.Type == "address_city").Value : "";
user.Country = (claims.Claims.Where(x => x.Type == "address_country").Count() > 0) ? claims.Claims.First(x => x.Type == "address_country").Value : "";
user.Email = (claims.Claims.Where(x => x.Type == "email").Count() > 0) ? claims.Claims.First(x => x.Type == "email").Value : "";
user.Id = (claims.Claims.Where(x => x.Type == "sub").Count() > 0) ? claims.Claims.First(x => x.Type == "sub").Value : "";
user.LastName = (claims.Claims.Where(x => x.Type == "last_name").Count() > 0) ? claims.Claims.First(x => x.Type == "last_name").Value : "";
user.Name = (claims.Claims.Where(x => x.Type == "name").Count() > 0) ? claims.Claims.First(x => x.Type == "name").Value : "";
user.PhoneNumber = (claims.Claims.Where(x => x.Type == "phone_number").Count() > 0) ? claims.Claims.First(x => x.Type == "phone_number").Value : "";
user.SecurityNumber = (claims.Claims.Where(x => x.Type == "card_security_number").Count() > 0) ? claims.Claims.First(x => x.Type == "card_security_number").Value : "";
user.State = (claims.Claims.Where(x => x.Type == "address_state").Count() > 0) ? claims.Claims.First(x => x.Type == "address_state").Value : "";
user.Street = (claims.Claims.Where(x => x.Type == "address_street").Count() > 0) ? claims.Claims.First(x => x.Type == "address_street").Value : "";
user.ZipCode = (claims.Claims.Where(x => x.Type == "address_zip_code").Count() > 0) ? claims.Claims.First(x => x.Type == "address_zip_code").Value : "";
return user;
CardHolderName = claims.Claims.FirstOrDefault(x => x.Type == "card_holder")?.Value ?? "",
CardNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_number")?.Value ?? "",
Expiration = claims.Claims.FirstOrDefault(x => x.Type == "card_expiration")?.Value ?? "",
CardType = int.Parse(claims.Claims.FirstOrDefault(x => x.Type == "missing")?.Value ?? "0"),
City = claims.Claims.FirstOrDefault(x => x.Type == "address_city")?.Value ?? "",
Country = claims.Claims.FirstOrDefault(x => x.Type == "address_country")?.Value ?? "",
Email = claims.Claims.FirstOrDefault(x => x.Type == "email")?.Value ?? "",
Id = claims.Claims.FirstOrDefault(x => x.Type == "sub")?.Value ?? "",
LastName = claims.Claims.FirstOrDefault(x => x.Type == "last_name")?.Value ?? "",
Name = claims.Claims.FirstOrDefault(x => x.Type == "name")?.Value ?? "",
PhoneNumber = claims.Claims.FirstOrDefault(x => x.Type == "phone_number")?.Value ?? "",
SecurityNumber = claims.Claims.FirstOrDefault(x => x.Type == "card_security_number")?.Value ?? "",
State = claims.Claims.FirstOrDefault(x => x.Type == "address_state")?.Value ?? "",
Street = claims.Claims.FirstOrDefault(x => x.Type == "address_street")?.Value ?? "",
ZipCode = claims.Claims.FirstOrDefault(x => x.Type == "address_zip_code")?.Value ?? ""
};
}
throw new ArgumentException(message: "The principal must be a ClaimsPrincipal", paramName: nameof(principal));
}
}
}


+ 7
- 19
src/Web/WebMVC/Services/Utilities/HttpApiClient.cs View File

@ -16,30 +16,18 @@ namespace WebMVC.Services.Utilities
_client = new HttpClient();
_logger = new LoggerFactory().CreateLogger(nameof(HttpApiClientWrapper));
}
public Task<string> GetStringAsync(string uri) =>
_client.GetStringAsync(uri);
public async Task<string> GetStringAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.GetStringAsync(uri));
}
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
{
var contentString = new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json");
return await HttpInvoker(async () =>
await _client.PostAsync(uri, contentString));
}
public async Task<HttpResponseMessage> DeleteAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.DeleteAsync(uri));
return _client.PostAsync(uri, contentString);
}
private async Task<T> HttpInvoker<T>(Func<Task<T>> action)
{
return await action();
}
public Task<HttpResponseMessage> DeleteAsync(string uri) =>
_client.DeleteAsync(uri);
}
}

+ 58
- 60
src/Web/WebMVC/Services/Utilities/HttpApiClientWrapper.cs View File

@ -24,76 +24,74 @@ namespace WebMVC.Services.Utilities
CreateRetryPolicy(),
CreateCircuitBreakerPolicy()
);
}
private Policy CreateCircuitBreakerPolicy()
{
return Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
5,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) => {
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() => {
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
}
);
}
private Policy CreateRetryPolicy()
{
return Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
5,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
_logger.LogTrace($"Retry {retryCount} " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.");
});
}
private Policy CreateCircuitBreakerPolicy() =>
Policy.Handle<HttpRequestException>()
.CircuitBreakerAsync(
// number of exceptions before breaking circuit
3,
// time circuit opened before retry
TimeSpan.FromMinutes(1),
(exception, duration) =>
{
// on circuit opened
_logger.LogTrace("Circuit breaker opened");
},
() =>
{
// on circuit closed
_logger.LogTrace("Circuit breaker reset");
}
);
public async Task<string> GetStringAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.GetStringAsync(uri));
}
private Policy CreateRetryPolicy() =>
Policy.Handle<HttpRequestException>()
.WaitAndRetryAsync(
// number of retries
3,
// exponential backofff
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
// on retry
(exception, timeSpan, retryCount, context) =>
{
_logger.LogTrace($"Retry {retryCount} " +
$"of {context.PolicyKey} " +
$"at {context.ExecutionKey}, " +
$"due to: {exception}.");
}
);
public async Task<HttpResponseMessage> PostAsync<T>(string uri, T item)
{
// Notice that these (and other methods below) are Task
// returning asynchronous methods. But, they do not
// have the 'async' modifier, and do not contain
// any 'await statements. In each of these methods,
// the only asynchronous call is the last (or only)
// statement of the method. In those instances,
// a Task returning method that does not use the
// async modifier is preferred. The compiler generates
// synchronous code for this method, but returns the
// task from the underlying asynchronous method. The
// generated code does not contain the state machine
// generated for asynchronous methods.
public Task<string> GetStringAsync(string uri) =>
HttpInvoker(() => _client.GetStringAsync(uri));
public Task<HttpResponseMessage> PostAsync<T>(string uri, T item) =>
// a new StringContent must be created for each retry
// as it is disposed after each call
return await HttpInvoker(async () =>
await _client.PostAsync(uri,
new StringContent(JsonConvert.SerializeObject(item),
System.Text.Encoding.UTF8, "application/json")));
}
HttpInvoker(() =>_client.PostAsync(uri,
new StringContent(JsonConvert.SerializeObject(item),
System.Text.Encoding.UTF8, "application/json")));
public async Task<HttpResponseMessage> DeleteAsync(string uri)
{
return await HttpInvoker(async () =>
await _client.DeleteAsync(uri));
}
public Task<HttpResponseMessage> DeleteAsync(string uri) =>
HttpInvoker(() => _client.DeleteAsync(uri));
private async Task<T> HttpInvoker<T>(Func<Task<T>> action)
{
private Task<T> HttpInvoker<T>(Func<Task<T>> action) =>
// Executes the action applying all
// the policies defined in the wrapper
return await _policyWrapper
.ExecuteAsync(async () => await action());
}
_policyWrapper.ExecuteAsync(() => action());
}
}

+ 1
- 4
src/Web/WebMVC/ViewComponents/Cart.cs View File

@ -13,10 +13,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
{
private readonly IBasketService _cartSvc;
public Cart(IBasketService cartSvc)
{
_cartSvc = cartSvc;
}
public Cart(IBasketService cartSvc) => _cartSvc = cartSvc;
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
{


+ 3
- 8
src/Web/WebMVC/ViewComponents/CartList.cs View File

@ -12,19 +12,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewComponents
{
private readonly IBasketService _cartSvc;
public CartList(IBasketService cartSvc)
{
_cartSvc = cartSvc;
}
public CartList(IBasketService cartSvc) => _cartSvc = cartSvc;
public async Task<IViewComponentResult> InvokeAsync(ApplicationUser user)
{
var item = await GetItemsAsync(user);
return View(item);
}
private async Task<Basket> GetItemsAsync(ApplicationUser user)
{
return await _cartSvc.GetBasket(user);
}
private Task<Basket> GetItemsAsync(ApplicationUser user) => _cartSvc.GetBasket(user);
}
}

+ 13
- 4
src/Web/WebMVC/ViewModels/Annotations/CardExpiration.cs View File

@ -14,11 +14,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations
if (value == null)
return false;
var month = value.ToString().Split('/')[0];
var year = $"20{value.ToString().Split('/')[1]}";
DateTime d = new DateTime(int.Parse(year), int.Parse(month), 1);
var monthString = value.ToString().Split('/')[0];
var yearString = $"20{value.ToString().Split('/')[1]}";
// Use the 'out' variable initializer to simplify
// the logic of validating the expiration date
if ((int.TryParse(monthString, out var month)) &&
(int.TryParse(yearString, out var year)))
{
DateTime d = new DateTime(year, month, 1);
return d > DateTime.UtcNow;
return d > DateTime.UtcNow;
} else
{
return false;
}
}
}
}

+ 5
- 5
src/Web/WebMVC/ViewModels/Basket.cs View File

@ -7,11 +7,11 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Basket
{
public Basket()
{
Items = new List<BasketItem>();
}
public List<BasketItem> Items { get; set; }
// Use property initializer syntax.
// While this is often more useful for read only
// auto implemented properties, it can simplify logic
// for read/write properties.
public List<BasketItem> Items { get; set; } = new List<BasketItem>();
public string BuyerId { get; set; }
public decimal Total()


+ 1
- 1
src/Web/WebMVC/ViewModels/CartViewModels/IndexViewModel.cs View File

@ -9,6 +9,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.CartViewModels
public class CartComponentViewModel
{
public int ItemsCount { get; set; }
public string Disabled { get { return (ItemsCount == 0) ? "is-disabled" : ""; } }
public string Disabled => (ItemsCount == 0) ? "is-disabled" : "";
}
}

+ 4
- 5
src/Web/WebMVC/ViewModels/Order.cs View File

@ -11,10 +11,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{
public class Order
{
public Order() {
OrderItems = new List<OrderItem>();
}
public string OrderNumber {get;set;}
public DateTime Date {get;set;}
@ -53,7 +49,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
public string Buyer { get; set; }
public List<OrderItem> OrderItems { get; }
// See the property initializer syntax below. This
// initializes the compiler generated field for this
// auto-implemented property.
public List<OrderItem> OrderItems { get; } = new List<OrderItem>();
[Required]
public Guid RequestId { get; set; }


Loading…
Cancel
Save