I have an ASP.NET Identity 2 implementation (no user data yet just base tables) that I have with a userId of type UNIQUEIDENTIFIER.
The application is a code first and I am using EF6.
Here's the DDL:
CREATE TABLE [dbo].[AspNetUsers] (
[Id] UNIQUEIDENTIFIER NOT NULL,
[FirstName] NVARCHAR (MAX) NULL,
[LastName] NVARCHAR (MAX) NULL,
[Email] NVARCHAR (256) NULL,
[EmailConfirmed] BIT NOT NULL,
[PasswordHash] NVARCHAR (MAX) NULL,
[SecurityStamp] NVARCHAR (MAX) NULL,
[PhoneNumber] NVARCHAR (MAX) NULL,
[PhoneNumberConfirmed] BIT NOT NULL,
[TwoFactorEnabled] BIT NOT NULL,
[LockoutEndDateUtc] DATETIME NULL,
[LockoutEnabled] BIT NOT NULL,
[AccessFailedCount] INT NOT NULL,
[UserName] NVARCHAR (256) NOT NULL,
[SubjectId] INT DEFAULT ((0)) NOT NULL,
[SubjectIds] VARCHAR (50) NULL,
[OrganizationId] INT DEFAULT ((0)) NOT NULL,
[OrganizationIds] VARCHAR (50) NULL,
[RoleId] INT DEFAULT ((0)) NOT NULL,
CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED ([Id] ASC)
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex]
ON [dbo].[AspNetUsers]([UserName] ASC);
I understand that normal the GUID create is a normal GUID.
Can someone tell me how I can make this create a newSequential GUID?
Please note
I am looking for the correct way to do this specifically with ASP.Net Identity 2. In particular I would like to know if any changes are needed to the Identity 2 UserManager etc.
I was finally able to build the project and run it. A newsequentialid() is assigned to the ID field after creation using Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ApplicationUser>().Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<CustomUserRole>().HasKey(x => new
{
x.RoleId,
x.UserId
});
modelBuilder.Entity<CustomUserLogin>().HasKey(x => new
{
x.UserId,
x.ProviderKey,
x.LoginProvider
});
}
The result was SQL table that scripted as:
/****** Object: Table [dbo].[AspNetUsers] Script Date: 4/11/2015 3:40:51 PM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[AspNetUsers](
[Id] [uniqueidentifier] NOT NULL,
[Email] [nvarchar](256) NULL,
[EmailConfirmed] [bit] NOT NULL,
[PasswordHash] [nvarchar](max) NULL,
[SecurityStamp] [nvarchar](max) NULL,
[PhoneNumber] [nvarchar](max) NULL,
[PhoneNumberConfirmed] [bit] NOT NULL,
[TwoFactorEnabled] [bit] NOT NULL,
[LockoutEndDateUtc] [datetime] NULL,
[LockoutEnabled] [bit] NOT NULL,
[AccessFailedCount] [int] NOT NULL,
[UserName] [nvarchar](256) NOT NULL,
CONSTRAINT [PK_dbo.AspNetUsers] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE [dbo].[AspNetUsers] ADD DEFAULT (newsequentialid()) FOR [Id]
GO
Had to change the other Entity Types:
public class ApplicationUser : IdentityUser<Guid, CustomUserLogin, CustomUserRole,
CustomUserClaim>
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public override Guid Id { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser, Guid> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
return userIdentity;
}
}
public class CustomUserRole : IdentityUserRole<Guid> { }
public class CustomUserClaim : IdentityUserClaim<Guid> { }
public class CustomUserLogin : IdentityUserLogin<Guid> { }
public class CustomRole : IdentityRole<Guid, CustomUserRole>
{
public CustomRole() { }
public CustomRole(string name) { Name = name; }
}
public class CustomUserStore : UserStore<ApplicationUser, CustomRole, Guid,
CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public CustomUserStore(ApplicationDbContext context)
: base(context)
{
}
}
public class CustomRoleStore : RoleStore<CustomRole, Guid, CustomUserRole>
{
public CustomRoleStore(ApplicationDbContext context)
: base(context)
{
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, CustomRole,
Guid, CustomUserLogin, CustomUserRole, CustomUserClaim>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
In the Startup.Auth.cs, I changed
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
LoginPath = new PathString("/Account/Login"),
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator
.OnValidateIdentity<ApplicationUserManager, ApplicationUser, Guid>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) =>
user.GenerateUserIdentityAsync(manager),
getUserIdCallback: (id) => new Guid(id.GetUserId()))
}
});
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
In the IdentityConfig.cs, I changed altered the ApplicationUserManager
Here:
public class ApplicationUserManager : UserManager<ApplicationUser, Guid>
{
public ApplicationUserManager(IUserStore<ApplicationUser, Guid> store)
: base(store)
{
}
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
var manager = new ApplicationUserManager(
new CustomUserStore(context.Get<ApplicationDbContext>()));
// Configure validation logic for usernames manager.UserValidator = new UserValidator<ApplicationUser>(manager)
manager.UserValidator = new UserValidator<ApplicationUser, Guid>(manager)
{
AllowOnlyAlphanumericUserNames = false,
RequireUniqueEmail = true
};
And
manager.RegisterTwoFactorProvider("Phone Code", new PhoneNumberTokenProvider<ApplicationUser, Guid>
{
MessageFormat = "Your security code is {0}"
});
manager.RegisterTwoFactorProvider("Email Code", new EmailTokenProvider<ApplicationUser, Guid>
{
Subject = "Security Code",
BodyFormat = "Your security code is {0}"
});
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();
var dataProtectionProvider = options.DataProtectionProvider;
if (dataProtectionProvider != null)
{
manager.UserTokenProvider =
new DataProtectorTokenProvider<ApplicationUser, Guid>(dataProtectionProvider.Create("ASP.NET Identity"));
}
return manager;
}
}
// Configure the application sign-in manager which is used in this application.
public class ApplicationSignInManager : SignInManager<ApplicationUser, Guid>
In ManageController.cs, I added
public class ManageController : Controller
{
private ApplicationSignInManager _signInManager;
private ApplicationUserManager _userManager;
private Guid userGuidId;
public ManageController()
{
userGuidId= new Guid(User.Identity.GetUserId());
}
Replacing userGuidId instead everywhere that I saw userId
I had to use a ToString() here:
BrowserRemembered = await AuthenticationManager.TwoFactorBrowserRememberedAsync(userGuidId.ToString())
In Account Controller, I seem to have only changed
[AllowAnonymous]
public async Task<ActionResult> ConfirmEmail(string userId, string code)
{
Guid GuidUserId = new Guid(userId);
if (userId == null || code == null)
{
return View("Error");
}
var result = await UserManager.ConfirmEmailAsync(GuidUserId, code);
return View(result.Succeeded ? "ConfirmEmail" : "Error");
}
First create non-generic version of the "IdentityUser" based classes...
public class AppUserClaim : IdentityUserClaim<Guid> { }
public class AppUserLogin : IdentityUserLogin<Guid> { }
public class AppUserRole : IdentityUserRole<Guid> { }
...then the same for IdentityRole and UserStore and `UserManager...
public class AppRole : IdentityRole<Guid, AppUserRole>
{
}
public class AppUserStore : UserStore<AppUser, AppRole, Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppUserStore(DbContext context)
: base(context)
{
}
}
public class AppUserManager : UserManager<AppUser, Guid>
{
public AppUserManager(IUserStore<AppUser, Guid> store)
: base(store)
{
}
}
... and finally the IdentityDbContext...
public class AppIdentityContext : IdentityDbContext<AppUser, AppRole, Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
public AppIdentityContext()
: base("name=AspNetIdentity")
{
}
}
Throughout all these new classes you will notice that the base classes use the generic version of the Identity classes and we are using the AppUserClaim, AppUserLogin, AppUserRole and AppRole in place of the Identity counterparts.
For the user we create a class named AppUser that will derive from IdentityUser:
public class AppUser : IdentityUser<Guid, AppUserLogin, AppUserRole, AppUserClaim>
{
[DllImport("rpcrt4.dll", SetLastError = true)]
private static extern int UuidCreateSequential(out Guid guid);
private Guid _id;
public AppUser()
{
UuidCreateSequential(out _id);
}
/// <summary>
/// User ID (Primary Key)
/// </summary>
public override Guid Id
{
get { return _id; }
set { _id = value; }
}
}
In the constructor we use the UuidCreateSequential function to create a new ID and return that through the Id property. I wanted to setup the Id column in the database to use newsequentialid() as a default value and use that instead of a DllImport, but I've not worked that out yet.
To use in an controller action:
public async Task<ActionResult> ActionName()
{
AppIdentityContext dbContext = new AppIdentityContext();
AppUserStore store = new AppUserStore(dbContext);
AppUserManager manager = new AppUserManager(store);
AppUser user = new AppUser { UserName = "<name>", Email = "<email>" };
await manager.CreateAsync(user);
return this.View();
}
A few things to note:
If you are using an existing database, i.e. one created with an SQL script and where the Id column in AspNetUsers is nvarchar, then you will need to change the following columns to a uniqueidentifier:
Using the GetUserId extension method on the IIdentity interface within you ASP.NET MVC controllers, i.e. this.User.Identity.GetUserId(), will return a string so you will have to use the following when converting the return value to a string:
new Guid(this.User.Identity.GetUserId())
There is a generic version of this method, but underneath it uses Convert.ChangeType and that requires the value being passed in implements IConvertable and Guid does not.
I have not been able to fully test this, but hopefully it will provide a useful base if it doesn't fully meet your needs.
UPDATE #1: These are the steps I went through:
Add the following NuGet packages
Add all the code samples to a file named Identity.cs in the App_Start folder
NOTE: Exclude the controller action sample..this will be done in step #6
Remove all the Entity Framework parts from the web.config
web.config named AspNetIdentity Index action on the HomeController and replace the <name> and <email> partsAspNetIdentity to your SQL ServerIf you use the ASP.NET MVC template that has the Individual User Accounts authentication option selected, then there will be a few errors that will have to be fixed. These are mostly centred around changing references to the IdentityUser* classes to the new AppUser* based classes and replace calls to User.Identity.GetUserId() to use the code sample provided in step #2 in my original answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With