Merge pull request 'merge rider back to EFCore' (#5) from rider into EFCore

Reviewed-on: sascha/Gremlin#5
pull/1/head
Sascha Woitschetzki 2023-01-13 15:01:19 +07:00
commit 860ddaee90
164 changed files with 7156 additions and 2979 deletions

@ -1,12 +1,14 @@
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
<CascadingAuthenticationState>
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>

@ -0,0 +1,10 @@
@page
@model AccessDeniedModel
@{
ViewData["Title"] = "Access denied";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">You do not have access to this resource.</p>
</header>

@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class AccessDeniedModel : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public void OnGet()
{
}
}
}

@ -0,0 +1,8 @@
@page
@model ConfirmEmailModel
@{
ViewData["Title"] = "Confirm email";
}
<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />

@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class ConfirmEmailModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
public ConfirmEmailModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync(string userId, string code)
{
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}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ConfirmEmailAsync(user, code);
StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email.";
return Page();
}
}
}

@ -0,0 +1,8 @@
@page
@model ConfirmEmailChangeModel
@{
ViewData["Title"] = "Confirm email change";
}
<h1>@ViewData["Title"]</h1>
<partial name="_StatusMessage" model="Model.StatusMessage" />

@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class ConfirmEmailChangeModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public ConfirmEmailChangeModel(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync(string userId, string email, string code)
{
if (userId == null || email == null || code == null)
{
return RedirectToPage("/Index");
}
var user = await _userManager.FindByIdAsync(userId);
if (user == null)
{
return NotFound($"Unable to load user with ID '{userId}'.");
}
code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
var result = await _userManager.ChangeEmailAsync(user, email, code);
if (!result.Succeeded)
{
StatusMessage = "Error changing email.";
return Page();
}
// In our UI email and user name are one and the same, so when we update the email
// we need to update the user name.
var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
if (!setUserNameResult.Succeeded)
{
StatusMessage = "Error changing user name.";
return Page();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Thank you for confirming your email change.";
return Page();
}
}
}

@ -0,0 +1,33 @@
@page
@model ExternalLoginModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<h2 id="external-login-title">Associate your @Model.ProviderDisplayName account.</h2>
<hr />
<p id="external-login-description" class="text-info">
You've successfully authenticated with <strong>@Model.ProviderDisplayName</strong>.
Please enter an email address for this site below and click the Register button to finish
logging in.
</p>
<div class="row">
<div class="col-md-4">
<form asp-page-handler="Confirmation" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" autocomplete="email" placeholder="Please enter your email."/>
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,223 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ExternalLoginModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly IUserStore<IdentityUser> _userStore;
private readonly IUserEmailStore<IdentityUser> _emailStore;
private readonly IEmailSender _emailSender;
private readonly ILogger<ExternalLoginModel> _logger;
public ExternalLoginModel(
SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
IUserStore<IdentityUser> userStore,
ILogger<ExternalLoginModel> logger,
IEmailSender emailSender)
{
_signInManager = signInManager;
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
_logger = logger;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ProviderDisplayName { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string ErrorMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
}
public IActionResult OnGet() => RedirectToPage("./Login");
public IActionResult OnPost(string provider, string returnUrl = null)
{
// Request a redirect to the external login provider.
var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl });
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
if (remoteError != null)
{
ErrorMessage = $"Error from external provider: {remoteError}";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
_logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
return RedirectToPage("./Lockout");
}
else
{
// If the user does not have an account, then ask the user to create an account.
ReturnUrl = returnUrl;
ProviderDisplayName = info.ProviderDisplayName;
if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email))
{
Input = new InputModel
{
Email = info.Principal.FindFirstValue(ClaimTypes.Email)
};
}
return Page();
}
}
public async Task<IActionResult> OnPostConfirmationAsync(string returnUrl = null)
{
returnUrl = returnUrl ?? Url.Content("~/");
// Get the information about the user from the external login provider
var info = await _signInManager.GetExternalLoginInfoAsync();
if (info == null)
{
ErrorMessage = "Error loading external login information during confirmation.";
return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
}
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user);
if (result.Succeeded)
{
result = await _userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
_logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, 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>.");
// If account confirmation is required, we need to show the link if we don't have a real email sender
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email });
}
await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider);
return LocalRedirect(returnUrl);
}
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
}
ProviderDisplayName = info.ProviderDisplayName;
ReturnUrl = returnUrl;
return Page();
}
private IdentityUser CreateUser()
{
try
{
return Activator.CreateInstance<IdentityUser>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
$"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
$"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml");
}
}
private IUserEmailStore<IdentityUser> GetEmailStore()
{
if (!_userManager.SupportsUserEmail)
{
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<IdentityUser>)_userStore;
}
}
}

@ -0,0 +1,26 @@
@page
@model ForgotPasswordModel
@{
ViewData["Title"] = "Forgot your password?";
}
<h1>@ViewData["Title"]</h1>
<h2>Enter your email.</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Reset Password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,84 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class ForgotPasswordModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _emailSender;
public ForgotPasswordModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
{
_userManager = userManager;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
}
public async Task<IActionResult> OnPostAsync()
{
if (ModelState.IsValid)
{
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null || !(await _userManager.IsEmailConfirmedAsync(user)))
{
// Don't reveal that the user does not exist or is not confirmed
return RedirectToPage("./ForgotPasswordConfirmation");
}
// For more information on how to enable account confirmation and password reset please
// visit https://go.microsoft.com/fwlink/?LinkID=532713
var code = await _userManager.GeneratePasswordResetTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ResetPassword",
pageHandler: null,
values: new { area = "Identity", code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.Email,
"Reset Password",
$"Please reset your password by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
return RedirectToPage("./ForgotPasswordConfirmation");
}
return Page();
}
}
}

@ -0,0 +1,10 @@
@page
@model ForgotPasswordConfirmation
@{
ViewData["Title"] = "Forgot password confirmation";
}
<h1>@ViewData["Title"]</h1>
<p>
Please check your email to reset your password.
</p>

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[AllowAnonymous]
public class ForgotPasswordConfirmation : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public void OnGet()
{
}
}
}

@ -0,0 +1,10 @@
@page
@model LockoutModel
@{
ViewData["Title"] = "Locked out";
}
<header>
<h1 class="text-danger">@ViewData["Title"]</h1>
<p class="text-danger">This account has been locked out, please try again later.</p>
</header>

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[AllowAnonymous]
public class LockoutModel : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public void OnGet()
{
}
}
}

@ -0,0 +1,15 @@
@page
@using Microsoft.AspNetCore.Identity
@attribute [IgnoreAntiforgeryToken]
@inject SignInManager<IdentityUser> SignInManager
@functions {
public async Task<IActionResult> OnPost()
{
if (SignInManager.IsSignedIn(User))
{
await SignInManager.SignOutAsync();
}
return Redirect("~/");
}
}

@ -0,0 +1,83 @@
@page
@model LoginModel
@{
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<form id="account" method="post">
<h2>Use a local account to log in.</h2>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
<label asp-for="Input.Email" class="form-label">Email</label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
<label asp-for="Input.Password" class="form-label">Password</label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="checkbox mb-3">
<label asp-for="Input.RememberMe" class="form-label">
<input class="form-check-input" asp-for="Input.RememberMe" />
@Html.DisplayNameFor(m => m.Input.RememberMe)
</label>
</div>
<div>
<button id="login-submit" type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
</div>
<div>
<p>
<a id="forgot-password" asp-page="./ForgotPassword">Forgot your password?</a>
</p>
<p>
<a asp-page="./Register" asp-route-returnUrl="@Model.ReturnUrl">Register as a new user</a>
</p>
<p>
<a id="resend-confirmation" asp-page="./ResendEmailConfirmation">Resend email confirmation</a>
</p>
</div>
</form>
</section>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h3>Use another service to log in.</h3>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class LoginModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LoginModel> _logger;
public LoginModel(SignInManager<IdentityUser> signInManager, ILogger<LoginModel> logger)
{
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string ErrorMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
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)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
// 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: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
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();
}
}
}

@ -0,0 +1,39 @@
@page
@model LoginWith2faModel
@{
ViewData["Title"] = "Two-factor authentication";
}
<h1>@ViewData["Title"]</h1>
<hr />
<p>Your login is protected with an authenticator app. Enter your authenticator code below.</p>
<div class="row">
<div class="col-md-4">
<form method="post" asp-route-returnUrl="@Model.ReturnUrl">
<input asp-for="RememberMe" type="hidden" />
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.TwoFactorCode" class="form-control" autocomplete="off" />
<label asp-for="Input.TwoFactorCode" class="form-label"></label>
<span asp-validation-for="Input.TwoFactorCode" class="text-danger"></span>
</div>
<div class="checkbox mb-3">
<label asp-for="Input.RememberMachine" class="form-label">
<input asp-for="Input.RememberMachine" />
@Html.DisplayNameFor(m => m.Input.RememberMachine)
</label>
</div>
<div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
</div>
</form>
</div>
</div>
<p>
Don't have access to your authenticator device? You can
<a id="recovery-code-login" asp-page="./LoginWithRecoveryCode" asp-route-returnUrl="@Model.ReturnUrl">log in with a recovery code</a>.
</p>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,131 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class LoginWith2faModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<LoginWith2faModel> _logger;
public LoginWith2faModel(
SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
ILogger<LoginWith2faModel> logger)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool RememberMe { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Display(Name = "Remember this machine")]
public bool RememberMachine { get; set; }
}
public async Task<IActionResult> OnGetAsync(bool rememberMe, string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
RememberMe = rememberMe;
return Page();
}
public async Task<IActionResult> OnPostAsync(bool rememberMe, string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty);
var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine);
var userId = await _userManager.GetUserIdAsync(user);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
return LocalRedirect(returnUrl);
}
else if (result.IsLockedOut)
{
_logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Page();
}
}
}
}

@ -0,0 +1,29 @@
@page
@model LoginWithRecoveryCodeModel
@{
ViewData["Title"] = "Recovery code verification";
}
<h1>@ViewData["Title"]</h1>
<hr />
<p>
You have requested to log in with a recovery code. This login will not be remembered until you provide
an authenticator app code at log in or disable 2FA and log in again.
</p>
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.RecoveryCode" class="form-control" autocomplete="off" placeholder="RecoveryCode" />
<label asp-for="Input.RecoveryCode" class="form-label"></label>
<span asp-validation-for="Input.RecoveryCode" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,112 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class LoginWithRecoveryCodeModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<LoginWithRecoveryCodeModel> _logger;
public LoginWithRecoveryCodeModel(
SignInManager<IdentityUser> signInManager,
UserManager<IdentityUser> userManager,
ILogger<LoginWithRecoveryCodeModel> logger)
{
_signInManager = signInManager;
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
[Required]
[DataType(DataType.Text)]
[Display(Name = "Recovery Code")]
public string RecoveryCode { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
// Ensure the user has gone through the username & password screen first
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _signInManager.GetTwoFactorAuthenticationUserAsync();
if (user == null)
{
throw new InvalidOperationException($"Unable to load two-factor authentication user.");
}
var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty);
var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode);
var userId = await _userManager.GetUserIdAsync(user);
if (result.Succeeded)
{
_logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
return LocalRedirect(returnUrl ?? Url.Content("~/"));
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
_logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return Page();
}
}
}
}

@ -0,0 +1,36 @@
@page
@model ChangePasswordModel
@{
ViewData["Title"] = "Change password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h3>@ViewData["Title"]</h3>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="change-password-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.OldPassword" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your old password." />
<label asp-for="Input.OldPassword" class="form-label"></label>
<span asp-validation-for="Input.OldPassword" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your new password." />
<label asp-for="Input.NewPassword" class="form-label"></label>
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your new password."/>
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Update password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,127 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class ChangePasswordModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<ChangePasswordModel> _logger;
public ChangePasswordModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<ChangePasswordModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[DataType(DataType.Password)]
[Display(Name = "Current password")]
public string OldPassword { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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 async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return RedirectToPage("./SetPassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword);
if (!changePasswordResult.Succeeded)
{
foreach (var error in changePasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
_logger.LogInformation("User changed their password successfully.");
StatusMessage = "Your password has been changed.";
return RedirectToPage();
}
}
}

@ -0,0 +1,33 @@
@page
@model DeletePersonalDataModel
@{
ViewData["Title"] = "Delete Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h3>@ViewData["Title"]</h3>
<div class="alert alert-warning" role="alert">
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
</div>
<div>
<form id="delete-user" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
@if (Model.RequirePassword)
{
<div class="form-floating mb-3">
<input asp-for="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="Please enter your password." />
<label asp-for="Input.Password" class="form-label"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
}
<button class="w-100 btn btn-lg btn-danger" type="submit">Delete data and close my account</button>
</form>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class DeletePersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<DeletePersonalDataModel> _logger;
public DeletePersonalDataModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<DeletePersonalDataModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool RequirePassword { get; set; }
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
RequirePassword = await _userManager.HasPasswordAsync(user);
if (RequirePassword)
{
if (!await _userManager.CheckPasswordAsync(user, Input.Password))
{
ModelState.AddModelError(string.Empty, "Incorrect password.");
return Page();
}
}
var result = await _userManager.DeleteAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!result.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred deleting user.");
}
await _signInManager.SignOutAsync();
_logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
return Redirect("~/");
}
}
}

@ -0,0 +1,25 @@
@page
@model Disable2faModel
@{
ViewData["Title"] = "Disable two-factor authentication (2FA)";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
<div class="alert alert-warning" role="alert">
<p>
<strong>This action only disables 2FA.</strong>
</p>
<p>
Disabling 2FA does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
</p>
</div>
<div>
<form method="post">
<button class="btn btn-danger" type="submit">Disable 2FA</button>
</form>
</div>

@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class Disable2faModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<Disable2faModel> _logger;
public Disable2faModel(
UserManager<IdentityUser> userManager,
ILogger<Disable2faModel> logger)
{
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!await _userManager.GetTwoFactorEnabledAsync(user))
{
throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false);
if (!disable2faResult.Succeeded)
{
throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
}
_logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User));
StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
return RedirectToPage("./TwoFactorAuthentication");
}
}
}

@ -0,0 +1,12 @@
@page
@model DownloadPersonalDataModel
@{
ViewData["Title"] = "Download Your Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h3>@ViewData["Title"]</h3>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class DownloadPersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<DownloadPersonalDataModel> _logger;
public DownloadPersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<DownloadPersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public IActionResult OnGet()
{
return NotFound();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
_logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User));
// Only include personal data for download
var personalData = new Dictionary<string, string>();
var personalDataProps = typeof(IdentityUser).GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute)));
foreach (var p in personalDataProps)
{
personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null");
}
var logins = await _userManager.GetLoginsAsync(user);
foreach (var l in logins)
{
personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey);
}
personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user));
Response.Headers.Add("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
}
}
}

@ -0,0 +1,44 @@
@page
@model EmailModel
@{
ViewData["Title"] = "Manage Email";
ViewData["ActivePage"] = ManageNavPages.Email;
}
<h3>@ViewData["Title"]</h3>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="email-form" method="post">
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
@if (Model.IsEmailConfirmed)
{
<div class="form-floating mb-3 input-group">
<input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
<div class="input-group-append">
<span class="h-100 input-group-text text-success font-weight-bold">✓</span>
</div>
<label asp-for="Email" class="form-label"></label>
</div>
}
else
{
<div class="form-floating mb-3">
<input asp-for="Email" class="form-control" placeholder="Please enter your email." disabled />
<label asp-for="Email" class="form-label"></label>
<button id="email-verification" type="submit" asp-page-handler="SendVerificationEmail" class="btn btn-link">Send verification email</button>
</div>
}
<div class="form-floating mb-3">
<input asp-for="Input.NewEmail" class="form-control" autocomplete="email" aria-required="true" placeholder="Please enter new email." />
<label asp-for="Input.NewEmail" class="form-label"></label>
<span asp-validation-for="Input.NewEmail" class="text-danger"></span>
</div>
<button id="change-email-button" type="submit" asp-page-handler="ChangeEmail" class="w-100 btn btn-lg btn-primary">Change email</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,171 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class EmailModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IEmailSender _emailSender;
public EmailModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IEmailSender emailSender)
{
_userManager = userManager;
_signInManager = signInManager;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool IsEmailConfirmed { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
[Display(Name = "New email")]
public string NewEmail { get; set; }
}
private async Task LoadAsync(IdentityUser user)
{
var email = await _userManager.GetEmailAsync(user);
Email = email;
Input = new InputModel
{
NewEmail = email,
};
IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user);
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostChangeEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var email = await _userManager.GetEmailAsync(user);
if (Input.NewEmail != email)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmailChange",
pageHandler: null,
values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
Input.NewEmail,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Confirmation link to change email sent. Please check your email.";
return RedirectToPage();
}
StatusMessage = "Your email is unchanged.";
return RedirectToPage();
}
public async Task<IActionResult> OnPostSendVerificationEmailAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var userId = await _userManager.GetUserIdAsync(user);
var email = await _userManager.GetEmailAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code },
protocol: Request.Scheme);
await _emailSender.SendEmailAsync(
email,
"Confirm your email",
$"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");
StatusMessage = "Verification email sent. Please check your email.";
return RedirectToPage();
}
}
}

