如何在Microsoft.AspNet.Identity.EntityFramework.IdentityUser中更改ID的类型


67

(ASP.NET MVC 5,EF6,VS2013)

我试图弄清楚如何将“ Id”字段的类型从字符串更改为int类型:

Microsoft.AspNet.Identity.EntityFramework.IdentityUser

为了使新用户帐户与整数ID而不是GUID相关联。但是,这似乎比在派生的用户类中简单地添加一个类型为int的新Id属性要复杂得多。看一下这个方法签名:

(来自程序集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);
  ...
  }

因此,似乎还有其他方法已植入ASP.NET身份框架中,这些方法要求userId为字符串。我还需要重新实现这些类吗?

关于为什么我不想在用户表中存储ID的GUID的解释:

-将有其他表通过外键将数据与用户表相关联。(当用户将内容保存在网站上时。)我没有理由使用较大的字段类型并花费额外的数据库空间,而没有明显的优势。(我知道还有其他关于使用GUID和int id的文章,但是似乎很多人建议int id更快,使用更少的空间,这仍然让我感到疑惑。)

-我计划公开一个宁静的端点,以允许用户检索有关特定用户的数据。我认为:

/users/123/name

比...干净

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

有谁知道ASP.NET团队为何决定以这种方式实现ID?在尝试将其更改为int类型时,我是不是目光短浅?(也许我缺少一些好处。)

谢谢...

-本


我用一些示例代码更新了我的答案,该示例代码说明了如何在最新的每晚1.1-alpha1中更改类型
郝公

6
此问题的未来读者请注意:ASP.NET身份版本2.0.0(于2014年3月20日发布)现在内置了更改/扩展ID /主键类型的功能。见blogs.msdn.com/b/webdev/archive/2014/03/20/...
粉化工业

2
并为未来的阅读器,有一个与User.ID的试样溶液为整数:aspnet.codeplex.com/SourceControl/latest#Samples/Identity/...
trailmax

关于该主题,有一篇很好解释的ASP.NET官方文章:在ASP.NET Identity中更改用户的主键
Deilan 2015年

您的用户界面问题对安全实施团队来说应该没有任何意义!
Pascal

Answers:


31

因此,如果需要int id,则需要创建自己的POCO IUser类,并为1.0 RTM版本中的自定义IUser类实现IUserStore。

这是我们没有时间支持的事情,但是我现在正在考虑在1.1中使它变得容易。希望不久后晚上的构建中会提供一些东西。

更新了1.1-alpha1示例: 如何获得夜间构建

如果您更新到最新的夜间版本,则可以尝试使用新的1.1-alpha1 api,它现在应该可以使此操作变得更加容易:例如,这是插入Guid而不是字符串的示例

    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);
        }
    }

谢谢您的澄清,郝。您能解释为什么使用GUID而不是整数吗?
BenjiFB

1
因此,我们决定使用字符串键来避免必须处理键序列化问题,EF默认实现可以使用int作为主键,GUID只是生成随机唯一字符串键的简单方法。
郝宫

3
@Hao-为什么在数据库中使用“字符串”字段而不是uniqueidentifier实现?有什么方法可以在数据库中使用sql uniqueidentifier?
Piotr Stulinski 2013年

@Hao,谢谢您的示例,但是您可以举一个示例,将int32用作原始问题的类型吗?似乎这要复杂一点,因为在GUID示例中,您只是要求一个新的GUID,但我认为对于int而言,该过程将有所不同。谢谢...
BenjiFB

另外,我们在哪里可以找到每晚的东西?
BenjiFB 2013年

51

通过使用Stefan Cebulak的答案和Ben Foster的精彩博客文章ASP.NET Identity Striped Bare,我提出了以下解决方案,该解决方案已应用于Visual Studio 2013生成的ASP.NET Identity 2.0AccountController

该解决方案使用整数作为用户的主键,还允许在不访问数据库的情况下获取当前登录用户的ID。

这是步骤,您需要遵循:

1.创建与用户相关的自定义类

默认情况下,AccountController使用类(使用string)作为主键的类型。我们需要创建以下类,而将使用int代替。我在一个文件中定义了以下所有类: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> { }

拥有自定义的ClaimsPrincipal也会很有用,该声明可以轻松公开用户的ID

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

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

2.创建一个自定义 IdentityDbContext

我们应用程序的数据库上下文将扩展IdentityDbContext,默认情况下实现所有与身份验证相关的DbSet。即使DbContext.OnModelCreating是空方法,我也不确定IdentityDbContext.OnModelCreating,所以在覆盖时,请记住调用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.创建自定义UserStoreUserManager,将在上面使用

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.修改AccountController以使用您的自定义类

将所有更改UserManagerAppUserManager,更改UserStoreAppUserStoreetc。以以下构造函数为例:

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

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

5.添加用户ID作为声明ClaimIdentity存储在cookie中

