ASP.NET Core 3.1 - User Management

This article will demonstrate the implementation of user management. I will assume you have downloaded the ASP.NET Core 3.1 - Users Without Identity Project or created a new ASP.NET Core 3.1 Razor Pages project. See Tutorial: Get started with Razor Pages in ASP.NET Core. You should review the earlier articles of the Users Without Identity Project series.

Users Without Identity Project and Article Series

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.
Update 02/23/2021

I added the Enhanced User Series' article links.

Ken Haggerty
Created 02/01/20
Updated 02/24/21 00:16 GMT

Log In or Reset Quota to read more.

Successfully completed. Thank you for contributing.
Processing...
Something went wrong. Please try again.
Contribute to enjoy content without advertisments.
You can contribute without registering.

Comments(0)

Loading...
Loading...

Not accepting new comments.

Submit your comment. Comments are moderated.

User Image.
DisplayedName - Member Since ?