@ -0,0 +1,53 @@
@page
@model EnableAuthenticatorModel
@{
ViewData["Title"] = "Configure authenticator app";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
<div>
<p>To use an authenticator app go through the following steps:</p>
<ol class="list">
<li>
<p>
Download a two-factor authenticator app like Microsoft Authenticator for
<a href="https://go.microsoft.com/fwlink/?Linkid=825072">Android</a> and
<a href="https://go.microsoft.com/fwlink/?Linkid=825073">iOS</a> or
Google Authenticator for
<a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;hl=en">Android</a> and
<a href="https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8">iOS</a>.
</p>
</li>
<li>
<p>Scan the QR Code or enter this key <kbd>@Model.SharedKey</kbd> into your two factor authenticator app. Spaces and casing do not matter.</p>
<div class="alert alert-info">Learn how to <a href="https://go.microsoft.com/fwlink/?Linkid=852423">enable QR code generation</a>.</div>
<div id="qrCode"></div>
<div id="qrCodeData" data-url="@Model.AuthenticatorUri"></div>
</li>
<li>
<p>
Once you have scanned the QR code or input the key above, your two factor authentication app will provide you
with a unique code. Enter the code in the confirmation box below.
</p>
<div class="row">
<div class="col-md-6">
<form id="send-code" method="post">
<div class="form-floating mb-3">
<input asp-for="Input.Code" class="form-control" autocomplete="off" placeholder="Please enter the code."/>
<label asp-for="Input.Code" class="control-label form-label">Verification Code</label>
<span asp-validation-for="Input.Code" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Verify</button>
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
</form>
</div>
</div>
</li>
</ol>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,188 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class EnableAuthenticatorModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<EnableAuthenticatorModel> _logger;
private readonly UrlEncoder _urlEncoder;
private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6";
public EnableAuthenticatorModel(
UserManager<IdentityUser> userManager,
ILogger<EnableAuthenticatorModel> logger,
UrlEncoder urlEncoder)
{
_userManager = userManager;
_logger = logger;
_urlEncoder = urlEncoder;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string SharedKey { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string AuthenticatorUri { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string[] RecoveryCodes { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
// Strip spaces and hyphens
var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty);
var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync(
user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode);
if (!is2faTokenValid)
{
ModelState.AddModelError("Input.Code", "Verification code is invalid.");
await LoadSharedKeyAndQrCodeUriAsync(user);
return Page();
}
await _userManager.SetTwoFactorEnabledAsync(user, true);
var userId = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
StatusMessage = "Your authenticator app has been verified.";
if (await _userManager.CountRecoveryCodesAsync(user) == 0)
{
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray();
return RedirectToPage("./ShowRecoveryCodes");
}
else
{
return RedirectToPage("./TwoFactorAuthentication");
}
}
private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user)
{
// Load the authenticator key & QR code URI to display on the form
var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
if (string.IsNullOrEmpty(unformattedKey))
{
await _userManager.ResetAuthenticatorKeyAsync(user);
unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user);
}
SharedKey = FormatKey(unformattedKey);
var email = await _userManager.GetEmailAsync(user);
AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey);
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();
int currentPosition = 0;
while (currentPosition + 4 < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' ');
currentPosition += 4;
}
if (currentPosition < unformattedKey.Length)
{
result.Append(unformattedKey.AsSpan(currentPosition));
}
return result.ToString().ToLowerInvariant();
}
private string GenerateQrCodeUri(string email, string unformattedKey)
{
return string.Format(
CultureInfo.InvariantCulture,
AuthenticatorUriFormat,
_urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"),
_urlEncoder.Encode(email),
unformattedKey);
}
}
}

@ -0,0 +1,53 @@
@page
@model ExternalLoginsModel
@{
ViewData["Title"] = "Manage your external logins";
ViewData["ActivePage"] = ManageNavPages.ExternalLogins;
}
<partial name="_StatusMessage" for="StatusMessage" />
@if (Model.CurrentLogins?.Count > 0)
{
<h3>Registered Logins</h3>
<table class="table">
<tbody>
@foreach (var login in Model.CurrentLogins)
{
<tr>
<td id="@($"login-provider-{login.LoginProvider}")">@login.ProviderDisplayName</td>
<td>
@if (Model.ShowRemoveButton)
{
<form id="@($"remove-login-{login.LoginProvider}")" asp-page-handler="RemoveLogin" method="post">
<div>
<input asp-for="@login.LoginProvider" name="LoginProvider" type="hidden" />
<input asp-for="@login.ProviderKey" name="ProviderKey" type="hidden" />
<button type="submit" class="btn btn-primary" title="Remove this @login.ProviderDisplayName login from your account">Remove</button>
</div>
</form>
}
else
{
@: &nbsp;
}
</td>
</tr>
}
</tbody>
</table>
}
@if (Model.OtherLogins?.Count > 0)
{
<h4>Add another service to log in.</h4>
<hr />
<form id="link-login-form" asp-page-handler="LinkLogin" method="post" class="form-horizontal">
<div id="socialLoginList">
<p>
@foreach (var provider in Model.OtherLogins)
{
<button id="@($"link-login-button-{provider.Name}")" type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}

@ -0,0 +1,141 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class ExternalLoginsModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly IUserStore<IdentityUser> _userStore;
public ExternalLoginsModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
IUserStore<IdentityUser> userStore)
{
_userManager = userManager;
_signInManager = signInManager;
_userStore = userStore;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<UserLoginInfo> CurrentLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<AuthenticationScheme> OtherLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool ShowRemoveButton { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
CurrentLogins = await _userManager.GetLoginsAsync(user);
OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync())
.Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider))
.ToList();
string passwordHash = null;
if (_userStore is IUserPasswordStore<IdentityUser> userPasswordStore)
{
passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted);
}
ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1;
return Page();
}
public async Task<IActionResult> OnPostRemoveLoginAsync(string loginProvider, string providerKey)
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey);
if (!result.Succeeded)
{
StatusMessage = "The external login was not removed.";
return RedirectToPage();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "The external login was removed.";
return RedirectToPage();
}
public async Task<IActionResult> OnPostLinkLoginAsync(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.Page("./ExternalLogins", pageHandler: "LinkLoginCallback");
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User));
return new ChallengeResult(provider, properties);
}
public async Task<IActionResult> OnGetLinkLoginCallbackAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var userId = await _userManager.GetUserIdAsync(user);
var info = await _signInManager.GetExternalLoginInfoAsync(userId);
if (info == null)
{
throw new InvalidOperationException($"Unexpected error occurred loading external login info.");
}
var result = await _userManager.AddLoginAsync(user, info);
if (!result.Succeeded)
{
StatusMessage = "The external login was not added. External logins can only be associated with one account.";
return RedirectToPage();
}
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
StatusMessage = "The external login was added.";
return RedirectToPage();
}
}
}

@ -0,0 +1,27 @@
@page
@model GenerateRecoveryCodesModel
@{
ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
<p>
Generating new recovery codes does not change the keys used in authenticator apps. If you wish to change the key
used in an authenticator app you should <a asp-page="./ResetAuthenticator">reset your authenticator keys.</a>
</p>
</div>
<div>
<form method="post">
<button class="btn btn-danger" type="submit">Generate Recovery Codes</button>
</form>
</div>

@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class GenerateRecoveryCodesModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<GenerateRecoveryCodesModel> _logger;
public GenerateRecoveryCodesModel(
UserManager<IdentityUser> userManager,
ILogger<GenerateRecoveryCodesModel> logger)
{
_userManager = userManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string[] RecoveryCodes { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
if (!isTwoFactorEnabled)
{
throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
if (!isTwoFactorEnabled)
{
throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled.");
}
var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes.ToArray();
_logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
StatusMessage = "You have generated new recovery codes.";
return RedirectToPage("./ShowRecoveryCodes");
}
}
}

@ -0,0 +1,30 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Profile";
ViewData["ActivePage"] = ManageNavPages.Index;
}
<h3>@ViewData["Title"]</h3>
<partial name="_StatusMessage" for="StatusMessage" />
<div class="row">
<div class="col-md-6">
<form id="profile-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Username" class="form-control" placeholder="Please choose your username." disabled />
<label asp-for="Username" class="form-label"></label>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.PhoneNumber" class="form-control" placeholder="Please enter your phone number."/>
<label asp-for="Input.PhoneNumber" class="form-label"></label>
<span asp-validation-for="Input.PhoneNumber" class="text-danger"></span>
</div>
<button id="update-profile-button" type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,118 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class IndexModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public IndexModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Username { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Phone]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
}
private async Task LoadAsync(IdentityUser user)
{
var userName = await _userManager.GetUserNameAsync(user);
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
Username = userName;
Input = new InputModel
{
PhoneNumber = phoneNumber
};
}
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await LoadAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (!ModelState.IsValid)
{
await LoadAsync(user);
return Page();
}
var phoneNumber = await _userManager.GetPhoneNumberAsync(user);
if (Input.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
StatusMessage = "Unexpected error when trying to set phone number.";
return RedirectToPage();
}
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your profile has been updated";
return RedirectToPage();
}
}
}

@ -0,0 +1,123 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static class ManageNavPages
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string Index => "Index";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string Email => "Email";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string ChangePassword => "ChangePassword";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string DownloadPersonalData => "DownloadPersonalData";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string DeletePersonalData => "DeletePersonalData";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string ExternalLogins => "ExternalLogins";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string PersonalData => "PersonalData";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string TwoFactorAuthentication => "TwoFactorAuthentication";
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public static string PageNavClass(ViewContext viewContext, string page)
{
var activePage = viewContext.ViewData["ActivePage"] as string
?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName);
return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null;
}
}
}

@ -0,0 +1,27 @@
@page
@model PersonalDataModel
@{
ViewData["Title"] = "Personal Data";
ViewData["ActivePage"] = ManageNavPages.PersonalData;
}
<h3>@ViewData["Title"]</h3>
<div class="row">
<div class="col-md-6">
<p>Your account contains personal data that you have given us. This page allows you to download or delete that data.</p>
<p>
<strong>Deleting this data will permanently remove your account, and this cannot be recovered.</strong>
</p>
<form id="download-data" asp-page="DownloadPersonalData" method="post">
<button class="btn btn-primary" type="submit">Download</button>
</form>
<p>
<a id="delete" asp-page="DeletePersonalData" class="btn btn-danger">Delete</a>
</p>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class PersonalDataModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly ILogger<PersonalDataModel> _logger;
public PersonalDataModel(
UserManager<IdentityUser> userManager,
ILogger<PersonalDataModel> logger)
{
_userManager = userManager;
_logger = logger;
}
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
}
}

@ -0,0 +1,24 @@
@page
@model ResetAuthenticatorModel
@{
ViewData["Title"] = "Reset authenticator key";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
<div class="alert alert-warning" role="alert">
<p>
<span class="glyphicon glyphicon-warning-sign"></span>
<strong>If you reset your authenticator key your authenticator app will not work until you reconfigure it.</strong>
</p>
<p>
This process disables 2FA until you verify your authenticator app.
If you do not complete your authenticator app configuration you may lose access to your account.
</p>
</div>
<div>
<form id="reset-authenticator-form" method="post">
<button id="reset-authenticator-button" class="btn btn-danger" type="submit">Reset authenticator key</button>
</form>
</div>

@ -0,0 +1,67 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class ResetAuthenticatorModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<ResetAuthenticatorModel> _logger;
public ResetAuthenticatorModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager,
ILogger<ResetAuthenticatorModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGet()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _userManager.SetTwoFactorEnabledAsync(user, false);
await _userManager.ResetAuthenticatorKeyAsync(user);
var userId = await _userManager.GetUserIdAsync(user);
_logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";
return RedirectToPage("./EnableAuthenticator");
}
}
}

@ -0,0 +1,35 @@
@page
@model SetPasswordModel
@{
ViewData["Title"] = "Set password";
ViewData["ActivePage"] = ManageNavPages.ChangePassword;
}
<h3>Set your password</h3>
<partial name="_StatusMessage" for="StatusMessage" />
<p class="text-info">
You do not have a local username/password for this site. Add a local
account so you can log in without an external login.
</p>
<div class="row">
<div class="col-md-6">
<form id="set-password-form" method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.NewPassword" class="form-control" autocomplete="new-password" placeholder="Please enter your new password."/>
<label asp-for="Input.NewPassword" class="form-label"></label>
<span asp-validation-for="Input.NewPassword" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" placeholder="Please confirm your new password."/>
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Set password</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,114 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class SetPasswordModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
public SetPasswordModel(
UserManager<IdentityUser> userManager,
SignInManager<IdentityUser> signInManager)
{
_userManager = userManager;
_signInManager = signInManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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 async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (hasPassword)
{
return RedirectToPage("./ChangePassword");
}
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword);
if (!addPasswordResult.Succeeded)
{
foreach (var error in addPasswordResult.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
await _signInManager.RefreshSignInAsync(user);
StatusMessage = "Your password has been set.";
return RedirectToPage();
}
}
}

@ -0,0 +1,25 @@
@page
@model ShowRecoveryCodesModel
@{
ViewData["Title"] = "Recovery codes";
ViewData["ActivePage"] = "TwoFactorAuthentication";
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
<div class="alert alert-warning" role="alert">
<p>
<strong>Put these codes in a safe place.</strong>
</p>
<p>
If you lose your device and don't have the recovery codes you will lose access to your account.
</p>
</div>
<div class="row">
<div class="col-md-12">
@for (var row = 0; row < Model.RecoveryCodes.Length; row += 2)
{
<code class="recovery-code">@Model.RecoveryCodes[row]</code><text>&nbsp;</text><code class="recovery-code">@Model.RecoveryCodes[row + 1]</code><br />
}
</div>
</div>

@ -0,0 +1,46 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class ShowRecoveryCodesModel : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string[] RecoveryCodes { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IActionResult OnGet()
{
if (RecoveryCodes == null || RecoveryCodes.Length == 0)
{
return RedirectToPage("./TwoFactorAuthentication");
}
return Page();
}
}
}

@ -0,0 +1,71 @@
@page
@using Microsoft.AspNetCore.Http.Features
@model TwoFactorAuthenticationModel
@{
ViewData["Title"] = "Two-factor authentication (2FA)";
ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication;
}
<partial name="_StatusMessage" for="StatusMessage" />
<h3>@ViewData["Title"]</h3>
@{
var consentFeature = HttpContext.Features.Get<ITrackingConsentFeature>();
@if (consentFeature?.CanTrack ?? true)
{
@if (Model.Is2faEnabled)
{
if (Model.RecoveryCodesLeft == 0)
{
<div class="alert alert-danger">
<strong>You have no recovery codes left.</strong>
<p>You must <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a> before you can log in with a recovery code.</p>
</div>
}
else if (Model.RecoveryCodesLeft == 1)
{
<div class="alert alert-danger">
<strong>You have 1 recovery code left.</strong>
<p>You can <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
else if (Model.RecoveryCodesLeft <= 3)
{
<div class="alert alert-warning">
<strong>You have @Model.RecoveryCodesLeft recovery codes left.</strong>
<p>You should <a asp-page="./GenerateRecoveryCodes">generate a new set of recovery codes</a>.</p>
</div>
}
if (Model.IsMachineRemembered)
{
<form method="post" style="display: inline-block">
<button type="submit" class="btn btn-primary">Forget this browser</button>
</form>
}
<a asp-page="./Disable2fa" class="btn btn-primary">Disable 2FA</a>
<a asp-page="./GenerateRecoveryCodes" class="btn btn-primary">Reset recovery codes</a>
}
<h4>Authenticator app</h4>
@if (!Model.HasAuthenticator)
{
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Add authenticator app</a>
}
else
{
<a id="enable-authenticator" asp-page="./EnableAuthenticator" class="btn btn-primary">Set up authenticator app</a>
<a id="reset-authenticator" asp-page="./ResetAuthenticator" class="btn btn-primary">Reset authenticator app</a>
}
}
else
{
<div class="alert alert-danger">
<strong>Privacy and cookie policy have not been accepted.</strong>
<p>You must accept the policy before you can enable two factor authentication.</p>
</div>
}
}
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,89 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage
{
public class TwoFactorAuthenticationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<TwoFactorAuthenticationModel> _logger;
public TwoFactorAuthenticationModel(
UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager, ILogger<TwoFactorAuthenticationModel> logger)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool HasAuthenticator { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public int RecoveryCodesLeft { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public bool Is2faEnabled { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool IsMachineRemembered { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[TempData]
public string StatusMessage { get; set; }
public async Task<IActionResult> OnGetAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null;
Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user);
IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user);
RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user);
return Page();
}
public async Task<IActionResult> OnPostAsync()
{
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
await _signInManager.ForgetTwoFactorClientAsync();
StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code.";
return RedirectToPage();
}
}
}

