69

(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 更快并且使用更少的空间,这仍然让我想知道。)

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

/users/123/name

比干净

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

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

谢谢...

-本

4

5 回答 5

52

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

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

以下是您需要遵循的步骤:

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

默认情况下,AccountController使用的类string作为主键类型。我们需要创建下面的类,它将使用 anint来代替。我在一个文件中定义了以下所有类: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. 创建自定义UserStoreand UserManager,将在上面使用

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,等。以这个构造函数为例:UserStoreAppUserStore

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

BaseController6.用CurrentUser属性创建一个

要在您的控制器中轻松访问当前登录的用户 ID,请创建一个 abstract 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. 您需要强制注销所有用户或在AppClaimsPrincipal

于 2014-05-09T20:03:23.493 回答
31

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

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

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

如果您更新到最新的夜间位,您可以尝试新的 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);
        }
    }
于 2013-10-24T21:34:33.977 回答
6

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

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

于 2014-01-02T02:30:20.267 回答
4

如此处所述:

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

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

于 2015-02-13T05:58:10.147 回答
3

基本上你必须:

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

是链接,其中解释了实现此目的的所有步骤。

于 2014-07-28T16:28:14.443 回答