How to change type of id in Microsoft.AspNet.Identity.EntityFramework.IdentityUser

asp.net MvcEntity Frameworkasp.net Mvc-5Owinasp.net Identity

asp.net Mvc Problem Overview


(ASP.NET MVC 5, EF6, VS2013)

I'm trying to figure out how to change the type of the "Id" field from string to int in the type:

Microsoft.AspNet.Identity.EntityFramework.IdentityUser

in order to have new user accounts be associated with an integer ID rather than a GUID. But it seems like this will be more complicated than simply adding a new Id property with type int in my derived user class. Take a look at this method signature:

(from Assembly Microsoft.AspNet.Identity.Core.dll)

public class UserManager<TUser> : IDisposable where TUser : global::Microsoft.AspNet.Identity.IUser
  {
  ...
  public virtual Task<IdentityResult> AddLoginAsync(string userId, UserLoginInfo login);
  ...
  }

So it seems that there are other methods baked into the ASP.NET identity framework which require the userId to be a string. Would I need to reimplement these classes as well?

An explanation of why I don't want to store GUIDs for ids in the user table:

-There will be other tables that relate data to the users table via a foreign key. (When users save content on the site.) I see no reason to use the larger field type and spend extra database space with no clear advantages. (I know that there are other posts about using GUIDs vs int ids, but it seems like many suggest that int ids are faster and use less space, which still leaves me wondering.)

-I plan to expose a restful endpoint to allow users to retrieve data about a particular user. I think:

/users/123/name

is cleaner than

/users/{af54c891-69ba-4ddf-8cb6-00d368e58d77}/name