@ -0,0 +1,29 @@
@{
if (ViewData.TryGetValue("ParentLayout", out var parentLayout))
{
Layout = (string)parentLayout;
}
else
{
Layout = "/Areas/Identity/Pages/_Layout.cshtml";
}
}
<h1>Manage your account</h1>
<div>
<h2>Change your account settings</h2>
<hr />
<div class="row">
<div class="col-md-3">
<partial name="_ManageNav" />
</div>
<div class="col-md-9">
@RenderBody()
</div>
</div>
</div>
@section Scripts {
@RenderSection("Scripts", required: false)
}

@ -0,0 +1,15 @@
@inject SignInManager<IdentityUser> SignInManager
@{
var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any();
}
<ul class="nav nav-pills flex-column">
<li class="nav-item"><a class="nav-link @ManageNavPages.IndexNavClass(ViewContext)" id="profile" asp-page="./Index">Profile</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.EmailNavClass(ViewContext)" id="email" asp-page="./Email">Email</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.ChangePasswordNavClass(ViewContext)" id="change-password" asp-page="./ChangePassword">Password</a></li>
@if (hasExternalLogins)
{
<li id="external-logins" class="nav-item"><a id="external-login" class="nav-link @ManageNavPages.ExternalLoginsNavClass(ViewContext)" asp-page="./ExternalLogins">External logins</a></li>
}
<li class="nav-item"><a class="nav-link @ManageNavPages.TwoFactorAuthenticationNavClass(ViewContext)" id="two-factor" asp-page="./TwoFactorAuthentication">Two-factor authentication</a></li>
<li class="nav-item"><a class="nav-link @ManageNavPages.PersonalDataNavClass(ViewContext)" id="personal-data" asp-page="./PersonalData">Personal data</a></li>
</ul>

@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@Model
</div>
}

@ -0,0 +1 @@
@using Gremlin_BlazorServer.Areas.Identity.Pages.Account.Manage

@ -0,0 +1,67 @@
@page
@model RegisterModel
@{
ViewData["Title"] = "Register";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<form id="registerForm" asp-route-returnUrl="@Model.ReturnUrl" method="post">
<h2>Create a new account.</h2>
<hr />
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
<label asp-for="Input.Email">Email</label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
<label asp-for="Input.Password">Password</label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="password" />
<label asp-for="Input.ConfirmPassword">Confirm Password</label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button id="registerSubmit" type="submit" class="w-100 btn btn-lg btn-primary">Register</button>
</form>
</div>
<div class="col-md-6 col-md-offset-2">
<section>
<h3>Use another service to register.</h3>
<hr />
@{
if ((Model.ExternalLogins?.Count ?? 0) == 0)
{
<div>
<p>
There are no external authentication services configured. See this <a href="https://go.microsoft.com/fwlink/?LinkID=532715">article
about setting up this ASP.NET application to support logging in via external services</a>.
</p>
</div>
}
else
{
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
</p>
</div>
</form>
}
}
</section>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,180 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Logging;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class RegisterModel : PageModel
{
private readonly SignInManager<IdentityUser> _signInManager;
private readonly UserManager<IdentityUser> _userManager;
private readonly IUserStore<IdentityUser> _userStore;
private readonly IUserEmailStore<IdentityUser> _emailStore;
private readonly ILogger<RegisterModel> _logger;
private readonly IEmailSender _emailSender;
public RegisterModel(
UserManager<IdentityUser> userManager,
IUserStore<IdentityUser> userStore,
SignInManager<IdentityUser> signInManager,
ILogger<RegisterModel> logger,
IEmailSender emailSender)
{
_userManager = userManager;
_userStore = userStore;
_emailStore = GetEmailStore();
_signInManager = signInManager;
_logger = logger;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string ReturnUrl { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public IList<AuthenticationScheme> ExternalLogins { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
[Display(Name = "Email")]
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();
if (ModelState.IsValid)
{
var user = CreateUser();
await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None);
await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None);
var result = await _userManager.CreateAsync(user, Input.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
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>.");
if (_userManager.Options.SignIn.RequireConfirmedAccount)
{
return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl });
}
else
{
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();
}
private IdentityUser CreateUser()
{
try
{
return Activator.CreateInstance<IdentityUser>();
}
catch
{
throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " +
$"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " +
$"override the register page in /Areas/Identity/Pages/Account/Register.cshtml");
}
}
private IUserEmailStore<IdentityUser> GetEmailStore()
{
if (!_userManager.SupportsUserEmail)
{
throw new NotSupportedException("The default UI requires a user store with email support.");
}
return (IUserEmailStore<IdentityUser>)_userStore;
}
}
}

@ -0,0 +1,23 @@
@page
@model RegisterConfirmationModel
@{
ViewData["Title"] = "Register confirmation";
}
<h1>@ViewData["Title"]</h1>
@{
if (@Model.DisplayConfirmAccountLink)
{
<p>
This app does not currently have a real email sender registered, see <a href="https://aka.ms/aspaccountconf">these docs</a> for how to configure a real email sender.
Normally this would be emailed: <a id="confirm-link" href="@Model.EmailConfirmationUrl">Click here to confirm your account</a>
</p>
}
else
{
<p>
Please check your email to confirm your account.
</p>
}
}

@ -0,0 +1,79 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class RegisterConfirmationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _sender;
public RegisterConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender sender)
{
_userManager = userManager;
_sender = sender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool DisplayConfirmAccountLink { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string EmailConfirmationUrl { get; set; }
public async Task<IActionResult> OnGetAsync(string email, string returnUrl = null)
{
if (email == null)
{
return RedirectToPage("/Index");
}
returnUrl = returnUrl ?? Url.Content("~/");
var user = await _userManager.FindByEmailAsync(email);
if (user == null)
{
return NotFound($"Unable to load user with email '{email}'.");
}
Email = email;
// Once you add a real email sender, you should remove this code that lets you confirm the account
DisplayConfirmAccountLink = true;
if (DisplayConfirmAccountLink)
{
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
EmailConfirmationUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl },
protocol: Request.Scheme);
}
return Page();
}
}
}

@ -0,0 +1,26 @@
@page
@model ResendEmailConfirmationModel
@{
ViewData["Title"] = "Resend email confirmation";
}
<h1>@ViewData["Title"]</h1>
<h2>Enter your email.</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="All" class="text-danger" role="alert"></div>
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" aria-required="true" placeholder="name@example.com" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Resend</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,88 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
[AllowAnonymous]
public class ResendEmailConfirmationModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
private readonly IEmailSender _emailSender;
public ResendEmailConfirmationModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
{
_userManager = userManager;
_emailSender = emailSender;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
return Page();
}
var userId = await _userManager.GetUserIdAsync(user);
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
var callbackUrl = Url.Page(
"/Account/ConfirmEmail",
pageHandler: null,
values: new { userId = userId, 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>.");
ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email.");
return Page();
}
}
}

@ -0,0 +1,37 @@
@page
@model ResetPasswordModel
@{
ViewData["Title"] = "Reset password";
}
<h1>@ViewData["Title"]</h1>
<h2>Reset your password.</h2>
<hr />
<div class="row">
<div class="col-md-4">
<form method="post">
<div asp-validation-summary="ModelOnly" class="text-danger" role="alert"></div>
<input asp-for="Input.Code" type="hidden" />
<div class="form-floating mb-3">
<input asp-for="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
<label asp-for="Input.Email" class="form-label"></label>
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.Password" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please enter your password." />
<label asp-for="Input.Password" class="form-label"></label>
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-floating mb-3">
<input asp-for="Input.ConfirmPassword" class="form-control" autocomplete="new-password" aria-required="true" placeholder="Please confirm your password." />
<label asp-for="Input.ConfirmPassword" class="form-label"></label>
<span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
</div>
<button type="submit" class="w-100 btn btn-lg btn-primary">Reset</button>
</form>
</div>
</div>
@section Scripts {
<partial name="_ValidationScriptsPartial" />
}

@ -0,0 +1,117 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System;
using System.ComponentModel.DataAnnotations;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.WebUtilities;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
public class ResetPasswordModel : PageModel
{
private readonly UserManager<IdentityUser> _userManager;
public ResetPasswordModel(UserManager<IdentityUser> userManager)
{
_userManager = userManager;
}
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[BindProperty]
public InputModel Input { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class InputModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
[EmailAddress]
public string Email { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[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; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[Required]
public string Code { get; set; }
}
public IActionResult OnGet(string code = null)
{
if (code == null)
{
return BadRequest("A code must be supplied for password reset.");
}
else
{
Input = new InputModel
{
Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code))
};
return Page();
}
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
// Don't reveal that the user does not exist
return RedirectToPage("./ResetPasswordConfirmation");
}
var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password);
if (result.Succeeded)
{
return RedirectToPage("./ResetPasswordConfirmation");
}
foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}
return Page();
}
}
}

@ -0,0 +1,10 @@
@page
@model ResetPasswordConfirmationModel
@{
ViewData["Title"] = "Reset password confirmation";
}
<h1>@ViewData["Title"]</h1>
<p>
Your password has been reset. Please <a asp-page="./Login">click here to log in</a>.
</p>

@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages.Account
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[AllowAnonymous]
public class ResetPasswordConfirmationModel : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public void OnGet()
{
}
}
}

@ -0,0 +1,10 @@
@model string
@if (!String.IsNullOrEmpty(Model))
{
var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success";
<div class="alert alert-@statusMessageClass alert-dismissible" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@Model
</div>
}

@ -0,0 +1 @@
@using Gremlin_BlazorServer.Areas.Identity.Pages.Account

@ -0,0 +1,23 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>, and restarting the application.
</p>

@ -0,0 +1,41 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable disable
using System.Diagnostics;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace Gremlin_BlazorServer.Areas.Identity.Pages
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
[AllowAnonymous]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public string RequestId { get; set; }
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
/// <summary>
/// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

@ -0,0 +1,27 @@
@using Microsoft.AspNetCore.Identity
@inject SignInManager<IdentityUser> SignInManager
@inject UserManager<IdentityUser> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<ul class="navbar-nav">
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity?.Name!</a>
</li>
<li class="nav-item">
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
</form>
</li>
}
else
{
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
</li>
}
</ul>

@ -0,0 +1,4 @@
@using Microsoft.AspNetCore.Identity
@using Gremlin_BlazorServer.Areas.Identity
@using Gremlin_BlazorServer.Areas.Identity.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@ -0,0 +1,4 @@

@{
Layout = "/Pages/Shared/_Layout.cshtml";
}

@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
namespace Gremlin_BlazorServer.Areas.Identity
{
public class RevalidatingIdentityAuthenticationStateProvider<TUser> : RevalidatingServerAuthenticationStateProvider where TUser : class
{
private readonly IServiceScopeFactory scopeFactory;
private readonly IdentityOptions options;
public RevalidatingIdentityAuthenticationStateProvider(ILoggerFactory loggerFactory, IServiceScopeFactory scopeFactory, IOptions<IdentityOptions> optionsAccessor) : base(loggerFactory)
{
this.scopeFactory = scopeFactory;
options = optionsAccessor.Value;
}
protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30);
protected override async Task<bool> ValidateAuthenticationStateAsync(
AuthenticationState authenticationState, CancellationToken cancellationToken)
{
// Get the user manager from a new scope to ensure it fetches fresh data
IServiceScope scope = scopeFactory.CreateScope();
try
{
UserManager<TUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<TUser>>();
return await ValidateSecurityStampAsync(userManager, authenticationState.User);
}
finally
{
if (scope is IAsyncDisposable asyncDisposable)
{
await asyncDisposable.DisposeAsync();
}
else
{
scope.Dispose();
}
}
}
private async Task<bool> ValidateSecurityStampAsync(UserManager<TUser> userManager, ClaimsPrincipal principal)
{
TUser? user = await userManager.GetUserAsync(principal);
if (user == null)
{
return false;
}
else if (!userManager.SupportsUserSecurityStamp)
{
return true;
}
else
{
string? principalStamp = principal.FindFirstValue(options.ClaimsIdentity.SecurityStampClaimType);
string userStamp = await userManager.GetSecurityStampAsync(user);
return principalStamp == userStamp;
}
}
}
}

@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Gremlin_BlazorServer.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options)
{
}
}
}

File diff suppressed because it is too large Load Diff

