Custom User Management in ASP.net Core MVC With Identity - Codewithmukesh
Custom User Management in ASP.net Core MVC With Identity - Codewithmukesh
Enroll Now 🚀
#dotnet
In this article, let’s go in-depth and understand the functionalities you can achieve with the help
of Microsoft Identity. We will build a small yet practical implementation of Custom User
Management in ASP.NET Core MVC with Identity. This will cover most of the practical use cases
involved while developing User Management in ASP.NET Core. You can find the source code of
this implementation on my GitHub.
PRO TIP : This is yet another huge article with around 4600+ words (Probably my longest
article till now). Do bookmark this page and continue ?
Basics of Identity
Adding Custom Identity Properties
Seeding Default Users and Roles
Role Management
Assigning Users to Roles
and much more.
Now, Identity comes with certain basic features out of the box. But in real-time scenarios, we may
need much more than what Microsoft offers by default. This includes adding Profile Pictures, UI
for Role Management, Custom logic to log in to the user, and much more. This is exactly what we
will learn in the course of this article.
So, where are the view and controller located? During the announcement of .NET Core 2,
Microsoft made it official that the new project scaffoldings will no longer have the auto-
generated code for Identity. Rather, they decided to put all this code and HTML into the Identity
DLL. While this is a clean way of going about with identity, it makes it confusing for developers
who want to add custom features on top of the Identity.
Once it is added, you can see a number of razor pages in the Areas folder. These are files that act
as the default Identity UI. Moving further we will learn about customizing Identity to match our
requirements.
Renaming the Default Identity Tables and Updating.
Before moving on, let’s update the database. As soon as we created our project, Visual Studio has
done the following for us already.
PS - You can change the connection string to that of the DBMS of your choice. To keep the
article simple, I am going on with the local db connection.
Since everything is set up for us, let’s apply the migrations and update the database. Open up the
package manager console and type in the following.
update-database
Once that’s done, open up the SQL Server Object Explorer in Visual Studio. You can see our newly
generated Identity tables here.
Now, there is one thing that catches the eyes of many. The Table Names. Quite ugly with the
ASPNET Naming convention, right? Let’s change that now.
drop-database
Also, delete the migrations folder (found inside the Data Folder), as we are going to generate a
new one. Here is a simple solution. Since we are by default using Entity Framework Core, let’s
open up the ApplicationDbContext.cs from the Data Folder. To modify the default ugly names of
the Identity Tables, add this override function,
With that out of the way, let’s add the migrations and update the database.
This is where you will need to extend the IdentityUser class with your own properties. In the
Models folder, create a new class Models/ApplicationUser.cs and inherit the Identity User.
Here, we are adding properties like FirstName, LastName, a byte array of Image, and
UserNameChangeLimit (we will talk about this particular property later on.)
Since we decided to change the default User class from IdentityUser to ApplicationUser, we
would have to make other changes in our existing code as well. Firstly, we will need to change
how the User class is being wired up with the DbContext. Navigate to
Startup.cs/ConfigureServices method and modify it.
After that comes the hard and time-consuming part. Remember the 40+ Identity pages we
imported via scaffolding? Now, each of these 40 pages would have a reference to IdentityUser
class. We will have to replace each of them with ApplicationUser. A simple step to get started is
to Search for IdentityUser in the entire solution with Visual Studio and to replace it with
Application User. But even after that, you would have to manually go to each page to add the
namespace reference. Once you have resolved each of the namespace issues, add the final
change. Navigate to ApplicaionDbContext and modify the first line of the class to accommodate
ApplicationUser.
Next, build the application to check for build errors. If you find any, resolve it by adding the
required namespace.
If you are new to Razor pages, the cshtml holds all the Razor engine contents (C# + HTML) and
the cs file contains Controller-like C# code.
Let’s first get done with the C# part. Navigate to Register.cs. In the InputModel class, let’s add 2
new properties, First Name, and Last Name.
PS - I have added dots (.) to denote that we keep the exising code. This is just to keep the code
snippets smaller and to the point.
Next, we will need to pass data to these properties and save it to the DB while registering. For
this, open up the OnPostAsync method in the Register.cs class.
if (ModelState.IsValid)
{
MailAddress address = new MailAddress(Input.Email);
string userName = address.User;
var user = new ApplicationUser
{
UserName = userName,
Email = Input.Email,
FirstName = Input.FirstName,
LastName = Input.LastName
};
.
.
}
Line 5 - 10 Adding new fields to the registration model and linking with C#
Line 3 - 4 Generating a username from the email address. For example, if the email address
entered by the user is [email protected], the username generated will be admin. You can
implement your own logic here.
This is all you have to do with the C# code. Let’s add the fields to the Register.cshtml page.
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
Let’s check out the result. Build and run the application. Navigate to the Register Page. You can
see the new fields now. Add some details to the form and try to register.
You can see that the application redirects you to the home page. In the navigation menu, you can
see the generated username too.
Now try to logout and log in back to the application. You will not be able to. Any Guesses?
By default, in Identity, the username and Email are the same. Now, in the login form, the
application expects the username in the email field. But our username is no longer an email id,
remember? Let’s fix this next.
Allow Login with both Username and Email
Ideally, you may want to allow your users to log in with both the username and the email id. This
is standard practice. Let’s get started. Navigate to Areas/Identity/Pages/Account/Login.cs
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
At the model level, we made the Email Property accept both email ids and plain text.
In the Login.cs , add a new function to check if the entered data is a valid email id or not.
Build the application and run. You would be able to log in with both the username and the email
id now.
PS, Since we are making quite a lot of changes, it is advisable to clean the solution once in
a while to rebuild the entire project from scratch. There are chances that Visual Studio
would not re-build few of the pages.
Log in to your application and click on the Hello {username} in the top navigation menu. You
will be redirected to the “Manage your account” Page. There is quite a lot of basic options here,
like changing your phone number, updating the email id, changing the password, and so on.
Let’s try to extend these pages in the coming sections.
As the first step, let’s try to add the First name and Last name fields to this form. Navigate to
Areas/Identity/Pages/Account/Manage/Index.cshtml.cs
Replace the Input Model. Here we added the new fields. (including Profile Picture, although we
will implement it in the next section)
Next, while loading the form we need to load these data to the memory as well.
Finally, in the OnPostAsync method, add this to update the newly entered FirstName or
LastName.
<div class="form-group">
<label asp-for="Input.FirstName"></label>
<input asp-for="Input.FirstName" class="form-control" />
<span asp-validation-for="Input.FirstName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.LastName"></label>
<input asp-for="Input.LastName" class="form-control" />
<span asp-validation-for="Input.LastName" class="text-danger"></span>
</div>
That’s it. Build and Run your application. Go to the “Manage your account” page. You will see the
changes here.
Remember the part where we added the Update code for FirstName and Lastname? Similarly, in
the OnPostAsync method, let’s save the Profile Picture as well.
if (Request.Form.Files.Count > 0)
{
IFormFile file = Request.Form.Files.FirstOrDefault();
using (var dataStream = new MemoryStream())
{
await file.CopyToAsync(dataStream);
user.ProfilePicture = dataStream.ToArray();
}
await _userManager.UpdateAsync(user);
}
In this case, we are trying to save our image to the database. Thus we use memory streams to
convert the image file to a memory object/byte array. Then we save this array to the Database. If
you want to learn more about uploading images/files/multiple-files to the database/file system, I
have written a detailed article where I build a small application that can upload files to the
database as well as to the file system. Check it out here.
Finally, let’s modify the Index.cshtml to add the container for images. You can have your
approach to do this. End of the day, it will be just an Image Input that is bound to the
ProfilePicture Property. I have added several customizations to it, which you might be interested
in as well.
</div>
</form>
Line #1- Here, we change the form type to “multipart/form-data”. This is because we not only
have plain texts but Image files too.
Line 29-46 - Here is where we define our Image Field.
Line 31-38 - If the user has an image uploaded to the database already, we get the byte array
from the model and convert it to an image. This image will be displayed on the page. Else, shows
a blank image container.
Line 39 - Defining the Input Button that can load images from our file system.
You can see that we are now able to upload images to our profile. Here is a little bonus. We have
the image only on the Profile Page. Ideally, websites have the profile pictures somewhere in the
navigation menu too, right? Let’s do the same.
@if (SignInManager.IsSignedIn(User))
{
<li class="nav-item" style="align-self: center;">
@if (UserManager.GetUserAsync(User).Result.ProfilePicture != null)
{
<img style="width:40px;height:40px; object-fit:cover; border-radius:30px" src
}
</li>
.
.
}
https://www.facebook.com/codewithmukesh
Here, ‘codewithmukesh’ is the username of our Facebook Page. What if we want to change this
username to something else. Facebook allows us to do, but only for a specific number of times.
Let’s implement this exact feature in our Solution as well.
First, we need a container in the HTML page that would hold a message like “You can change
your username x more time(s)“. In order to do this, we use TempData, as ‘x’ is a number that gets
generated via our C# end. Let’s create this TempData First. Navigate to Index.cshtml.cs, and add a
new TempData Property.
[TempData]
public string StatusMessage { get; set; }
[TempData]
public string UserNameChangeLimitMessage { get; set; }
[BindProperty]
public InputModel Input { get; set; }
Now, the logic to calculate the number of attempts left for a user to change his/her username.
Like we added the logic to save/update FirstName, LastName and so, we will add the logic to
save/update the remaining attempts left for the user.
if (user.UsernameChangeLimit > 0)
{
if (Input.Username != user.UserName)
{
var userNameExists = await _userManager.FindByNameAsync(Input.Username);
if (userNameExists != null)
{
StatusMessage = "User name already taken. Select a different username.";
return RedirectToPage();
}
var setUserName = await _userManager.SetUserNameAsync(user, Input.Username);
if (!setUserName.Succeeded)
{
StatusMessage = "Unexpected error when trying to set user name.";
return RedirectToPage();
}
else
{
user.UsernameChangeLimit -= 1;
await _userManager.UpdateAsync(user);
}
}
}
<h4>@ViewData["Title"]</h4>
<partial name="_StatusMessage" model="Model.StatusMessage" />
<partial name="_StatusMessage" model="Model.UserNameChangeLimitMessage" />
Build and Run your application. You will be able to change the username 10 times. After which
you will be blocked from doing so. Pretty Nice Feature to have, yeah?
User Roles - Overview
Now that we have added custom fields and Images to the User, let’s move on and talk about
User Roles. While Building Custom User Management in ASP.NET Core MVC with Identity, Roles
are quite important. For example, If we take the case of Invoice Management Application, it
would have User Roles like Manager, Operator, Super-Admin, and so on. User Roles help to
define the level of permission for each user.
In the upcoming sections, we will talk about Adding User Roles by default to an Application,
Assigning the user Roles to Users, and much more.
Create an Enum for the supported Roles. Add a new Enum at Enums/Roles.
In the data folder, add a ContextSeed.cs class. This class will be responsible for seeding default
data to the database. In this case, let’s add a function to seed the User Roles to the database.
Now we need an entry point to invoke the ContextSeed Methods. It depends on your preference,
but for this article, we will invoke the method in the Main Function(Program.cs) . That is, every
time the application fires up, It would check if the default user roles are present in the database.
Else, it seeds the required Roles.
That’s it. Just add the required References and Build & Run the Application. Now let’s check our
Identity. Role Table and ensure if the default Roles were added.
You can see that all our 4 default roles are already added. Simple, yeah? Similarly in the next
section, let’s learn to add a default user (super admin) via seeding.
}
}
Like we did in the previous section, add the below line of code to the Program.cs/Main Method.
This will invoke the Method that seeds the default user to the database.
Build the application and try to log in with the default credentials that we defined.
There you go, we have successfully seeded our default user. It was easy, right?
This will be quite simple as we have already set up the required pieces of code. Navigate to the
Register.cs file and add the highlighted line below.
Start by adding a new Empty MVC Controller to the Controllers folder. Name it
RoleManagerController.cs
Right-click on the Index Method and click on Add View. Name it Index.cshtml. This creates a new
View for this Controller. Modify the View as the following.
@model List<Microsoft.AspNetCore.Identity.IdentityRole>
@{
ViewData["Title"] = "Role Manager";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>Role Manager</h1>
<form method="post" asp-action="AddRole" asp-controller="RoleManager">
<div class="input-group">
<input name="roleName" class="form-control w-25">
<span class="input-group-btn">
<button class="btn btn-info">Add New Role</button>
</span>
</div>
</form>
<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Role</th>
</tr>
</thead>
<tbody>
@foreach (var role in Model)
{
<tr>
<td>@role.Id</td>
<td>@role.Name</td>
</tr>
}
</tbody>
</table>
That’s it. Build and Run the application. Navigate to localhost:xxx/RoleManager. Here you will be
able to see all the visible Roles and Add New Roles as well.
Listing Users with Corresponding Roles
Now, let’s make an interface that lists all the users along with the Roles associated with them. We
will first create a View Model that holds the user details and the roles as a string List. Create a
new class in the Models Folders. Models/UserRolesViewModel.cs
Next, we will create a controller that throws out a view with a list of user details. Essentially it
would get all the users from the database and also the roles per user.
Finally, add a View to the Index Method and Add the Following.
@using UserManagement.MVC.Models
@model List<UserManagement.MVC.Models.UserRolesViewModel>
@{
ViewData["Title"] = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h1>User Roles</h1>
<table class="table table-striped">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Roles</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach (var user in Model)
{
<tr>
<td>@user.FirstName</td>
<td>@user.LastName</td>
<td>@user.Email</td>
<td>@string.Join(" , ", user.Roles.ToList())</td>
<td>
<a class="btn btn-primary" asp-controller="UserRoles" asp-action="Manage"
</td>
</tr>
}
</tbody>
</table>
Run the Application and navigate to /UserRoles. Here you can see the list of users along with the
assigned Roles. For instance, you can see that the Superadmin has all the roles assigned.
Once that is done, we go to the UserRolesController and add HTTP GET and HTTP POST Variants
of the Manage Method. The Get Method will be responsible to get the roles per user. Post
Method will handle the role-assigning part of the user.
Finally, add a new View for the Manage Method and Name it Manage.cshtml.
@model List<ManageUserRolesViewModel>
@{
ViewData["Title"] = "Manage";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<form method="post">
<div class="card">
<div class="card-header">
<h2>Manage User Roles</h2>
Add/Remove Roles for User / @ViewBag.UserName.
</div>
<div class="card-body">
@for (int i = 0; i < Model.Count; i++)
{
<div class="form-check m-1">
<input type="hidden" asp-for="@Model[i].RoleId" />
<input type="hidden" asp-for="@Model[i].RoleName" />
<input asp-for="@Model[i].Selected" class="form-check-input" />
<label class="form-check-label" asp-for="@Model[i].Selected">
@Model[i].RoleName
</label>
</div>
}
<div asp-validation-summary="All" class="text-danger"></div>
</div>
<div class="card-footer">
<input type="submit" value="Update" class="btn btn-primary"
style="width:auto" />
<a asp-action="EditUser" asp-route-id="@ViewBag.userId"
class="btn btn-primary" style="width:auto">Cancel</a>
</div>
</div>
</form>
That’s all. Run the Application and go to /UserRoles. Click on the manage button of any User. You
will be presented with a checkbox list of assigned User Roles. From here you can do the required
modifications and updates. This Redirects you back to the Index method.
You can see that We have a complete working model of User Role Manager UI for ASP.NET Core
MVC. I will wind up this article with this.
Further Improvements
You might have noticed that all the Views are accessible to anyone. You could limit this by adding
an Authorize Attribute Decorator over the Controller Methods. To be specific, we don’t want to
expose the user Roles Manager to the public, do we? On top of the Manage Method in the
UserRolesController, add this.
[Authorize(Roles = "SuperAdmin")]
This ensures that only SuperAdmins can Manage User Roles. This is also called Role-based
Authentication.
Summary
In this really long article, we have gone through nearly everything that you would need to know
to implement Custom User Management in ASP.NET Core MVC with Identity. Here is the
complete source code of the above implementations. Do Leave a star if you find it helpful! Did I
miss out on something? Comment below and I will try to integrate that too with this article.
Leave your suggestions and feedback in the comment section below. Do not forget to share this
article within your developer community. Thanks and Happy Coding! :D
Source Code ✌️
Grab the source code of the entire implementation by clicking here. Do Follow me
on GitHub .
Support ❤️
If you have enjoyed my content and code, do support me by buying a couple of coffees. This
will enable me to dedicate more time to research and create new content. Cheers!
3 reactions
👍 3
Enroll Now
codewithmukesh
web development simplified!