Taking the GUID out of ASP.NET Core Identity

As this post is pretty codey and technical there's not really any other option than to drop in a screen-grab of part of the ASP.NET Core MVC template!

I've been working on an ASP.NET Core web project using the built-in ASP.NET Core Identity solution. All the project scaffolding was done by choosing to create a new ASP.NET Core Web Application, choosing the 'Web Application (Model-View-Controller)' option and configuring the application to use 'Individual User Accounts'. After that Visual Studio did the heavy lifting of creating the project and populating it from the template (for a .NET Core project, does it actually shell out to the dotnet command, or programatically invoke that which the command does?).

Related posts:

  1. Taking the GUID out of ASP.NET Core Identity (this post)
  2. Splitting out ASP.NET Core Identity into a separate library
  3. Extending the ASP.NET Core Identity user
  4. Reading and writing custom ASP.NET Core Identity user properties
  5. Extending the ASP.NET Core Identity UserManager to set the Employee Id during registration
  6. The finishing touches to hooking into ASP.NET Core Identity user creation

Changing to an integer for Ids

However, the unique identifiers that are generated for users are GUIDs, stored in strings. Bleurgh. Changing this so that integers are used is reasonably simple and starts with the addition of two custom classes, a custom IdentityUser and a custom IdentityRole (so that IdentityRole's are identified by integers as well):

public class ApplicationIdentityUser : IdentityUser<int>
{
}

public class ApplicationRole : IdentityRole<int>
{
}

Once these classes have been created the next thing to do is to update the bits and pieces of the auto-generated code so that they're aware of these types of IdentityUser and IdentityRole. Starting with the ApplicationDbContext which is found in \Data\ApplicationDbContext.cs, update this so the class definition looks like this:

public class ApplicationDbContext : IdentityDbContext<ApplicationIdentityUser, ApplicationRole, int>

The three generic type parameters that are being specified are TUser, TRole and TKey. The first two should be fairly self-explanatory but the last is slightly less obvious; it's the "The type of the primary key for users and roles", so needs to match the type that we've specified in our ApplicationIdentityUser and ApplicationRole classes.

The next place that needs to be updated is in \Startup.cs where Identity is wired in to look like this:

services.AddDefaultIdentity<ApplicationIdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

Now that's done the last thing to do is to re-create the data migrations (so that the database will be created with the correct structure) before running the app up to create the database. The easiest way I've found to do this is to delete all the files in the \Data\Migrations folder and then from the command line (in the web application projects directory) run the following command:

dotnet ef migrations add IdentityModel -o Data\Migrations

That command instructs dotnet to add a new migration called IdentityModel and to place it in the Data\Migrations directory.

[Added 10/2019]: You need to update the view /Views/Shared/_LoginPartial.cshtml to change the two lines beginning with @inject to be:

@inject SignInManager<ApplicationIdentityUser> SignInManager
@inject UserManager<ApplicationIdentityUser> UserManager

This replaces IdentityUser with ApplicationIdentityUser as otherwise you'll see "InvalidOperationException: No service for type 'Microsoft.AspNetCore.Identity.UserManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' has been registered." when you first try to run the web app.

Before I spin-up the web application to create the database, I make one more small tweak - this time in appsettings.json. This is to change the connection string to point to a "real" instance of SQL Server rather than the LocalDB instance that's pointed to by default, solely because at this stage it makes it easier to poke around inside the database and have a look.

Running it up and creating the database

You can run the command dotnet ef database update to create the database, or run the web application and let it take care of it for you. Running the application, following the Register link and filling in details of a new account will trigger this page to be shown:

ASP.NET Core showing a developer helper page that can be used to trigger creation of the database

Clicking the Apply Migrations button will trigger the creation of the database and tables for each of the ASP.NET Identity objects. After a short while, the Apply Migrations button will change to look like this:

The Apply Migrations button will change to indicate that migrations have been applied, or in other words that the database has been created

Hit F5 and you should be redirected to the homepage, with your user logged in! A quick look in SQL Server Management Studio shows that the user table has been populated with the details used to register and that the Id column for the AspNetUsers table has been created as an integer column:

SQL Server Management Studio showing that the AspNetUser table has an Id column that's an integer

That's pretty much all there is to it, but it is worth bearing in mind that by changing to an auto-incrementing integer id you've made user id's much more guessable. Be careful to ensure that you secure any methods that take a User Id to ensure that someone can't, for example, call a URL suibstituting a low id number (e.g. 1) and perform an action on your Administrator user - like changing the email address associated with it!

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

6 Comments

  • Gravatar Image

    Thank you, this article was really useful. One gotcha for me was that after swapping to use an int when I first loaded identity server again I got an error on this line

    Microsoft.AspNetCore.Identity.UserStoreBase<TUser, TKey, TUserClaim, TUserLogin, TUserToken>.ConvertIdFromString(string id).

    Turns out I had a cookie using the old guid format, simple fix was to clear cookies.

  • Gravatar Image

    This doesn't work. Using asp.net.core 2.2 I followed this article to the letter.
    Now I get [TUser,TContext,TKey,TUserClaim,TUserLogin,TUserToken]' violates the constraint of type 'TUser' and I have no idea what it means or how to fix it :(

  • Gravatar Image

    Hi D,

    Sorry to hear you're having problems with this - can you tell me what line(s) of your code are generating this error and I'll see what I can do to help? :)

    Rob

  • Gravatar Image

    Thanks for writing this article. It was very easy to follow.
    I did run into one error when using this solution on logout.
    It was throwing this error "InvalidOperationException: No service for type 'Microsoft.AspNetCore.Identity.SignInManager`1[Microsoft.AspNetCore.Identity.IdentityUser]' has been registered.:=".

    I was able to fix it by editing the LogOut.cshtml file and change this:
    @inject SignInManager<IdentityUser> SignInManager
    to this:
    @inject SignInManager<ApplicationIdentityUser> SignInManager

  • Gravatar Image

    Fantastic article - using ASP.Net Core 7 this worked 99%.

    Upon creating the migration I also got the error, "[TUser,TContext,TKey,TUserClaim,TUserLogin,TUserToken]' violates the constraint of type 'TUser'" as reported by previous readers.

    The solution was simply in Program.cs (I believe formerly called Startup.cs) to change this:

    .AddRoles<IdentityRole>()

    ...to this:

    .AddRoles<IdentityRole<int>>()

  • Gravatar Image

    Scrap that last advice, the line in Program.cs should be:
    .AddRoles<ApplicationRole>()
    ...otherwise the login logic fails when it tries to map roles to the user following registration.

Add a Comment