Refactor to introduce nullable types and reduce compiler warnings (#801)

* Introduce nullable types to Core, Infrastructure and PublicApi projects

* Refactor unit tests

* Fixed failing tests

* Introduce Parameter object for CatalogItem update method

* Refactor CataloItemDetails param object

* Refactor following PR comments
This commit is contained in:
Philippe Vaillancourt
2022-10-04 16:57:40 +01:00
committed by GitHub
parent aa6305eab1
commit a72dd775ee
51 changed files with 168 additions and 256 deletions

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@@ -73,17 +74,17 @@ public class LoginModel : PageModel
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
//var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: true);
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, false, true);
var result = await _signInManager.PasswordSignInAsync(Input?.Email, Input?.Password, false, true);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
await TransferAnonymousBasketToUserAsync(Input.Email);
await TransferAnonymousBasketToUserAsync(Input?.Email);
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
{
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input?.RememberMe });
}
if (result.IsLockedOut)
{
@@ -108,6 +109,7 @@ public class LoginModel : PageModel
var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME];
if (Guid.TryParse(anonymousId, out var _))
{
Guard.Against.NullOrEmpty(userName, nameof(userName));
await _basketService.TransferBasketAsync(anonymousId, userName);
}
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
@@ -67,8 +68,8 @@ public class RegisterModel : PageModel
returnUrl = returnUrl ?? Url.Content("~/");
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = Input.Email, Email = Input.Email };
var result = await _userManager.CreateAsync(user, Input.Password);
var user = new ApplicationUser { UserName = Input?.Email, Email = Input?.Email };
var result = await _userManager.CreateAsync(user, Input?.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
@@ -80,7 +81,8 @@ public class RegisterModel : PageModel
values: new { userId = user.Id, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
Guard.Against.Null(callbackUrl, nameof(callbackUrl));
await _emailSender.SendEmailAsync(Input?.Email, "Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
await _signInManager.SignInAsync(user, isPersistent: false);

View File

@@ -22,12 +22,12 @@ public class RevokeAuthenticationEvents : CookieAuthenticationEvents
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
var userId = context.Principal.Claims.First(c => c.Type == ClaimTypes.Name);
var userId = context.Principal?.Claims.First(c => c.Type == ClaimTypes.Name);
var identityKey = context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];
if (_cache.TryGetValue($"{userId.Value}:{identityKey}", out var revokeKeys))
if (_cache.TryGetValue($"{userId?.Value}:{identityKey}", out var revokeKeys))
{
_logger.LogDebug($"Access has been revoked for: {userId.Value}.");
_logger.LogDebug($"Access has been revoked for: {userId?.Value}.");
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}

View File

@@ -1,5 +1,6 @@
using System.Text;
using System.Text.Encodings.Web;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@@ -119,6 +120,7 @@ public class ManageController : Controller
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
Guard.Against.Null(callbackUrl, nameof(callbackUrl));
var email = user.Email;
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
@@ -405,7 +407,7 @@ public class ManageController : Controller
}
// Strip spaces and hypens
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var verificationCode = model.Code?.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);

View File

@@ -1,4 +1,5 @@
using MediatR;
using Ardalis.GuardClauses;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.Web.Features.MyOrders;
@@ -20,7 +21,8 @@ public class OrderController : Controller
[HttpGet]
public async Task<IActionResult> MyOrders()
{
{
Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
return View(viewModel);
@@ -29,6 +31,7 @@ public class OrderController : Controller
[HttpGet("{orderId}")]
public async Task<IActionResult> Detail(int orderId)
{
Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));
var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));
if (viewModel == null)

View File