在步骤1中,我们创建了AppClaimsPrincipal,它公开了从中取出的UserId ClaimType.Sid。但是,要使此声明可用,我们需要在登录用户时添加它。在AccountController一个SingInAsync方法负责登录。我们需要添加一行这种方法,增加了这一说法。

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.创建一个BaseController带有CurrentUser属性的

要轻松访问控制器中当前登录的用户ID,请创建一个abstract BaseController,您的控制器将从中提取该ID 。在中BaseController,创建CurrentUser如下:

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


    public BaseController()
    {

    }
}

7.继承您的控制器BaseController并享受

从现在开始,您可以CurrentUser.UserId在控制器中使用来访问当前登录用户的ID,而无需访问数据库。您可以使用它来仅查询属于用户的对象。

您不必照顾用户主键的自动生成-毫不奇怪,在创建表时,默认情况下,Entity Framework使用Identity作为整数主键。

警告!请记住,如果在已发布的项目中实现它,则对于已经登录的用户ClaimsType.Sid将不存在,并且在FindFirst中将返回null AppClaimsPrincipal。您需要强制注销所有用户或处理以下情况:AppClaimsPrincipal


一切正常,但FindFirst(ClaimTypes.Sid)总会给我null,因此AppClaimsPrincipal.UserId出现异常。看起来登录用户之后没有收到新的声明(我添加了您的代码)
Piotr M 2016年

您确定步骤5中的所有代码都正在执行吗?另外,请删除您登录使用的任何记录(例如cookie),并查看登录后是否设置正确。
krzychu '16

是的,我敢肯定。使用具有所有必要声明的身份登录用户。当我查找用于用户在控制器,它只有1如权利要求(在GetAuthenticationManager().SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);线,它有4项权利要求中。
彼得中号

1
好的,当用户登录时,似乎无法在控制器中获取用户ID。重定向后,一切正常!
Piotr M

@krzychu我还查看了asp.net/identity/overview/extensibility/…上的帖子,但是不确定是否必须按照该页面上的“添加使用键类型的自定义身份类”步骤所述将自定义类添加到IdentityModels中。 。因为我现在正在创建项目,所以目前没有创建表。我的意思是我可以修改当前类而不是添加自定义类吗?
杰克

6

H

我已经成功地通过您的每晚版本构建了int id。User.Identity.GetUserId()问题仍然存在,但我现在只是做int.parse()。

最大的意外是,我不需要自己创建ID,db是用标识id构成的,它以某种方式自动为新用户设置了。

模型:

    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)
    {
    }
}

控制器:

        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; }

希望即将发布版本:D ...

PS不能写评论,所以我回答了,对不起。


Stefan,您为ChallengeResult()和做了什么AuthenticationManager.GetExternalLoginInfoAsync()。两种方法都位于中,AccountController()并且都期望使用UserIdas参数,但是都使用as string。因此您无法将其转换为int。您是否也解析User.Identity.GetUserId()为int?
Quoter

不,老实说,我什至没有注意到,但是Google和Facebook的身份验证工作正常。我想我不会更改它,而是等待发布。这似乎与XSRF保护有关。
Stefan Cebulak 2014年

等待发布版本意味着什么?最新版本是asp.net-identity昨天发布的吗?
Quoter

1
这是一个不错的惊喜:],已经完成了更新。默认情况下,User.Identity.GetUserId()中仍然存在字符串ID,我想知道是否需要创建自己的IdentityExtensions来更改此设置。我可能会等待示例,现在没有时间进行处理。
Stefan Cebulak 2014年

1
@威廉,看看我的答案。如果您遇到任何问题,请在评论中告诉我。
2014年

4

如前所述这里

在Visual Studio 2013中,默认Web应用程序使用字符串值作为用户帐户的密钥。ASP.NET Identity使您可以更改密钥的类型以满足您的数据要求。例如,您可以将键的类型从字符串更改为整数。

上面链接上的本主题显示了如何从默认Web应用程序开始,以及如何将用户帐户密钥更改为整数。您可以使用相同的修改在项目中实现任何类型的密钥。它显示了如何在默认Web应用程序中进行这些更改,但是您可以对自定义应用程序进行类似的修改。它显示了使用MVC或Web窗体时所需的更改。


该文本应正确引用。
Necreaux

@Necreaux你是什么意思“正确引用”?请清楚。
t.durden

@ t.durden,第一段是从链接的网站复制/粘贴的内容,没有署名和技术上的窃。由于自此评论以来没有人做任何事情,因此我继续尝试进行修复。
Necreaux

3

基本上,您必须:

-在Identity用户类
中将键的类型
更改为int-添加使用int作为键的自定义Identity类-将上下文类和用户管理器更改为将int用作键-
将启动配置更改为将int用作键
-Change AccountController传递int作为键

是说明所有步骤以实现此目的的链接。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.