ASP.NET Core 3.1 - User Management


Ken Haggerty
Created 02/01/2020 - Updated 02/02/2020 16:22

This article will demonstrate the implementation of user management. I will assume you have created a new ASP.NET Core 3.1 Razor Pages project and implemented the first 3 articles of the series. See Tutorial: Get started with Razor Pages in ASP.NET Core .

This article is part of a series about users without Identity.


Access to the research project source code may be purchased on KenHaggerty.Com at Manage > Assets.

A project which implements users without Identity has been published to demo.kenhaggerty.com.

I enjoy writing these articles. It often enhances and clarifies my coding. The research project is a result of a lot of refactoring and hopefully provides logical segues for the articles. Thank you for supporting my efforts.

Users can not self-register. We need an interface to create, read, update and delete users. We can scaffold Razor Pages with Entity Framework(CRUD) for the AppUser entity. Create a new folder under Admin named Users. I will create an Admin role in the next article which will restrict access to the Users folder. Right click on the new folder then Add then click New Scaffolded Item.

New Scaffolded Item
Razor Page Using Entity Framework

Select AppUser for the Model class and ApplicationDbContext for the Data context class. Leave the layout page option empty because it is set by _ViewStart.cshtml.

Razor Pages Entity Options

When complete, there will be Create, Delete, Details, Edit and Index pages in the Users folder. Add a link from the Admin Index page to the Users Index page.

<a class="btn btn-primary" asp-page="/Admin/Users/Index">Users</a>
Admin Index page

The Users Index page displays a list of all users including the user's LoginNameUppercase and PasswordHash. Remove the LoginNameUppercase and PasswordHash properties and add style to the page.

Users Index page

To create a new user we need a login name and password. I use an InputModel decorated with DataAttributes. If the model validates, check for an exisiting login name, hash the password then save the user.

Edit Admin/ Users/ Create.cshtml.cs:
public class CreateModel : PageModel
{
    private readonly ApplicationDbContext _context;
    public CreateModel(ApplicationDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public InputModel Input { get; set; }
    public class InputModel
    {
        [Required]
        [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)]
        [Display(Name = "Login Name")]
        public string LoginName { get; set; }

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

    public IActionResult OnGet()
    {
        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        // Test for an existing user with the same LoginName
        int testId = await _context.AppUsers
            .Where(u => u.LoginNameUppercase == Input.LoginName.ToUpper())
            .Select(u => u.Id)
            .FirstOrDefaultAsync();

        if (testId == 0)
        {
            var passwordHasher = new CustomPasswordHasher();
            var hashedPassword = passwordHasher.HashPassword(Input.Password);
            var newUser = new AppUser
            {
                LoginName = Input.LoginName,
                LoginNameUppercase = Input.LoginName.ToUpper(),
                PasswordHash = hashedPassword
            };

            _context.AppUsers.Add(newUser);
            try
            {
                _context.SaveChanges();
            }
            catch (DbUpdateException)
            {
                throw new InvalidOperationException("DbUpdateException occurred creating a new user.");
            }
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Login Name already exisits.");
            return Page();
        }

        return RedirectToPage("./Index");
    }
}

Update the page inputs and add a little style.

New User page

The edit InputModel includes the Id and does not require a password. The password hash is not updated if the InputModel password is empty. If a password is entered, it will be validated. If the model validates, check updating the login name, hash the password if updated then save the user.

Edit Admin/ Users/ Edit.cshtml.cs:
public class EditModel : PageModel
{
    private readonly ApplicationDbContext _context;
    public EditModel(ApplicationDbContext context)
    {
        _context = context;
    }

    [BindProperty]
    public InputModel Input { get; set; }
    public class InputModel
    {
        [Key]
        public int Id { get; set; }

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

        [StringLength(32, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 8)]
        public string Password { get; set; }
    }

    public async Task<IActionResult> OnGetAsync(int? id)
    {
        if (id == null)
        {
            return NotFound();
        }

        var user = await _context.AppUsers
            .AsNoTracking()
            .FirstOrDefaultAsync(m => m.Id == id);

        if (user == null)
        {
            return NotFound();
        }

        Input = new InputModel
        {
            Id = user.Id,
            LoginName = user.LoginName
        };

        return Page();
    }

    public async Task<IActionResult> OnPostAsync()
    {
        if (!ModelState.IsValid)
        {
            return Page();
        }

        var user = await _context.AppUsers.FirstOrDefaultAsync(m => m.Id == Input.Id);

        if (user == null)
        {
            return NotFound();
        }

        if (user.LoginNameUppercase != Input.LoginName.ToUpper())
        {
            // Test for an existing user with the new LoginName
            int testId = await _context.AppUsers
                .Where(u => u.LoginNameUppercase == Input.LoginName.ToUpper())
                .Select(u => u.Id)
                .FirstOrDefaultAsync();

            if (testId != 0)
            {
                ModelState.AddModelError(string.Empty, "Login Name already exisits.");
                return Page();
            }
        }

        // Hash new password if provided
        if (!string.IsNullOrEmpty(Input.Password))
        {
            var passwordHasher = new CustomPasswordHasher();
            var hashedPassword = passwordHasher.HashPassword(Input.Password);
            user.PasswordHash = hashedPassword;
        }

        user.LoginName = Input.LoginName;
        user.LoginNameUppercase = Input.LoginName.ToUpper();

        _context.Attach(user).State = EntityState.Modified;

        try
        {
            await _context.SaveChangesAsync();
        }
        catch (DbUpdateConcurrencyException)
        {
            if (!AppUserExists(user.Id))
            {
                return NotFound();
            }
            else
            {
                throw;
            }
        }

        return RedirectToPage("./Index");
    }

    private bool AppUserExists(int id)
    {
        return _context.AppUsers.Any(e => e.Id == id);
    }
}

Update the page inputs and add a little style.

Edit User page

The code for details and delete doesn't need modification but I remove the LoginNameUppercase and PasswordHash properties from the pages.

User Details page
Delete User page


1 Following


Comment Count = 0

Please log in to comment or follow.

Login Register