@@ -28,7 +28,7 @@ public class UserController : ControllerBase
private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)
{
if (!claimsPrincipal.Identity.IsAuthenticated)
if (claimsPrincipal.Identity == null || claimsPrincipal.Identity.Name == null || !claimsPrincipal.Identity.IsAuthenticated)
{
return UserInfo.Anonymous;
}

View File

@@ -2,7 +2,7 @@
public static class UrlHelperExtensions
{
public static string EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
public static string? EmailConfirmationLink(this IUrlHelper urlHelper, string userId, string code, string scheme)
{
return urlHelper.Action(
action: "GET",

View File

@@ -28,7 +28,7 @@ public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<Order
return orders.Select(o => new OrderViewModel
{
OrderDate = o.OrderDate,
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
OrderItems = o.OrderItems.Select(oi => new OrderItemViewModel()
{
PictureUrl = oi.ItemOrdered.PictureUri,
ProductId = oi.ItemOrdered.CatalogItemId,

View File

@@ -9,7 +9,7 @@ using Microsoft.eShopWeb.Web.ViewModels;
namespace Microsoft.eShopWeb.Web.Features.OrderDetails;
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel>
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel?>
{
private readonly IReadRepository<Order> _orderRepository;
@@ -18,11 +18,11 @@ public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderView
_orderRepository = orderRepository;
}
public async Task<OrderViewModel> Handle(GetOrderDetails request,
public async Task<OrderViewModel?> Handle(GetOrderDetails request,
CancellationToken cancellationToken)
{
var spec = new OrderWithItemsByIdSpec(request.OrderId);
var order = await _orderRepository.GetBySpecAsync(spec, cancellationToken);
var order = await _orderRepository.FirstOrDefaultAsync(spec, cancellationToken);
if (order == null)
{

View File

@@ -19,8 +19,8 @@ public class HomePageHealthCheck : IHealthCheck
HealthCheckContext context,
CancellationToken cancellationToken = default(CancellationToken))
{
var request = _httpContextAccessor.HttpContext.Request;
string myUrl = request.Scheme + "://" + request.Host.ToString();
var request = _httpContextAccessor.HttpContext?.Request;
string myUrl = request?.Scheme + "://" + request?.Host.ToString();
var client = new HttpClient();
var response = await client.GetAsync(myUrl);

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
@@ -68,6 +69,7 @@ public class CheckoutModel : PageModel
private async Task SetBasketModelAsync()
{
Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));
if (_signInManager.IsSignedIn(HttpContext.User))
{
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
@@ -75,7 +77,7 @@ public class CheckoutModel : PageModel
else
{
GetOrSetBasketCookieAndUserName();
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username!);
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
@@ -66,11 +67,13 @@ public class IndexModel : PageModel
private string GetOrSetBasketCookieAndUserName()
{
Guard.Against.Null(Request.HttpContext.User.Identity, nameof(Request.HttpContext.User.Identity));
string? userName = null;
if (Request.HttpContext.User.Identity.IsAuthenticated)
{
return Request.HttpContext.User.Identity.Name;
Guard.Against.Null(Request.HttpContext.User.Identity.Name, nameof(Request.HttpContext.User.Identity.Name));
return Request.HttpContext.User.Identity.Name!;
}
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))

View File

@@ -1,5 +1,6 @@
using System;
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.eShopWeb.Infrastructure.Identity;
@@ -33,17 +34,18 @@ public class Basket : ViewComponent
{
if (_signInManager.IsSignedIn(HttpContext.User))
{
Guard.Against.Null(User?.Identity?.Name, nameof(User.Identity.Name));
return await _basketService.CountTotalBasketItems(User.Identity.Name);
}
string anonymousId = GetAnnonymousIdFromCookie();
string? anonymousId = GetAnnonymousIdFromCookie();
if (anonymousId == null)
return 0;
return await _basketService.CountTotalBasketItems(anonymousId);
}
private string GetAnnonymousIdFromCookie()
private string? GetAnnonymousIdFromCookie()
{
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
{

View File

@@ -28,7 +28,7 @@ public class BasketViewModelService : IBasketViewModelService
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
{
var basketSpec = new BasketWithItemsSpecification(userName);
var basket = (await _basketRepository.GetBySpecAsync(basketSpec));
var basket = (await _basketRepository.FirstOrDefaultAsync(basketSpec));
if (basket == null)
{

View File

@@ -1,4 +1,4 @@
using System.Threading.Tasks;
using Ardalis.GuardClauses;
using Microsoft.eShopWeb.ApplicationCore.Entities;
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
using Microsoft.eShopWeb.Web.Interfaces;
@@ -18,7 +18,11 @@ public class CatalogItemViewModelService : ICatalogItemViewModelService
public async Task UpdateCatalogItem(CatalogItemViewModel viewModel)
{
var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id);
existingCatalogItem.UpdateDetails(viewModel.Name, existingCatalogItem.Description, viewModel.Price);
Guard.Against.Null(existingCatalogItem, nameof(existingCatalogItem));
CatalogItem.CatalogItemDetails details = new(viewModel.Name, existingCatalogItem.Description, viewModel.Price);
existingCatalogItem.UpdateDetails(details);
await _catalogItemRepository.UpdateAsync(existingCatalogItem);
}
}

View File

@@ -5,11 +5,13 @@ namespace Microsoft.eShopWeb.Web;
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string? TransformOutbound(object value)
public string? TransformOutbound(object? value)
{
if (value == null) { return null; }
string? str = value.ToString();
if (string.IsNullOrEmpty(str)) { return null; }
// Slugify value
return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
return Regex.Replace(str, "([a-z])([A-Z])", "$1-$2").ToLower();
}
}