Does anyone know why the ASP.NET team decided to implement IDs this way? Am I being short sighted in trying to change this to an int type? (Perhaps there are benefits I'm missing.)

Thanks...

-Ben

asp.net Mvc Solutions


Solution 1 - asp.net Mvc

Using a Stefan Cebulak's answer and a Ben Foster's great blog article ASP.NET Identity Stripped Bare I have came up with below solution, which I have applied to ASP.NET Identity 2.0 with a generated by Visual Studio 2013 AccountController.

The solution uses an integer as a primary key for users and also allows to get an ID of currently logged in user without making a trip to the database.

Here are the steps, you need to follow:

By default, the AccountController uses classes, which are using string, as a type of a primary key. We need to create below classes, which will use an int instead. I have defined all below classes in one file: AppUser.cs

public class AppUser :
    IdentityUser<int, AppUserLogin, AppUserRole, AppUserClaim>,
    IUser<int>
{

}

public class AppUserLogin : IdentityUserLogin<int> { }

public class AppUserRole : IdentityUserRole<int> { }

public class AppUserClaim : IdentityUserClaim<int> { }

public class AppRole : IdentityRole<int, AppUserRole> { }

It will also be useful, to have a custom ClaimsPrincipal, which will easily expose User's ID

public class AppClaimsPrincipal : ClaimsPrincipal
{
    public AppClaimsPrincipal( ClaimsPrincipal principal ) : base( principal )
    { }

    public int UserId
    {
        get { return int.Parse(this.FindFirst( ClaimTypes.Sid ).Value); }
    }
}

2. Create a custom IdentityDbContext

Our application's database context will extend IdentityDbContext, which implements by default all authentication-related DbSets. Even if DbContext.OnModelCreating is an empty method, I am not sure about the IdentityDbContext.OnModelCreating, so when overriding, remember to call base.OnModelCreating( modelBuilder ) AppDbContext.cs

public class AppDbContext :
    IdentityDbContext<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>
{
    public AppDbContext() : base("DefaultConnection")
    {
        // Here use initializer of your choice
        Database.SetInitializer( new CreateDatabaseIfNotExists<AppDbContext>() );
    }


    // Here you define your own DbSet's



    protected override void OnModelCreating( DbModelBuilder modelBuilder )
    {
        base.OnModelCreating( modelBuilder );

        // Here you can put FluentAPI code or add configuration map's
    }
}

3. Create custom UserStore and UserManager, which will use above

AppUserStore.cs

public interface IAppUserStore : IUserStore<AppUser, int>
{

}

public class AppUserStore :
    UserStore<AppUser, AppRole, int, AppUserLogin, AppUserRole, AppUserClaim>,
    IAppUserStore
{
    public AppUserStore() : base( new AppDbContext() )
    {

    }

    public AppUserStore(AppDbContext context) : base(context)
    {

    }
}

AppUserManager.cs

public class AppUserManager : UserManager<AppUser, int>
{
    public AppUserManager( IAppUserStore store ) : base( store )
    {

    }
}

4. Modify AccountController to use your custom classes

Change all UserManager to AppUserManager, UserStore to AppUserStore etc. Take an example of this constructors:

public AccountController()
    : this( new AppUserManager( new AppUserStore( new AppDbContext() ) ) )
{
}

public AccountController(AppUserManager userManager)
{
    UserManager = userManager;
}

In step 1, we have created AppClaimsPrincipal, which exposes UserId taken out of ClaimType.Sid. However, to have this claim available, we need to add it, when logging in the user. In AccountController a SingInAsync method is responsible for logging in. We need to add a line to this method, to add the claim.

private async Task SignInAsync(AppUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
    ClaimsIdentity identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);

    // Extend identity claims
    identity.AddClaim( new Claim( ClaimTypes.Sid, user.Id.ToString() ) );

    AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

6. Create a BaseController with a CurrentUser property

To have an easy access to a currently logged in user's ID in your controllers, create an abstract BaseController, from which your controllers will derive. In the BaseController, create a CurrentUser as follows:

public abstract class BaseController : Controller
{
    public AppClaimsPrincipal CurrentUser
    {
        get { return new AppClaimsPrincipal( ( ClaimsPrincipal )this.User ); }
    }


    public BaseController()
    {
        
    }
}

7. Inherit your controllers from BaseController and enjoy

From now on, you can use CurrentUser.UserId in your controllers to access an ID of a currently logged in user without trips to the database. You can use it, to query only objects, which belong to the user.

You don't have to take care of auto generation of user primary keys - no surprise, Entity Framework by default uses Identity for integer primary keys, when creating tables.

Warning! Keep in mind, that if you implement it in already released project, for already logged in users ClaimsType.Sid will not exist and FindFirst will return null in AppClaimsPrincipal. You need to either force logout all users or handle this scenario in AppClaimsPrincipal

Solution 2 - asp.net Mvc

So if you want int ids, you need to create your own POCO IUser class and implement your IUserStore for your custom IUser class in the 1.0 RTM release.

This is something we didn't have time to support, but I'm looking into making this easy(ier) in 1.1 right now. Hopefully something will be available in the nightly builds soon.

Updated with 1.1-alpha1 example: How to get nightly builts

If you update to the latest nightly bits, you can try out the new 1.1-alpha1 apis which should make this easier now: Here's what plugging in Guids instead of strings should look like for example

    public class GuidRole : IdentityRole<Guid, GuidUserRole> { 
        public GuidRole() {
            Id = Guid.NewGuid();
        }
        public GuidRole(string name) : this() { Name = name; }
    }
    public class GuidUserRole : IdentityUserRole<Guid> { }
    public class GuidUserClaim : IdentityUserClaim<Guid> { }
    public class GuidUserLogin : IdentityUserLogin<Guid> { }

    public class GuidUser : IdentityUser<Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> {
        public GuidUser() {
            Id = Guid.NewGuid();
        }
        public GuidUser(string name) : this() { UserName = name; }
    }

    private class GuidUserContext : IdentityDbContext<GuidUser, GuidRole, Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> { }
    private class GuidUserStore : UserStore<GuidUser, GuidRole, Guid, GuidUserLogin, GuidUserRole, GuidUserClaim> {
        public GuidUserStore(DbContext context)
            : base(context) {
        }
    }
    private class GuidRoleStore : RoleStore<GuidRole, Guid, GuidUserRole> {
        public GuidRoleStore(DbContext context)
            : base(context) {
        }
    }

    [TestMethod]
    public async Task CustomUserGuidKeyTest() {
        var manager = new UserManager<GuidUser, Guid>(new GuidUserStore(new GuidUserContext()));
        GuidUser[] users = {
            new GuidUser() { UserName = "test" },
            new GuidUser() { UserName = "test1" }, 
            new GuidUser() { UserName = "test2" },
            new GuidUser() { UserName = "test3" }
            };
        foreach (var user in users) {
            UnitTestHelper.IsSuccess(await manager.CreateAsync(user));
        }
        foreach (var user in users) {
            var u = await manager.FindByIdAsync(user.Id);
            Assert.IsNotNull(u);
            Assert.AreEqual(u.UserName, user.UserName);
        }
    }

Solution 3 - asp.net Mvc

@HaoKung

I've succeeded to make int id's with your nightly builds. User.Identity.GetUserId() problem is still there, but i just did int.parse() for now.

The biggest suprise was that i did not need to create ID by myself, db was made with identity id and it was somehow automatically set for new users Oo...

Model:

    public class ApplicationUser : IdentityUser<int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public ApplicationUser()
    {
    }
    public ApplicationUser(string name) : this() { UserName = name; }
}

