Shady nagy/net6 (#614)
* udated to .net6 * used the .net6 version RC2 * added editconfig. * App core new Scoped Namespaces style. * BlazorAdmin new Scoped Namespaces style. * Blazor Shared new Scoped Namespaces style. * Infra new Scoped Namespaces style. * public api new Scoped Namespaces style. * web new Scoped Namespaces style. * FunctionalTests new Scoped Namespaces style. * Integrational tests new Scoped Namespaces style. * unit tests new Scoped Namespaces style. * update github action. * update github action. * change the global.
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
[assembly: HostingStartup(typeof(Microsoft.eShopWeb.Web.Areas.Identity.IdentityHostingStartup))]
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity;
|
||||
|
||||
public class IdentityHostingStartup : IHostingStartup
|
||||
{
|
||||
public class IdentityHostingStartup : IHostingStartup
|
||||
public void Configure(IWebHostBuilder builder)
|
||||
{
|
||||
public void Configure(IWebHostBuilder builder)
|
||||
builder.ConfigureServices((context, services) =>
|
||||
{
|
||||
builder.ConfigureServices((context, services) => {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,38 +8,37 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class ConfirmEmailModel : PageModel
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class ConfirmEmailModel : PageModel
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
|
||||
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
_userManager = userManager;
|
||||
}
|
||||
|
||||
public ConfirmEmailModel(UserManager<ApplicationUser> userManager)
|
||||
public async Task<IActionResult> OnGetAsync(string userId, string code)
|
||||
{
|
||||
if (userId == null || code == null)
|
||||
{
|
||||
_userManager = userManager;
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnGetAsync(string userId, string code)
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
if (userId == null || code == null)
|
||||
{
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByIdAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
return NotFound($"Unable to load user with ID '{userId}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
|
||||
}
|
||||
|
||||
return Page();
|
||||
return NotFound($"Unable to load user with ID '{userId}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.ConfirmEmailAsync(user, code);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
|
||||
}
|
||||
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -7,117 +12,111 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class LoginModel : PageModel
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class LoginModel : PageModel
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LoginModel> _logger;
|
||||
private readonly IBasketService _basketService;
|
||||
|
||||
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService)
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LoginModel> _logger;
|
||||
private readonly IBasketService _basketService;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_basketService = basketService;
|
||||
}
|
||||
|
||||
public LoginModel(SignInManager<ApplicationUser> signInManager, ILogger<LoginModel> logger, IBasketService basketService)
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
|
||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
[TempData]
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
public async Task OnGetAsync(string returnUrl = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_basketService = basketService;
|
||||
ModelState.AddModelError(string.Empty, ErrorMessage);
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
public IList<AuthenticationScheme> ExternalLogins { get; set; }
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
|
||||
[TempData]
|
||||
public string ErrorMessage { get; set; }
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public class InputModel
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
{
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
// 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);
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
public async Task OnGetAsync(string returnUrl = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
if (result.Succeeded)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, ErrorMessage);
|
||||
_logger.LogInformation("User logged in.");
|
||||
await TransferAnonymousBasketToUserAsync(Input.Email);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToPage("./Lockout");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return Page();
|
||||
}
|
||||
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
|
||||
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
|
||||
private async Task TransferAnonymousBasketToUserAsync(string userName)
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
|
||||
if (ModelState.IsValid)
|
||||
var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
if (Guid.TryParse(anonymousId, out var _))
|
||||
{
|
||||
// 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);
|
||||
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_logger.LogInformation("User logged in.");
|
||||
await TransferAnonymousBasketToUserAsync(Input.Email);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
if (result.RequiresTwoFactor)
|
||||
{
|
||||
return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
|
||||
}
|
||||
if (result.IsLockedOut)
|
||||
{
|
||||
_logger.LogWarning("User account locked out.");
|
||||
return RedirectToPage("./Lockout");
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
|
||||
private async Task TransferAnonymousBasketToUserAsync(string userName)
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
var anonymousId = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
if (Guid.TryParse(anonymousId, out var _))
|
||||
{
|
||||
await _basketService.TransferBasketAsync(anonymousId, userName);
|
||||
}
|
||||
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
||||
await _basketService.TransferBasketAsync(anonymousId, userName);
|
||||
}
|
||||
Response.Cookies.Delete(Constants.BASKET_COOKIENAME);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -7,51 +11,46 @@ using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.eShopWeb.Web.Configuration;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
//TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario
|
||||
public class LogoutModel : PageModel
|
||||
{
|
||||
//TODO : replace IMemoryCache by distributed cache if you are in multi-host scenario
|
||||
public class LogoutModel : PageModel
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LogoutModel> _logger;
|
||||
private readonly IMemoryCache _cache;
|
||||
|
||||
public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger, IMemoryCache cache)
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly ILogger<LogoutModel> _logger;
|
||||
private readonly IMemoryCache _cache;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public LogoutModel(SignInManager<ApplicationUser> signInManager, ILogger<LogoutModel> logger, IMemoryCache cache)
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name);
|
||||
var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];
|
||||
_cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions
|
||||
{
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_cache = cache;
|
||||
AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod)
|
||||
});
|
||||
|
||||
_logger.LogInformation("User logged out.");
|
||||
if (returnUrl != null)
|
||||
{
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
|
||||
public void OnGet()
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(string returnUrl = null)
|
||||
{
|
||||
await _signInManager.SignOutAsync();
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var userId = _signInManager.Context.User.Claims.First(c => c.Type == ClaimTypes.Name);
|
||||
var identityKey = _signInManager.Context.Request.Cookies[ConfigureCookieSettings.IdentifierCookieName];
|
||||
_cache.Set($"{userId.Value}:{identityKey}", identityKey, new MemoryCacheEntryOptions
|
||||
{
|
||||
AbsoluteExpiration = DateTime.Now.AddMinutes(ConfigureCookieSettings.ValidityMinutesPeriod)
|
||||
});
|
||||
|
||||
_logger.LogInformation("User logged out.");
|
||||
if (returnUrl != null)
|
||||
{
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,89 +11,88 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account
|
||||
namespace Microsoft.eShopWeb.Web.Areas.Identity.Pages.Account;
|
||||
|
||||
[AllowAnonymous]
|
||||
public class RegisterModel : PageModel
|
||||
{
|
||||
[AllowAnonymous]
|
||||
public class RegisterModel : PageModel
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ILogger<RegisterModel> _logger;
|
||||
private readonly IEmailSender _emailSender;
|
||||
|
||||
public RegisterModel(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
ILogger<RegisterModel> logger,
|
||||
IEmailSender emailSender)
|
||||
{
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly ILogger<RegisterModel> _logger;
|
||||
private readonly IEmailSender _emailSender;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_emailSender = emailSender;
|
||||
}
|
||||
|
||||
public RegisterModel(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
ILogger<RegisterModel> logger,
|
||||
IEmailSender emailSender)
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
|
||||
public void OnGet(string returnUrl = null)
|
||||
{
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
{
|
||||
returnUrl = returnUrl ?? Url.Content("~/");
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_logger = logger;
|
||||
_emailSender = emailSender;
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public InputModel Input { get; set; }
|
||||
|
||||
public string ReturnUrl { get; set; }
|
||||
|
||||
public class InputModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
|
||||
public void OnGet(string returnUrl = null)
|
||||
{
|
||||
ReturnUrl = returnUrl;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
|
||||
{
|
||||
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);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
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.");
|
||||
_logger.LogInformation("User created a new account with password.");
|
||||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.Page(
|
||||
"/Account/ConfirmEmail",
|
||||
pageHandler: null,
|
||||
values: new { userId = user.Id, code = code },
|
||||
protocol: Request.Scheme);
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.Page(
|
||||
"/Account/ConfirmEmail",
|
||||
pageHandler: null,
|
||||
values: new { userId = user.Id, code = code },
|
||||
protocol: Request.Scheme);
|
||||
|
||||
await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
|
||||
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
|
||||
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);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
return LocalRedirect(returnUrl);
|
||||
}
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
|
||||
// If we got this far, something failed, redisplay form
|
||||
return Page();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,40 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Configuration
|
||||
namespace Microsoft.eShopWeb.Web.Configuration;
|
||||
|
||||
public static class ConfigureCookieSettings
|
||||
{
|
||||
public static class ConfigureCookieSettings
|
||||
{
|
||||
public const int ValidityMinutesPeriod = 60;
|
||||
public const string IdentifierCookieName = "EshopIdentifier";
|
||||
public const int ValidityMinutesPeriod = 60;
|
||||
public const string IdentifierCookieName = "EshopIdentifier";
|
||||
|
||||
public static IServiceCollection AddCookieSettings(this IServiceCollection services)
|
||||
public static IServiceCollection AddCookieSettings(this IServiceCollection services)
|
||||
{
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
|
||||
//TODO need to check that.
|
||||
//options.CheckConsentNeeded = context => true;
|
||||
options.MinimumSameSitePolicy = SameSiteMode.Strict;
|
||||
});
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
});
|
||||
services.ConfigureApplicationCookie(options =>
|
||||
{
|
||||
options.EventsType = typeof(RevokeAuthenticationEvents);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(ValidityMinutesPeriod);
|
||||
options.LoginPath = "/Account/Login";
|
||||
options.LogoutPath = "/Account/Logout";
|
||||
options.Cookie = new CookieBuilder
|
||||
{
|
||||
options.EventsType = typeof(RevokeAuthenticationEvents);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(ValidityMinutesPeriod);
|
||||
options.LoginPath = "/Account/Login";
|
||||
options.LogoutPath = "/Account/Logout";
|
||||
options.Cookie = new CookieBuilder
|
||||
{
|
||||
Name = IdentifierCookieName,
|
||||
IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy
|
||||
Name = IdentifierCookieName,
|
||||
IsEssential = true // required for auth to work without explicit user consent; adjust to suit your privacy policy
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
services.AddScoped<RevokeAuthenticationEvents>();
|
||||
services.AddScoped<RevokeAuthenticationEvents>();
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,24 +6,23 @@ using Microsoft.eShopWeb.Infrastructure.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Configuration
|
||||
namespace Microsoft.eShopWeb.Web.Configuration;
|
||||
|
||||
public static class ConfigureCoreServices
|
||||
{
|
||||
public static class ConfigureCoreServices
|
||||
public static IServiceCollection AddCoreServices(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
public static IServiceCollection AddCoreServices(this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
|
||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||
services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
|
||||
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
|
||||
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
services.AddScoped<IOrderService, OrderService>();
|
||||
services.AddScoped<IBasketQueryService, BasketQueryService>();
|
||||
services.AddSingleton<IUriComposer>(new UriComposer(configuration.Get<CatalogSettings>()));
|
||||
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
services.AddScoped<IBasketService, BasketService>();
|
||||
services.AddScoped<IOrderService, OrderService>();
|
||||
services.AddScoped<IBasketQueryService, BasketQueryService>();
|
||||
services.AddSingleton<IUriComposer>(new UriComposer(configuration.Get<CatalogSettings>()));
|
||||
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
|
||||
return services;
|
||||
}
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,19 @@ using Microsoft.eShopWeb.Web.Services;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Configuration
|
||||
{
|
||||
public static class ConfigureWebServices
|
||||
{
|
||||
public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(BasketViewModelService).Assembly);
|
||||
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
|
||||
services.AddScoped<CatalogViewModelService>();
|
||||
services.AddScoped<ICatalogItemViewModelService, CatalogItemViewModelService>();
|
||||
services.Configure<CatalogSettings>(configuration);
|
||||
services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();
|
||||
namespace Microsoft.eShopWeb.Web.Configuration;
|
||||
|
||||
return services;
|
||||
}
|
||||
public static class ConfigureWebServices
|
||||
{
|
||||
public static IServiceCollection AddWebServices(this IServiceCollection services, IConfiguration configuration)
|
||||
{
|
||||
services.AddMediatR(typeof(BasketViewModelService).Assembly);
|
||||
services.AddScoped<IBasketViewModelService, BasketViewModelService>();
|
||||
services.AddScoped<CatalogViewModelService>();
|
||||
services.AddScoped<ICatalogItemViewModelService, CatalogItemViewModelService>();
|
||||
services.Configure<CatalogSettings>(configuration);
|
||||
services.AddScoped<ICatalogViewModelService, CachedCatalogViewModelService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Configuration
|
||||
namespace Microsoft.eShopWeb.Web.Configuration;
|
||||
|
||||
//TODO : replace IMemoryCache with a distributed cache if you are in multi-host scenario
|
||||
public class RevokeAuthenticationEvents : CookieAuthenticationEvents
|
||||
{
|
||||
//TODO : replace IMemoryCache with a distributed cache if you are in multi-host scenario
|
||||
public class RevokeAuthenticationEvents : CookieAuthenticationEvents
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public RevokeAuthenticationEvents(IMemoryCache cache, ILogger<RevokeAuthenticationEvents> logger)
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly ILogger _logger;
|
||||
_cache = cache;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public RevokeAuthenticationEvents(IMemoryCache cache, ILogger<RevokeAuthenticationEvents> logger)
|
||||
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
|
||||
{
|
||||
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))
|
||||
{
|
||||
_cache = cache;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
|
||||
{
|
||||
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))
|
||||
{
|
||||
_logger.LogDebug($"Access has been revoked for: {userId.Value}.");
|
||||
context.RejectPrincipal();
|
||||
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
_logger.LogDebug($"Access has been revoked for: {userId.Value}.");
|
||||
context.RejectPrincipal();
|
||||
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace Microsoft.eShopWeb.Web
|
||||
namespace Microsoft.eShopWeb.Web;
|
||||
|
||||
public static class Constants
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string BASKET_COOKIENAME = "eShop";
|
||||
public const int ITEMS_PER_PAGE = 10;
|
||||
public const string DEFAULT_USERNAME = "Guest";
|
||||
public const string BASKET_ID = "BasketId";
|
||||
}
|
||||
public const string BASKET_COOKIENAME = "eShop";
|
||||
public const int ITEMS_PER_PAGE = 10;
|
||||
public const string DEFAULT_USERNAME = "Guest";
|
||||
public const string BASKET_ID = "BasketId";
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Controllers.Api
|
||||
{
|
||||
// No longer used - shown for reference only if using full controllers instead of Endpoints for APIs
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class BaseApiController : ControllerBase
|
||||
{ }
|
||||
}
|
||||
namespace Microsoft.eShopWeb.Web.Controllers.Api;
|
||||
|
||||
// No longer used - shown for reference only if using full controllers instead of Endpoints for APIs
|
||||
[Route("api/[controller]/[action]")]
|
||||
[ApiController]
|
||||
public class BaseApiController : ControllerBase
|
||||
{ }
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@@ -6,495 +11,489 @@ using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.eShopWeb.Web.Services;
|
||||
using Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Controllers
|
||||
namespace Microsoft.eShopWeb.Web.Controllers;
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
|
||||
[Route("[controller]/[action]")]
|
||||
public class ManageController : Controller
|
||||
{
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
|
||||
[Route("[controller]/[action]")]
|
||||
public class ManageController : Controller
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly IAppLogger<ManageController> _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
|
||||
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
|
||||
public ManageController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
IAppLogger<ManageController> logger,
|
||||
UrlEncoder urlEncoder)
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly IAppLogger<ManageController> _logger;
|
||||
private readonly UrlEncoder _urlEncoder;
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_urlEncoder = urlEncoder;
|
||||
}
|
||||
|
||||
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
public ManageController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IEmailSender emailSender,
|
||||
IAppLogger<ManageController> logger,
|
||||
UrlEncoder urlEncoder)
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MyAccount()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_signInManager = signInManager;
|
||||
_emailSender = emailSender;
|
||||
_logger = logger;
|
||||
_urlEncoder = urlEncoder;
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[TempData]
|
||||
public string StatusMessage { get; set; }
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MyAccount()
|
||||
var model = new IndexViewModel
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
Username = user.UserName,
|
||||
Email = user.Email,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsEmailConfirmed = user.EmailConfirmed,
|
||||
StatusMessage = StatusMessage
|
||||
};
|
||||
|
||||
var model = new IndexViewModel
|
||||
{
|
||||
Username = user.UserName,
|
||||
Email = user.Email,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsEmailConfirmed = user.EmailConfirmed,
|
||||
StatusMessage = StatusMessage
|
||||
};
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> MyAccount(IndexViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> MyAccount(IndexViewModel model)
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var email = user.Email;
|
||||
if (model.Email != email)
|
||||
{
|
||||
var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
|
||||
if (!setEmailResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
|
||||
}
|
||||
}
|
||||
|
||||
var phoneNumber = user.PhoneNumber;
|
||||
if (model.PhoneNumber != phoneNumber)
|
||||
{
|
||||
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
|
||||
if (!setPhoneResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
|
||||
}
|
||||
}
|
||||
|
||||
StatusMessage = "Your profile has been updated";
|
||||
return RedirectToAction(nameof(MyAccount));
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SendVerificationEmail(IndexViewModel model)
|
||||
var email = user.Email;
|
||||
if (model.Email != email)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
|
||||
if (!setEmailResult.Succeeded)
|
||||
{
|
||||
return View(model);
|
||||
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
var email = user.Email;
|
||||
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
|
||||
|
||||
StatusMessage = "Verification email sent. Please check your email.";
|
||||
return RedirectToAction(nameof(MyAccount));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ChangePassword()
|
||||
var phoneNumber = user.PhoneNumber;
|
||||
if (model.PhoneNumber != phoneNumber)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
|
||||
if (!setPhoneResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
|
||||
}
|
||||
}
|
||||
|
||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
||||
if (!hasPassword)
|
||||
{
|
||||
return RedirectToAction(nameof(SetPassword));
|
||||
}
|
||||
StatusMessage = "Your profile has been updated";
|
||||
return RedirectToAction(nameof(MyAccount));
|
||||
}
|
||||
|
||||
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SendVerificationEmail(IndexViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
|
||||
if (!changePasswordResult.Succeeded)
|
||||
{
|
||||
AddErrors(changePasswordResult);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User changed their password successfully.");
|
||||
StatusMessage = "Your password has been changed.";
|
||||
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> SetPassword()
|
||||
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
|
||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||
var email = user.Email;
|
||||
await _emailSender.SendEmailConfirmationAsync(email, callbackUrl);
|
||||
|
||||
StatusMessage = "Verification email sent. Please check your email.";
|
||||
return RedirectToAction(nameof(MyAccount));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ChangePassword()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
||||
|
||||
if (hasPassword)
|
||||
{
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
}
|
||||
|
||||
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
|
||||
return View(model);
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
|
||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
||||
if (!hasPassword)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword);
|
||||
if (!addPasswordResult.Succeeded)
|
||||
{
|
||||
AddErrors(addPasswordResult);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "Your password has been set.";
|
||||
|
||||
return RedirectToAction(nameof(SetPassword));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ExternalLogins()
|
||||
var model = new ChangePasswordViewModel { StatusMessage = StatusMessage };
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
|
||||
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
||||
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
|
||||
.ToList();
|
||||
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
|
||||
model.StatusMessage = StatusMessage;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LinkLogin(string provider)
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
// Request a redirect to the external login provider to link a login for the current user
|
||||
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
|
||||
return new ChallengeResult(provider, properties);
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LinkLoginCallback()
|
||||
var changePasswordResult = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
|
||||
if (!changePasswordResult.Succeeded)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
|
||||
if (info == null)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.AddLoginAsync(user, info);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
StatusMessage = "The external login was added.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "The external login was removed.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> TwoFactorAuthentication()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var model = new TwoFactorAuthenticationViewModel
|
||||
{
|
||||
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null,
|
||||
Is2faEnabled = user.TwoFactorEnabled,
|
||||
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user),
|
||||
};
|
||||
|
||||
AddErrors(changePasswordResult);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Disable2faWarning()
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
_logger.LogInformation("User changed their password successfully.");
|
||||
StatusMessage = "Your password has been changed.";
|
||||
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> SetPassword()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
return View(nameof(Disable2fa));
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Disable2fa()
|
||||
var hasPassword = await _userManager.HasPasswordAsync(user);
|
||||
|
||||
if (hasPassword)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
|
||||
if (!disable2faResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("User with ID {UserId} has disabled 2fa.", user.Id);
|
||||
return RedirectToAction(nameof(TwoFactorAuthentication));
|
||||
return RedirectToAction(nameof(ChangePassword));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> EnableAuthenticator()
|
||||
var model = new SetPasswordViewModel { StatusMessage = StatusMessage };
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> SetPassword(SetPasswordViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
if (string.IsNullOrEmpty(unformattedKey))
|
||||
{
|
||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
var model = new EnableAuthenticatorViewModel
|
||||
{
|
||||
SharedKey = FormatKey(unformattedKey),
|
||||
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
// Strip spaces and hypens
|
||||
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
|
||||
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
|
||||
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
|
||||
|
||||
if (!is2faTokenValid)
|
||||
{
|
||||
ModelState.AddModelError("model.TwoFactorCode", "Verification code is invalid.");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, true);
|
||||
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
|
||||
return RedirectToAction(nameof(GenerateRecoveryCodes));
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ResetAuthenticatorWarning()
|
||||
var addPasswordResult = await _userManager.AddPasswordAsync(user, model.NewPassword);
|
||||
if (!addPasswordResult.Succeeded)
|
||||
{
|
||||
return View(nameof(ResetAuthenticator));
|
||||
AddErrors(addPasswordResult);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ResetAuthenticator()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "Your password has been set.";
|
||||
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, false);
|
||||
return RedirectToAction(nameof(SetPassword));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> ExternalLogins()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var model = new ExternalLoginsViewModel { CurrentLogins = await _userManager.GetLoginsAsync(user) };
|
||||
model.OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
|
||||
.Where(auth => model.CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
|
||||
.ToList();
|
||||
model.ShowRemoveButton = await _userManager.HasPasswordAsync(user) || model.CurrentLogins.Count > 1;
|
||||
model.StatusMessage = StatusMessage;
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> LinkLogin(string provider)
|
||||
{
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
// Request a redirect to the external login provider to link a login for the current user
|
||||
var redirectUrl = Url.Action(nameof(LinkLoginCallback));
|
||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
|
||||
return new ChallengeResult(provider, properties);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> LinkLoginCallback()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var info = await _signInManager.GetExternalLoginInfoAsync(user.Id);
|
||||
if (info == null)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred loading external login info for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.AddLoginAsync(user, info);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred adding external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
// Clear the existing external cookie to ensure a clean login process
|
||||
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
|
||||
|
||||
StatusMessage = "The external login was added.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> RemoveLogin(RemoveLoginViewModel model)
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var result = await _userManager.RemoveLoginAsync(user, model.LoginProvider, model.ProviderKey);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred removing external login for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
StatusMessage = "The external login was removed.";
|
||||
return RedirectToAction(nameof(ExternalLogins));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> TwoFactorAuthentication()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var model = new TwoFactorAuthenticationViewModel
|
||||
{
|
||||
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null,
|
||||
Is2faEnabled = user.TwoFactorEnabled,
|
||||
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user),
|
||||
};
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Disable2faWarning()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
return View(nameof(Disable2fa));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Disable2fa()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
|
||||
if (!disable2faResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occured disabling 2FA for user with ID '{user.Id}'.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("User with ID {UserId} has disabled 2fa.", user.Id);
|
||||
return RedirectToAction(nameof(TwoFactorAuthentication));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> EnableAuthenticator()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
if (string.IsNullOrEmpty(unformattedKey))
|
||||
{
|
||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
||||
_logger.LogInformation("User with id '{UserId}' has reset their authentication app key.", user.Id);
|
||||
|
||||
return RedirectToAction(nameof(EnableAuthenticator));
|
||||
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes()
|
||||
var model = new EnableAuthenticatorViewModel
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
SharedKey = FormatKey(unformattedKey),
|
||||
AuthenticatorUri = GenerateQrCodeUri(user.Email, unformattedKey)
|
||||
};
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
|
||||
|
||||
_logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> EnableAuthenticator(EnableAuthenticatorViewModel model)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private void AddErrors(IdentityResult result)
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
private string FormatKey(string unformattedKey)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
int currentPosition = 0;
|
||||
while (currentPosition + 4 < unformattedKey.Length)
|
||||
{
|
||||
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
|
||||
currentPosition += 4;
|
||||
}
|
||||
if (currentPosition < unformattedKey.Length)
|
||||
{
|
||||
result.Append(unformattedKey.Substring(currentPosition));
|
||||
}
|
||||
// Strip spaces and hypens
|
||||
var verificationCode = model.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
|
||||
|
||||
return result.ToString().ToLowerInvariant();
|
||||
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
|
||||
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
|
||||
|
||||
if (!is2faTokenValid)
|
||||
{
|
||||
ModelState.AddModelError("model.TwoFactorCode", "Verification code is invalid.");
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, true);
|
||||
_logger.LogInformation("User with ID {UserId} has enabled 2FA with an authenticator app.", user.Id);
|
||||
return RedirectToAction(nameof(GenerateRecoveryCodes));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public IActionResult ResetAuthenticatorWarning()
|
||||
{
|
||||
return View(nameof(ResetAuthenticator));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> ResetAuthenticator()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
return string.Format(
|
||||
AuthenticatorUriFormat,
|
||||
_urlEncoder.Encode("eShopOnWeb"),
|
||||
_urlEncoder.Encode(email),
|
||||
unformattedKey);
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
await _userManager.SetTwoFactorEnabledAsync(user, false);
|
||||
await _userManager.ResetAuthenticatorKeyAsync(user);
|
||||
_logger.LogInformation("User with id '{UserId}' has reset their authentication app key.", user.Id);
|
||||
|
||||
return RedirectToAction(nameof(EnableAuthenticator));
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GenerateRecoveryCodes()
|
||||
{
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
throw new ApplicationException($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
|
||||
}
|
||||
|
||||
if (!user.TwoFactorEnabled)
|
||||
{
|
||||
throw new ApplicationException($"Cannot generate recovery codes for user with ID '{user.Id}' as they do not have 2FA enabled.");
|
||||
}
|
||||
|
||||
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
|
||||
var model = new GenerateRecoveryCodesViewModel { RecoveryCodes = recoveryCodes.ToArray() };
|
||||
|
||||
_logger.LogInformation("User with ID {UserId} has generated new 2FA recovery codes.", user.Id);
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private void AddErrors(IdentityResult result)
|
||||
{
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
}
|
||||
|
||||
private string FormatKey(string unformattedKey)
|
||||
{
|
||||
var result = new StringBuilder();
|
||||
int currentPosition = 0;
|
||||
while (currentPosition + 4 < unformattedKey.Length)
|
||||
{
|
||||
result.Append(unformattedKey.Substring(currentPosition, 4)).Append(" ");
|
||||
currentPosition += 4;
|
||||
}
|
||||
if (currentPosition < unformattedKey.Length)
|
||||
{
|
||||
result.Append(unformattedKey.Substring(currentPosition));
|
||||
}
|
||||
|
||||
return result.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
private string GenerateQrCodeUri(string email, string unformattedKey)
|
||||
{
|
||||
return string.Format(
|
||||
AuthenticatorUriFormat,
|
||||
_urlEncoder.Encode("eShopOnWeb"),
|
||||
_urlEncoder.Encode(email),
|
||||
unformattedKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,42 @@
|
||||
using MediatR;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.Web.Features.MyOrders;
|
||||
using Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Controllers
|
||||
namespace Microsoft.eShopWeb.Web.Controllers;
|
||||
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
|
||||
[Route("[controller]/[action]")]
|
||||
public class OrderController : Controller
|
||||
{
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
[Authorize] // Controllers that mainly require Authorization still use Controller/View; other pages use Pages
|
||||
[Route("[controller]/[action]")]
|
||||
public class OrderController : Controller
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public OrderController(IMediator mediator)
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
public OrderController(IMediator mediator)
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MyOrders()
|
||||
{
|
||||
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpGet("{orderId}")]
|
||||
public async Task<IActionResult> Detail(int orderId)
|
||||
{
|
||||
var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));
|
||||
|
||||
if (viewModel == null)
|
||||
{
|
||||
_mediator = mediator;
|
||||
return BadRequest("No such order found for this user.");
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> MyOrders()
|
||||
{
|
||||
var viewModel = await _mediator.Send(new GetMyOrders(User.Identity.Name));
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[HttpGet("{orderId}")]
|
||||
public async Task<IActionResult> Detail(int orderId)
|
||||
{
|
||||
var viewModel = await _mediator.Send(new GetOrderDetails(User.Identity.Name, orderId));
|
||||
|
||||
if (viewModel == null)
|
||||
{
|
||||
return BadRequest("No such order found for this user.");
|
||||
}
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
return View(viewModel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +1,74 @@
|
||||
using BlazorShared.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BlazorShared.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Controllers
|
||||
namespace Microsoft.eShopWeb.Web.Controllers;
|
||||
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
[Route("[controller]")]
|
||||
[ApiController]
|
||||
public class UserController : ControllerBase
|
||||
private readonly ITokenClaimsService _tokenClaimsService;
|
||||
|
||||
public UserController(ITokenClaimsService tokenClaimsService)
|
||||
{
|
||||
private readonly ITokenClaimsService _tokenClaimsService;
|
||||
|
||||
public UserController(ITokenClaimsService tokenClaimsService)
|
||||
{
|
||||
_tokenClaimsService = tokenClaimsService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetCurrentUser() =>
|
||||
Ok(User.Identity.IsAuthenticated ? await CreateUserInfo(User) : UserInfo.Anonymous);
|
||||
|
||||
private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (!claimsPrincipal.Identity.IsAuthenticated)
|
||||
{
|
||||
return UserInfo.Anonymous;
|
||||
}
|
||||
|
||||
var userInfo = new UserInfo
|
||||
{
|
||||
IsAuthenticated = true
|
||||
};
|
||||
|
||||
if (claimsPrincipal.Identity is ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
userInfo.NameClaimType = claimsIdentity.NameClaimType;
|
||||
userInfo.RoleClaimType = claimsIdentity.RoleClaimType;
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfo.NameClaimType = "name";
|
||||
userInfo.RoleClaimType = "role";
|
||||
}
|
||||
|
||||
if (claimsPrincipal.Claims.Any())
|
||||
{
|
||||
var claims = new List<ClaimValue>();
|
||||
var nameClaims = claimsPrincipal.FindAll(userInfo.NameClaimType);
|
||||
foreach (var claim in nameClaims)
|
||||
{
|
||||
claims.Add(new ClaimValue(userInfo.NameClaimType, claim.Value));
|
||||
}
|
||||
|
||||
foreach (var claim in claimsPrincipal.Claims.Except(nameClaims))
|
||||
{
|
||||
claims.Add(new ClaimValue(claim.Type, claim.Value));
|
||||
}
|
||||
|
||||
userInfo.Claims = claims;
|
||||
}
|
||||
|
||||
var token = await _tokenClaimsService.GetTokenAsync(claimsPrincipal.Identity.Name);
|
||||
userInfo.Token = token;
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
_tokenClaimsService = tokenClaimsService;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> GetCurrentUser() =>
|
||||
Ok(User.Identity.IsAuthenticated ? await CreateUserInfo(User) : UserInfo.Anonymous);
|
||||
|
||||
private async Task<UserInfo> CreateUserInfo(ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (!claimsPrincipal.Identity.IsAuthenticated)
|
||||
{
|
||||
return UserInfo.Anonymous;
|
||||
}
|
||||
|
||||
var userInfo = new UserInfo
|
||||
{
|
||||
IsAuthenticated = true
|
||||
};
|
||||
|
||||
if (claimsPrincipal.Identity is ClaimsIdentity claimsIdentity)
|
||||
{
|
||||
userInfo.NameClaimType = claimsIdentity.NameClaimType;
|
||||
userInfo.RoleClaimType = claimsIdentity.RoleClaimType;
|
||||
}
|
||||
else
|
||||
{
|
||||
userInfo.NameClaimType = "name";
|
||||
userInfo.RoleClaimType = "role";
|
||||
}
|
||||
|
||||
if (claimsPrincipal.Claims.Any())
|
||||
{
|
||||
var claims = new List<ClaimValue>();
|
||||
var nameClaims = claimsPrincipal.FindAll(userInfo.NameClaimType);
|
||||
foreach (var claim in nameClaims)
|
||||
{
|
||||
claims.Add(new ClaimValue(userInfo.NameClaimType, claim.Value));
|
||||
}
|
||||
|
||||
foreach (var claim in claimsPrincipal.Claims.Except(nameClaims))
|
||||
{
|
||||
claims.Add(new ClaimValue(claim.Type, claim.Value));
|
||||
}
|
||||
|
||||
userInfo.Claims = claims;
|
||||
}
|
||||
|
||||
var token = await _tokenClaimsService.GetTokenAsync(claimsPrincipal.Identity.Name);
|
||||
userInfo.Token = token;
|
||||
|
||||
return userInfo;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Extensions
|
||||
namespace Microsoft.eShopWeb.Web.Extensions;
|
||||
|
||||
public static class CacheHelpers
|
||||
{
|
||||
public static class CacheHelpers
|
||||
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30);
|
||||
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}";
|
||||
|
||||
public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromSeconds(30);
|
||||
private static readonly string _itemsKeyTemplate = "items-{0}-{1}-{2}-{3}";
|
||||
return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId);
|
||||
}
|
||||
|
||||
public static string GenerateCatalogItemCacheKey(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
return string.Format(_itemsKeyTemplate, pageIndex, itemsPage, brandId, typeId);
|
||||
}
|
||||
public static string GenerateBrandsCacheKey()
|
||||
{
|
||||
return "brands";
|
||||
}
|
||||
|
||||
public static string GenerateBrandsCacheKey()
|
||||
{
|
||||
return "brands";
|
||||
}
|
||||
|
||||
public static string GenerateTypesCacheKey()
|
||||
{
|
||||
return "types";
|
||||
}
|
||||
public static string GenerateTypesCacheKey()
|
||||
{
|
||||
return "types";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
public static class EmailSenderExtensions
|
||||
{
|
||||
public static class EmailSenderExtensions
|
||||
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
|
||||
{
|
||||
public static Task SendEmailConfirmationAsync(this IEmailSender emailSender, string email, string link)
|
||||
{
|
||||
return emailSender.SendEmailAsync(email, "Confirm your email",
|
||||
$"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>link</a>");
|
||||
}
|
||||
return emailSender.SendEmailAsync(email, "Confirm your email",
|
||||
$"Please confirm your account by clicking this link: <a href='{HtmlEncoder.Default.Encode(link)}'>link</a>");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
namespace Microsoft.AspNetCore.Mvc;
|
||||
|
||||
public static class UrlHelperExtensions
|
||||
{
|
||||
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",
|
||||
controller: "ConfirmEmail",
|
||||
values: new { userId, code },
|
||||
protocol: scheme);
|
||||
}
|
||||
return urlHelper.Action(
|
||||
action: "GET",
|
||||
controller: "ConfirmEmail",
|
||||
values: new { userId, code },
|
||||
protocol: scheme);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders;
|
||||
|
||||
public class GetMyOrders : IRequest<IEnumerable<OrderViewModel>>
|
||||
{
|
||||
public class GetMyOrders : IRequest<IEnumerable<OrderViewModel>>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public string UserName { get; set; }
|
||||
|
||||
public GetMyOrders(string userName)
|
||||
{
|
||||
UserName = userName;
|
||||
}
|
||||
public GetMyOrders(string userName)
|
||||
{
|
||||
UserName = userName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,45 +1,44 @@
|
||||
using MediatR;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders
|
||||
namespace Microsoft.eShopWeb.Web.Features.MyOrders;
|
||||
|
||||
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
|
||||
{
|
||||
public class GetMyOrdersHandler : IRequestHandler<GetMyOrders, IEnumerable<OrderViewModel>>
|
||||
private readonly IReadRepository<Order> _orderRepository;
|
||||
|
||||
public GetMyOrdersHandler(IReadRepository<Order> orderRepository)
|
||||
{
|
||||
private readonly IReadRepository<Order> _orderRepository;
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public GetMyOrdersHandler(IReadRepository<Order> orderRepository)
|
||||
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
|
||||
var orders = await _orderRepository.ListAsync(specification, cancellationToken);
|
||||
|
||||
return orders.Select(o => new OrderViewModel
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<OrderViewModel>> Handle(GetMyOrders request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var specification = new CustomerOrdersWithItemsSpecification(request.UserName);
|
||||
var orders = await _orderRepository.ListAsync(specification, cancellationToken);
|
||||
|
||||
return orders.Select(o => new OrderViewModel
|
||||
OrderDate = o.OrderDate,
|
||||
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
|
||||
{
|
||||
OrderDate = o.OrderDate,
|
||||
OrderItems = o.OrderItems?.Select(oi => new OrderItemViewModel()
|
||||
{
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = o.Id,
|
||||
ShippingAddress = o.ShipToAddress,
|
||||
Total = o.Total()
|
||||
});
|
||||
}
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = o.Id,
|
||||
ShippingAddress = o.ShipToAddress,
|
||||
Total = o.Total()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails
|
||||
{
|
||||
public class GetOrderDetails : IRequest<OrderViewModel>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public int OrderId { get; set; }
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
|
||||
public GetOrderDetails(string userName, int orderId)
|
||||
{
|
||||
UserName = userName;
|
||||
OrderId = orderId;
|
||||
}
|
||||
public class GetOrderDetails : IRequest<OrderViewModel>
|
||||
{
|
||||
public string UserName { get; set; }
|
||||
public int OrderId { get; set; }
|
||||
|
||||
public GetOrderDetails(string userName, int orderId)
|
||||
{
|
||||
UserName = userName;
|
||||
OrderId = orderId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,49 +1,48 @@
|
||||
using MediatR;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails
|
||||
namespace Microsoft.eShopWeb.Web.Features.OrderDetails;
|
||||
|
||||
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel>
|
||||
{
|
||||
public class GetOrderDetailsHandler : IRequestHandler<GetOrderDetails, OrderViewModel>
|
||||
private readonly IReadRepository<Order> _orderRepository;
|
||||
|
||||
public GetOrderDetailsHandler(IReadRepository<Order> orderRepository)
|
||||
{
|
||||
private readonly IReadRepository<Order> _orderRepository;
|
||||
_orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public GetOrderDetailsHandler(IReadRepository<Order> orderRepository)
|
||||
public async Task<OrderViewModel> Handle(GetOrderDetails request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var spec = new OrderWithItemsByIdSpec(request.OrderId);
|
||||
var order = await _orderRepository.GetBySpecAsync(spec, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
{
|
||||
_orderRepository = orderRepository;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<OrderViewModel> Handle(GetOrderDetails request,
|
||||
CancellationToken cancellationToken)
|
||||
return new OrderViewModel
|
||||
{
|
||||
var spec = new OrderWithItemsByIdSpec(request.OrderId);
|
||||
var order = await _orderRepository.GetBySpecAsync(spec, cancellationToken);
|
||||
|
||||
if (order == null)
|
||||
OrderDate = order.OrderDate,
|
||||
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new OrderViewModel
|
||||
{
|
||||
OrderDate = order.OrderDate,
|
||||
OrderItems = order.OrderItems.Select(oi => new OrderItemViewModel
|
||||
{
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = order.Id,
|
||||
ShippingAddress = order.ShipToAddress,
|
||||
Total = order.Total()
|
||||
};
|
||||
}
|
||||
PictureUrl = oi.ItemOrdered.PictureUri,
|
||||
ProductId = oi.ItemOrdered.CatalogItemId,
|
||||
ProductName = oi.ItemOrdered.ProductName,
|
||||
UnitPrice = oi.UnitPrice,
|
||||
Units = oi.Units
|
||||
}).ToList(),
|
||||
OrderNumber = order.Id,
|
||||
ShippingAddress = order.ShipToAddress,
|
||||
Total = order.Total()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,39 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.HealthChecks
|
||||
namespace Microsoft.eShopWeb.Web.HealthChecks;
|
||||
|
||||
public class ApiHealthCheck : IHealthCheck
|
||||
{
|
||||
public class ApiHealthCheck : IHealthCheck
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
|
||||
public ApiHealthCheck(IHttpContextAccessor httpContextAccessor, LinkGenerator linkGenerator)
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_linkGenerator = linkGenerator;
|
||||
}
|
||||
|
||||
public ApiHealthCheck(IHttpContextAccessor httpContextAccessor, LinkGenerator linkGenerator)
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext.Request;
|
||||
|
||||
string apiLink = _linkGenerator.GetPathByAction("List", "Catalog");
|
||||
string myUrl = request.Scheme + "://" + request.Host.ToString() + apiLink;
|
||||
var client = new HttpClient();
|
||||
var response = await client.GetAsync(myUrl);
|
||||
var pageContents = await response.Content.ReadAsStringAsync();
|
||||
if (pageContents.Contains(".NET Bot Black Sweatshirt"))
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_linkGenerator = linkGenerator;
|
||||
return HealthCheckResult.Healthy("The check indicates a healthy result.");
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext.Request;
|
||||
|
||||
string apiLink = _linkGenerator.GetPathByAction("List", "Catalog");
|
||||
string myUrl = request.Scheme + "://" + request.Host.ToString() + apiLink;
|
||||
var client = new HttpClient();
|
||||
var response = await client.GetAsync(myUrl);
|
||||
var pageContents = await response.Content.ReadAsStringAsync();
|
||||
if (pageContents.Contains(".NET Bot Black Sweatshirt"))
|
||||
{
|
||||
return HealthCheckResult.Healthy("The check indicates a healthy result.");
|
||||
}
|
||||
|
||||
return HealthCheckResult.Unhealthy("The check indicates an unhealthy result.");
|
||||
}
|
||||
return HealthCheckResult.Unhealthy("The check indicates an unhealthy result.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,35 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.HealthChecks
|
||||
namespace Microsoft.eShopWeb.Web.HealthChecks;
|
||||
|
||||
public class HomePageHealthCheck : IHealthCheck
|
||||
{
|
||||
public class HomePageHealthCheck : IHealthCheck
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public HomePageHealthCheck(IHttpContextAccessor httpContextAccessor)
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public HomePageHealthCheck(IHttpContextAccessor httpContextAccessor)
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext.Request;
|
||||
string myUrl = request.Scheme + "://" + request.Host.ToString();
|
||||
|
||||
var client = new HttpClient();
|
||||
var response = await client.GetAsync(myUrl);
|
||||
var pageContents = await response.Content.ReadAsStringAsync();
|
||||
if (pageContents.Contains(".NET Bot Black Sweatshirt"))
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
return HealthCheckResult.Healthy("The check indicates a healthy result.");
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(
|
||||
HealthCheckContext context,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
var request = _httpContextAccessor.HttpContext.Request;
|
||||
string myUrl = request.Scheme + "://" + request.Host.ToString();
|
||||
|
||||
var client = new HttpClient();
|
||||
var response = await client.GetAsync(myUrl);
|
||||
var pageContents = await response.Content.ReadAsStringAsync();
|
||||
if (pageContents.Contains(".NET Bot Black Sweatshirt"))
|
||||
{
|
||||
return HealthCheckResult.Healthy("The check indicates a healthy result.");
|
||||
}
|
||||
|
||||
return HealthCheckResult.Unhealthy("The check indicates an unhealthy result.");
|
||||
}
|
||||
return HealthCheckResult.Unhealthy("The check indicates an unhealthy result.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
using Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Interfaces
|
||||
namespace Microsoft.eShopWeb.Web.Interfaces;
|
||||
|
||||
public interface IBasketViewModelService
|
||||
{
|
||||
public interface IBasketViewModelService
|
||||
{
|
||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||
Task<BasketViewModel> GetOrCreateBasketForUser(string userName);
|
||||
|
||||
Task<int> CountTotalBasketItems(string username);
|
||||
Task<int> CountTotalBasketItems(string username);
|
||||
|
||||
Task<BasketViewModel> Map(Basket basket);
|
||||
}
|
||||
Task<BasketViewModel> Map(Basket basket);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Interfaces
|
||||
namespace Microsoft.eShopWeb.Web.Interfaces;
|
||||
|
||||
public interface ICatalogItemViewModelService
|
||||
{
|
||||
public interface ICatalogItemViewModelService
|
||||
{
|
||||
Task UpdateCatalogItem(CatalogItemViewModel viewModel);
|
||||
}
|
||||
Task UpdateCatalogItem(CatalogItemViewModel viewModel);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
public interface ICatalogViewModelService
|
||||
{
|
||||
public interface ICatalogViewModelService
|
||||
{
|
||||
Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId);
|
||||
Task<IEnumerable<SelectListItem>> GetBrands();
|
||||
Task<IEnumerable<SelectListItem>> GetTypes();
|
||||
}
|
||||
Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId);
|
||||
Task<IEnumerable<SelectListItem>> GetBrands();
|
||||
Task<IEnumerable<SelectListItem>> GetTypes();
|
||||
}
|
||||
|
||||
@@ -1,39 +1,38 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Admin
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Admin;
|
||||
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
|
||||
public class EditCatalogItemModel : PageModel
|
||||
{
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
|
||||
public class EditCatalogItemModel : PageModel
|
||||
private readonly ICatalogItemViewModelService _catalogItemViewModelService;
|
||||
|
||||
public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService)
|
||||
{
|
||||
private readonly ICatalogItemViewModelService _catalogItemViewModelService;
|
||||
_catalogItemViewModelService = catalogItemViewModelService;
|
||||
}
|
||||
|
||||
public EditCatalogItemModel(ICatalogItemViewModelService catalogItemViewModelService)
|
||||
[BindProperty]
|
||||
public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel();
|
||||
|
||||
public void OnGet(CatalogItemViewModel catalogModel)
|
||||
{
|
||||
CatalogModel = catalogModel;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
_catalogItemViewModelService = catalogItemViewModelService;
|
||||
await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel);
|
||||
}
|
||||
|
||||
[BindProperty]
|
||||
public CatalogItemViewModel CatalogModel { get; set; } = new CatalogItemViewModel();
|
||||
|
||||
public void OnGet(CatalogItemViewModel catalogModel)
|
||||
{
|
||||
CatalogModel = catalogModel;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPostAsync()
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
await _catalogItemViewModelService.UpdateCatalogItem(CatalogModel);
|
||||
}
|
||||
|
||||
return RedirectToPage("/Admin/Index");
|
||||
}
|
||||
return RedirectToPage("/Admin/Index");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Constants;
|
||||
using Microsoft.eShopWeb.Web.Extensions;
|
||||
using Microsoft.eShopWeb.Web.Services;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Admin
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Admin;
|
||||
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
[Authorize(Roles = BlazorShared.Authorization.Constants.Roles.ADMINISTRATORS)]
|
||||
public class IndexModel : PageModel
|
||||
public IndexModel()
|
||||
{
|
||||
public IndexModel()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket
|
||||
{
|
||||
public class BasketItemViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CatalogItemId { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Quantity must be bigger than 0")]
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
public class BasketItemViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int CatalogItemId { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal OldUnitPrice { get; set; }
|
||||
|
||||
[Range(0, int.MaxValue, ErrorMessage = "Quantity must be bigger than 0")]
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,17 +2,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket
|
||||
{
|
||||
public class BasketViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>();
|
||||
public string BuyerId { get; set; }
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
|
||||
public decimal Total()
|
||||
{
|
||||
return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2);
|
||||
}
|
||||
public class BasketViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public List<BasketItemViewModel> Items { get; set; } = new List<BasketItemViewModel>();
|
||||
public string BuyerId { get; set; }
|
||||
|
||||
public decimal Total()
|
||||
{
|
||||
return Math.Round(Items.Sum(x => x.UnitPrice * x.Quantity), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -9,94 +13,89 @@ using Microsoft.eShopWeb.ApplicationCore.Exceptions;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
|
||||
[Authorize]
|
||||
public class CheckoutModel : PageModel
|
||||
{
|
||||
[Authorize]
|
||||
public class CheckoutModel : PageModel
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IOrderService _orderService;
|
||||
private string _username = null;
|
||||
private readonly IBasketViewModelService _basketViewModelService;
|
||||
private readonly IAppLogger<CheckoutModel> _logger;
|
||||
|
||||
public CheckoutModel(IBasketService basketService,
|
||||
IBasketViewModelService basketViewModelService,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOrderService orderService,
|
||||
IAppLogger<CheckoutModel> logger)
|
||||
{
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
private readonly IOrderService _orderService;
|
||||
private string _username = null;
|
||||
private readonly IBasketViewModelService _basketViewModelService;
|
||||
private readonly IAppLogger<CheckoutModel> _logger;
|
||||
_basketService = basketService;
|
||||
_signInManager = signInManager;
|
||||
_orderService = orderService;
|
||||
_basketViewModelService = basketViewModelService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public CheckoutModel(IBasketService basketService,
|
||||
IBasketViewModelService basketViewModelService,
|
||||
SignInManager<ApplicationUser> signInManager,
|
||||
IOrderService orderService,
|
||||
IAppLogger<CheckoutModel> logger)
|
||||
{
|
||||
_basketService = basketService;
|
||||
_signInManager = signInManager;
|
||||
_orderService = orderService;
|
||||
_basketViewModelService = basketViewModelService;
|
||||
_logger = logger;
|
||||
}
|
||||
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
|
||||
|
||||
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
|
||||
public async Task OnGet()
|
||||
{
|
||||
await SetBasketModelAsync();
|
||||
}
|
||||
|
||||
public async Task OnGet()
|
||||
public async Task<IActionResult> OnPost(IEnumerable<BasketItemViewModel> items)
|
||||
{
|
||||
try
|
||||
{
|
||||
await SetBasketModelAsync();
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
||||
await _basketService.SetQuantities(BasketModel.Id, updateModel);
|
||||
await _orderService.CreateOrderAsync(BasketModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
|
||||
await _basketService.DeleteBasketAsync(BasketModel.Id);
|
||||
}
|
||||
catch (EmptyBasketOnCheckoutException emptyBasketOnCheckoutException)
|
||||
{
|
||||
//Redirect to Empty Basket page
|
||||
_logger.LogWarning(emptyBasketOnCheckoutException.Message);
|
||||
return RedirectToPage("/Basket/Index");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(IEnumerable<BasketItemViewModel> items)
|
||||
return RedirectToPage("Success");
|
||||
}
|
||||
|
||||
private async Task SetBasketModelAsync()
|
||||
{
|
||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||
{
|
||||
try
|
||||
{
|
||||
await SetBasketModelAsync();
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
||||
await _basketService.SetQuantities(BasketModel.Id, updateModel);
|
||||
await _orderService.CreateOrderAsync(BasketModel.Id, new Address("123 Main St.", "Kent", "OH", "United States", "44240"));
|
||||
await _basketService.DeleteBasketAsync(BasketModel.Id);
|
||||
}
|
||||
catch (EmptyBasketOnCheckoutException emptyBasketOnCheckoutException)
|
||||
{
|
||||
//Redirect to Empty Basket page
|
||||
_logger.LogWarning(emptyBasketOnCheckoutException.Message);
|
||||
return RedirectToPage("/Basket/Index");
|
||||
}
|
||||
|
||||
return RedirectToPage("Success");
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
|
||||
}
|
||||
|
||||
private async Task SetBasketModelAsync()
|
||||
else
|
||||
{
|
||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||
{
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(User.Identity.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
GetOrSetBasketCookieAndUserName();
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetOrSetBasketCookieAndUserName()
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
_username = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
}
|
||||
if (_username != null) return;
|
||||
|
||||
_username = Guid.NewGuid().ToString();
|
||||
var cookieOptions = new CookieOptions();
|
||||
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
||||
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);
|
||||
GetOrSetBasketCookieAndUserName();
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(_username);
|
||||
}
|
||||
}
|
||||
|
||||
private void GetOrSetBasketCookieAndUserName()
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
_username = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
}
|
||||
if (_username != null) return;
|
||||
|
||||
_username = Guid.NewGuid().ToString();
|
||||
var cookieOptions = new CookieOptions();
|
||||
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
||||
Response.Cookies.Append(Constants.BASKET_COOKIENAME, _username, cookieOptions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,93 +1,92 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IBasketViewModelService _basketViewModelService;
|
||||
|
||||
public IndexModel(IBasketService basketService,
|
||||
IBasketViewModelService basketViewModelService)
|
||||
{
|
||||
private readonly IBasketService _basketService;
|
||||
private readonly IBasketViewModelService _basketViewModelService;
|
||||
_basketService = basketService;
|
||||
_basketViewModelService = basketViewModelService;
|
||||
}
|
||||
|
||||
public IndexModel(IBasketService basketService,
|
||||
IBasketViewModelService basketViewModelService)
|
||||
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
|
||||
|
||||
public async Task OnGet()
|
||||
{
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)
|
||||
{
|
||||
if (productDetails?.Id == null)
|
||||
{
|
||||
_basketService = basketService;
|
||||
_basketViewModelService = basketViewModelService;
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
public BasketViewModel BasketModel { get; set; } = new BasketViewModel();
|
||||
var username = GetOrSetBasketCookieAndUserName();
|
||||
var basket = await _basketService.AddItemToBasket(username,
|
||||
productDetails.Id, productDetails.Price);
|
||||
|
||||
public async Task OnGet()
|
||||
BasketModel = await _basketViewModelService.Map(basket);
|
||||
|
||||
return RedirectToPage();
|
||||
}
|
||||
|
||||
public async Task OnPostUpdate(IEnumerable<BasketItemViewModel> items)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
BasketModel = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||
return;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> OnPost(CatalogItemViewModel productDetails)
|
||||
var basketView = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
||||
var basket = await _basketService.SetQuantities(basketView.Id, updateModel);
|
||||
BasketModel = await _basketViewModelService.Map(basket);
|
||||
}
|
||||
|
||||
private string GetOrSetBasketCookieAndUserName()
|
||||
{
|
||||
string userName = null;
|
||||
|
||||
if (Request.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
if (productDetails?.Id == null)
|
||||
{
|
||||
return RedirectToPage("/Index");
|
||||
}
|
||||
|
||||
var username = GetOrSetBasketCookieAndUserName();
|
||||
var basket = await _basketService.AddItemToBasket(username,
|
||||
productDetails.Id, productDetails.Price);
|
||||
|
||||
BasketModel = await _basketViewModelService.Map(basket);
|
||||
|
||||
return RedirectToPage();
|
||||
return Request.HttpContext.User.Identity.Name;
|
||||
}
|
||||
|
||||
public async Task OnPostUpdate(IEnumerable<BasketItemViewModel> items)
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
userName = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
|
||||
if (!Request.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var basketView = await _basketViewModelService.GetOrCreateBasketForUser(GetOrSetBasketCookieAndUserName());
|
||||
var updateModel = items.ToDictionary(b => b.Id.ToString(), b => b.Quantity);
|
||||
var basket = await _basketService.SetQuantities(basketView.Id, updateModel);
|
||||
BasketModel = await _basketViewModelService.Map(basket);
|
||||
}
|
||||
|
||||
private string GetOrSetBasketCookieAndUserName()
|
||||
{
|
||||
string userName = null;
|
||||
|
||||
if (Request.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
return Request.HttpContext.User.Identity.Name;
|
||||
}
|
||||
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
userName = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
|
||||
if (!Request.HttpContext.User.Identity.IsAuthenticated)
|
||||
if (!Guid.TryParse(userName, out var _))
|
||||
{
|
||||
if (!Guid.TryParse(userName, out var _))
|
||||
{
|
||||
userName = null;
|
||||
}
|
||||
userName = null;
|
||||
}
|
||||
}
|
||||
if (userName != null) return userName;
|
||||
|
||||
userName = Guid.NewGuid().ToString();
|
||||
var cookieOptions = new CookieOptions { IsEssential = true };
|
||||
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
||||
Response.Cookies.Append(Constants.BASKET_COOKIENAME, userName, cookieOptions);
|
||||
|
||||
return userName;
|
||||
}
|
||||
if (userName != null) return userName;
|
||||
|
||||
userName = Guid.NewGuid().ToString();
|
||||
var cookieOptions = new CookieOptions { IsEssential = true };
|
||||
cookieOptions.Expires = DateTime.Today.AddYears(10);
|
||||
Response.Cookies.Append(Constants.BASKET_COOKIENAME, userName, cookieOptions);
|
||||
|
||||
return userName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,13 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket
|
||||
{
|
||||
[Authorize]
|
||||
public class SuccessModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
|
||||
[Authorize]
|
||||
public class SuccessModel : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages
|
||||
namespace Microsoft.eShopWeb.Web.Pages;
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public class ErrorModel : PageModel
|
||||
{
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public class ErrorModel : PageModel
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
public void OnGet()
|
||||
{
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Microsoft.eShopWeb.Web.Services;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages
|
||||
namespace Microsoft.eShopWeb.Web.Pages;
|
||||
|
||||
public class IndexModel : PageModel
|
||||
{
|
||||
public class IndexModel : PageModel
|
||||
private readonly ICatalogViewModelService _catalogViewModelService;
|
||||
|
||||
public IndexModel(ICatalogViewModelService catalogViewModelService)
|
||||
{
|
||||
private readonly ICatalogViewModelService _catalogViewModelService;
|
||||
_catalogViewModelService = catalogViewModelService;
|
||||
}
|
||||
|
||||
public IndexModel(ICatalogViewModelService catalogViewModelService)
|
||||
{
|
||||
_catalogViewModelService = catalogViewModelService;
|
||||
}
|
||||
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();
|
||||
|
||||
public CatalogIndexViewModel CatalogModel { get; set; } = new CatalogIndexViewModel();
|
||||
|
||||
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
|
||||
{
|
||||
CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
|
||||
}
|
||||
public async Task OnGet(CatalogIndexViewModel catalogModel, int? pageId)
|
||||
{
|
||||
CatalogModel = await _catalogViewModelService.GetCatalogItems(pageId ?? 0, Constants.ITEMS_PER_PAGE, catalogModel.BrandFilterApplied, catalogModel.TypesFilterApplied);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages
|
||||
namespace Microsoft.eShopWeb.Web.Pages;
|
||||
|
||||
public class PrivacyModel : PageModel
|
||||
{
|
||||
public class PrivacyModel : PageModel
|
||||
public void OnGet()
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +1,59 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent
|
||||
namespace Microsoft.eShopWeb.Web.Pages.Shared.Components.BasketComponent;
|
||||
|
||||
public class Basket : ViewComponent
|
||||
{
|
||||
public class Basket : ViewComponent
|
||||
private readonly IBasketViewModelService _basketService;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
|
||||
public Basket(IBasketViewModelService basketService,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
{
|
||||
private readonly IBasketViewModelService _basketService;
|
||||
private readonly SignInManager<ApplicationUser> _signInManager;
|
||||
_basketService = basketService;
|
||||
_signInManager = signInManager;
|
||||
}
|
||||
|
||||
public Basket(IBasketViewModelService basketService,
|
||||
SignInManager<ApplicationUser> signInManager)
|
||||
public async Task<IViewComponentResult> InvokeAsync()
|
||||
{
|
||||
var vm = new BasketComponentViewModel
|
||||
{
|
||||
_basketService = basketService;
|
||||
_signInManager = signInManager;
|
||||
ItemsCount = await CountTotalBasketItems()
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private async Task<int> CountTotalBasketItems()
|
||||
{
|
||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||
{
|
||||
return await _basketService.CountTotalBasketItems(User.Identity.Name);
|
||||
}
|
||||
|
||||
public async Task<IViewComponentResult> InvokeAsync()
|
||||
{
|
||||
var vm = new BasketComponentViewModel
|
||||
{
|
||||
ItemsCount = await CountTotalBasketItems()
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
string anonymousId = GetAnnonymousIdFromCookie();
|
||||
if (anonymousId == null)
|
||||
return 0;
|
||||
|
||||
private async Task<int> CountTotalBasketItems()
|
||||
return await _basketService.CountTotalBasketItems(anonymousId);
|
||||
}
|
||||
|
||||
private string GetAnnonymousIdFromCookie()
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
if (_signInManager.IsSignedIn(HttpContext.User))
|
||||
var id = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
|
||||
if (Guid.TryParse(id, out var _))
|
||||
{
|
||||
return await _basketService.CountTotalBasketItems(User.Identity.Name);
|
||||
return id;
|
||||
}
|
||||
|
||||
string anonymousId = GetAnnonymousIdFromCookie();
|
||||
if (anonymousId == null)
|
||||
return 0;
|
||||
|
||||
return await _basketService.CountTotalBasketItems(anonymousId);
|
||||
}
|
||||
|
||||
private string GetAnnonymousIdFromCookie()
|
||||
{
|
||||
if (Request.Cookies.ContainsKey(Constants.BASKET_COOKIENAME))
|
||||
{
|
||||
var id = Request.Cookies[Constants.BASKET_COOKIENAME];
|
||||
|
||||
if (Guid.TryParse(id, out var _))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.eShopWeb.Infrastructure.Data;
|
||||
using Microsoft.eShopWeb.Infrastructure.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web
|
||||
namespace Microsoft.eShopWeb.Web;
|
||||
|
||||
public class Program
|
||||
{
|
||||
public class Program
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
public static async Task Main(string[] args)
|
||||
var host = CreateHostBuilder(args)
|
||||
.Build();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
{
|
||||
var host = CreateHostBuilder(args)
|
||||
.Build();
|
||||
|
||||
using (var scope = host.Services.CreateScope())
|
||||
var services = scope.ServiceProvider;
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
try
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
try
|
||||
{
|
||||
var catalogContext = services.GetRequiredService<CatalogContext>();
|
||||
await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory);
|
||||
var catalogContext = services.GetRequiredService<CatalogContext>();
|
||||
await CatalogContextSeed.SeedAsync(catalogContext, loggerFactory);
|
||||
|
||||
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger<Program>();
|
||||
logger.LogError(ex, "An error occurred seeding the DB.");
|
||||
}
|
||||
var userManager = services.GetRequiredService<UserManager<ApplicationUser>>();
|
||||
var roleManager = services.GetRequiredService<RoleManager<IdentityRole>>();
|
||||
await AppIdentityDbContextSeed.SeedAsync(userManager, roleManager);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var logger = loggerFactory.CreateLogger<Program>();
|
||||
logger.LogError(ex, "An error occurred seeding the DB.");
|
||||
}
|
||||
|
||||
host.Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
host.Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args)
|
||||
.ConfigureWebHostDefaults(webBuilder =>
|
||||
{
|
||||
webBuilder.UseStartup<Startup>();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,97 +1,96 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.BasketAggregate;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.Pages.Basket;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
public class BasketViewModelService : IBasketViewModelService
|
||||
{
|
||||
public class BasketViewModelService : IBasketViewModelService
|
||||
private readonly IRepository<Basket> _basketRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly IBasketQueryService _basketQueryService;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
|
||||
public BasketViewModelService(IRepository<Basket> basketRepository,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IUriComposer uriComposer,
|
||||
IBasketQueryService basketQueryService)
|
||||
{
|
||||
private readonly IRepository<Basket> _basketRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
private readonly IBasketQueryService _basketQueryService;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
_basketRepository = basketRepository;
|
||||
_uriComposer = uriComposer;
|
||||
_basketQueryService = basketQueryService;
|
||||
_itemRepository = itemRepository;
|
||||
}
|
||||
|
||||
public BasketViewModelService(IRepository<Basket> basketRepository,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IUriComposer uriComposer,
|
||||
IBasketQueryService basketQueryService)
|
||||
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(userName);
|
||||
var basket = (await _basketRepository.GetBySpecAsync(basketSpec));
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
_basketRepository = basketRepository;
|
||||
_uriComposer = uriComposer;
|
||||
_basketQueryService = basketQueryService;
|
||||
_itemRepository = itemRepository;
|
||||
return await CreateBasketForUser(userName);
|
||||
}
|
||||
var viewModel = await Map(basket);
|
||||
return viewModel;
|
||||
}
|
||||
|
||||
public async Task<BasketViewModel> GetOrCreateBasketForUser(string userName)
|
||||
private async Task<BasketViewModel> CreateBasketForUser(string userId)
|
||||
{
|
||||
var basket = new Basket(userId);
|
||||
await _basketRepository.AddAsync(basket);
|
||||
|
||||
return new BasketViewModel()
|
||||
{
|
||||
var basketSpec = new BasketWithItemsSpecification(userName);
|
||||
var basket = (await _basketRepository.GetBySpecAsync(basketSpec));
|
||||
BuyerId = basket.BuyerId,
|
||||
Id = basket.Id,
|
||||
};
|
||||
}
|
||||
|
||||
if (basket == null)
|
||||
{
|
||||
return await CreateBasketForUser(userName);
|
||||
}
|
||||
var viewModel = await Map(basket);
|
||||
return viewModel;
|
||||
}
|
||||
private async Task<List<BasketItemViewModel>> GetBasketItems(IReadOnlyCollection<BasketItem> basketItems)
|
||||
{
|
||||
var catalogItemsSpecification = new CatalogItemsSpecification(basketItems.Select(b => b.CatalogItemId).ToArray());
|
||||
var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);
|
||||
|
||||
private async Task<BasketViewModel> CreateBasketForUser(string userId)
|
||||
var items = basketItems.Select(basketItem =>
|
||||
{
|
||||
var basket = new Basket(userId);
|
||||
await _basketRepository.AddAsync(basket);
|
||||
var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);
|
||||
|
||||
return new BasketViewModel()
|
||||
var basketItemViewModel = new BasketItemViewModel
|
||||
{
|
||||
BuyerId = basket.BuyerId,
|
||||
Id = basket.Id,
|
||||
Id = basketItem.Id,
|
||||
UnitPrice = basketItem.UnitPrice,
|
||||
Quantity = basketItem.Quantity,
|
||||
CatalogItemId = basketItem.CatalogItemId,
|
||||
PictureUrl = _uriComposer.ComposePicUri(catalogItem.PictureUri),
|
||||
ProductName = catalogItem.Name
|
||||
};
|
||||
}
|
||||
return basketItemViewModel;
|
||||
}).ToList();
|
||||
|
||||
private async Task<List<BasketItemViewModel>> GetBasketItems(IReadOnlyCollection<BasketItem> basketItems)
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<BasketViewModel> Map(Basket basket)
|
||||
{
|
||||
return new BasketViewModel()
|
||||
{
|
||||
var catalogItemsSpecification = new CatalogItemsSpecification(basketItems.Select(b => b.CatalogItemId).ToArray());
|
||||
var catalogItems = await _itemRepository.ListAsync(catalogItemsSpecification);
|
||||
BuyerId = basket.BuyerId,
|
||||
Id = basket.Id,
|
||||
Items = await GetBasketItems(basket.Items)
|
||||
};
|
||||
}
|
||||
|
||||
var items = basketItems.Select(basketItem =>
|
||||
{
|
||||
var catalogItem = catalogItems.First(c => c.Id == basketItem.CatalogItemId);
|
||||
public async Task<int> CountTotalBasketItems(string username)
|
||||
{
|
||||
var counter = await _basketQueryService.CountTotalBasketItems(username);
|
||||
|
||||
var basketItemViewModel = new BasketItemViewModel
|
||||
{
|
||||
Id = basketItem.Id,
|
||||
UnitPrice = basketItem.UnitPrice,
|
||||
Quantity = basketItem.Quantity,
|
||||
CatalogItemId = basketItem.CatalogItemId,
|
||||
PictureUrl = _uriComposer.ComposePicUri(catalogItem.PictureUri),
|
||||
ProductName = catalogItem.Name
|
||||
};
|
||||
return basketItemViewModel;
|
||||
}).ToList();
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<BasketViewModel> Map(Basket basket)
|
||||
{
|
||||
return new BasketViewModel()
|
||||
{
|
||||
BuyerId = basket.BuyerId,
|
||||
Id = basket.Id,
|
||||
Items = await GetBasketItems(basket.Items)
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> CountTotalBasketItems(string username)
|
||||
{
|
||||
var counter = await _basketQueryService.CountTotalBasketItems(username);
|
||||
|
||||
return counter;
|
||||
}
|
||||
return counter;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,50 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.eShopWeb.Web.Extensions;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
public class CachedCatalogViewModelService : ICatalogViewModelService
|
||||
{
|
||||
public class CachedCatalogViewModelService : ICatalogViewModelService
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly CatalogViewModelService _catalogViewModelService;
|
||||
|
||||
public CachedCatalogViewModelService(IMemoryCache cache,
|
||||
CatalogViewModelService catalogViewModelService)
|
||||
{
|
||||
private readonly IMemoryCache _cache;
|
||||
private readonly CatalogViewModelService _catalogViewModelService;
|
||||
_cache = cache;
|
||||
_catalogViewModelService = catalogViewModelService;
|
||||
}
|
||||
|
||||
public CachedCatalogViewModelService(IMemoryCache cache,
|
||||
CatalogViewModelService catalogViewModelService)
|
||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||
{
|
||||
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetBrands();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);
|
||||
|
||||
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
|
||||
{
|
||||
_cache = cache;
|
||||
_catalogViewModelService = catalogViewModelService;
|
||||
}
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||
{
|
||||
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>
|
||||
{
|
||||
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateBrandsCacheKey(), async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetBrands();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
var cacheKey = CacheHelpers.GenerateCatalogItemCacheKey(pageIndex, Constants.ITEMS_PER_PAGE, brandId, typeId);
|
||||
|
||||
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetCatalogItems(pageIndex, itemsPage, brandId, typeId);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||
{
|
||||
return await _cache.GetOrCreateAsync(CacheHelpers.GenerateTypesCacheKey(), async entry =>
|
||||
{
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetTypes();
|
||||
});
|
||||
}
|
||||
entry.SlidingExpiration = CacheHelpers.DefaultCacheDuration;
|
||||
return await _catalogViewModelService.GetTypes();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.Interfaces;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
public class CatalogItemViewModelService : ICatalogItemViewModelService
|
||||
{
|
||||
public class CatalogItemViewModelService : ICatalogItemViewModelService
|
||||
private readonly IRepository<CatalogItem> _catalogItemRepository;
|
||||
|
||||
public CatalogItemViewModelService(IRepository<CatalogItem> catalogItemRepository)
|
||||
{
|
||||
private readonly IRepository<CatalogItem> _catalogItemRepository;
|
||||
_catalogItemRepository = catalogItemRepository;
|
||||
}
|
||||
|
||||
public CatalogItemViewModelService(IRepository<CatalogItem> catalogItemRepository)
|
||||
{
|
||||
_catalogItemRepository = catalogItemRepository;
|
||||
}
|
||||
|
||||
public async Task UpdateCatalogItem(CatalogItemViewModel viewModel)
|
||||
{
|
||||
var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id);
|
||||
existingCatalogItem.UpdateDetails(viewModel.Name, existingCatalogItem.Description, viewModel.Price);
|
||||
await _catalogItemRepository.UpdateAsync(existingCatalogItem);
|
||||
}
|
||||
public async Task UpdateCatalogItem(CatalogItemViewModel viewModel)
|
||||
{
|
||||
var existingCatalogItem = await _catalogItemRepository.GetByIdAsync(viewModel.Id);
|
||||
existingCatalogItem.UpdateDetails(viewModel.Name, existingCatalogItem.Description, viewModel.Price);
|
||||
await _catalogItemRepository.UpdateAsync(existingCatalogItem);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,112 +1,111 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Interfaces;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Specifications;
|
||||
using Microsoft.eShopWeb.Web.ViewModels;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Services
|
||||
namespace Microsoft.eShopWeb.Web.Services;
|
||||
|
||||
/// <summary>
|
||||
/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works
|
||||
/// with UI-specific types (view models and SelectListItem types).
|
||||
/// </summary>
|
||||
public class CatalogViewModelService : ICatalogViewModelService
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a UI-specific service so belongs in UI project. It does not contain any business logic and works
|
||||
/// with UI-specific types (view models and SelectListItem types).
|
||||
/// </summary>
|
||||
public class CatalogViewModelService : ICatalogViewModelService
|
||||
private readonly ILogger<CatalogViewModelService> _logger;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IRepository<CatalogBrand> _brandRepository;
|
||||
private readonly IRepository<CatalogType> _typeRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
|
||||
public CatalogViewModelService(
|
||||
ILoggerFactory loggerFactory,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IRepository<CatalogBrand> brandRepository,
|
||||
IRepository<CatalogType> typeRepository,
|
||||
IUriComposer uriComposer)
|
||||
{
|
||||
private readonly ILogger<CatalogViewModelService> _logger;
|
||||
private readonly IRepository<CatalogItem> _itemRepository;
|
||||
private readonly IRepository<CatalogBrand> _brandRepository;
|
||||
private readonly IRepository<CatalogType> _typeRepository;
|
||||
private readonly IUriComposer _uriComposer;
|
||||
_logger = loggerFactory.CreateLogger<CatalogViewModelService>();
|
||||
_itemRepository = itemRepository;
|
||||
_brandRepository = brandRepository;
|
||||
_typeRepository = typeRepository;
|
||||
_uriComposer = uriComposer;
|
||||
}
|
||||
|
||||
public CatalogViewModelService(
|
||||
ILoggerFactory loggerFactory,
|
||||
IRepository<CatalogItem> itemRepository,
|
||||
IRepository<CatalogBrand> brandRepository,
|
||||
IRepository<CatalogType> typeRepository,
|
||||
IUriComposer uriComposer)
|
||||
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
_logger.LogInformation("GetCatalogItems called.");
|
||||
|
||||
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
||||
var filterPaginatedSpecification =
|
||||
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);
|
||||
|
||||
// the implementation below using ForEach and Count. We need a List.
|
||||
var itemsOnPage = await _itemRepository.ListAsync(filterPaginatedSpecification);
|
||||
var totalItems = await _itemRepository.CountAsync(filterSpecification);
|
||||
|
||||
var vm = new CatalogIndexViewModel()
|
||||
{
|
||||
_logger = loggerFactory.CreateLogger<CatalogViewModelService>();
|
||||
_itemRepository = itemRepository;
|
||||
_brandRepository = brandRepository;
|
||||
_typeRepository = typeRepository;
|
||||
_uriComposer = uriComposer;
|
||||
}
|
||||
|
||||
public async Task<CatalogIndexViewModel> GetCatalogItems(int pageIndex, int itemsPage, int? brandId, int? typeId)
|
||||
{
|
||||
_logger.LogInformation("GetCatalogItems called.");
|
||||
|
||||
var filterSpecification = new CatalogFilterSpecification(brandId, typeId);
|
||||
var filterPaginatedSpecification =
|
||||
new CatalogFilterPaginatedSpecification(itemsPage * pageIndex, itemsPage, brandId, typeId);
|
||||
|
||||
// the implementation below using ForEach and Count. We need a List.
|
||||
var itemsOnPage = await _itemRepository.ListAsync(filterPaginatedSpecification);
|
||||
var totalItems = await _itemRepository.CountAsync(filterSpecification);
|
||||
|
||||
var vm = new CatalogIndexViewModel()
|
||||
CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel()
|
||||
{
|
||||
CatalogItems = itemsOnPage.Select(i => new CatalogItemViewModel()
|
||||
{
|
||||
Id = i.Id,
|
||||
Name = i.Name,
|
||||
PictureUri = _uriComposer.ComposePicUri(i.PictureUri),
|
||||
Price = i.Price
|
||||
}).ToList(),
|
||||
Brands = (await GetBrands()).ToList(),
|
||||
Types = (await GetTypes()).ToList(),
|
||||
BrandFilterApplied = brandId ?? 0,
|
||||
TypesFilterApplied = typeId ?? 0,
|
||||
PaginationInfo = new PaginationInfoViewModel()
|
||||
{
|
||||
ActualPage = pageIndex,
|
||||
ItemsPerPage = itemsOnPage.Count,
|
||||
TotalItems = totalItems,
|
||||
TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString())
|
||||
}
|
||||
};
|
||||
Id = i.Id,
|
||||
Name = i.Name,
|
||||
PictureUri = _uriComposer.ComposePicUri(i.PictureUri),
|
||||
Price = i.Price
|
||||
}).ToList(),
|
||||
Brands = (await GetBrands()).ToList(),
|
||||
Types = (await GetTypes()).ToList(),
|
||||
BrandFilterApplied = brandId ?? 0,
|
||||
TypesFilterApplied = typeId ?? 0,
|
||||
PaginationInfo = new PaginationInfoViewModel()
|
||||
{
|
||||
ActualPage = pageIndex,
|
||||
ItemsPerPage = itemsOnPage.Count,
|
||||
TotalItems = totalItems,
|
||||
TotalPages = int.Parse(Math.Ceiling(((decimal)totalItems / itemsPage)).ToString())
|
||||
}
|
||||
};
|
||||
|
||||
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";
|
||||
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";
|
||||
vm.PaginationInfo.Next = (vm.PaginationInfo.ActualPage == vm.PaginationInfo.TotalPages - 1) ? "is-disabled" : "";
|
||||
vm.PaginationInfo.Previous = (vm.PaginationInfo.ActualPage == 0) ? "is-disabled" : "";
|
||||
|
||||
return vm;
|
||||
}
|
||||
return vm;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||
{
|
||||
_logger.LogInformation("GetBrands called.");
|
||||
var brands = await _brandRepository.ListAsync();
|
||||
public async Task<IEnumerable<SelectListItem>> GetBrands()
|
||||
{
|
||||
_logger.LogInformation("GetBrands called.");
|
||||
var brands = await _brandRepository.ListAsync();
|
||||
|
||||
var items = brands
|
||||
.Select(brand => new SelectListItem() { Value = brand.Id.ToString(), Text = brand.Brand })
|
||||
.OrderBy(b => b.Text)
|
||||
.ToList();
|
||||
var items = brands
|
||||
.Select(brand => new SelectListItem() { Value = brand.Id.ToString(), Text = brand.Brand })
|
||||
.OrderBy(b => b.Text)
|
||||
.ToList();
|
||||
|
||||
var allItem = new SelectListItem() { Value = null, Text = "All", Selected = true };
|
||||
items.Insert(0, allItem);
|
||||
var allItem = new SelectListItem() { Value = null, Text = "All", Selected = true };
|
||||
items.Insert(0, allItem);
|
||||
|
||||
return items;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||
{
|
||||
_logger.LogInformation("GetTypes called.");
|
||||
var types = await _typeRepository.ListAsync();
|
||||
public async Task<IEnumerable<SelectListItem>> GetTypes()
|
||||
{
|
||||
_logger.LogInformation("GetTypes called.");
|
||||
var types = await _typeRepository.ListAsync();
|
||||
|
||||
var items = types
|
||||
.Select(type => new SelectListItem() { Value = type.Id.ToString(), Text = type.Type })
|
||||
.OrderBy(t => t.Text)
|
||||
.ToList();
|
||||
var items = types
|
||||
.Select(type => new SelectListItem() { Value = type.Id.ToString(), Text = type.Type })
|
||||
.OrderBy(t => t.Text)
|
||||
.ToList();
|
||||
|
||||
var allItem = new SelectListItem() { Value = null, Text = "All", Selected = true };
|
||||
items.Insert(0, allItem);
|
||||
var allItem = new SelectListItem() { Value = null, Text = "All", Selected = true };
|
||||
items.Insert(0, allItem);
|
||||
|
||||
return items;
|
||||
}
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web
|
||||
namespace Microsoft.eShopWeb.Web;
|
||||
|
||||
public class SlugifyParameterTransformer : IOutboundParameterTransformer
|
||||
{
|
||||
|
||||
public class SlugifyParameterTransformer : IOutboundParameterTransformer
|
||||
public string TransformOutbound(object value)
|
||||
{
|
||||
public string TransformOutbound(object value)
|
||||
{
|
||||
if (value == null) { return null; }
|
||||
if (value == null) { return null; }
|
||||
|
||||
// Slugify value
|
||||
return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
|
||||
}
|
||||
// Slugify value
|
||||
return Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2").ToLower();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
using Ardalis.ListStartupServices;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
using Ardalis.ListStartupServices;
|
||||
using BlazorAdmin;
|
||||
using BlazorAdmin.Services;
|
||||
using Blazored.LocalStorage;
|
||||
@@ -20,220 +26,212 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Mime;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web
|
||||
namespace Microsoft.eShopWeb.Web;
|
||||
|
||||
public class Startup
|
||||
{
|
||||
public class Startup
|
||||
private IServiceCollection _services;
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
private IServiceCollection _services;
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
public void ConfigureDevelopmentServices(IServiceCollection services)
|
||||
{
|
||||
// use in-memory database
|
||||
ConfigureInMemoryDatabases(services);
|
||||
|
||||
public void ConfigureDevelopmentServices(IServiceCollection services)
|
||||
{
|
||||
// use in-memory database
|
||||
ConfigureInMemoryDatabases(services);
|
||||
// use real database
|
||||
//ConfigureProductionServices(services);
|
||||
}
|
||||
|
||||
// use real database
|
||||
//ConfigureProductionServices(services);
|
||||
}
|
||||
public void ConfigureDockerServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDataProtection()
|
||||
.SetApplicationName("eshopwebmvc")
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(@"./"));
|
||||
|
||||
public void ConfigureDockerServices(IServiceCollection services)
|
||||
{
|
||||
services.AddDataProtection()
|
||||
.SetApplicationName("eshopwebmvc")
|
||||
.PersistKeysToFileSystem(new DirectoryInfo(@"./"));
|
||||
ConfigureDevelopmentServices(services);
|
||||
}
|
||||
|
||||
ConfigureDevelopmentServices(services);
|
||||
}
|
||||
private void ConfigureInMemoryDatabases(IServiceCollection services)
|
||||
{
|
||||
// use in-memory database
|
||||
services.AddDbContext<CatalogContext>(c =>
|
||||
c.UseInMemoryDatabase("Catalog"));
|
||||
|
||||
private void ConfigureInMemoryDatabases(IServiceCollection services)
|
||||
{
|
||||
// use in-memory database
|
||||
services.AddDbContext<CatalogContext>(c =>
|
||||
c.UseInMemoryDatabase("Catalog"));
|
||||
// Add Identity DbContext
|
||||
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||
options.UseInMemoryDatabase("Identity"));
|
||||
|
||||
// Add Identity DbContext
|
||||
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||
options.UseInMemoryDatabase("Identity"));
|
||||
ConfigureServices(services);
|
||||
}
|
||||
|
||||
ConfigureServices(services);
|
||||
}
|
||||
public void ConfigureProductionServices(IServiceCollection services)
|
||||
{
|
||||
// use real database
|
||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
||||
services.AddDbContext<CatalogContext>(c =>
|
||||
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection")));
|
||||
|
||||
public void ConfigureProductionServices(IServiceCollection services)
|
||||
{
|
||||
// use real database
|
||||
// Requires LocalDB which can be installed with SQL Server Express 2016
|
||||
// https://www.microsoft.com/en-us/download/details.aspx?id=54284
|
||||
services.AddDbContext<CatalogContext>(c =>
|
||||
c.UseSqlServer(Configuration.GetConnectionString("CatalogConnection")));
|
||||
// Add Identity DbContext
|
||||
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
|
||||
|
||||
// Add Identity DbContext
|
||||
services.AddDbContext<AppIdentityDbContext>(options =>
|
||||
options.UseSqlServer(Configuration.GetConnectionString("IdentityConnection")));
|
||||
ConfigureServices(services);
|
||||
}
|
||||
|
||||
ConfigureServices(services);
|
||||
}
|
||||
|
||||
public void ConfigureTestingServices(IServiceCollection services)
|
||||
{
|
||||
ConfigureInMemoryDatabases(services);
|
||||
}
|
||||
public void ConfigureTestingServices(IServiceCollection services)
|
||||
{
|
||||
ConfigureInMemoryDatabases(services);
|
||||
}
|
||||
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCookieSettings();
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddCookieSettings();
|
||||
|
||||
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
});
|
||||
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddDefaultUI()
|
||||
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
|
||||
|
||||
services.AddCoreServices(Configuration);
|
||||
services.AddWebServices(Configuration);
|
||||
|
||||
// Add memory cache services
|
||||
services.AddMemoryCache();
|
||||
services.AddRouting(options =>
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
});
|
||||
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddDefaultUI()
|
||||
.AddEntityFrameworkStores<AppIdentityDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
|
||||
services.AddScoped<ITokenClaimsService, IdentityTokenClaimService>();
|
||||
|
||||
services.AddCoreServices(Configuration);
|
||||
services.AddWebServices(Configuration);
|
||||
|
||||
// Add memory cache services
|
||||
services.AddMemoryCache();
|
||||
services.AddRouting(options =>
|
||||
{
|
||||
// Replace the type and the name used to refer to it with your own
|
||||
// IOutboundParameterTransformer implementation
|
||||
options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer);
|
||||
});
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
options.Conventions.Add(new RouteTokenTransformerConvention(
|
||||
new SlugifyParameterTransformer()));
|
||||
|
||||
});
|
||||
services.AddControllersWithViews();
|
||||
services.AddRazorPages(options =>
|
||||
{
|
||||
options.Conventions.AuthorizePage("/Basket/Checkout");
|
||||
});
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddHealthChecks();
|
||||
services.Configure<ServiceConfig>(config =>
|
||||
{
|
||||
config.Services = new List<ServiceDescriptor>(services);
|
||||
|
||||
config.Path = "/allservices";
|
||||
});
|
||||
|
||||
|
||||
var baseUrlConfig = new BaseUrlConfiguration();
|
||||
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
|
||||
services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig);
|
||||
// Blazor Admin Required Services for Prerendering
|
||||
services.AddScoped<HttpClient>(s => new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(baseUrlConfig.WebBase)
|
||||
});
|
||||
|
||||
// add blazor services
|
||||
services.AddBlazoredLocalStorage();
|
||||
services.AddServerSideBlazor();
|
||||
|
||||
|
||||
services.AddScoped<ToastService>();
|
||||
services.AddScoped<HttpService>();
|
||||
services.AddBlazorServices();
|
||||
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
_services = services; // used to debug registered services
|
||||
}
|
||||
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
});
|
||||
services.AddMvc(options =>
|
||||
{
|
||||
var catalogBaseUrl = Configuration.GetValue(typeof(string), "CatalogBaseUrl") as string;
|
||||
if (!string.IsNullOrEmpty(catalogBaseUrl))
|
||||
{
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
context.Request.PathBase = new PathString(catalogBaseUrl);
|
||||
return next();
|
||||
});
|
||||
}
|
||||
options.Conventions.Add(new RouteTokenTransformerConvention(
|
||||
new SlugifyParameterTransformer()));
|
||||
|
||||
app.UseHealthChecks("/health",
|
||||
new HealthCheckOptions
|
||||
{
|
||||
ResponseWriter = async (context, report) =>
|
||||
{
|
||||
var result = new
|
||||
{
|
||||
status = report.Status.ToString(),
|
||||
errors = report.Entries.Select(e => new
|
||||
{
|
||||
key = e.Key,
|
||||
value = Enum.GetName(typeof(HealthStatus), e.Value.Status)
|
||||
})
|
||||
}.ToJson();
|
||||
context.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
await context.Response.WriteAsync(result);
|
||||
}
|
||||
});
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseShowAllServicesMiddleware();
|
||||
app.UseMigrationsEndPoint();
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
});
|
||||
services.AddControllersWithViews();
|
||||
services.AddRazorPages(options =>
|
||||
{
|
||||
options.Conventions.AuthorizePage("/Basket/Checkout");
|
||||
});
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddHealthChecks();
|
||||
services.Configure<ServiceConfig>(config =>
|
||||
{
|
||||
config.Services = new List<ServiceDescriptor>(services);
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
config.Path = "/allservices";
|
||||
});
|
||||
|
||||
app.UseCookiePolicy();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
|
||||
endpoints.MapRazorPages();
|
||||
endpoints.MapHealthChecks("home_page_health_check");
|
||||
endpoints.MapHealthChecks("api_health_check");
|
||||
//endpoints.MapBlazorHub("/admin");
|
||||
endpoints.MapFallbackToFile("index.html");
|
||||
});
|
||||
}
|
||||
var baseUrlConfig = new BaseUrlConfiguration();
|
||||
Configuration.Bind(BaseUrlConfiguration.CONFIG_NAME, baseUrlConfig);
|
||||
services.AddScoped<BaseUrlConfiguration>(sp => baseUrlConfig);
|
||||
// Blazor Admin Required Services for Prerendering
|
||||
services.AddScoped<HttpClient>(s => new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(baseUrlConfig.WebBase)
|
||||
});
|
||||
|
||||
// add blazor services
|
||||
services.AddBlazoredLocalStorage();
|
||||
services.AddServerSideBlazor();
|
||||
|
||||
|
||||
services.AddScoped<ToastService>();
|
||||
services.AddScoped<HttpService>();
|
||||
services.AddBlazorServices();
|
||||
|
||||
services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
_services = services; // used to debug registered services
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
var catalogBaseUrl = Configuration.GetValue(typeof(string), "CatalogBaseUrl") as string;
|
||||
if (!string.IsNullOrEmpty(catalogBaseUrl))
|
||||
{
|
||||
app.Use((context, next) =>
|
||||
{
|
||||
context.Request.PathBase = new PathString(catalogBaseUrl);
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
app.UseHealthChecks("/health",
|
||||
new HealthCheckOptions
|
||||
{
|
||||
ResponseWriter = async (context, report) =>
|
||||
{
|
||||
var result = new
|
||||
{
|
||||
status = report.Status.ToString(),
|
||||
errors = report.Entries.Select(e => new
|
||||
{
|
||||
key = e.Key,
|
||||
value = Enum.GetName(typeof(HealthStatus), e.Value.Status)
|
||||
})
|
||||
}.ToJson();
|
||||
context.Response.ContentType = MediaTypeNames.Application.Json;
|
||||
await context.Response.WriteAsync(result);
|
||||
}
|
||||
});
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseShowAllServicesMiddleware();
|
||||
app.UseMigrationsEndPoint();
|
||||
app.UseWebAssemblyDebugging();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
app.UseCookiePolicy();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute("default", "{controller:slugify=Home}/{action:slugify=Index}/{id?}");
|
||||
endpoints.MapRazorPages();
|
||||
endpoints.MapHealthChecks("home_page_health_check");
|
||||
endpoints.MapHealthChecks("api_health_check");
|
||||
//endpoints.MapBlazorHub("/admin");
|
||||
endpoints.MapFallbackToFile("index.html");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account;
|
||||
|
||||
public class LoginViewModel
|
||||
{
|
||||
public class LoginViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
[Display(Name = "Remember me?")]
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account;
|
||||
|
||||
public class LoginWith2faViewModel
|
||||
{
|
||||
public class LoginWith2faViewModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Authenticator code")]
|
||||
public string TwoFactorCode { get; set; }
|
||||
[Required]
|
||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Authenticator code")]
|
||||
public string TwoFactorCode { get; set; }
|
||||
|
||||
[Display(Name = "Remember this machine")]
|
||||
public bool RememberMachine { get; set; }
|
||||
[Display(Name = "Remember this machine")]
|
||||
public bool RememberMachine { get; set; }
|
||||
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
public bool RememberMe { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account;
|
||||
|
||||
public class RegisterViewModel
|
||||
{
|
||||
public class RegisterViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
[Display(Name = "Email")]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Password")]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Account;
|
||||
|
||||
public class ResetPasswordViewModel
|
||||
{
|
||||
public class ResetPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
public string Password { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm password")]
|
||||
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string Code { get; set; }
|
||||
}
|
||||
public string Code { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class BasketComponentViewModel
|
||||
{
|
||||
public class BasketComponentViewModel
|
||||
{
|
||||
public int ItemsCount { get; set; }
|
||||
}
|
||||
public int ItemsCount { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class CatalogIndexViewModel
|
||||
{
|
||||
public class CatalogIndexViewModel
|
||||
{
|
||||
public List<CatalogItemViewModel> CatalogItems { get; set; }
|
||||
public List<SelectListItem> Brands { get; set; }
|
||||
public List<SelectListItem> Types { get; set; }
|
||||
public int? BrandFilterApplied { get; set; }
|
||||
public int? TypesFilterApplied { get; set; }
|
||||
public PaginationInfoViewModel PaginationInfo { get; set; }
|
||||
}
|
||||
public List<CatalogItemViewModel> CatalogItems { get; set; }
|
||||
public List<SelectListItem> Brands { get; set; }
|
||||
public List<SelectListItem> Types { get; set; }
|
||||
public int? BrandFilterApplied { get; set; }
|
||||
public int? TypesFilterApplied { get; set; }
|
||||
public PaginationInfoViewModel PaginationInfo { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class CatalogItemViewModel
|
||||
{
|
||||
public class CatalogItemViewModel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string PictureUri { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
}
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string PictureUri { get; set; }
|
||||
public decimal Price { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.File
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.File;
|
||||
|
||||
public class FileViewModel
|
||||
{
|
||||
public class FileViewModel
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string DataBase64 { get; set; }
|
||||
}
|
||||
public string FileName { get; set; }
|
||||
public string Url { get; set; }
|
||||
public string DataBase64 { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class ChangePasswordViewModel
|
||||
{
|
||||
public class ChangePasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Current password")]
|
||||
public string OldPassword { get; set; }
|
||||
[Required]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Current password")]
|
||||
public string OldPassword { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class EnableAuthenticatorViewModel
|
||||
{
|
||||
public class EnableAuthenticatorViewModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Verification Code")]
|
||||
public string Code { get; set; }
|
||||
[Required]
|
||||
[StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Text)]
|
||||
[Display(Name = "Verification Code")]
|
||||
public string Code { get; set; }
|
||||
|
||||
[ReadOnly(true)]
|
||||
public string SharedKey { get; set; }
|
||||
[ReadOnly(true)]
|
||||
public string SharedKey { get; set; }
|
||||
|
||||
public string AuthenticatorUri { get; set; }
|
||||
}
|
||||
public string AuthenticatorUri { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class ExternalLoginsViewModel
|
||||
{
|
||||
public class ExternalLoginsViewModel
|
||||
{
|
||||
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
||||
public IList<AuthenticationScheme> OtherLogins { get; set; }
|
||||
public bool ShowRemoveButton { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
public IList<UserLoginInfo> CurrentLogins { get; set; }
|
||||
public IList<AuthenticationScheme> OtherLogins { get; set; }
|
||||
public bool ShowRemoveButton { get; set; }
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class GenerateRecoveryCodesViewModel
|
||||
{
|
||||
public class GenerateRecoveryCodesViewModel
|
||||
{
|
||||
public string[] RecoveryCodes { get; set; }
|
||||
}
|
||||
public string[] RecoveryCodes { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class IndexViewModel
|
||||
{
|
||||
public class IndexViewModel
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Username { get; set; }
|
||||
|
||||
public bool IsEmailConfirmed { get; set; }
|
||||
public bool IsEmailConfirmed { get; set; }
|
||||
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
[Required]
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
[Phone]
|
||||
[Display(Name = "Phone number")]
|
||||
public string PhoneNumber { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class RemoveLoginViewModel
|
||||
{
|
||||
public class RemoveLoginViewModel
|
||||
{
|
||||
public string LoginProvider { get; set; }
|
||||
public string ProviderKey { get; set; }
|
||||
}
|
||||
public string LoginProvider { get; set; }
|
||||
public string ProviderKey { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class SetPasswordViewModel
|
||||
{
|
||||
public class SetPasswordViewModel
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "New password")]
|
||||
public string NewPassword { get; set; }
|
||||
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
[DataType(DataType.Password)]
|
||||
[Display(Name = "Confirm new password")]
|
||||
[Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")]
|
||||
public string ConfirmPassword { get; set; }
|
||||
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
public string StatusMessage { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels.Manage;
|
||||
|
||||
public class TwoFactorAuthenticationViewModel
|
||||
{
|
||||
public class TwoFactorAuthenticationViewModel
|
||||
{
|
||||
public bool HasAuthenticator { get; set; }
|
||||
public int RecoveryCodesLeft { get; set; }
|
||||
public bool Is2faEnabled { get; set; }
|
||||
}
|
||||
public bool HasAuthenticator { get; set; }
|
||||
public int RecoveryCodesLeft { get; set; }
|
||||
public bool Is2faEnabled { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class OrderItemViewModel
|
||||
{
|
||||
public class OrderItemViewModel
|
||||
{
|
||||
public int ProductId { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal Discount => 0;
|
||||
public int Units { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
public int ProductId { get; set; }
|
||||
public string ProductName { get; set; }
|
||||
public decimal UnitPrice { get; set; }
|
||||
public decimal Discount => 0;
|
||||
public int Units { get; set; }
|
||||
public string PictureUrl { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class OrderViewModel
|
||||
{
|
||||
public class OrderViewModel
|
||||
{
|
||||
private const string DEFAULT_STATUS = "Pending";
|
||||
private const string DEFAULT_STATUS = "Pending";
|
||||
|
||||
public int OrderNumber { get; set; }
|
||||
public DateTimeOffset OrderDate { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string Status => DEFAULT_STATUS;
|
||||
public Address ShippingAddress { get; set; }
|
||||
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
|
||||
}
|
||||
public int OrderNumber { get; set; }
|
||||
public DateTimeOffset OrderDate { get; set; }
|
||||
public decimal Total { get; set; }
|
||||
public string Status => DEFAULT_STATUS;
|
||||
public Address ShippingAddress { get; set; }
|
||||
public List<OrderItemViewModel> OrderItems { get; set; } = new List<OrderItemViewModel>();
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels
|
||||
namespace Microsoft.eShopWeb.Web.ViewModels;
|
||||
|
||||
public class PaginationInfoViewModel
|
||||
{
|
||||
public class PaginationInfoViewModel
|
||||
{
|
||||
public int TotalItems { get; set; }
|
||||
public int ItemsPerPage { get; set; }
|
||||
public int ActualPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public string Previous { get; set; }
|
||||
public string Next { get; set; }
|
||||
}
|
||||
public int TotalItems { get; set; }
|
||||
public int ItemsPerPage { get; set; }
|
||||
public int ActualPage { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public string Previous { get; set; }
|
||||
public string Next { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,35 +1,34 @@
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.eShopWeb.Web.Views.Manage
|
||||
namespace Microsoft.eShopWeb.Web.Views.Manage;
|
||||
|
||||
public static class ManageNavPages
|
||||
{
|
||||
public static class ManageNavPages
|
||||
public static string ActivePageKey => "ActivePage";
|
||||
|
||||
public static string Index => "Index";
|
||||
|
||||
public static string ChangePassword => "ChangePassword";
|
||||
|
||||
public static string ExternalLogins => "ExternalLogins";
|
||||
|
||||
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
|
||||
|
||||
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
|
||||
|
||||
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
|
||||
|
||||
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
|
||||
|
||||
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
|
||||
|
||||
public static string PageNavClass(ViewContext viewContext, string page)
|
||||
{
|
||||
public static string ActivePageKey => "ActivePage";
|
||||
|
||||
public static string Index => "Index";
|
||||
|
||||
public static string ChangePassword => "ChangePassword";
|
||||
|
||||
public static string ExternalLogins => "ExternalLogins";
|
||||
|
||||
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
|
||||
|
||||
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
|
||||
|
||||
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
|
||||
|
||||
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
|
||||
|
||||
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
|
||||
|
||||
public static string PageNavClass(ViewContext viewContext, string page)
|
||||
{
|
||||
var activePage = viewContext.ViewData["ActivePage"] as string;
|
||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
|
||||
}
|
||||
|
||||
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
|
||||
var activePage = viewContext.ViewData["ActivePage"] as string;
|
||||
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
|
||||
}
|
||||
|
||||
public static void AddActivePage(this ViewDataDictionary viewData, string activePage) => viewData[ActivePageKey] = activePage;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<RootNamespace>Microsoft.eShopWeb.Web</RootNamespace>
|
||||
<UserSecretsId>aspnet-Web2-1FA3F72E-E7E3-4360-9E49-1CCCD7FE85F7</UserSecretsId>
|
||||
<LangVersion>latest</LangVersion>
|
||||
|
||||
Reference in New Issue
Block a user