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

Posted

技术标签:

【中文标题】如何更改 Microsoft.AspNet.Identity.EntityFramework.IdentityUser 中的 id 类型【英文标题】:How to change type of id in Microsoft.AspNet.Identity.EntityFramework.IdentityUser 【发布时间】:2013-11-02 10:07:50 【问题描述】:

(ASP.NET MVC 5、EF6、VS2013)

我正在尝试弄清楚如何在类型中将“Id”字段的类型从字符串更改为 int

Microsoft.AspNet.Identity.EntityFramework.IdentityUser

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

(来自 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);
  ...
  

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

为什么我不想在用户表中存储 id 的 GUID:

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

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

/users/123/name

比干净

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

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

谢谢...

-本

【问题讨论】:

我用一些示例代码更新了我的答案,说明如何在最新的每晚 1.1-alpha1 中更改类型 此问题的未来读者请注意:ASP.NET Identity 版本 2.0.0(2014 年 3 月 20 日发布)现在包含更改/扩展 ID/主键类型的内置功能。见blogs.msdn.com/b/webdev/archive/2014/03/20/… 对于未来的读者,有一个 User.ID 为整数的示例解决方案:aspnet.codeplex.com/SourceControl/latest#Samples/Identity/… 关于该主题有一篇很好解释的官方 ASP.NET 文章:Change Primary Key for Users in ASP.NET Identity 安全实施团队不应该对您的用户界面问题感兴趣! 【参考方案1】:

使用 Stefan Cebulak 的回答和 Ben Foster 的精彩博客文章 ASP.NET Identity Stripped Bare 我想出了以下解决方案,我已将其应用于 ASP.NET Identity 2.0,并由Visual Studio 2013 AccountController.

该解决方案使用整数作为用户的主键,并且还允许在不访问数据库的情况下获取当前登录用户的 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 以使用您的自定义类

将所有UserManager 更改为AppUserManager,将UserStore 更改为AppUserStore 等。以这个构造函数为例:

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



public AccountController(AppUserManager userManager)

    UserManager = userManager;

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

在第 1 步中,我们创建了 AppClaimsPrincipal,它公开了从 ClaimType.Sid 中取出的 UserId。但是,要使此声明可用,我们需要在用户登录时添加它。在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。使用 CurrentUser 属性创建 BaseController

要在您的控制器中轻松访问当前登录的用户 ID,请创建一个抽象 BaseController,您的控制器将从中派生。在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 将在 AppClaimsPrincipal 中返回 null。您需要强制注销所有用户或在AppClaimsPrincipal 中处理这种情况

【讨论】:

一切正常,除了这个 FindFirst(ClaimTypes.Sid) 总是给我 null,所以我在 AppClaimsPrincipal.UserId 遇到了一个异常。看起来登录后用户没有收到新的声明(我添加了您的代码) 您确定第 5 步中的所有代码都在执行吗?另外请删除您登录时使用的任何跟踪(例如cookie),并查看登录后是否正确设置。 是的,我确定。用户使用具有所有必要声明的身份登录。当我在控制器中查找用户时,它只有 1 个声明(在 GetAuthenticationManager().SignIn(new AuthenticationProperties() IsPersistent = isPersistent , identity); 行,它有 4 个声明。 好的,当用户登录时,您似乎无法在控制器中获取用户 ID。重定向后一切正常! @krzychu 我还查看了asp.net/identity/overview/extensibility/… 上的帖子,但不确定我是否必须向 IdentityModels 添加自定义类,如该页面上的步骤“添加使用密钥类型的自定义身份类”中所述.因为我现在正在创建项目,目前还没有创建表。我的意思是我可以修改当前类而不是添加自定义类吗?【参考方案2】:

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

这是我们没有时间支持的东西,但我现在正在考虑在 1.1 中让这个更容易(ier)。希望很快会在夜间构建中提供一些东西。

更新为 1.1-alpha1 示例: How to get nightly builts

如果您更新到最新的 nightly 位,您可以尝试新的 1.1-alpha1 api,它现在应该让这更容易:下面是插入 Guids 而不是字符串的示例

    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 而不是整数吗? 所以我们决定使用字符串键以避免处理键序列化问题,EF 默认实现可以使用整数作为主键,GUID 只是生成随机唯一字符串键的简单方法. @Hao - 为什么实现在数据库中使用“字符串”字段而不是唯一标识符?有什么方法可以在数据库中使用 sql 唯一标识符? @Hao,谢谢你的例子,但你能举一个例子,你使用 int32 作为原始问题的类型吗?看起来这会有点复杂,因为在 GUID 示例中,您只需要一个新的 GUID,但我想这个过程对于 int 会有所不同。谢谢... 另外,我们在哪里可以找到每晚的片段?【参考方案3】:

@HaoKung

我已经成功地为你的夜间构建创建了 int id。 User.Identity.GetUserId() 问题仍然存在,但我现在只是做了 int.parse()。

最大的惊喜是我不需要自己创建 ID,db 是用身份 ID 制作的,并且它以某种方式自动为新用户设置 Oo...

型号:

    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...

附:不会写cmets所以我做了一个回答,对不起。

【讨论】:

Stefan,你为ChallengeResult()AuthenticationManager.GetExternalLoginInfoAsync() 做了什么。这两种方法都位于AccountController() 中,并且都期望UserId 作为参数但作为string。所以你不能把它转换成int。您是否也将 User.Identity.GetUserId() 解析为 int ? 不,老实说我什至没有注意到这一点,但 Google 和 Facebook 的身份验证工作正常。我想我不会改变它,而是等待发布版本。这似乎与 XSRF 保护有关。 等待发布构建是什么意思? asp.net-identity的最新版本昨天刚刚发布? 这是一个不错的惊喜 :],已经完成更新。默认情况下,字符串 id 在 User.Identity.GetUserId() 中仍然存在,我想知道我是否需要创建自己的 IdentityExtensions 来更改它。我可能会等待示例,现在没有时间处理它。 @William,看看我的回答。如果您遇到任何问题,请在 cmets 中告诉我。【参考方案4】:

如here所述:

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

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

【讨论】:

此文应正确引用。 @Necreaux “正确引用”是什么意思?请说清楚。 @t.durden 第一段是从链接网站复制/粘贴,没有署名和技术剽窃。由于自此评论以来没有人做任何事情,我继续尝试修复它。【参考方案5】:

基本上你必须:

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

here 是解释所有步骤以实现此目的的链接。

【讨论】:

以上是关于如何更改 Microsoft.AspNet.Identity.EntityFramework.IdentityUser 中的 id 类型的主要内容,如果未能解决你的问题,请参考以下文章

更改模式时如何更改导航阴影颜色?

如何更改分区ID为AF?

更改标签文本时如何更改标签颜色

如何更改环境的字体大小?

如何使用 sqitch postgresql 验证更改列数据类型更改?

如何更改列表框项目的文本?