public class ApplicationDbContext : IntUserContext
{
    public ApplicationDbContext()
    {

    }
}

private class IntRole : IdentityRole<int, IntUserRole>
{
    public IntRole()
    {
    }
    public IntRole(string name) : this() { Name = name; }
}
private class IntUserRole : IdentityUserRole<int> { }
private class IntUserClaim : IdentityUserClaim<int> { }
private class IntUserLogin : IdentityUserLogin<int> { }
private class IntUserContext : IdentityDbContext<ApplicationUser, IntRole, int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public IntUserContext()
        : base("DefaultConnection")
    {

    }
}
private class IntUserStore : UserStore<ApplicationUser, IntRole, int, IntUserLogin, IntUserRole, IntUserClaim>
{
    public IntUserStore(DbContext context)
        : base(context)
    {

    }
}
private class IntRoleStore : RoleStore<IntRole, int, IntUserRole>
{
    public IntRoleStore(DbContext context)
        : base(context)
    {
    }
}

Controller:

        public AccountController()
        : this(new UserManager<ApplicationUser, int>(new IntUserStore(new ApplicationDbContext())))
    {
    }

    public AccountController(UserManager<ApplicationUser, int> userManager)
    {
        UserManager = userManager;
    }

    public UserManager<ApplicationUser, int> UserManager { get; private set; }

Hope release build will come soon :D...

P.S. Can't write comments so i did an answer, sorry.

Solution 4 - asp.net Mvc

As stated here:

> In Visual Studio 2013, the default web application uses a string value > for the key for user accounts. ASP.NET Identity enables you to change > the type of the key to meet your data requirements. For example, you > can change the type of the key from a string to an integer.

This topic on the above link shows how to start with the default web application and change the user account key to an integer. You can use the same modifications to implement any type of key in your project. It shows how to make these changes in the default web application, but you could apply similar modifications to a customized application. It shows the changes needed when working with MVC or Web Forms.

Solution 5 - asp.net Mvc

Basically you have to :

-Change the type of the key to int in the Identity user class
-Add customized Identity classes that use int as key
-Change the context class and user manager to use int as key
-Change start-up configuration to use int as key
-Change the AccountController to pass int as key

[here](http://www.asp.net/identity/overview/extensibility/change-primary-key-for-users-in-aspnet-identity "Change Primary Key for Users in ASP.NET Identity") is link where all steps are explained to achieve this.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBenjiFBView Question on Stackoverflow
Solution 1 - asp.net MvckrzychuView Answer on Stackoverflow
Solution 2 - asp.net MvcHao KungView Answer on Stackoverflow
Solution 3 - asp.net MvcStefan CebulakView Answer on Stackoverflow
Solution 4 - asp.net MvcManik AroraView Answer on Stackoverflow
Solution 5 - asp.net MvclyzView Answer on Stackoverflow