@ -44,7 +44,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public static string ToInsecureString(SecureString input)
{
string returnValue = string.Empty;
string returnValue;
IntPtr ptr = System.Runtime.InteropServices.Marshal.SecureStringToBSTR(input);
try
{

@ -9,38 +9,38 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public void Configure(EntityTypeBuilder<Account> accountEntity)
{
_ = accountEntity.HasKey(e => e.AccountId);
_ = accountEntity.HasMany(d => d.Contacts).WithOne(p => p.Account).IsRequired(true);
_ = accountEntity.HasOne(d => d.AccountType).WithMany(p => p.Accounts).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
_ = accountEntity.HasOne(d => d.SubMarket).WithMany(p => p.Accounts).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
_ = accountEntity.HasAlternateKey(e => e.SAPAccountNumber); // =Unique
_ = accountEntity.HasMany(d => d.Contacts).WithOne(p => p.Account).IsRequired();
_ = accountEntity.HasOne(d => d.AccountType).WithMany(p => p.Accounts).IsRequired().OnDelete(DeleteBehavior.Restrict);
_ = accountEntity.HasOne(d => d.SubMarket).WithMany(p => p.Accounts).IsRequired().OnDelete(DeleteBehavior.Restrict);
_ = accountEntity.HasAlternateKey(e => e.SapAccountNumber); // =Unique
_ = accountEntity.Property(e => e.AccountId).ValueGeneratedOnAdd();
_ = accountEntity.Property(e => e.ParentAccountId).HasDefaultValue(0);
_ = accountEntity.Property(e => e.AccountName).IsRequired(true).HasMaxLength(250);
_ = accountEntity.Property(e => e.AccountName).IsRequired().HasMaxLength(250);
_ = accountEntity.Property(e => e.Notes).HasDefaultValue("");
_ = accountEntity.Property(e => e.Street).IsRequired(true).HasMaxLength(100);
_ = accountEntity.Property(e => e.ZIP).IsRequired(true).HasColumnType("Char(5)");
_ = accountEntity.Property(e => e.City).IsRequired(true).HasMaxLength(50);
_ = accountEntity.Property(e => e.Street).IsRequired().HasMaxLength(100);
_ = accountEntity.Property(e => e.Zip).IsRequired().HasColumnType("Char(5)");
_ = accountEntity.Property(e => e.City).IsRequired().HasMaxLength(50);
_ = accountEntity.Property(e => e.FloorOrBuilding).HasMaxLength(50);
_ = accountEntity.Property(e => e.Longitude).HasDefaultValue(0);
_ = accountEntity.Property(e => e.Latitude).HasDefaultValue(0);
_ = accountEntity.Property(e => e.PhoneNumber).IsRequired(true).HasMaxLength(30);
_ = accountEntity.Property(e => e.PhoneNumber).IsRequired().HasMaxLength(30);
_ = accountEntity.Property(e => e.FaxNumber).HasMaxLength(30).HasDefaultValue("");
_ = accountEntity.Property(e => e.Webpage).HasMaxLength(250).HasDefaultValue("");
_ = accountEntity.Property(e => e.EMail).HasMaxLength(150).HasDefaultValue("");
_ = accountEntity.Property(e => e.SAPAccountNumber).IsRequired(true);
_ = accountEntity.Property(e => e.AccountCreatedInSAPOn).IsRequired(true);
_ = accountEntity.Property(e => e.SapAccountNumber).IsRequired();
_ = accountEntity.Property(e => e.AccountCreatedInSapOn).IsRequired();
_ = accountEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = accountEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = accountEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = accountEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = accountEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = accountEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = accountEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = accountEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
//.HasDefaultValue("Active") //Default-Wert wird nicht gesetzt?!? Bug in EF Core?
_ = accountEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = accountEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
//.IsRowVersion() impliziert .ValueGeneratedOnAddOrUpdate() und .IsConcurrencyToken(true)
_ = accountEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = accountEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -49,14 +49,14 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public void Configure(EntityTypeBuilder<Contact> contactEntity)
{
_ = contactEntity.HasKey(e => e.ContactId);
_ = contactEntity.HasOne(p => p.Account).WithMany(d => d.Contacts).IsRequired(true);
_ = contactEntity.HasOne(p => p.Account).WithMany(d => d.Contacts).IsRequired();
//entity.HasAlternateKey(e => e.SAPContactNumber);
_ = contactEntity.Property(e => e.ContactId);
_ = contactEntity.Property(e => e.SAPContactNumber).IsRequired(true);
_ = contactEntity.Property(e => e.SapContactNumber).IsRequired();
_ = contactEntity.Property(e => e.AcademicTitle).HasDefaultValue("");
_ = contactEntity.Property(e => e.FirstName).HasDefaultValue("");
_ = contactEntity.Property(e => e.LastName).IsRequired(true);
_ = contactEntity.Property(e => e.LastName).IsRequired();
_ = contactEntity.Property(e => e.Gender).HasDefaultValue(0);
//.IsRequired(true) darf nicht gesetzt werden, da sonst vom DB-Engine NULL nicht erlaubt wird (trotz Definition als Bool? im Code. MySQL kennt kein Bool, sondern wandelt das intern in Tinyint um).
_ = contactEntity.Property(e => e.OptInStatus).HasDefaultValue(false);
@ -67,13 +67,13 @@ namespace Gremlin_BlazorServer.Data.DBClasses
_ = contactEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = contactEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = contactEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = contactEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = contactEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = contactEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = contactEntity.Property(e => e.DataStatus).IsRequired(true);
_ = contactEntity.Property(e => e.DataStatus).IsRequired();
//.HasDefaultValue("Active") //Default-Wert wird nicht gesetzt?!? Bug in EF Core?
_ = contactEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = contactEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
//.IsRowVersion() impliziert .ValueGeneratedOnAddOrUpdate() und .IsConcurrencyToken(true)
_ = contactEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = contactEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -84,30 +84,30 @@ namespace Gremlin_BlazorServer.Data.DBClasses
_ = quoteEntity.HasKey(e => e.QuoteId);
_ = quoteEntity.HasMany(d => d.LineItems).WithOne(p => p.Quote).IsRequired(false).OnDelete(DeleteBehavior.Cascade);
_ = quoteEntity.Property(e => e.QuotationNumber).HasColumnType("VARCHAR(255)").IsRequired(true).ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.QuotationDate).IsRequired(true).ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.QuotationNumber).HasColumnType("VARCHAR(255)").IsRequired().ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.QuotationDate).IsRequired().ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.ValidUntil);
_ = quoteEntity.Property(e => e.ValidFor).IsRequired(true);
_ = quoteEntity.Property(e => e.ValidFor).IsRequired();
_ = quoteEntity.Ignore("SalesRep");
_ = quoteEntity.Property(e => e.TotalListprice);
_ = quoteEntity.Property(e => e.TotalDiscount);
_ = quoteEntity.Property(e => e.TotalNet);
_ = quoteEntity.Property(e => e.VAT);
_ = quoteEntity.Property(e => e.Vat);
_ = quoteEntity.Property(e => e.TotalGross);
_ = quoteEntity.Property(e => e.QuoteContains3PP).HasDefaultValue(false);
_ = quoteEntity.Property(e => e.QuoteContainsRB).HasDefaultValue(false);
_ = quoteEntity.Property(e => e.QuoteContains3Pp).HasDefaultValue(false);
_ = quoteEntity.Property(e => e.QuoteContainsRb).HasDefaultValue(false);
_ = quoteEntity.Property(e => e.QuoteTemplate).HasDefaultValue("");
_ = quoteEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = quoteEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = quoteEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = quoteEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = quoteEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active"); ;
_ = quoteEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = quoteEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = quoteEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = quoteEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = quoteEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -116,33 +116,33 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public void Configure(EntityTypeBuilder<LineItem> entity)
{
_ = entity.HasKey(e => e.LineItemId);
_ = entity.HasOne(p => p.Quote).WithMany(d => d.LineItems).HasForeignKey(fk => fk.QuoteId).IsRequired(true).OnDelete(DeleteBehavior.Cascade);
_ = entity.HasOne(p => p.Quote).WithMany(d => d.LineItems).HasForeignKey(fk => fk.QuoteId).IsRequired().OnDelete(DeleteBehavior.Cascade);
_ = entity.Property(e => e.Position).IsRequired(true);
_ = entity.Property(e => e.Amount).IsRequired(true);
_ = entity.Property(e => e.ProductNumber).IsRequired(true);
_ = entity.Property(e => e.Position).IsRequired();
_ = entity.Property(e => e.Amount).IsRequired();
_ = entity.Property(e => e.ProductNumber).IsRequired();
_ = entity.Property(e => e.OptionNumber).HasDefaultValue("");
_ = entity.Property(e => e.SapShortDescription).HasDefaultValue("");
_ = entity.Property(e => e.SapLongDescription).HasDefaultValue("");
_ = entity.Property(e => e.ProductLine).HasDefaultValue("");
_ = entity.Property(e => e.TotalDiscount).IsRequired(true).HasDefaultValue(0);
_ = entity.Property(e => e.SalesDiscount).IsRequired(true).HasDefaultValue(0);
_ = entity.Property(e => e.PromotionalDiscount).IsRequired(true).HasDefaultValue(0);
_ = entity.Property(e => e.ContractualDiscount).IsRequired(true).HasDefaultValue(0);
_ = entity.Property(e => e.DemoDiscount).IsRequired(true).HasDefaultValue(0);
_ = entity.Property(e => e.ListPrice).IsRequired(true);
_ = entity.Property(e => e.ExtendedListPrice).IsRequired(true);
_ = entity.Property(e => e.NetPrice).IsRequired(true);
_ = entity.Property(e => e.Total).IsRequired(true);
_ = entity.Property(e => e.TotalDiscount).IsRequired().HasDefaultValue(0);
_ = entity.Property(e => e.SalesDiscount).IsRequired().HasDefaultValue(0);
_ = entity.Property(e => e.PromotionalDiscount).IsRequired().HasDefaultValue(0);
_ = entity.Property(e => e.ContractualDiscount).IsRequired().HasDefaultValue(0);
_ = entity.Property(e => e.DemoDiscount).IsRequired().HasDefaultValue(0);
_ = entity.Property(e => e.ListPrice).IsRequired();
_ = entity.Property(e => e.ExtendedListPrice).IsRequired();
_ = entity.Property(e => e.NetPrice).IsRequired();
_ = entity.Property(e => e.Total).IsRequired();
_ = entity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = entity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = entity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = entity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = entity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = entity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = entity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = entity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = entity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = entity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = entity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = entity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -151,28 +151,28 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public void Configure(EntityTypeBuilder<Product> productEntity)
{
_ = productEntity.HasKey(e => e.ProductId);
_ = productEntity.HasOne(d => d.CustomDescription).WithMany(p => p.Products).HasForeignKey("CustomDescriptionId").IsRequired(true).OnDelete(DeleteBehavior.SetNull);
_ = productEntity.HasOne(p => p.ProductLine).WithMany(d => d.Products).HasForeignKey("ProductLineCode").IsRequired(true).OnDelete(DeleteBehavior.Restrict);
_ = productEntity.HasOne(d => d.CustomDescription).WithMany(p => p.Products).HasForeignKey("CustomDescriptionId").IsRequired().OnDelete(DeleteBehavior.SetNull);
_ = productEntity.HasOne(p => p.ProductLine).WithMany(d => d.Products).HasForeignKey("ProductLineCode").IsRequired().OnDelete(DeleteBehavior.Restrict);
_ = productEntity.Property(e => e.CustomDescriptionId).IsRequired(true);
_ = productEntity.Property(e => e.CustomDescriptionId).IsRequired();
_ = productEntity.Property(e => e.ProductNumber).IsRequired(true);
_ = productEntity.Property(e => e.ProductNumber).IsRequired();
_ = productEntity.Property(e => e.OptionNumber).HasDefaultValue("");
_ = productEntity.Property(e => e.SapShortDescription).HasDefaultValue("");
_ = productEntity.Property(e => e.SapLongDescription).HasDefaultValue("");
_ = productEntity.Property(e => e.Weight).HasDefaultValue(0);
_ = productEntity.Property(e => e.ProductStatus).HasDefaultValue("Active");
_ = productEntity.Property(e => e.IntroductionDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = productEntity.Property(e => e.ListPrice).IsRequired(true);
_ = productEntity.Property(e => e.ListPrice).IsRequired();
_ = productEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = productEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = productEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = productEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = productEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = productEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = productEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = productEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = productEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = productEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = productEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = productEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -182,11 +182,11 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
_ = customDescriptionEntity.HasKey(e => e.CustomDescriptionId);
_ = customDescriptionEntity.HasMany(p => p.Products).WithOne(d => d.CustomDescription).IsRequired(false);
_ = customDescriptionEntity.HasOne(p => p.Supplier).WithMany(d => d.CustomDescriptions).IsRequired(true);
_ = customDescriptionEntity.HasOne(p => p.Supplier).WithMany(d => d.CustomDescriptions).IsRequired();
_ = customDescriptionEntity.Property(e => e.ProductNumber).IsRequired(true);
_ = customDescriptionEntity.Property(e => e.ProductNumber).IsRequired();
_ = customDescriptionEntity.Property(e => e.OptionNumber).HasDefaultValue("");
_ = customDescriptionEntity.Property(e => e.Heading).IsRequired(true);
_ = customDescriptionEntity.Property(e => e.Heading).IsRequired();
_ = customDescriptionEntity.Property(e => e.DescriptionText).HasDefaultValue("");
_ = customDescriptionEntity.Property(e => e.CoverletterText).HasDefaultValue("");
_ = customDescriptionEntity.Property(e => e.Notes).HasDefaultValue("");
@ -194,11 +194,11 @@ namespace Gremlin_BlazorServer.Data.DBClasses
_ = customDescriptionEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = customDescriptionEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = customDescriptionEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = customDescriptionEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = customDescriptionEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = customDescriptionEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = customDescriptionEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = customDescriptionEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = customDescriptionEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = customDescriptionEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = customDescriptionEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = customDescriptionEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -206,18 +206,18 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
public void Configure(EntityTypeBuilder<ProductLine> productLineEntity)
{
_ = productLineEntity.HasMany(p => p.Products).WithOne(d => d.ProductLine).IsRequired(true).OnDelete(DeleteBehavior.Restrict);
_ = productLineEntity.HasMany(p => p.Products).WithOne(d => d.ProductLine).IsRequired().OnDelete(DeleteBehavior.Restrict);
_ = productLineEntity.Property(e => e.ProductLineDescription).IsRequired(true);
_ = productLineEntity.Property(e => e.ProductLineDescription).IsRequired();
_ = productLineEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = productLineEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = productLineEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = productLineEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = productLineEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = productLineEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = productLineEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = productLineEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = productLineEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = productLineEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = productLineEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = productLineEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -227,17 +227,17 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
//entity.HasKey(e => e.AccountTypeCode);
//entity.HasMany(p => p.Accounts).WithOne(d => d.AccountType); //already defined in class Account
_ = accountTypeEntity.Property(e => e.AccountTypeCode).IsRequired(true).HasColumnType("Char(3)");
_ = accountTypeEntity.Property(e => e.AccountTypeDescription).HasColumnType("Varchar(1000)").IsRequired(true);
_ = accountTypeEntity.Property(e => e.AccountTypeCode).IsRequired().HasColumnType("Char(3)");
_ = accountTypeEntity.Property(e => e.AccountTypeDescription).HasColumnType("Varchar(1000)").IsRequired();
_ = accountTypeEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = accountTypeEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = accountTypeEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = accountTypeEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = accountTypeEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = accountTypeEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = accountTypeEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = accountTypeEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = accountTypeEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = accountTypeEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = accountTypeEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = accountTypeEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -248,16 +248,16 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//entity.HasKey(e => e.SubMarketCode);
//entity.HasMany(p => p.Accounts).WithOne(d => d.SubMarket); //already defined in class Account
_ = subMarketEntity.Property(e => e.SubMarketCode).HasColumnType("Char(3)");
_ = subMarketEntity.Property(e => e.SubMarketDescription).HasColumnType("Varchar(1000)").IsRequired(true);
_ = subMarketEntity.Property(e => e.SubMarketDescription).HasColumnType("Varchar(1000)").IsRequired();
_ = subMarketEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = subMarketEntity.Property(e => e.DataValidFrom).HasColumnType("DATETIME").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = subMarketEntity.Property(e => e.DataValidUntil).HasColumnType("DATETIME").HasDefaultValueSql("'9999-12-31 23:59:59.000000'").ValueGeneratedOnAdd();
_ = subMarketEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired(true);
_ = subMarketEntity.Property(e => e.DataVersionNumber).HasDefaultValue(1).IsRequired();
_ = subMarketEntity.Property(e => e.DataVersionComment).HasDefaultValue("");
_ = subMarketEntity.Property(e => e.DataStatus).IsRequired(true).HasDefaultValue("Active");
_ = subMarketEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = subMarketEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired(true).HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
_ = subMarketEntity.Property(e => e.DataStatus).IsRequired().HasDefaultValue("Active");
_ = subMarketEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = subMarketEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").IsRequired().HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
@ -265,30 +265,30 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
public void Configure(EntityTypeBuilder<RegisteredUser> registeredUserEntity)
{
_ = registeredUserEntity.HasKey(e => e.RegisteredUserID);
_ = registeredUserEntity.HasKey(e => e.RegisteredUserId);
_ = registeredUserEntity.HasMany(d => d.RUSettings).WithOne(p => p.RegisteredUser).IsRequired(true);
_ = registeredUserEntity.Property(e => e.UserName).IsRequired(true);
_ = registeredUserEntity.Property(e => e.PasswordHash).IsRequired(true);
_ = registeredUserEntity.HasMany(d => d.RuSettings).WithOne(p => p.RegisteredUser).IsRequired();
_ = registeredUserEntity.Property(e => e.UserName).IsRequired();
_ = registeredUserEntity.Property(e => e.PasswordHash).IsRequired();
_ = registeredUserEntity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = registeredUserEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = registeredUserEntity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = registeredUserEntity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}
public class RUSettingsConfiguration : IEntityTypeConfiguration<RUSettings>
public class RuSettingsConfiguration : IEntityTypeConfiguration<RuSettings>
{
public void Configure(EntityTypeBuilder<RUSettings> rUSettingsEntitity)
public void Configure(EntityTypeBuilder<RuSettings> rUSettingsEntitity)
{
_ = rUSettingsEntitity.HasKey(e => e.RUSettingsID);
_ = rUSettingsEntitity.HasKey(e => e.RuSettingsId);
_ = rUSettingsEntitity.HasOne(d => d.RegisteredUser).WithMany(p => p.RUSettings).IsRequired(true);
_ = rUSettingsEntitity.Property(e => e.SettingKey).IsRequired(true);
_ = rUSettingsEntitity.Property(e => e.SettingValue).IsRequired(true);
_ = rUSettingsEntitity.HasOne(d => d.RegisteredUser).WithMany(p => p.RuSettings).IsRequired();
_ = rUSettingsEntitity.Property(e => e.SettingKey).IsRequired();
_ = rUSettingsEntitity.Property(e => e.SettingValue).IsRequired();
_ = rUSettingsEntitity.Property(e => e.DataCreationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP").ValueGeneratedOnAdd();
_ = rUSettingsEntitity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken(true);
_ = rUSettingsEntitity.Property(e => e.DataModificationDate).HasColumnType("TIMESTAMP").HasDefaultValueSql("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP").ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();
_ = rUSettingsEntitity.Property(e => e.DataModificationByUser).HasColumnType("TINYTEXT").HasDefaultValueSql("ON INSERT CURRENT_USER() ON UPDATE CURRENT_USER()");
}
}

@ -1,10 +1,10 @@
using Gremlin_BlazorServer.Data.EntityClasses;
using Gremlin_BlazorServer.Services;
using Gremlin_BlazorServer.Services.GUClasses;
using Microsoft.VisualBasic.FileIO;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Gremlin_BlazorServer.Utilities.GUClasses;
using static Gremlin_BlazorServer.Data.EntityClasses.Enums;
namespace Gremlin_BlazorServer.Data.DBClasses
@ -12,51 +12,26 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public static class GenericImporter
{
//Private members
private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
private static readonly DateTime farInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
private static Encoding encoding = Encoding.UTF8;
private static TextFieldParser csvParser = new(Filepath, encoding, true);
private static string filepath = string.Empty;
internal static GremlinContext db = new();
private static readonly GremlinDb gremlinDb = new();
//Public properties
public static string Filepath
{
get => filepath;
set
{
filepath = value;
if (encoding != null)
{
TextFieldParser _csvParser = new(Filepath, encoding, true);
_csvParser.SetDelimiters(Separators);
_csvParser.HasFieldsEnclosedInQuotes = true;
}
}
}
public static string Filepath { get; set; } = string.Empty;
public static string[] Separators
private static string[] Separators
{
get => csvParser.Delimiters!;
set
{
if (value.Length > 0)
{
csvParser.SetDelimiters(value);
}
}
set { if (value.Length > 0) csvParser.SetDelimiters(value); }
}
public static bool DataHasHeadings { get; set; }
public static Encoding Encoding
private static Encoding Encoding
{
get => encoding;
set
{
encoding = value;
ResetParser();
}
set { encoding = value; ResetParser(); }
}
////Constructors
@ -101,72 +76,66 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//Public methods
public static void SetFilepath()
private static void SetFilepath()
{
//Filepath = FileIO.GetFilepathFromUser();
}
public static void ResetParser()
private static void ResetParser()
{
if (Filepath != "")
{
TextFieldParser _newParser = new(Filepath, encoding);
_newParser.SetDelimiters(Separators);
_newParser.HasFieldsEnclosedInQuotes = true;
csvParser = _newParser;
TextFieldParser newParser = new(Filepath, encoding);
newParser.SetDelimiters(Separators);
newParser.HasFieldsEnclosedInQuotes = true;
csvParser = newParser;
}
}
public static bool Run(string filepath, string separator, string encoding)
{
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
Filepath = filepath.Replace(@"\\", @"\");
try
{
Encoding = Encoding.GetEncoding(encoding);
}
catch (Exception)
{
Encoding = FileService.GetEncoding(GenericImporter.filepath);
Encoding = FileService.GetEncoding(Filepath);
}
Separators = new string[] { separator };
Separators = new[] { separator };
return ImportFile();
}
public static async Task<bool> RunAsync(string filepath, string separator, string encoding)
{
if (filepath is "" or null)
{
return false;
}
if (filepath is "" or null) return false;
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
Filepath = filepath.Replace(@"\\", @"\");
try
{
Encoding = Encoding.GetEncoding(encoding);
}
catch (Exception)
{
Encoding = FileService.GetEncoding(GenericImporter.filepath);
Encoding = FileService.GetEncoding(Filepath);
}
Separators = new string[] { separator };
Separators = new[] { separator };
return await Task.Run(ImportFile);
}
public static string GuessSeparator(string filepath)//, string encoding)
{
if (filepath is "" or null)
{
return string.Empty;
}
if (filepath is "" or null) return string.Empty;
string[] candidates = new string[] { "|", ",", ";" };
string[] candidates = new[] { "|", ",", ";" };
int numberOfCandidates = candidates.Length;
int numberOfLinesToEvaluate = 100;
const int numberOfLinesToEvaluate = 100;
int[,] score = new int[numberOfLinesToEvaluate, numberOfCandidates];
GenericImporter.filepath = filepath.Replace(@"\\", @"\");
Filepath = filepath.Replace(@"\\", @"\");
//if (csvParser == null)
//{
// try
@ -181,12 +150,11 @@ namespace Gremlin_BlazorServer.Data.DBClasses
using (csvParser)
{
string line;
for (int i = 0; i < numberOfLinesToEvaluate; i++)
{
if (!csvParser.EndOfData)
{
line = csvParser.ReadLine()!;
string line = csvParser.ReadLine()!;
for (int j = 0; j < numberOfCandidates; j++)
{
score[i, j] = line.Split(candidates[j]).Length;
@ -196,18 +164,13 @@ namespace Gremlin_BlazorServer.Data.DBClasses
}
List<(string, int, float)> scoreBoard = new(); //Item1 = Separator, Item2 = Score (Anzahl aufeinanderfolgender Zeilen mit gleicher Anzahl von Fields), Item3 = Count (durchschnittliche Anzahl von Fields in Zeile)
int x;
float average;
for (int j = 0; j < numberOfCandidates; j++)
{
x = 0;
average = 0;
int x = 0;
float average = 0;
for (int i = 0; i < numberOfLinesToEvaluate - 1; i++)
{
if (score[i, j] == score[i + 1, j] && score[i, j] > 1)
{
x++;
}
if (score[i, j] == score[i + 1, j] && score[i, j] > 1) x++;
average += score[i, j];
}
average += score[numberOfLinesToEvaluate - 1, j];
@ -219,7 +182,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
return scoreBoard.Find(f => f.Item2 == scoreBoard.Max(x => x.Item2) && f.Item3 == scoreBoard.Max(x => x.Item3)).Item1;
}
public static bool ImportFile()
private static bool ImportFile()
//Ein (möglichst) generischer Importer
//1. Dateipfad erfassen
//2. Column <-> Property Mapping
@ -227,15 +190,8 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//4. Daten einlesen, konvertieren, validieren, Metadaten setzen und alles in Liste(n) speichern
//5. Datenliste(n) in DB speichern
{
if (filepath == "")
{
SetFilepath();
}
if (filepath == "")
{
return false;
}
if (Filepath == "") SetFilepath();
if (Filepath == "") return false;
using (csvParser)
{
@ -246,8 +202,8 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//dynamische Spaltenzuordnung in Dictonary speichern
string[] fields = csvParser.ReadFields()!;
Dictionary<string, string> MappingDictionary = ReadMappingDictionaryFromFile();
Dictionary<string, int> mappingTable = MapDataHeading(fields, MappingDictionary);
Dictionary<string, string> mappingDictionary = ReadMappingDictionaryFromFile();
Dictionary<string, int> mappingTable = MapDataHeading(fields, mappingDictionary);
//determine data type to be imported
DataIdentificator dataIdentificator = new(mappingTable);
@ -267,7 +223,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
"SubMarket" => ImportSubMarket(csvParser, mappingTable),
"Account" => ImportAccounts(mappingTable),
"Contact" => ImportContacts(mappingTable),
"LSAG" => ImportLSAG(mappingTable),
"LSAG" => ImportLsag(mappingTable),
"Product" => ImportProducts(mappingTable),
"CustomDescription" => ImportCustomDescriptions(),// mappingTable);
_ => false,
@ -277,7 +233,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
public static bool ImportFile(string filepath)
{
GenericImporter.filepath = filepath;
Filepath = filepath;
return ImportFile();
}
@ -288,13 +244,13 @@ namespace Gremlin_BlazorServer.Data.DBClasses
using (csvParser)
{
// Skip the row with the column names:
_ = csvParser.ReadLine();
csvParser.ReadLine();
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
string[] fields = csvParser.ReadFields()!;
CustomDescription ImportedCD = new()
CustomDescription importedCd = new()
{
ProductNumber = fields[0],
OptionNumber = fields[1],
@ -304,18 +260,15 @@ namespace Gremlin_BlazorServer.Data.DBClasses
Notes = fields[6],
DataModificationByUser = "Importer",
DataStatus = Status.Active.ToString(),
DataValidUntil = FarInTheFuture,
DataValidUntil = farInTheFuture,
DataVersionComment = "Initial Importer by CD-ImporterFomCsv",
Products = new List<Product>(),
Supplier = new()
{
AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2]
}
Supplier = new() { AccountName = fields[2] is "" or "RB" ? "Agilent Technologies" : fields[2] }
};
ImportedCD.DataCreationDate = ImportedCD.DataValidFrom = ImportedCD.DataModificationDate = DateTime.Now;
ImportedCD.DataVersionNumber = 1;
importedCd.DataCreationDate = importedCd.DataValidFrom = importedCd.DataModificationDate = DateTime.Now;
importedCd.DataVersionNumber = 1;
cDsReadFromFile.Add(ImportedCD);
cDsReadFromFile.Add(importedCd);
}
//Eingelesenen Custum Desciptions in DB schreiben:
@ -324,21 +277,21 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//Step 1b: Add list to db.
//Step 2: Add CDs to products.
using (GremlinContext db = new())
using (GremlinDb gremlinDb = new())
{
//Step 1a
List<Product> thirdPartyProductsFromImportedCDs = new();
foreach (CustomDescription CD in cDsReadFromFile)
foreach (CustomDescription cd in cDsReadFromFile)
{
if (CD.Supplier.AccountName != "Agilent Technologies")
if (cd.Supplier.AccountName != "Agilent Technologies")
{
Product new3PPProduct = new()
Product new3PpProduct = new()
{
CustomDescription = CD,
CustomDescription = cd,
HasBreakPrices = false,
ListPrice = 0,
ProductNumber = CD.ProductNumber,
OptionNumber = CD.OptionNumber,
ProductNumber = cd.ProductNumber,
OptionNumber = cd.OptionNumber,
ProductStatus = Status.Active.ToString(),
SapLongDescription = "",
SapShortDescription = "",
@ -346,59 +299,47 @@ namespace Gremlin_BlazorServer.Data.DBClasses
IntroductionDate = DateTime.Now.Date,
BreakRangeFrom = 0,
BreakRangeTo = 0,
ProductLine = DbHelper.ResolveProductLine(db, "3P")
ProductLine = DbHelper.ResolveProductLine(gremlinDb, "3P")
};
new3PPProduct.CustomDescription.Supplier = DbHelper.ResolveAccountByName(db, new3PPProduct.CustomDescription.Supplier.AccountName);
_ = MetaDataSetter.ForImport(new3PPProduct, "GenericImporter-Method", "Created at import from Custom Descriptions.");
thirdPartyProductsFromImportedCDs.Add(new3PPProduct);
new3PpProduct.CustomDescription.Supplier = DbHelper.ResolveAccountByName(gremlinDb, new3PpProduct.CustomDescription.Supplier.AccountName);
MetaDataSetter.ForImport(new3PpProduct, "GenericImporter-Method", "Created at import from Custom Descriptions.");
thirdPartyProductsFromImportedCDs.Add(new3PpProduct);
}
};
}
//Step 1b
db.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions!
_ = db.SaveChanges();
gremlinDb.Products.AddRange(thirdPartyProductsFromImportedCDs); //Imports both the products and the associated custom descriptions!
gremlinDb.SaveChanges();
// Produkt aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
//Step 2
List<CustomDescription> importedCDsWithEFReferences = new(100000);
List<CustomDescription> cDsWithoutEFReferences = new(100); //nur zur Kontrolle, wird nicht verwendet.
List<Product> productsInDb = db.Products.ToList();
Account agilent = db.Accounts.Where(a => a.AccountName == "Agilent Technologies").Single();
List<CustomDescription> importedCDsWithEfReferences = new(100000);
List<CustomDescription> cDsWithoutEfReferences = new(100); //nur zur Kontrolle, wird nicht verwendet.
List<Product> productsInDb = gremlinDb.Products.ToList();
Account agilent = gremlinDb.Accounts.Single(a => a.AccountName == "Agilent Technologies");
foreach (CustomDescription cD in cDsReadFromFile)
{
if (cD.Products == null) { continue; }
//Skip Desciptions, if it has been already imported above (as part from 3PP)
if (thirdPartyProductsFromImportedCDs.Intersect(cD.Products).Any()) { continue; }
//Establish EF Reference. If no PN/Opt found, then skip this custom description.
//CD.Product = GetProduct(db, CD.ProductNumber, CD.OptionNumber); //ResolveXY-functions return null, if no match is found in db.
cD.Products = productsInDb.Where(product => product.ProductNumber == cD.ProductNumber && product.OptionNumber == cD.OptionNumber).ToList();
if (cD.Products == null)
{
cDsWithoutEFReferences.Add(cD);
continue;
}
//Establish EF Reference: If no Supplier-Account found, then skip this custom description (shouldn't happen), else set Supplier to Agilent (3PP-Supplier have been processed before and their products should not be part of this list).
if (cD.Supplier == null)
{
cDsWithoutEFReferences.Add(cD);
continue;
}
cD.Supplier = agilent;
//prepare properties
_ = MetaDataSetter.ForImport(cD, "GenericImporter-Method", "Initial import by CSV Importer (Function GenericImporter.ImportCustomDescriptions)");
MetaDataSetter.ForImport(cD, "GenericImporter-Method", "Initial import by CSV Importer (Function GenericImporter.ImportCustomDescriptions)");
//add to final list of CDs, that will go into the db.
importedCDsWithEFReferences.Add(cD);
importedCDsWithEfReferences.Add(cD);
}
db.CustomDescriptions.AddRange(importedCDsWithEFReferences);
_ = db.SaveChanges();
gremlinDb.CustomDescriptions.AddRange(importedCDsWithEfReferences);
gremlinDb.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {importedCDsWithEFReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt.");
Debug.WriteLine($"Es wurden {importedCDsWithEfReferences.Count} eigene Beschreibungen erfolgreich der Datenbank hinzugefügt.{Environment.NewLine}Es wurden {thirdPartyProductsFromImportedCDs.Count} 3PP-Produkte neu angelegt.");
}
return true;
}
@ -406,49 +347,34 @@ namespace Gremlin_BlazorServer.Data.DBClasses
private static bool ImportProducts(Dictionary<string, int> mappingTable)
{
List<Product> ProductsReadFromFile = new(ParseProductFile(mappingTable));
return InsertProducts(ProductsReadFromFile);
List<Product> productsReadFromFile = new(ParseProductFile(mappingTable));
return InsertProducts(productsReadFromFile);
}
private static bool InsertProducts(List<Product> products)
{
using (GremlinContext db = new())
using GremlinDb gremlinDb = new();
List<ProductLine> productLines = gremlinDb.ProductLines.ToList();
foreach (Product product in products)
{
List<ProductLine> productLines = db.ProductLines.ToList();
foreach (Product product in products)
{
//var query = db.ProductLines
// .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation)
// .First();
//product.ProductLine = query;
//product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation);
product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode) ?? new ProductLine();
_ = MetaDataSetter.ForImport(product, "GenericImporter-Method");
}
db.Products.AddRange(products);
_ = db.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
return true;
//var query = db.ProductLines
// .Where(a => a.ProductLineAbbreviation == product.ProductLine.ProductLineAbbreviation)
// .First();
//product.ProductLine = query;
//product.ProductLine = ResolveProductLine(db, product.ProductLine.ProductLineAbbreviation);
product.ProductLine = productLines.Find(x => x.ProductLineCode == product.ProductLine.ProductLineCode) ?? new ProductLine();
MetaDataSetter.ForImport(product, "GenericImporter-Method");
}
gremlinDb.Products.AddRange(products);
gremlinDb.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {products.Count} Produkte erfolgreich der Datenbank hinzugefügt.");
return true;
}
private static List<Product> ParseProductFile(Dictionary<string, int> columnNumberOf)
///Importiert Produkt-Daten aus CSV (erstellt aus PriceSurfer-CPL)
/// - Encoding: Latin1
/// - Separator = ;
/// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
/// 1) Position = ID/PK
/// 2) Partnumber
/// 3) Option
/// 4) (Short) Description
/// 5) Current Month Price (EUR)
/// 6) ProductLineID
/// 7) Status
/// 8) (Long) Description
///Kommentarzeichen: # (hardcoded, string array)
{
List<Product> results = new(100000);
using (csvParser)
@ -456,10 +382,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
while (!csvParser.EndOfData)
{
// Read current line fields, pointer moves to the next line.
Product ImportedProduct = new()
{
ProductLine = new()
};
Product importedProduct = new() { ProductLine = new() };
string[] fields = csvParser.ReadFields()!;
//Kontrolle, ob Trennzeichen in Daten vorhanden ist:
@ -498,81 +421,64 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//fields[23] = End of Production Date
//fields[24] = End of Support Date
ImportedProduct.ProductNumber = fields[columnNumberOf["ProductNumber"]];
importedProduct.ProductNumber = fields[columnNumberOf["ProductNumber"]];
if (fields[columnNumberOf["OptionNumber"]].Length == 4) //Optionsnummer mit führendem Apostroph
{
ImportedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]].Substring(1); //schneidet erstes Zeichen/Apostroph weg
importedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]].Substring(1); //schneidet erstes Zeichen/Apostroph weg
}
else if (fields[columnNumberOf["OptionNumber"]].Length == 3) //3-stellige Optionsnummer übernehmen; keine Aktion bei leerem Feld (keine Optionsnummer)
{
ImportedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]];
importedProduct.OptionNumber = fields[columnNumberOf["OptionNumber"]];
}
ImportedProduct.SapShortDescription = fields[columnNumberOf["SapShortDescription"]];
importedProduct.SapShortDescription = fields[columnNumberOf["SapShortDescription"]];
ImportedProduct.ListPrice = decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change?
importedProduct.ListPrice = decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")); //parsing! compare with old value (either from CSV or from DB) -> price change?
//if (fields[columnNumberOf["ListPrice"]] != "")
// if (decimal.Parse(fields[columnNumberOf["ListPrice"]], new CultureInfo("de-de")) != ImportedProduct.ListPrice)
// listpriceHasChanged = true;
ImportedProduct.BreakRangeFrom = fields[columnNumberOf["BreakRangeFrom"]] == "" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeFrom"]]);
ImportedProduct.HasBreakPrices = ImportedProduct.BreakRangeFrom > 0;
ImportedProduct.BreakRangeTo = fields[columnNumberOf["BreakRangeTo"]] is "" or "+" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeTo"]]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich)
importedProduct.BreakRangeFrom = fields[columnNumberOf["BreakRangeFrom"]] == "" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeFrom"]]);
importedProduct.HasBreakPrices = importedProduct.BreakRangeFrom > 0;
importedProduct.BreakRangeTo = fields[columnNumberOf["BreakRangeTo"]] is "" or "+" ? 0 : Convert.ToInt32(fields[columnNumberOf["BreakRangeTo"]]); //erfasst sowohl Produkte ohne Break-Preise ("") als auch "+" bei Mengenangaben a la "100+" (= von 100 bis unendlich)
ImportedProduct.ProductLine.ProductLineCode = fields[columnNumberOf["ProductLineCode"]];
importedProduct.ProductLine.ProductLineCode = fields[columnNumberOf["ProductLineCode"]];
switch (fields[columnNumberOf["ProductStatus"]])
{
case "Active":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.Active.ToString();
importedProduct.DataStatus = Status.Active.ToString();
importedProduct.ProductStatus = Status.Active.ToString();
break;
case "New Product" or "New":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.New.ToString();
importedProduct.DataStatus = Status.Active.ToString();
importedProduct.ProductStatus = Status.New.ToString();
break;
case "Price Changed":
ImportedProduct.DataStatus = Status.Active.ToString();
ImportedProduct.ProductStatus = Status.PriceUpdated.ToString();
importedProduct.DataStatus = Status.Active.ToString();
importedProduct.ProductStatus = Status.PriceUpdated.ToString();
break;
default:
ImportedProduct.DataStatus = Status.StatusUpdated.ToString();
importedProduct.DataStatus = Status.StatusUpdated.ToString();
break;
}
if (fields[columnNumberOf["Weight"]] != null)
{
ImportedProduct.Weight = ParseWeight(fields[columnNumberOf["Weight"]]);
}
if (fields[columnNumberOf["WeightUnit"]] == "G")
{
ImportedProduct.Weight /= 1000; //Umrechnung g in kg
}
if (fields[columnNumberOf["IntroductionDate"]] != "")
{
ImportedProduct.IntroductionDate = DateTime.Parse(fields[columnNumberOf["IntroductionDate"]]);
}
ImportedProduct.SapLongDescription = fields[columnNumberOf["SapLongDescription"]];
results.Add(ImportedProduct);
importedProduct.Weight = ParseWeight(fields[columnNumberOf["Weight"]]);
if (fields[columnNumberOf["WeightUnit"]] == "G") importedProduct.Weight /= 1000; //Umrechnung g in kg
if (fields[columnNumberOf["IntroductionDate"]] != "") importedProduct.IntroductionDate = DateTime.Parse(fields[columnNumberOf["IntroductionDate"]]);
importedProduct.SapLongDescription = fields[columnNumberOf["SapLongDescription"]];
results.Add(importedProduct);
}
}
return results;
}
private static float ParseWeight(string input)
{
StringBuilder sb = new();
if (input.StartsWith("."))
{
input = sb.Append(0).Append(input).ToString();
}
if (input.StartsWith(".")) input = sb.Append(0).Append(input).ToString();
try
{
@ -586,46 +492,45 @@ namespace Gremlin_BlazorServer.Data.DBClasses
}
private static bool ImportLSAG(Dictionary<string, int> mappingTable)
private static bool ImportLsag(Dictionary<string, int> mappingTable)
{
bool result;
result = ImportAccounts(mappingTable);
bool result = ImportAccounts(mappingTable);
ResetParser();
_ = csvParser.ReadFields(); //Skip Heading
csvParser.ReadFields(); //Skip Heading
result = result && ImportContacts(mappingTable);
return result;
}
public static bool ImportContacts(Dictionary<string, int> columnNumberOf)
private static bool ImportContacts(Dictionary<string, int> columnNumberOf)
{
List<Contact> ContactsReadFromFile = new(8000);
List<Contact> contactsReadFromFile = new(8000);
using (csvParser)
{
while (!csvParser.EndOfData)
{
Contact ImportedContact = new();
Contact importedContact = new();
string[] fields = csvParser.ReadFields()!;
//No conversion
ImportedContact.AcademicTitle = fields[columnNumberOf["AcademicTitle"]];
ImportedContact.FirstName = fields[columnNumberOf["FirstName"]];
ImportedContact.LastName = fields[columnNumberOf["LastName"]];
ImportedContact.EMail = fields[columnNumberOf["EMail"]];
ImportedContact.Department = fields[columnNumberOf["Department"]];
ImportedContact.Room = fields[columnNumberOf["Room"]];
ImportedContact.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
ImportedContact.Function = fields[columnNumberOf["Function"]];
ImportedContact.MobileNumber = fields[columnNumberOf["MobileNumber"]];
importedContact.AcademicTitle = fields[columnNumberOf["AcademicTitle"]];
importedContact.FirstName = fields[columnNumberOf["FirstName"]];
importedContact.LastName = fields[columnNumberOf["LastName"]];
importedContact.EMail = fields[columnNumberOf["EMail"]];
importedContact.Department = fields[columnNumberOf["Department"]];
importedContact.Room = fields[columnNumberOf["Room"]];
importedContact.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
importedContact.Function = fields[columnNumberOf["Function"]];
importedContact.MobileNumber = fields[columnNumberOf["MobileNumber"]];
//Convert Gender
ImportedContact.Gender = fields[columnNumberOf["Gender"]] == "M"
importedContact.Gender = fields[columnNumberOf["Gender"]] == "M"
? (byte)Gender.Male
: fields[columnNumberOf["Gender"]] == "F"
? (byte)Gender.Female
: (byte)Gender.Unknown;
//Convert OptIn Status
ImportedContact.OptInStatus = fields[columnNumberOf["OptInStatus"]] switch
importedContact.OptInStatus = fields[columnNumberOf["OptInStatus"]] switch
{
"Opt In" => true,
"Opt Out" => false,
@ -635,7 +540,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
//Convert "SAP Contact Number"
try
{
ImportedContact.SAPContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]], CultureInfo.CurrentCulture);
importedContact.SapContactNumber = Convert.ToInt32(fields[columnNumberOf["SAPContactNumber"]], CultureInfo.CurrentCulture);
}
catch (FormatException ex)
{
@ -668,88 +573,61 @@ namespace Gremlin_BlazorServer.Data.DBClasses
int year = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(0, 4), CultureInfo.CurrentCulture);
int month = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(4, 2), CultureInfo.CurrentCulture);
int day = Convert.ToInt32(fields[columnNumberOf["SAPContactCreationDate"]].Substring(6, 2), CultureInfo.CurrentCulture);
ImportedContact.SAPContactCreationDate = new DateTime(year, month, day);
importedContact.SapContactCreationDate = new(year, month, day);
//Convert "No Phone Calls"
if (fields[columnNumberOf["NoPhoneCalls"]] == "1")
{
ImportedContact.NoPhoneCalls = true;
}
if (fields[columnNumberOf["NoPhoneCalls"]] == "1") importedContact.NoPhoneCalls = true;
//Convert "No Hardcopy Mailing"
if (fields[columnNumberOf["NoHardcopyMailing"]] == "1")
{
ImportedContact.NoHardcopyMailing = true;
}
if (fields[columnNumberOf["NoHardcopyMailing"]] == "1") importedContact.NoHardcopyMailing = true;
//SAPAccountID in Contact.Notes speichern, um den entsprechenden Account aus DB heraussuchen zu können:
ImportedContact.Notes = fields[columnNumberOf["SAPAccountNumber"]];
ContactsReadFromFile.Add(ImportedContact);
importedContact.Notes = fields[columnNumberOf["SAPAccountNumber"]];
contactsReadFromFile.Add(importedContact);
}
//Eingelesenen Account in DB schreiben:
using (GremlinContext db = new())
using (GremlinDb gremlinDb = new())
{
foreach (Contact contact in ContactsReadFromFile)
foreach (Contact contact in contactsReadFromFile)
{
// AccountID aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
contact.Account = DbHelper.ResolveAccountById(db, Convert.ToUInt32(contact.Notes));
contact.Account = DbHelper.ResolveAccountById(gremlinDb, Convert.ToUInt32(contact.Notes));
contact.Notes = "";
_ = MetaDataSetter.ForImport(contact, "GenericImporter-Method");
MetaDataSetter.ForImport(contact, "GenericImporter-Method");
}
db.Contacts.AddRange(ContactsReadFromFile);
_ = db.SaveChanges();
gremlinDb.Contacts.AddRange(contactsReadFromFile);
gremlinDb.SaveChanges();
}
}
//Bestätigung senden
Debug.WriteLine($"Es wurden {ContactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
Debug.WriteLine($"Es wurden {contactsReadFromFile.Count} Contacts erfolgreich der Datenbank hinzugefügt.");
return true;
}
public static bool ImportAccounts(Dictionary<string, int> columnNumberOf)
///Importiert Account Daten aus CSV (erstellt aus LSAG_Contact_List_Tool.xlsx)
///
/// - Encoding: Latin1/ISO-8859-1
/// - fixe Spaltenzahl und -folge (Überschriften werden nicht untersucht/verwendet):
/// 1) SAPAccountNumber
/// 2) AccountName
/// 3) ZIP
/// 4) City
/// 5) Street
/// 6) AccountSubMarketCode
/// 7) AccountTypeCode
/// 8) PhoneNumber
/// 9) AccountsCreatedInSAP
///Kommentarzeichen: # (hardcoded, string array)
///Rückgabe 'false' bei Fehler oder User-Abbruch, ansonsten 'true'.
///
///Argumente:
/// 1.
private static bool ImportAccounts(Dictionary<string, int> columnNumberOf)
{
List<Account> AccountsReadFromFile = new(1000);
List<Account> accountsReadFromFile = new(1000);
while (!csvParser.EndOfData)
{
bool DataHasError = false;
bool dataHasError = false;
// Read current line fields, pointer moves to the next line.
Account ImportedAccount = new();
Account importedAccount = new();
{
ImportedAccount.SubMarket = new();
ImportedAccount.AccountType = new();
ImportedAccount.Contacts = new List<Contact>();
importedAccount.SubMarket = new();
importedAccount.AccountType = new();
importedAccount.Contacts = new List<Contact>();
}
string[] fields = csvParser.ReadFields()!;
//Konvertierung erforderlich:
try
{
ImportedAccount.SAPAccountNumber = Convert.ToUInt32(fields[columnNumberOf["SAPAccountNumber"]]);
importedAccount.SapAccountNumber = Convert.ToUInt32(fields[columnNumberOf["SAPAccountNumber"]]);
}
catch (FormatException ex)
{
@ -760,7 +638,6 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
errorRaiser = "No Account Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
@ -773,14 +650,13 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
errorRaiser = "No Account Number in this row!";
}
DbHelper.DisplayErrorDetails(ex, errorRaiser);
return false;
}
try
{
ImportedAccount.ZIP = Convert.ToUInt32(fields[columnNumberOf["ZIP"]]);
importedAccount.Zip = Convert.ToUInt32(fields[columnNumberOf["ZIP"]]);
}
catch (FormatException ex)
{
@ -818,77 +694,61 @@ namespace Gremlin_BlazorServer.Data.DBClasses
int year = Convert.ToInt32(fields[columnNumber].Substring(0, 4));
int month = Convert.ToInt32(fields[columnNumber].Substring(4, 2));
int day = Convert.ToInt32(fields[columnNumber].Substring(6, 2));
ImportedAccount.AccountCreatedInSAPOn = new DateTime(year, month, day);
importedAccount.AccountCreatedInSapOn = new(year, month, day);
}
}
//Convert City von Großschreibung zu Normalschreibung
ImportedAccount.City = fields[columnNumberOf["City"]].First().ToString() + fields[columnNumberOf["City"]].Substring(1).ToLower();
importedAccount.City = fields[columnNumberOf["City"]].First().ToString() + fields[columnNumberOf["City"]].Substring(1).ToLower();
//keine Konvertierung nötig:
ImportedAccount.AccountName = fields[columnNumberOf["AccountName"]];
ImportedAccount.Street = fields[columnNumberOf["Street"]];
ImportedAccount.SubMarket.SubMarketCode = fields[columnNumberOf["SubMarketCode"]];
ImportedAccount.SubMarket.DataStatus = Status.Active.ToString();
ImportedAccount.AccountType.AccountTypeCode = fields[columnNumberOf["AccountTypeCode"]];
ImportedAccount.AccountType.DataStatus = Status.Active.ToString();
ImportedAccount.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
ImportedAccount.DataStatus = Status.Active.ToString();
importedAccount.AccountName = fields[columnNumberOf["AccountName"]];
importedAccount.Street = fields[columnNumberOf["Street"]];
importedAccount.SubMarket.SubMarketCode = fields[columnNumberOf["SubMarketCode"]];
importedAccount.SubMarket.DataStatus = Status.Active.ToString();
importedAccount.AccountType.AccountTypeCode = fields[columnNumberOf["AccountTypeCode"]];
importedAccount.AccountType.DataStatus = Status.Active.ToString();
importedAccount.PhoneNumber = fields[columnNumberOf["PhoneNumber"]];
importedAccount.DataStatus = Status.Active.ToString();
//Validierungen:
if (ImportedAccount.AccountName == ""
|| ImportedAccount.City == ""
|| ImportedAccount.Street == ""
|| ImportedAccount.SubMarket.SubMarketCode == ""
|| ImportedAccount.AccountType.AccountTypeCode == ""
|| ImportedAccount.SAPAccountNumber == 0)
{
DataHasError = true;
}
if (importedAccount.AccountName == "" || importedAccount.City == "" || importedAccount.Street == "" || importedAccount.SubMarket.SubMarketCode == "" || importedAccount.AccountType.AccountTypeCode == "" || importedAccount.SapAccountNumber == 0)
dataHasError = true;
//Validierten Account der Liste hinzufügen:
if (DataHasError == false)
{
_ = ImportedAccount.AddIfUniqueTo(AccountsReadFromFile);
}
if (dataHasError == false)
importedAccount.AddIfUniqueTo(accountsReadFromFile);
}
//Eingelesenen Account in DB schreiben:
DateTime now = DateTime.Now;
foreach (Account account in AccountsReadFromFile)
foreach (Account account in accountsReadFromFile)
{
// AccountType aus DB laden, damit der Datensatz vom Context verfolgt wird und EF Core nicht versucht, diesen standardmäßig neu anzulegen.
AccountType accountType = db.AccountTypes
.Where(a => a.AccountTypeCode == account.AccountType.AccountTypeCode)
.First();
AccountType? accountType = gremlinDb.AccountTypes.First(a => a.AccountTypeCode == account.AccountType.AccountTypeCode);
account.AccountType = accountType;
account.AccountType = DbHelper.ResolveAccountType(db, account.AccountType.AccountTypeCode);
account.AccountType = DbHelper.ResolveAccountType(gremlinDb, account.AccountType.AccountTypeCode);
SubMarket subMarket = db.SubMarkets
.Where(a => a.SubMarketCode == account.SubMarket.SubMarketCode)
.First();
SubMarket subMarket = gremlinDb.SubMarkets.First(a => a.SubMarketCode == account.SubMarket.SubMarketCode);
account.SubMarket = subMarket;
account.SubMarket = DbHelper.ResolveSubmarket(db, account.SubMarket.SubMarketCode);
account.SubMarket = DbHelper.ResolveSubmarket(gremlinDb, account.SubMarket.SubMarketCode);
_ = MetaDataSetter.ForImport(account, "GenericImporter-Method");
MetaDataSetter.ForImport(account, "GenericImporter-Method");
//account.DataVersionComment = "Initial import by CSV Importer (Function DbHelper.ImportAccountsFromCSV)";
}
db.Accounts.AddRange(AccountsReadFromFile);
_ = db.SaveChanges();
gremlinDb.Accounts.AddRange(accountsReadFromFile);
gremlinDb.SaveChanges();
//Bestätigung senden
Debug.WriteLine($"Es wurden {AccountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt.");
Debug.WriteLine($"Es wurden {accountsReadFromFile.Count} Accounts erfolgreich der Datenbank hinzugefügt.");
return true;
}
public static bool ImportProductLine(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
private static bool ImportProductLine(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
@ -905,17 +765,16 @@ namespace Gremlin_BlazorServer.Data.DBClasses
importedProductLine.ProductLineCode = fields[mappingTable["ProductLineCode"]];
importedProductLine.ProductLineDescription = fields[mappingTable["ProductLineDescription"]];
productLinesReadFromFile.Add(importedProductLine);
_ = MetaDataSetter.ForImport(importedProductLine, "GenericImporter-Method");
MetaDataSetter.ForImport(importedProductLine, "GenericImporter-Method");
}
}
db.ProductLines.AddRange(productLinesReadFromFile);
_ = db.SaveChanges();
gremlinDb.ProductLines.AddRange(productLinesReadFromFile);
gremlinDb.SaveChanges();
return true;
}
public static bool ImportAccountType(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
private static bool ImportAccountType(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
@ -932,17 +791,16 @@ namespace Gremlin_BlazorServer.Data.DBClasses
importedAccountType.AccountTypeCode = fields[mappingTable["AccountTypeCode"]];
importedAccountType.AccountTypeDescription = fields[mappingTable["AccountTypeDescription"]];
accountTypesReadFromFile.Add(importedAccountType);
_ = MetaDataSetter.ForImport(importedAccountType, "GenericImporter-Method");
MetaDataSetter.ForImport(importedAccountType, "GenericImporter-Method");
}
}
db.AccountTypes.AddRange(accountTypesReadFromFile);
_ = db.SaveChanges();
gremlinDb.AccountTypes.AddRange(accountTypesReadFromFile);
gremlinDb.SaveChanges();
return true;
}
public static bool ImportSubMarket(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
private static bool ImportSubMarket(TextFieldParser csvParser, Dictionary<string, int> mappingTable)
{
//foreach line in file:
//read seed data, parse/split, save to object with metadata
@ -959,13 +817,12 @@ namespace Gremlin_BlazorServer.Data.DBClasses
importedSubMarket.SubMarketCode = fields[mappingTable["SubMarketCode"]];
importedSubMarket.SubMarketDescription = fields[mappingTable["SubMarketDescription"]];
subMarketsReadFromFile.Add(importedSubMarket);
_ = MetaDataSetter.ForImport(importedSubMarket, "GenericImporter-Method");
MetaDataSetter.ForImport(importedSubMarket, "GenericImporter-Method");
}
}
db.SubMarkets.AddRange(subMarketsReadFromFile);
_ = db.SaveChanges();
gremlinDb.SubMarkets.AddRange(subMarketsReadFromFile);
gremlinDb.SaveChanges();
return true;
}
@ -1000,37 +857,26 @@ namespace Gremlin_BlazorServer.Data.DBClasses
// return Activator.CreateInstance(Gremlin.ToString(), "Gremlin." + detectedDataType).Unwrap();
//}
public static Dictionary<string, string> ReadMappingDictionaryFromFile()
private static Dictionary<string, string> ReadMappingDictionaryFromFile()
{
Dictionary<string, string> result = new();
string fileInput = FileService.ReadResource("MappingDictionary.txt");
string[] lines = fileInput.Split(Environment.NewLine);
foreach (string line in lines)
{
string[] fields;
fields = line.Split("|");
string[] fields = line.Split("|");
result.Add(fields[0], fields[1]);
}
return result;
}
public static Dictionary<string, int> MapDataHeading(string[] headings, Dictionary<string, string> columnPropertyMapping)
private static Dictionary<string, int> MapDataHeading(string[] headings, Dictionary<string, string> columnPropertyMapping)
{
Dictionary<string, int> result = new();
for (int i = 0; i < headings.Length; i++)
{
string heading = headings[i].ToLower(CultureInfo.CurrentCulture)
.Trim()
.Replace(" ", "")
.Replace("-", "")
.Replace("_", "")
.Replace(".", "")
.Replace(":", "");
if (columnPropertyMapping.TryGetValue(heading, out string? value))
{
result.Add(value, i);
}
string heading = headings[i].ToLower(CultureInfo.CurrentCulture).Trim().Replace(" ", "").Replace("-", "").Replace("_", "").Replace(".", "").Replace(":", "");
if (columnPropertyMapping.TryGetValue(heading, out string? value)) result.Add(value, i);
}
return result;
}

