Merge pull request #126 from BillWagner/csharp7-on-mvcapp

C# 7 language feature updates
This commit is contained in:
Eduard Tomàs 2017-03-21 13:32:05 +01:00 committed by GitHub
commit cc98475182
15 changed files with 147 additions and 168 deletions

View File

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

View File

@ -14,10 +14,8 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
{ {
private ICatalogService _catalogSvc; private ICatalogService _catalogSvc;
public CatalogController(ICatalogService catalogSvc) public CatalogController(ICatalogService catalogSvc) =>
{
_catalogSvc = catalogSvc; _catalogSvc = catalogSvc;
}
public async Task<IActionResult> Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page) public async Task<IActionResult> Index(int? BrandFilterApplied, int? TypesFilterApplied, int? page)
{ {
@ -35,7 +33,7 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers
ActualPage = page ?? 0, ActualPage = page ?? 0,
ItemsPerPage = catalog.Data.Count, ItemsPerPage = catalog.Data.Count,
TotalItems = catalog.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); return View(vm);
} }
public IActionResult Error() public IActionResult Error() => View();
{
return View();
}
} }
} }

View File

@ -11,20 +11,14 @@ namespace Microsoft.eShopOnContainers.WebMVC.Extensions
{ {
public static class HttpClientExtensions 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); 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); 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); client.SetToken(JwtConstants.TokenType, token);
}
} }
public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue public class BasicAuthenticationHeaderValue : AuthenticationHeaderValue

View File

@ -8,10 +8,8 @@ using System.Threading.Tasks;
public static class SessionExtensions 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)); session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetObject<T>(this ISession session, string key) public static T GetObject<T>(this ISession session, string key)
{ {

View File

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

View File

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

View File

@ -12,26 +12,31 @@ namespace Microsoft.eShopOnContainers.WebMVC.Services
{ {
public ApplicationUser Parse(IPrincipal principal) public ApplicationUser Parse(IPrincipal principal)
{ {
var user = new ApplicationUser(); // Pattern matching 'is' expression
var claims = (ClaimsPrincipal)principal; // 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 : ""; CardHolderName = claims.Claims.FirstOrDefault(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 : ""; CardNumber = claims.Claims.FirstOrDefault(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 : ""; Expiration = claims.Claims.FirstOrDefault(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; CardType = int.Parse(claims.Claims.FirstOrDefault(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 : ""; City = claims.Claims.FirstOrDefault(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 : ""; Country = claims.Claims.FirstOrDefault(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 : ""; Email = claims.Claims.FirstOrDefault(x => x.Type == "email")?.Value ?? "",
user.Id = (claims.Claims.Where(x => x.Type == "sub").Count() > 0) ? claims.Claims.First(x => x.Type == "sub").Value : ""; Id = claims.Claims.FirstOrDefault(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 : ""; LastName = claims.Claims.FirstOrDefault(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 : ""; Name = claims.Claims.FirstOrDefault(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 : ""; PhoneNumber = claims.Claims.FirstOrDefault(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 : ""; SecurityNumber = claims.Claims.FirstOrDefault(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 : ""; State = claims.Claims.FirstOrDefault(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 : ""; Street = claims.Claims.FirstOrDefault(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 : ""; ZipCode = claims.Claims.FirstOrDefault(x => x.Type == "address_zip_code")?.Value ?? ""
};
return user; }
throw new ArgumentException(message: "The principal must be a ClaimsPrincipal", paramName: nameof(principal));
} }
} }
} }

View File

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

View File

@ -24,76 +24,74 @@ namespace WebMVC.Services.Utilities
CreateRetryPolicy(), CreateRetryPolicy(),
CreateCircuitBreakerPolicy() CreateCircuitBreakerPolicy()
); );
}
private Policy CreateCircuitBreakerPolicy()
{
return 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");
}
);
} }
private Policy CreateRetryPolicy() private Policy CreateCircuitBreakerPolicy() =>
{ Policy.Handle<HttpRequestException>()
return Policy .CircuitBreakerAsync(
.Handle<HttpRequestException>() // number of exceptions before breaking circuit
.WaitAndRetryAsync( 3,
// number of retries // time circuit opened before retry
3, TimeSpan.FromMinutes(1),
// exponential backofff (exception, duration) =>
retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), {
// on retry // on circuit opened
(exception, timeSpan, retryCount, context) => _logger.LogTrace("Circuit breaker opened");
{ },
_logger.LogTrace($"Retry {retryCount} " + () =>
$"of {context.PolicyKey} " + {
$"at {context.ExecutionKey}, " + // on circuit closed
$"due to: {exception}."); _logger.LogTrace("Circuit breaker reset");
}); }
} );
public async Task<string> GetStringAsync(string uri) private Policy CreateRetryPolicy() =>
{ Policy.Handle<HttpRequestException>()
return await HttpInvoker(async () => .WaitAndRetryAsync(
await _client.GetStringAsync(uri)); // 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 // a new StringContent must be created for each retry
// as it is disposed after each call // as it is disposed after each call
return await HttpInvoker(async () => HttpInvoker(() =>_client.PostAsync(uri,
await _client.PostAsync(uri, new StringContent(JsonConvert.SerializeObject(item),
new StringContent(JsonConvert.SerializeObject(item), System.Text.Encoding.UTF8, "application/json")));
System.Text.Encoding.UTF8, "application/json")));
}
public async Task<HttpResponseMessage> DeleteAsync(string uri) public Task<HttpResponseMessage> DeleteAsync(string uri) =>
{ HttpInvoker(() => _client.DeleteAsync(uri));
return await HttpInvoker(async () =>
await _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 // Executes the action applying all
// the policies defined in the wrapper // the policies defined in the wrapper
return await _policyWrapper _policyWrapper.ExecuteAsync(() => action());
.ExecuteAsync(async () => await action());
}
} }
} }

View File

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

View File

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

View File

@ -14,11 +14,20 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels.Annotations
if (value == null) if (value == null)
return false; return false;
var month = value.ToString().Split('/')[0]; var monthString = value.ToString().Split('/')[0];
var year = $"20{value.ToString().Split('/')[1]}"; var yearString = $"20{value.ToString().Split('/')[1]}";
DateTime d = new DateTime(int.Parse(year), int.Parse(month), 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;
}
} }
} }
} }

View File

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

View File

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

View File

@ -11,10 +11,6 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
{ {
public class Order public class Order
{ {
public Order() {
OrderItems = new List<OrderItem>();
}
public string OrderNumber {get;set;} public string OrderNumber {get;set;}
public DateTime Date {get;set;} public DateTime Date {get;set;}
@ -53,7 +49,10 @@ namespace Microsoft.eShopOnContainers.WebMVC.ViewModels
public string Buyer { get; set; } 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] [Required]
public Guid RequestId { get; set; } public Guid RequestId { get; set; }