asp.net core series 47 Identity custom user data

1. Overview

Following the WebAppIdentityDemo project in the previous article, add custom user data to Identity DB. The user data class of custom extension should inherit the IdentityUser class, and the file name is Areas/Identity/Data/{project name}User.cs. Customized user data model attributes need to be modified with [PersonalData] for automatic download and deletion. Enabling data to be downloaded and deleted helps meet GDPR requirements.

 1.1 Custom user data class WebAppIdentityDemo.Areas.Identity.Data.

 public class WebAppIdentityDemoUser:IdentityUser
    {
        /// <summary>
        /// Full name
        /// </summary>
        [PersonalData]
        public string Name { get; set; }

        /// <summary>
        /// Birth Date
        /// </summary>
        [PersonalData]
        public DateTime DOB { get; set; }
    }

Using attributes to modify PersonalData properties is:

Use the Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml page to call UserManager.Delete

Use the Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml page to download user data

1.2 Modify IdentityHostingStartup

Move the identity-related services in Startup to this file for centralized management.

 public class IdentityHostingStartup : IHostingStartup
    {
        public void Configure(IWebHostBuilder builder)
        {
            builder.ConfigureServices((context, services) => {
                services.AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(
                        context.Configuration.GetConnectionString("DefaultConnection")));

                services.AddDefaultIdentity<WebAppIdentityDemoUser>()
                   .AddDefaultUI()
                   .AddEntityFrameworkStores<ApplicationDbContext>();

                services. Configure<IdentityOptions>(options =>
                {
                    // Password settings.
                    options.Password.RequireDigit = true;
                    options.Password.RequireLowercase = true;
                    options.Password.RequireNonAlphanumeric = true;
                    options.Password.RequireUppercase = true;
                    options.Password.RequiredLength = 6;
                    options.Password.RequiredUniqueChars = 1;

                    // Lockout settings.
                    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
                    options.Lockout.MaxFailedAccessAttempts = 5;
                    options.Lockout.AllowedForNewUsers = true;

                    // User settings.
                    options.User.AllowedUserNameCharacters =
                    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@ + ";
                    options.User.RequireUniqueEmail = false;
                });

                services. ConfigureApplicationCookie(options =>
                {
                    // Cookie settings
                    options.Cookie.HttpOnly = true;
                    options.ExpireTimeSpan = TimeSpan.FromMinutes(5);

                    options. LoginPath = $"/Identity/Account/Login";
                    options.LogoutPath = $"/Identity/Account/Logout";
                    options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
                });
            });
        }
    }

 1.3 Modify DbContext context

 // public class ApplicationDbContext : IdentityDbContext
    public class ApplicationDbContext : IdentityDbContext<WebAppIdentityDemoUser>
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            :base(options)
        {
        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);
            // Customize the ASP.NET Core Identity model and override the defaults if needed.
            // For example, you can rename the ASP.NET Core Identity table names and more.
            // Add your customizations after calling base.OnModelCreating(builder);
        }
    }

 1.4 Database version migration

 PM> Add-Migration IdentitySchema2
PM> Update-Database IdentitySchema2

1.5 Update registration page