@ -1,80 +0,0 @@
using Gremlin_BlazorServer.Data.EntityClasses;
using Gremlin_BlazorServer.Services;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
namespace Gremlin_BlazorServer.Data.DBClasses
{
public class GremlinContext : DbContext
{
public DbSet<Contact> Contacts { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<Quote> Quotes { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<LineItem> LineItems { get; set; }
public DbSet<CustomDescription> CustomDescriptions { get; set; }
public DbSet<ProductLine> ProductLines { get; set; }
public DbSet<AccountType> AccountTypes { get; set; }
public DbSet<SubMarket> SubMarkets { get; set; }
public DbSet<RUSettings> RUSettings { get; set; }
public DbSet<RegisteredUser> RegisteredUser { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
//string connectionString = $"server={Properties.Settings.Default.server};" +
// $"port={Properties.Settings.Default.port};" +
// $"database={Properties.Settings.Default.database};" +
// $"user={Properties.Settings.Default.user};" +
// $"password={Encryption.ToInsecureString(Encryption.DecryptString(Properties.Settings.Default.password))};" +
// $"SslMode={Properties.Settings.Default.SslMode};" +
// $"SslCa={Properties.Settings.Default.SslCA}";
string connectionString = $"server=woitschetzki.de;" +
$"port=3306;" +
$"database=regulus;" +
$"user=root;" +
$"password=lungretter1;" +
$"SslMode=;" +
$"SslCa=";
try
{
_ = optionsBuilder
.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
//mySqlOptionsAction => mySqlOptionsAction.CharSetBehavior(Pomelo.EntityFrameworkCore.MySql.Infrastructure.CharSetBehavior.NeverAppend)).EnableDetailedErrors()
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
//ChooseDB chooseDB = new();
//_ = chooseDB.ShowDialog();
OnConfiguring(optionsBuilder);
throw;
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//wozu dient die folgende Zeile?
base.OnModelCreating(modelBuilder);
////alle Fluent-Konfigurationen aufrufen:
//TO BE TESTED!
_ = modelBuilder.ApplyConfigurationsFromAssembly(typeof(GremlinContext).Assembly);
////Fluent-Konfiguration einzeln für eine Entity aufrufen:
//new AccountConfiguration().Configure(modelBuilder.Entity<Account>());
//new ContactConfiguration().Configure(modelBuilder.Entity<Contact>());
//new QuoteConfiguration().Configure(modelBuilder.Entity<Quote>());
//new ProductConfiguration().Configure(modelBuilder.Entity<Product>());
//new LineItemConfiguration().Configure(modelBuilder.Entity<LineItem>());
//new CustomDescriptionConfiguration().Configure(modelBuilder.Entity<CustomDescription>());
//new ProductLineConfiguration().Configure(modelBuilder.Entity<ProductLine>());
//new AccountTypeConfiguration().Configure(modelBuilder.Entity<AccountType>());
//new SubMarketConfiguration().Configure(modelBuilder.Entity<SubMarket>());
}
}
}

@ -0,0 +1,96 @@
using Gremlin_BlazorServer.Data.EntityClasses;
using Microsoft.EntityFrameworkCore;
using System.Diagnostics;
namespace Gremlin_BlazorServer.Data.DBClasses
{
public class GremlinDb : DbContext
{
public DbSet<Contact>? Contacts { get; set; }
public DbSet<Account>? Accounts { get; set; }
public DbSet<Quote>? Quotes { get; set; }
public DbSet<Product>? Products { get; set; }
public DbSet<LineItem>? LineItems { get; set; }
public DbSet<CustomDescription>? CustomDescriptions { get; set; }
public DbSet<ProductLine>? ProductLines { get; set; }
public DbSet<AccountType>? AccountTypes { get; set; }
public DbSet<SubMarket>? SubMarkets { get; set; }
public DbSet<RuSettings>? RuSettings { get; set; }
public DbSet<RegisteredUser>? RegisteredUser { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
const string connectionString = "server=woitschetzki.de;port=3306;database=regulus;user=root;password=lungretter1;SslMode=;SslCa=";
try
{
optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)).EnableSensitiveDataLogging().EnableDetailedErrors();
}
catch (Exception e)
{
Debug.WriteLine(e);
OnConfiguring(optionsBuilder);
throw;
}
}
// void ConfigureAutoIncludeFor<T>(ModelBuilder modelBuilder) where T: class
// {
// var type = typeof(T);
//
// foreach (var property in type.GetProperties())
// {
// if (typeof(IMetadata).IsAssignableFrom(property.PropertyType)
// ||
// property.PropertyType.IsGenericType
// && typeof(IList<>).IsAssignableFrom(property.PropertyType.GetGenericTypeDefinition()))
// {
// modelBuilder.Entity<T>().Navigation(e => property.GetValue(e)).AutoInclude();
// }
// }
// }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//wozu dient die folgende Zeile?
base.OnModelCreating(modelBuilder);
////alle Fluent-Konfigurationen aufrufen:
//TO BE TESTED!
modelBuilder.ApplyConfigurationsFromAssembly(typeof(GremlinDb).Assembly);
//AutoInclude all NavigationParameters in Entities
modelBuilder.Entity<Account>().Navigation(db => db.Contacts).AutoInclude();
modelBuilder.Entity<Account>().Navigation(db => db.AccountType).AutoInclude();
modelBuilder.Entity<Account>().Navigation(db => db.SubMarket).AutoInclude();
modelBuilder.Entity<Account>().Navigation(db => db.CustomDescriptions).AutoInclude();
modelBuilder.Entity<Contact>().Navigation(db => db.Account).AutoInclude();
modelBuilder.Entity<Contact>().Navigation(db => db.Quotes).AutoInclude();
//Takes too much time
//modelBuilder.Entity<ProductLine>().Navigation(db => db.Products).AutoInclude();
//modelBuilder.Entity<Quote>().Navigation(db => db.LineItems).AutoInclude();
//modelBuilder.Entity<Quote>().Navigation(db => db.Recipient).AutoInclude();
modelBuilder.Entity<Product>().Navigation(db => db.CustomDescription).AutoInclude();
modelBuilder.Entity<Product>().Navigation(db => db.ProductLine).AutoInclude();
//Generic AutoInclude method not yet working
//ConfigureAutoIncludeFor<Account>(modelBuilder);
////Fluent-Konfiguration einzeln für eine Entity aufrufen:
//new AccountConfiguration().Configure(modelBuilder.Entity<Account>());
//new ContactConfiguration().Configure(modelBuilder.Entity<Contact>());
//new QuoteConfiguration().Configure(modelBuilder.Entity<Quote>());
//new ProductConfiguration().Configure(modelBuilder.Entity<Product>());
//new LineItemConfiguration().Configure(modelBuilder.Entity<LineItem>());
//new CustomDescriptionConfiguration().Configure(modelBuilder.Entity<CustomDescription>());
//new ProductLineConfiguration().Configure(modelBuilder.Entity<ProductLine>());
//new AccountTypeConfiguration().Configure(modelBuilder.Entity<AccountType>());
//new SubMarketConfiguration().Configure(modelBuilder.Entity<SubMarket>());
}
}
}

@ -1,13 +0,0 @@
namespace Gremlin_BlazorServer.Data.DBClasses
{
internal class GremlinTypeConverter
{
internal static object Convert(string stringToConvert)
{
//noch nicht implementiert.
//für vollkommen generischen Importer
//
return stringToConvert;
}
}
}

@ -7,52 +7,22 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
public static class MetaDataSetter
{
private static readonly DateTime FarInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
private static readonly DateTime farInTheFuture = DateTime.Parse("2050-12-31t00:00:00.000000z", CultureInfo.CurrentCulture);
public static IMetadata ForImport(
IMetadata entity,
string datamodifiedby = "",
string dataversioncomment = "",
[CallerMemberName] string callername = "")
public static IMetadata ForImport(IMetadata entity, string datamodifiedby = "", string dataversioncomment = "", [CallerMemberName] string callername = "")
{
_ = SetMetaData(entity, datamodifiedby, dataversioncomment, callername);
SetMetaData(entity, datamodifiedby, dataversioncomment, callername);
return entity;
}
public static List<IMetadata> ForImport(
List<IMetadata> entities,
string datamodifiedby = "",
string dataversioncomment = "",
[CallerMemberName] string callername = "")
{
//check if entities implements IMetaData:
//Ist das überhaupt nötig?
if ((entities is IMetadata) == false)
{
//no action / return list unchanged
return entities;
}
//set metadata
foreach (IMetadata entity in entities)
{
_ = SetMetaData(entity, datamodifiedby, dataversioncomment, callername);
}
return entities;
}
private static IMetadata SetMetaData(
IMetadata entity,
string datamodifiedby = "",
string dataversioncomment = "",
[CallerMemberName] string callername = "")
private static IMetadata SetMetaData(IMetadata entity, string datamodifiedby = "", string dataversioncomment = "", [CallerMemberName] string callername = "")
{
entity.DataCreationDate = DateTime.Now;
entity.DataModificationDate = DateTime.Now;
entity.DataModificationByUser = datamodifiedby == "" ? callername : datamodifiedby;
entity.DataStatus = Status.Active.ToString();
entity.DataValidFrom = DateTime.Now;
entity.DataValidUntil = FarInTheFuture;
entity.DataValidUntil = farInTheFuture;
entity.DataVersionNumber++;
entity.DataVersionComment = dataversioncomment;

@ -19,7 +19,7 @@ namespace Gremlin_BlazorServer.Data.DBClasses
{
if (product == null) { return 0; }
StringBuilder sb = new();
_ = sb.Append(product.ProductNumber).Append(product.OptionNumber).Append(product.BreakRangeFrom).Append(product.BreakRangeTo);
sb.Append(product.ProductNumber).Append(product.OptionNumber).Append(product.BreakRangeFrom).Append(product.BreakRangeTo);
return sb.ToString().GetHashCode();
}
}

@ -7,29 +7,29 @@
public uint AccountId { get; set; }
//foreign keys:
public IList<Contact> Contacts { get; set; } = new List<Contact>();
public IList<Contact> Contacts { get; set; }
public uint ParentAccountId { get; set; }
public AccountType AccountType { get; set; } = default!;
public SubMarket SubMarket { get; set; } = default!;
public IList<CustomDescription> CustomDescriptions { get; set; } = new List<CustomDescription>();
public AccountType? AccountType { get; set; }
public SubMarket? SubMarket { get; set; }
public IList<CustomDescription> CustomDescriptions { get; set; }
//class properties:
public string AccountName { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty;
public string Street { get; set; } = string.Empty;
public uint ZIP { get; set; }
public string City { get; set; } = string.Empty;
public string FloorOrBuilding { get; set; } = string.Empty;
public float Longitude { get; set; } = 0;
public float Latitude { get; set; } = 0;
public string PhoneNumber { get; set; } = string.Empty;
public string FaxNumber { get; set; } = string.Empty;
public string Webpage { get; set; } = string.Empty;
public string EMail { get; set; } = string.Empty;
public string AccountName { get; set; }
public string Notes { get; set; }
public string Street { get; set; }
public uint Zip { get; set; }
public string City { get; set; }
public string FloorOrBuilding { get; set; }
public float Longitude { get; set; }
public float Latitude { get; set; }
public string PhoneNumber { get; set; }
public string FaxNumber { get; set; }
public string Webpage { get; set; }
public string EMail { get; set; }
//Agilent-specific Properties:
public uint SAPAccountNumber { get; set; }
public DateTime AccountCreatedInSAPOn { get; set; }
public uint SapAccountNumber { get; set; }
public DateTime AccountCreatedInSapOn { get; set; }
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
@ -38,8 +38,8 @@
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = "Gremlin";
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataStatus { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; }
//IBase
//tbd
@ -87,14 +87,14 @@
public List<Account> AddIfUniqueTo(List<Account> accounts)
{
if (accounts.Count > 0 && SAPAccountNumber == accounts[accounts.Count - 1].SAPAccountNumber)
if (accounts.Count > 0 && SapAccountNumber == accounts[^1].SapAccountNumber)
{
return accounts;
}
foreach (Account account in accounts)
{
if (SAPAccountNumber == account.SAPAccountNumber)
if (SapAccountNumber == account.SapAccountNumber)
{
return accounts;
}

@ -7,22 +7,22 @@ namespace Gremlin_BlazorServer.Data.EntityClasses
//primary key:
//public uint AccountTypeId { get; set; }
[Key] //Fluent API .HasKey funktioniert nicht
public string AccountTypeCode { get; set; } = string.Empty;
public string AccountTypeCode { get; set; }
//navigation properties:
public IList<Account> Accounts { get; set; } = new List<Account>();
public IList<Account> Accounts { get; set; }
//class properties:
public string AccountTypeDescription { get; set; } = string.Empty;
public string AccountTypeDescription { get; set; }
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataStatus { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; }
}
}

@ -9,47 +9,47 @@
public uint AccountId { get; set; }
//navigation properties:
public Account Account { get; set; } = new Account();
public IList<Quote> Quotes { get; set; } = new List<Quote>();
public Account Account { get; set; }
public IList<Quote> Quotes { get; set; }
//class properties:
public string AcademicTitle { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string AcademicTitle { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public byte Gender { get; set; }
public bool OptInStatus { get; set; }
public string Department { get; set; } = string.Empty;
public string Function { get; set; } = string.Empty;
public string Room { get; set; } = string.Empty;
public string Department { get; set; }
public string Function { get; set; }
public string Room { get; set; }
public bool IsReference { get; set; }
public string Notes { get; set; } = string.Empty;
public string PhoneNumber { get; set; } = string.Empty;
public string MobileNumber { get; set; } = string.Empty;
public string EMail { get; set; } = string.Empty;
public string PreferredContactMethod { get; set; } = string.Empty;
public string Notes { get; set; }
public string PhoneNumber { get; set; }
public string MobileNumber { get; set; }
public string EMail { get; set; }
public string PreferredContactMethod { get; set; }
public bool NoPhoneCalls { get; set; }
public bool EmailBounced { get; set; }
public bool NoHardcopyMailing { get; set; }
public bool ValidatedContact { get; set; }
//Agilent-specific Properties:
public int SAPContactNumber { get; set; }
public DateTime SAPContactCreationDate { get; set; }
public DateTime SAPContactModifiedDate { get; set; }
public string SAPJobFunction { get; set; } = string.Empty;
public string SAPProductInterest { get; set; } = string.Empty;
public string SAPApplicationInterest { get; set; } = string.Empty;
public string SAPJobLevel { get; set; } = string.Empty;
public string SAPCompetitiveIBase { get; set; } = string.Empty;
public int SapContactNumber { get; set; }
public DateTime SapContactCreationDate { get; set; }
public DateTime SapContactModifiedDate { get; set; }
public string SapJobFunction { get; set; }
public string SapProductInterest { get; set; }
public string SapApplicationInterest { get; set; }
public string SapJobLevel { get; set; }
public string SapCompetitiveIBase { get; set; }
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; } = "Active";
}
}

@ -10,16 +10,16 @@
public uint AccountId { get; set; }
//navigation properties:
public IList<Product> Products { get; set; } = new List<Product>();
public Account Supplier { get; set; } = new();
public IList<Product> Products { get; set; }
public Account Supplier { get; set; }
//class properties:
public string ProductNumber { get; set; } = string.Empty;
public string OptionNumber { get; set; } = string.Empty;
public string Heading { get; set; } = string.Empty;
public string DescriptionText { get; set; } = string.Empty;
public string CoverletterText { get; set; } = string.Empty;
public string Notes { get; set; } = string.Empty; //Hinweise, Tipps, Caveats, etc. für Konfiguration, Verwendung, Best Practice usw.
public string ProductNumber { get; set; }
public string OptionNumber { get; set; }
public string Heading { get; set; }
public string DescriptionText { get; set; }
public string CoverletterText { get; set; }
public string Notes { get; set; } //Hinweise, Tipps, Caveats, etc. für Konfiguration, Verwendung, Best Practice usw.
//Agilent-Specific properties:
//NONE
@ -29,9 +29,9 @@
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataStatus { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; }
}
}