During development, the membership system needs to reuse the modified page and change IdentityUser to WebAppIdentityDemoUser. If it is not changed, an error will be reported: the service IdentityUser is not resolved. The red part is the modified code

 [AllowAnonymous]
    public class RegisterModel : PageModel
    {
        // private readonly SignInManager<IdentityUser> _signInManager;
        //private readonly UserManager<IdentityUser> _userManager;
        private readonly SignInManager<WebAppIdentityDemoUser> _signInManager;
        private readonly UserManager<WebAppIdentityDemoUser> _userManager;

        private readonly ILogger<RegisterModel> _logger;
        private readonly IEmailSender _emailSender;

        public RegisterModel(
            UserManager<WebAppIdentityDemoUser> userManager,
            SignInManager<WebAppIdentityDemoUser> signInManager,
            ILogger<RegisterModel> logger,
            IEmailSender emailSender)
        {
            _userManager = userManager;
            _signInManager = signInManager;
            _logger = logger;
            _emailSender = emailSender;
        }

        [BindProperty]
        public InputModel Input { get; set; }

        public string ReturnUrl { get; set; }

        public class InputModel
        {
            [Required]
            [DataType(DataType. Text)]
            [Display(Name = "Full name")]
            public string Name { get; set; }

            [Required]
            [Display(Name = "Birth Date")]
            [DataType(DataType. Date)]
            public DateTime DOB { get; set; }


            [Required]
            [EmailAddress]
            [Display(Name = "Email")]
            public string Email { get; set; }

            [Required]
            [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
            [DataType(DataType. Password)]
            [Display(Name = "Password")]
            public string Password { get; set; }

            [DataType(DataType. Password)]
            [Display(Name = "Confirm password")]
            [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
            public string ConfirmPassword { get; set; }
        }

        public void OnGet(string returnUrl = null)
        {
            ReturnUrl = returnUrl;
        }

        public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl = returnUrl  Url.Content("~/");
            if (ModelState.IsValid)
            {
                // var user = new IdentityUser { UserName = Input.Email, Email = Input.Email };
                var user = new WebAppIdentityDemoUser
                {
                    UserName = Input.Email,
                    Email = Input.Email,
                    Name = Input.Name,
                    DOB = Input.DOB
                };
                var result = await _userManager.CreateAsync(user, Input.Password);
                if (result.Succeeded)
                {
                    _logger.LogInformation("User created a new account with password.");

                    var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                    var callbackUrl = Url.Page(
                        "/Account/ConfirmEmail",
                        pageHandler: null,
                        values: new { userId = user.Id, code = code },
                        protocol: Request.Scheme);

                    await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                        $"Please confirm your account by <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                    await _signInManager.SignInAsync(user, isPersistent: false);
                    return LocalRedirect(returnUrl);
                }
                foreach (var error in result.Errors)
                {
                    ModelState.AddModelError(string.Empty, error.Description);
                }
            }

            // If we got this far, something failed, redisplay form
            return Page();
        }
    }
@page
@model RegisterModel
@{
    ViewData["Title"] = "Register";
}

<h1>@ViewData["Title"]</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-route-returnUrl="@Model.ReturnUrl" method="post">
            <h4>Create a new account.</h4>
            <hr />
            <div asp-validation-summary="All" class="text-danger"></div>


            <div class="form-group">
                <label asp-for="Input.Name"></label>
                <input asp-for="Input.Name" class="form-control" />
                <span asp-validation-for="Input.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.DOB"></label>
                <input asp-for="Input.DOB" class="form-control" />
                <span asp-validation-for="Input.DOB" class="text-danger"></span>
            </div>


            <div class="form-group">
                <label asp-for="Input.Email"></label>
                <input asp-for="Input.Email" class="form-control" />
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.Password"></label>
                <input asp-for="Input.Password" class="form-control" />
                <span asp-validation-for="Input.Password" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Input.ConfirmPassword"></label>
                <input asp-for="Input.ConfirmPassword" class="form-control" />
                <span asp-validation-for="Input.ConfirmPassword" class="text-danger"></span>
            </div>
            <button type="submit" class="btn btn-primary">Register</button>
        </form>
    </div>
</div>

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

Start the program, run the registration page, and after saving successfully, view the database, as shown below:

1.6 Modifications to other related pages

The following most basic pages require changing IdentityUser to WebAppIdentityDemoUser.

Login page /Identity/Account/Login

Registration page Identity/Account/Register

Member management backend home page /Identity/Account/Manage/index

  Personal data page /Identity/Account/Manage/PersonalData

   Personal data deletion /Identity/Account/Manage/DeletePersonalData

   Personal data download/Identity/Account/Manage/ DownloadPersonalData.cshtml

Branch page _ManageNav.cshtml

The following is the Account/Manage/Index.cshtml page, with two customized user field information added. Please see the official website for the specific update code.

The following is the /Identity/Account/Manage/PersonalData.cshtml page, which can already download and delete user data

references

Custom user data