@ -1,6 +1,6 @@
namespace Gremlin_BlazorServer.Data.EntityClasses
{
public class Enums
public abstract class Enums
{
public enum Status : byte
{
@ -34,7 +34,7 @@
Unknown = 0,
Male = 1,
Female = 2,
divers = 3,
Divers = 3,
}
}

@ -9,16 +9,16 @@
public uint QuoteId { get; set; }
//navigation properties:
public Quote Quote { get; set; } = new Quote();
public Quote Quote { get; set; }
//class properties:
public ushort Position { get; set; }
public ushort Amount { get; set; }
public string ProductNumber { get; set; } = string.Empty;
public string OptionNumber { get; set; } = string.Empty;
public string SapShortDescription { get; set; } = string.Empty;
public string SapLongDescription { get; set; } = string.Empty;
public string ProductLine { get; set; } = string.Empty;
public string ProductNumber { get; set; }
public string OptionNumber { get; set; }
public string SapShortDescription { get; set; }
public string SapLongDescription { get; set; }
public string ProductLine { get; set; }
public decimal TotalDiscount { get; set; }
public decimal SalesDiscount { get; set; }
public decimal PromotionalDiscount { get; set; }
@ -31,12 +31,12 @@
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public string DataStatus { get; set; } = string.Empty;
public string DataStatus { get; set; }
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataVersionComment { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public uint DataVersionNumber { get; set; }
}
}

@ -1,4 +1,6 @@
namespace Gremlin_BlazorServer.Data.EntityClasses
using System.ComponentModel.DataAnnotations.Schema;
namespace Gremlin_BlazorServer.Data.EntityClasses
{
public class Product : IMetadata
{
@ -6,20 +8,20 @@
public uint ProductId { get; set; }
//navigation properties:
public CustomDescription CustomDescription { get; set; } = new CustomDescription();
public ProductLine ProductLine { get; set; }= new ProductLine();
public CustomDescription CustomDescription { get; set; }
public ProductLine ProductLine { get; set; }
//foreign keys
public uint CustomDescriptionId { get; set; } = 999999999;
public string ProductLineCode { get; set; } = string.Empty;
public uint CustomDescriptionId { get; set; }
public string ProductLineCode { get; set; }
//Agilent-specific properties:
public string ProductNumber { get; set; } = string.Empty;
public string OptionNumber { get; set; } = string.Empty;
public string SapShortDescription { get; set; } = string.Empty;
public string SapLongDescription { get; set; } = string.Empty;
public string ProductNumber { get; set; }
public string OptionNumber { get; set; }
public string SapShortDescription { get; set; }
public string SapLongDescription { get; set; }
public float Weight { get; set; }
public string ProductStatus { get; set; } = string.Empty;
public string ProductStatus { get; set; }
public DateTime IntroductionDate { get; set; } = DateTime.Now;
public decimal ListPrice { get; set; }
public bool HasBreakPrices { get; set; }
@ -31,9 +33,9 @@
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; } = "Active";
}

@ -7,22 +7,22 @@ namespace Gremlin_BlazorServer.Data.EntityClasses
//primary key:
//public uint ProductLineId { get; set; }
[Key]
public string ProductLineCode { get; set; } = string.Empty;
public string ProductLineCode { get; set; }
//navigation properties:
public List<Product> Products { get; set; }= new List<Product>();
public List<Product> Products { get; set; }
//class properties:
public string ProductLineDescription { get; set; } = string.Empty;
public string ProductLineDescription { get; set; }
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataStatus { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; }
}
}

@ -9,65 +9,48 @@
public uint ContactId { get; set; }
//navigation properties:
public Contact Recipient { get; set; } = new Contact();
public IList<LineItem> LineItems { get; set; } = new List<LineItem>();
public Contact Recipient { get; set; }
public IList<LineItem> LineItems { get; set; }
//class properties:
public Contact SalesRep { get; set; } = new Contact();
public string QuotationNumber { get; set; } = string.Empty;
public Contact SalesRep { get; set; }
public string QuotationNumber { get; set; }
public DateTime QuotationDate { get; set; } = DateTime.Now;
public DateTime ValidUntil { get; set; } = DateTime.Now.AddDays(60);
public byte ValidFor { get; set; } = 60;
public decimal TotalListprice { get; set; }
public decimal TotalDiscount { get; set; }
public decimal TotalNet { get; set; }
public float VAT { get; set; } = 19f;
public float Vat { get; set; } = 19f;
public decimal TotalGross { get; set; }
public bool QuoteContains3PP { get; set; }
public bool QuoteContainsRB { get; set; }
public string QuoteTemplate { get; set; } = string.Empty;
public bool QuoteContains3Pp { get; set; }
public bool QuoteContainsRb { get; set; }
public string QuoteTemplate { get; set; }
public int Warranty { get; set; } = 12;
public decimal TotalFreightOnly { get; set; }
public decimal TotalFreight { get; set; }
public decimal TotalVAT { get; set; }
public decimal TotalFreight { get; set; }
public decimal TotalVat { get; set; }
public decimal Freight { get; set; } = 3;
public bool IsPriceInformation { get; set; }
public bool ShowSinglePrices { get; set; } = true;
public bool ShowDiscounts { get; set; } = true;
public bool ShowBrutto { get; set; } = true;
public string QuoteDescription { get; set; } = string.Empty;
public string Tex {get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string QuoteDescription { get; set; }
public string Tex { get; set; }
public string Description { get; set; }
public string Path { get; set; } = Directory.GetCurrentDirectory();
//new properties
//metadata:
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public string DataStatus { get; set; } = "Active";
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataVersionComment { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public uint DataVersionNumber { get; set; }
//constructor
public Quote() { }
public Quote(Contact salesRep, bool randomQuoteNumber = true)
{
SalesRep = salesRep;
if (randomQuoteNumber)
{
Random random = new();
QuotationNumber = SalesRep.LastName switch
{
"Woitschetzki" => $"DE-83PE89-{DateTime.Now:My}-{random.Next(999999)}",
"Welsch" => $"DE-83RE32-{DateTime.Now:My}-{random.Next(999999)}",
_ => $"DE-XXYYXX-{DateTime.Now:My}-{random.Next(999999)}",
};
}
}
}
}

@ -1,17 +1,17 @@
namespace Gremlin_BlazorServer.Data.EntityClasses
{
public class RUSettings : IDisposable
public class RuSettings : IDisposable
{
private bool disposedValue;
//primary key
public uint RUSettingsID { get; set; }
public uint RuSettingsId { get; set; }
//forgein key
public uint RegisteredUserID { get; set; }
public uint RegisteredUserId { get; set; }
//navigation properties
public RegisteredUser RegisteredUser { get; set; } = new RegisteredUser();
public RegisteredUser RegisteredUser { get; set; } = new();
//class properties
public string SettingKey { get; set; } = string.Empty;

@ -3,18 +3,18 @@
public class RegisteredUser
{
//primary key
public uint RegisteredUserID { get; set; }
public uint RegisteredUserId { get; set; }
//navigation properties
public IList<RUSettings> RUSettings { get; set; } = new List<RUSettings>();
public IList<RuSettings> RuSettings { get; set; }
//class properties
public string UserName { get; set; } = string.Empty;
public string PasswordHash { get; set; } = string.Empty;
public string UserName { get; set; }
public string PasswordHash { get; set; }
//metadata (subset of IMetadata)
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
}
}

@ -7,22 +7,21 @@ namespace Gremlin_BlazorServer.Data.EntityClasses
//primary key:
//public uint SubMarketId { get; set; }
[Key]
public string SubMarketCode { get; set; } = string.Empty;
public string SubMarketCode { get; set; }
//navigation properties:
public IList<Account> Accounts { get; set; } = new List<Account>();
public IList<Account> Accounts { get; set; }
//class properties:
public string SubMarketDescription { get; set; } = string.Empty;
public string SubMarketDescription { get; set; }
//metadata
public DateTime DataCreationDate { get; set; } = DateTime.Now;
public DateTime DataModificationDate { get; set; } = DateTime.Now;
public DateTime DataValidFrom { get; set; } = DateTime.Now;
public DateTime DataValidUntil { get; set; } = DateTime.MaxValue;
public string DataModificationByUser { get; set; } = string.Empty;
public string DataModificationByUser { get; set; }
public uint DataVersionNumber { get; set; }
public string DataVersionComment { get; set; } = string.Empty;
public string DataStatus { get; set; } = string.Empty;
public string DataVersionComment { get; set; }
public string DataStatus { get; set; }
}
}

@ -0,0 +1,219 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Gremlin_BlazorServer.Data.Migrations
{
public partial class CreateIdentitySchema : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AspNetRoles",
columns: table => new
{
Id = table.Column<string>(nullable: false),
Name = table.Column<string>(maxLength: 256, nullable: true),
NormalizedName = table.Column<string>(maxLength: 256, nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoles", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetUsers",
columns: table => new
{
Id = table.Column<string>(nullable: false),
UserName = table.Column<string>(maxLength: 256, nullable: true),
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
Email = table.Column<string>(maxLength: 256, nullable: true),
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
SecurityStamp = table.Column<string>(nullable: true),
ConcurrencyStamp = table.Column<string>(nullable: true),
PhoneNumber = table.Column<string>(nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
TwoFactorEnabled = table.Column<bool>(nullable: false),
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
LockoutEnabled = table.Column<bool>(nullable: false),
AccessFailedCount = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUsers", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AspNetRoleClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
RoleId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserClaims",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
UserId = table.Column<string>(nullable: false),
ClaimType = table.Column<string>(nullable: true),
ClaimValue = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
table.ForeignKey(
name: "FK_AspNetUserClaims_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserLogins",
columns: table => new
{
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
ProviderKey = table.Column<string>(maxLength: 128, nullable: false),
ProviderDisplayName = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
table.ForeignKey(
name: "FK_AspNetUserLogins_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserRoles",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
RoleId = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
column: x => x.RoleId,
principalTable: "AspNetRoles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_AspNetUserRoles_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "AspNetUserTokens",
columns: table => new
{
UserId = table.Column<string>(nullable: false),
LoginProvider = table.Column<string>(maxLength: 128, nullable: false),
Name = table.Column<string>(maxLength: 128, nullable: false),
Value = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
table.ForeignKey(
name: "FK_AspNetUserTokens_AspNetUsers_UserId",
column: x => x.UserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_AspNetRoleClaims_RoleId",
table: "AspNetRoleClaims",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "RoleNameIndex",
table: "AspNetRoles",
column: "NormalizedName",
unique: true,
filter: "[NormalizedName] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserClaims_UserId",
table: "AspNetUserClaims",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserLogins_UserId",
table: "AspNetUserLogins",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_AspNetUserRoles_RoleId",
table: "AspNetUserRoles",
column: "RoleId");
migrationBuilder.CreateIndex(
name: "EmailIndex",
table: "AspNetUsers",
column: "NormalizedEmail");
migrationBuilder.CreateIndex(
name: "UserNameIndex",
table: "AspNetUsers",
column: "NormalizedUserName",
unique: true,
filter: "[NormalizedUserName] IS NOT NULL");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AspNetRoleClaims");
migrationBuilder.DropTable(
name: "AspNetUserClaims");
migrationBuilder.DropTable(
name: "AspNetUserLogins");
migrationBuilder.DropTable(
name: "AspNetUserRoles");
migrationBuilder.DropTable(
name: "AspNetUserTokens");
migrationBuilder.DropTable(
name: "AspNetRoles");
migrationBuilder.DropTable(
name: "AspNetUsers");
}
}
}

@ -0,0 +1,274 @@
// <auto-generated />
using Gremlin_BlazorServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
namespace Gremlin_BlazorServer.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "3.0.0")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Name")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasName("RoleNameIndex")
.HasFilter("[NormalizedName] IS NOT NULL");
b.ToTable("AspNetRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<int>("AccessFailedCount")
.HasColumnType("int");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("nvarchar(max)");
b.Property<string>("Email")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.HasColumnType("bit");
b.Property<bool>("LockoutEnabled")
.HasColumnType("bit");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("datetimeoffset");
b.Property<string>("NormalizedEmail")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnType("nvarchar(max)");
b.Property<string>("PhoneNumber")
.HasColumnType("nvarchar(max)");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("bit");
b.Property<string>("SecurityStamp")
.HasColumnType("nvarchar(max)");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("bit");
b.Property<string>("UserName")
.HasColumnType("nvarchar(256)")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasName("UserNameIndex")
.HasFilter("[NormalizedUserName] IS NOT NULL");
b.ToTable("AspNetUsers");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int")
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ClaimType")
.HasColumnType("nvarchar(max)");
b.Property<string>("ClaimValue")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("ProviderDisplayName")
.HasColumnType("nvarchar(max)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("RoleId")
.HasColumnType("nvarchar(450)");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("nvarchar(450)");
b.Property<string>("LoginProvider")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Name")
.HasColumnType("nvarchar(128)")
.HasMaxLength(128);
b.Property<string>("Value")
.HasColumnType("nvarchar(max)");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lsag/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

@ -13,17 +13,17 @@
<PackageReference Include="Blazorise.Components" Version="1.1.5" />
<PackageReference Include="Blazorise.DataGrid" Version="1.1.5" />
<PackageReference Include="Blazorise.Icons.FontAwesome" Version="1.1.5" />
<PackageReference Include="DocumentFormat.OpenXml" Version="2.18.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1">
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.1" />
<PackageReference Include="morelinq" Version="3.3.2" />
<PackageReference Include="MySqlConnector" Version="2.2.2" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0-alpha.1" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.2" />
<PackageReference Include="MySqlConnector" Version="2.3.0-beta.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0-silver.1" />
</ItemGroup>
<ItemGroup>

@ -0,0 +1,43 @@
@page "/AccountTypes"
@using Gremlin_BlazorServer.Data.EntityClasses;
@using Gremlin_BlazorServer.Services;
@inject GenericTypeController<AccountType> AccountTypeService
<h1>AccountTypes</h1>
<DataGrid TItem="AccountType" Data="@accountTypes" SelectedRow="@selectedAccountType" SelectedRowChanged="@OnSelectedAccountTypeChanged" Editable ShowPager Bordered Hoverable Sortable Filterable Striped Responsive>
<DataGridCommandColumn />
<DataGridColumn Field="@nameof(AccountType.AccountTypeCode)" Caption="AccountTypeCode" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(AccountType.AccountTypeDescription)" Caption="AccountTypeDescription" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(AccountType.DataStatus)" Caption="DataStatus" Filterable Sortable Editable/>
</DataGrid>
<h2>@selectedAccountType.AccountTypeCode: @selectedAccountType.AccountTypeDescription</h2>
<DataGrid TItem="Account" Data="@accountsOfSelectedAccountType" Editable ShowPager Bordered Hoverable Sortable Filterable Striped Responsive>
<DataGridCommandColumn />
<DataGridColumn Field="@nameof(Account.AccountId)" Caption="AccountId" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(Account.AccountName)" Caption="AccountName" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(Account.Street)" Caption="Street" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(Account.Zip)" Caption="Zip" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(Account.City)" Caption="City" Filterable Sortable Editable/>
<DataGridColumn Field="@nameof(Account.SapAccountNumber)" Caption="SapAccountNumber" Filterable Sortable Editable/>
</DataGrid>
@code {
List<AccountType> accountTypes = new();
AccountType selectedAccountType = new();
List<Account> accountsOfSelectedAccountType = new();
protected override async Task OnInitializedAsync()
{
accountTypes = await Task.Run(() => AccountTypeService.GetAllAsync());
selectedAccountType = accountTypes.First();
}
private async Task OnSelectedAccountTypeChanged(AccountType sAt)
{
selectedAccountType = sAt;
accountsOfSelectedAccountType = await AccountTypeService.GetAllAccountsAsync(selectedAccountType);
}
}

Some files were not shown because too many files have changed in this diff Show More