ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings

Posted on July 13 2014 10:45 AM by jatten in ASP.NET MVC, ASP.Net, C#, CodeProject   ||   Comments (12)

Identity-240The ASP.NET Identity framework was released to manufacture on March 20 2014, bringing with it a slew of long-awaited enhancements, delivering a fully-formed authentication and authorization platform to the ASP.NET developer community.

In previous posts, we have taken a broad look at the structure of the new framework, and how it differs from the 1.0 release. We've also walked through implementing email account confirmation and two-factor authentication, as well as extending the basic User and Role models (which requires a bit more effort than you might think).

Image by Josh Cowper  | Some Rights Reserved

In this post we're going take a deeper look at extending the core set of models afforded by the Identity 2.0 framework, and re-implementing the basic Identity Samples project using integer keys for all of our models, instead of the default string keys which are the default.

Source Code on Github

In the course of this article, we will basically re-implement the Identity Samples project with integer keys. you can clone the completed source code from my Github repo. Also, if you find bugs and/or have suggestions, please do open an issue and/or shoot me a pull request!

Why Does ASP.NET Identity Use String Keys in the First Place?

A popular, and somewhat confounding question is "why did the Identity team choose string keys as the default for the Identity framework models? Many of us who grew up using databases tend towards easy, auto-incrementing integers as database primary keys, because it's easy, and at least in theory, there are some performance advantages with respect to table indexes and such.

The decision of the Identity Team to use strings as keys is best summarized in a Stack Overflow answer by Rick Anderson, writer for ASP.NET at Microsoft:

  1. The Identity runtime prefers strings for the user ID because we don’t want to be in the business of figuring out proper serialization of the user IDs (we use strings for claims as well for the same reason), e.g. all (or most) of the Identity interfaces refer to user ID as a string.
  2. People that customize the persistence layer, e.g. the entity types, can choose whatever type they want for keys, but then they own providing us with a string representation of the keys.
  3. By default we use the string representation of GUIDs for each new user, but that is just because it provides a very easy way for us to automatically generate unique IDs.

The decision is not without its detractors in the community. The default string key described above is essentially a string representation of a Guid. As this discussion on Reddit illustrates, there is contention about the performance aspects of this against a relational database backend.

The concerns noted in the Reddit discussion focus mainly on database index performance, and are unlikely to be an issue for a large number of smaller sites and web applications, and particularly for learning projects and students. However, as noted previously, for many of us, the auto-incrementing integer is the database primary key of choice (even in cases where it is not the BEST choice), and we want our web application to follow suit.

Identity 2.0 Core Classes use Generic Type Arguments

As we discussed in the post on customizing ASP.NET Identity 2.0 Users and Roles, the framework is built up from a structure of generic Interfaces and base classes. At the lowest level, we find interfaces, such as IUser<TKey> and IRole<TKey>. These, and related Interfaces and base classes are defined in the Microsoft.AspNet.Identity.Core library.

Moving up a level of abstraction, we can look at the Microsoft.AspNet.Identity.EntityFramework library, which uses the components defined in …Identity.Core to build the useful, ready-to-use classes commonly used in applications, and in particular by the Identity Samples project we have been using to explore Identity 2.0.

The Identity.EntityFramework library gives us some Generic base classes, as well as a default concrete implementation for each. For example, Identity.EntityFramework gives us the following generic base implementation for a class IdentityRole:

Generic Base for IdentityRole:
public class IdentityRole<TKey, TUserRole> : IRole<TKey>
where TUserRole : IdentityUserRole<TKey>
{
    public TKey Id { get; set; }
    public string Name { get; set; }
    public ICollection<TUserRole> Users { get; set; }
    public IdentityRole()
    {
        this.Users = new List<TUserRole>();
    }
}

 

As we can see, the above defines IdentityRole in terms of generic type arguments for the key and UserRole, and must implement the interface IRole<TKey>. Note that Identity defines both an IdentityRole class, as well as an IdentityUserRole class, both of which are required to make things work. More on this later.

The Identity team also provides what amounts to a default implementation of this class:

Default Implementation of IdentityRole with non-generic type arguments:
public class IdentityRole : IdentityRole<string, IdentityUserRole>
{
    public IdentityRole()
    {
        base.Id = Guid.NewGuid().ToString();
    }
  
  
    public IdentityRole(string roleName) : this()
    {
        base.Name = roleName;
    }
}

 

Notice how the default implementation class is defined in terms of a string key and a specific implementation of IdentityUserRole?

This means that we can only pass strings as keys, and in fact the IdentityRole model will be defined in our database with a string-type primary key. It also means that the specific, non-generic implementation of IdentityUserRole will be what is passed to the type argument into the base class.

If we steal a page from the previous post, and take a look at the default type definitions provided by Identity 2.0, we find the following (it's not exhaustive, but these are what we will be dealing with later):

Default Identity 2.0 Class Signatures with Default Type Arguments:
public class IdentityUserRole 
    : IdentityUserRole<string>
  
public class IdentityRole 
    : IdentityRole<string, IdentityUserRole>
  
public class IdentityUserClaim 
    : IdentityUserClaim<string>
  
public class IdentityUserLogin 
    : IdentityUserLogin<string>
  
public class IdentityUser 
    : IdentityUser<string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, IUser, IUser<string>
  
public class IdentityDbContext 
    : IdentityDbContext<IdentityUser, IdentityRole, string, 
        IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
  
public class UserStore<TUser> 
    : UserStore<TUser, IdentityRole, string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, 
        IUserStore<TUser>, IUserStore<TUser, string>, IDisposable
    where TUser : IdentityUser
  
public class RoleStore<TRole> 
    : RoleStore<TRole, string, IdentityUserRole>, IQueryableRoleStore<TRole>, 
        IQueryableRoleStore<TRole, string>, IRoleStore<TRole, string>, IDisposable
    where TRole : IdentityRole, new()

 

We can see that, starting with IdentityUserRole, the types are defined with string keys, and as importantly, progressively defined in terms of the others. This means that if we want to use integer keys instead of string keys for all of our models (and corresponding database tables), we need to basically implement our own version of the stack above.

Implementing Integer Keys Using Identity 2.0 and the Identity Samples Project

As in previous posts, we are going to use the Identity Samples project as our base for creating an Identity 2.0 MVC application. The Identity team has put together the Identity Samples project primarily (I assume) as a demonstration platform, but in fact it contains everything one might need (after a few tweaks, anyway) in order to build out a complete ASP.NET MVC project using the Identity 2.0 framework.

The concepts we are going to look at here apply equally well if you are building up your own Identity-based application from scratch. The ways and means might vary according to your needs, but in general, much of what we see here will apply whether you are starting from the Identity Samples project as a base, or "rolling your own" so to speak.

The important thing to bear in mind is that the generic base types and interfaces provided by Identity framework allow great flexibility, but also introduced complexity related to the dependencies introduced by the generic type arguments. In particular, the type specified as the key for each model must propagate through the stack, or the compiler gets angry.

Getting Started - Installing the Identity Samples Project

The Identity Samples project is available on Nuget. First, create an empty ASP.NET Web Project (It is important that you use the "Empty" template here, not MVC, not Webforms, EMPTY). Then open the Package Manager console and type:

Install Identity Samples from the Package Manager Console:
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

 

This may take a minute or two to run. When complete, your will see a basic ASP.NET MVC project in the VS Solution Explorer. Take a good look around the Identity 2.0 Samples project, and become familiar with what things are and where they are at.

Re-Engineering the Basic Identity Models

To get started, we need to re-engineer the basic model classes defined in the Identity Samples project, as well as add a few new ones. Because Identity Samples uses string-based keys for entity models, the authors, in many cases get away with depending upon the default class implementations provided by the framework itself. Where they extend, they extend from the default classes, meaning the string-based keys are still baked in to the derived classes.

Since we want to use integer keys for all of our models, we get to provide our own implementations for most of the models.

In many cases, this isn't as bad as it sounds. For example, there are a handful of model classes we need only define in terms of the generic arguments, and from there the base class implementation does the rest of the work.

NOTE: As we proceed to modify/add new classes here, the error list in Visual Studio will begin to light up like a Christmas tree until we are done. Leave that be for the moment. If we do this correctly, there should be no errors left when we finish. IF there are, they will help us find things we missed.

In the Models => IdentityModels.cs file, we find the model classes used by the Identity Samples application. To get started, we are going to add our own definitions for IndentityUserLogin, IdentityUserClaim, and IdentityUserRole. The Identity Samples project simply depended upon the default framework implementations for these classes, and we need our own integer based versions. Add the following to the IdentityModels.cs file:

Integer-Based Definitions for UserLogin, UserClaim, and UserRole:
public class ApplicationUserLogin : IdentityUserLogin<int> { }
public class ApplicationUserClaim : IdentityUserClaim<int> { }
public class ApplicationUserRole : IdentityUserRole<int> { }

 

Now, with that out of the way, we can define our own implementation of IdentityRole. The Samples project also depended upon the framework version for IdentityRole, and we are going to provide our own again. This time, though, there's a little more to it:

Integer-Based Definition for IdentityRole:
public class ApplicationRole : IdentityRole<int, ApplicationUserRole>, IRole<int>
{
    public string Description { get; set; }
  
    public ApplicationRole() { }
    public ApplicationRole(string name)
        : this()
    {
        this.Name = name;
    }
  
    public ApplicationRole(string name, string description)
        : this(name)
    {
        this.Description = description;
    }
}

 

Notice above, we have defined ApplicationRole in terms of an integer key, and also in terms of our custom class ApplicationUserRole? This is important, and will continue on up the stack as we re-implement the Identity classes we need for the Identity Samples project to run as expected.

Next, we are going to modify the existing definition for ApplicationUser. Currently, the IdentitySamples.cs file includes a fairly simple definition for ApplicationUser which derives from the default IdentityUser class provided by the framework, which requires no type arguments because they have already been provided in the default implementation. We need to basically re-define ApplicationUser starting from the ground up.

The existing ApplicationUser class in the IdentityModels.cs file looks like this:

Existing ApplicationUser Class in IdentityModels.cs:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
        GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

 

We need to replace the above in its entirety with the following:

Custom Implementation for ApplicationUser:
public class ApplicationUser 
: IdentityUser<int, ApplicationUserLogin, 
    ApplicationUserRole, ApplicationUserClaim>, IUser<int>
{
    public async Task<ClaimsIdentity>
        GenerateUserIdentityAsync(UserManager<ApplicationUser, int> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

 

Once again, instead of deriving from the default Identity framework implementation for IdentityUser, we have instead used the generic base, and provided our own custom type arguments. Also again, we have defined our custom ApplicationUser in terms of an integer key, and our own custom types.

Modified Application Db Context

Also in the IdentityModels.cs file is an ApplicationDbContext class.

Now that we have built out the basic models we are going to need, we also need to re-define the ApplicationDbContext in terms of these new models. As previously, the existing ApplicationDbContext used in the Identity Samples application is expressed only in terms of ApplicationUser, relying (again) upon the default concrete implementation provided by the framework.

If we look under the covers, we find the ApplicationDbContext<ApplicationUser> actually inherits from IdentityDbContext<ApplicationUser>, which in turn is derived from:

IdentityDbContext<TUser, IdentityRole, string, IdentityUserLogin,
    IdentityUserRole, IdentityUserClaim>
    where TUser : Microsoft.AspNet.Identity.EntityFramework.IdentityUser

In other words, we once again have a default concrete implementation which is defined in terms of the other default framework types, all of which further depend upon a string-based key.

In order to define a DbContext which will work with our new custom types, we need to express our concrete class in terms of integer keys, and our own custom derived types.

Replace the existing ApplicationDbContext code with the following:

Modified ApplicationDbContext:
public class ApplicationDbContext 
    : IdentityDbContext<ApplicationUser, ApplicationRole, int, 
    ApplicationUserLogin, ApplicationUserRole, ApplicationUserClaim>
{
    public ApplicationDbContext()
        : base("DefaultConnection")
    {
    }
  
    static ApplicationDbContext()
    {
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }
  
    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

 

Once again, we have now expressed ApplicationDbContext in terms of our own custom types, all of which use an integer key instead of a string.

Custom User and Role Stores

I am willing to bet that if you take a look at the Visual Studio Error Window right now, it is likely a block of what seems to be endless red error indicators. As mentioned previously, that's fine for now - ignore it.

Identity framework defines the notion of User and Role stores for accessing user and role information. As with most everything else to this point, the default framework implementations for UserStore and RoleStore are defined in terms of the other default classes we have seen to this point - in other words, they won't work with our new custom classes. We need to express a custom User store, and a custom Role store, in terms of integer keys and our own custom classes.

Add the following to the IdentityModels.cs file:

Adding a Custom User Store:
    public class ApplicationUserStore : 
        UserStore<ApplicationUser, ApplicationRole, int,
        ApplicationUserLogin, ApplicationUserRole, 
        ApplicationUserClaim>, IUserStore<ApplicationUser, int>, 
        IDisposable
    {
        public ApplicationUserStore() : this(new IdentityDbContext())
        {
            base.DisposeContext = true;
        }
  
        public ApplicationUserStore(DbContext context)
            : base(context)
        {
        }
    }
  
  
    public class ApplicationRoleStore 
        : RoleStore<ApplicationRole, int, ApplicationUserRole>, 
        IQueryableRoleStore<ApplicationRole, int>, 
        IRoleStore<ApplicationRole, int>, IDisposable
    {
        public ApplicationRoleStore()
            : base(new IdentityDbContext())
        {
            base.DisposeContext = true;
        }
  
        public ApplicationRoleStore(DbContext context)
            : base(context)
        {
        }
    }

 

Re-Engineering Identity Configuration Classes

The Identity Samples project includes a file named App_Start => IdentityConfig.cs. In this file is a bunch of code which basically configures the Identity System for use in your application. The changes we introduced on our IdentityModels.cs file will cause issues here (and basically, throughout the application) until they are addressed in the client code.

In most cases, we will either be replacing a reference to a default Identity class with one of our new custom classes, and/or calling method overrides which allow the passing of custom type arguments.

In the IdentityConfig.cs file, we find an ApplicationUserManager class, which contains code commonly called by our application to, well, manage users and behaviors. we will replace the existing code with the following, which essentially expresses ApplicationUserManager in terms of integer keys, and our new custom UserStore. If you look closely, we have added an int type argument to many of the method calls.

Customized ApplicationUserManager Class:
// *** PASS IN TYPE ARGUMENT TO BASE CLASS:
public class ApplicationUserManager : UserManager<ApplicationUser, int>
{
    // *** ADD INT TYPE ARGUMENT TO CONSTRUCTOR CALL:
    public ApplicationUserManager(IUserStore<ApplicationUser, int> store)
        : base(store)
    {
    }
  
    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options,
        IOwinContext context)
    {
        // *** PASS CUSTOM APPLICATION USER STORE AS CONSTRUCTOR ARGUMENT:
        var manager = new ApplicationUserManager(
            new ApplicationUserStore(context.Get<ApplicationDbContext>()));
  
        // Configure validation logic for usernames
  
        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.UserValidator = new UserValidator<ApplicationUser, int>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
  
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6,
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };
  
        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;
   
        // Register two factor authentication providers. 
        // This application uses Phone and Emails as a step of receiving a 
        // code for verifying the user You can write your own provider and plug in here.
  
        // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.RegisterTwoFactorProvider("PhoneCode", 
            new PhoneNumberTokenProvider<ApplicationUser, int>
        {
            MessageFormat = "Your security code is: {0}"
        });
  
          // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
        manager.RegisterTwoFactorProvider("EmailCode", 
            new EmailTokenProvider<ApplicationUser, int>
        {
            Subject = "SecurityCode",
            BodyFormat = "Your security code is {0}"
        });
  
        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            // *** ADD INT TYPE ARGUMENT TO METHOD CALL:
            manager.UserTokenProvider =
                new DataProtectorTokenProvider<ApplicationUser, int>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
}

 

That's a lot of code there. Fortunately, modifying the ApplicationRoleManager class is not such a big deal. We're essentially doing the same thing - expressing ApplicationRoleManager in terms of integer type arguments, and our custom classes.

Replace the ApplicationRoleManager code with the following:

Customized ApplicationRoleManager Class:
// PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO BASE:
public class ApplicationRoleManager : RoleManager<ApplicationRole, int>
{
    // PASS CUSTOM APPLICATION ROLE AND INT AS TYPE ARGUMENTS TO CONSTRUCTOR:
    public ApplicationRoleManager(IRoleStore<ApplicationRole, int> roleStore)
        : base(roleStore)
    {
    }
  
    // PASS CUSTOM APPLICATION ROLE AS TYPE ARGUMENT:
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new ApplicationRoleStore(context.Get<ApplicationDbContext>()));
    }
}

 

Modify The Application Database Initializer and Sign-in Manager

The ApplicationDbInitializer class is what manages the creation and seeding of the backing database for our application. In this class we create a basic admin role user, and set up additional items such as the Email and SMS messaging providers.

The only thing we need to change here is where we initialize an instance of ApplicationRole. In the existing code, the ApplicationDbInitializer class instantiates an instance of IdentityRole, and we need to create an instance of our own ApplicationRole instead.

Replace the existing code with the following, or make the change highlighted below:

Modify the ApplicationDbInitializer Class:
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }
  
    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    public static void InitializeIdentityForEF(ApplicationDbContext db) {
        var userManager = HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
        var roleManager = HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
        const string name = "admin@example.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";
  
        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);
        if (role == null) {
            // *** INITIALIZE WITH CUSTOM APPLICATION ROLE CLASS:
            role = new ApplicationRole(roleName);
            var roleresult = roleManager.Create(role);
        }
  
        var user = userManager.FindByName(name);
        if (user == null) {
            user = new ApplicationUser { UserName = name, Email = name };
            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }
  
        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);
        if (!rolesForUser.Contains(role.Name)) {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}

 

Fixing up the ApplicationSignInManager is even more simple. Just change the string type argument in the class declaration to int:

Modify the ApplicationSignInManager Class:
// PASS INT AS TYPE ARGUMENT TO BASE INSTEAD OF STRING:
public class ApplicationSignInManager : SignInManager<ApplicationUser, int>
{
    public ApplicationSignInManager(
        ApplicationUserManager userManager, IAuthenticationManager authenticationManager) : 
        base(userManager, authenticationManager) { }
  
    public override Task<ClaimsIdentity> CreateUserIdentityAsync(ApplicationUser user)
    {
        return user.GenerateUserIdentityAsync((ApplicationUserManager)UserManager);
    }
  
    public static ApplicationSignInManager Create(
     IdentityFactoryOptions<ApplicationSignInManager> options, IOwinContext context)
    {
        return new ApplicationSignInManager(context.GetUserManager<ApplicationUserManager>(), context.Authentication);
    }
}

 

Cookie Authentication Configuration

In the file App_Start => Startup.Auth there is a partial class definition, Startup. in the single method call defined in the partial class, there is a call to app.UseCookieAuthentication(). Now that our application is using integers as keys instead of strings, we need to make a modification to the way the CookieAuthenticationProvider is instantiated.

The existing call to app.UseCookieAuthentication (found smack in the middle of the middle of the ConfigureAuth() method) needs to be modified. Where the code calls OnVlidateIdentity the existing code passes ApplicationUserManager and ApplicationUser as type arguments. What is not obvious is that this is an override which assumes a third, string type argument for the key (yep - we're back to that whole string keys thing again).

We need to change this code to call another override, which accepts a third type argument, and pass it an int argument.

The existing code looks like this:

Existing Call to app.UseCookieAuthentication:
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>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentity: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager))
    }
});

 

We need to modify this code in a couple of non-obvious ways. First, as mentioned above, we need to add a third type argument specifying that TKey is an int.

Less obvious is that we also need to change the name of the second argument from regenerateIdentity to regenerateIdentityCallback. Same argument, but different name in the overload we are using.

Also less than obvious is the third Func we need to pass into the call as getUserIdCallback. Here, we need to retreive a user id from a claim, which stored the Id as a string. We need to parse the result back into an int.

Replace the existing code above with the following:

Modified Call to app.UseCookieAuthentication:
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
            // ADD AN INT AS A THIRD TYPE ARGUMENT:
            .OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                validateInterval: TimeSpan.FromMinutes(30),
                // THE NAMED ARGUMENT IS DIFFERENT:
                regenerateIdentityCallback: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager),
                    // Need to add THIS line because we added the third type argument (int) above:
                    getUserIdCallback:  (claim) => int.Parse(claim.GetUserId()))
    }
});

 

With that, most of the Identity infrastructure is in place. Now we need to update a few things within our application.

Update Admin View Models

The Models => AdminViewModels.cs file contains  class definitions for a RolesAdminViewModel and a UsersAdminViewModel. In both cases, we need to change the type of the Id property from string to int:

Modify the Admin View Models:
public class RoleViewModel
{
    // Change the Id type from string to int:
    public int Id { get; set; }
    
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "RoleName")]
    public string Name { get; set; }
}
  
public class EditUserViewModel
{
    // Change the Id Type from string to int:
    public int Id { get; set; }
  
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "Email")]
    [EmailAddress]
    public string Email { get; set; }
  
    public IEnumerable<SelectListItem> RolesList { get; set; }
}

 

Update Controller Method Parameter Arguments

A good many of the controller action methods currently expect an id argument of type string. We need to go through all of the methods in our controllers and change the type of the id argument from string to int.

In each of the following controllers, we need to change the existing Id from string to int as shown for the action methods indicated (we're only showing the modified method signatures here):

Account Controller:
public async Task<ActionResult> ConfirmEmail(int userId, string code)

 

Roles Admin Controller:
public async Task<ActionResult> Edit(int id)
public async Task<ActionResult> Details(int id)
public async Task<ActionResult> Delete(int id)
public async Task<ActionResult> DeleteConfirmed(int id, string deleteUser)

 

Users Admin Controller:
public async Task<ActionResult> Details(int id)
public async Task<ActionResult> Edit(int id)
public async Task<ActionResult> Delete(int id)
public async Task<ActionResult> DeleteConfirmed(int id)

 

Update the Create Method on Roles Admin Controller

Anywhere we are creating a new instance of a Role, we need to make sure we are using our new ApplicationRole instead of the default IdentityRole. Specifically, in the Create() method of the RolesAdminController:

Instantiate a new ApplicationRole Instead of IdentityRole:
[HttpPost]
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        // Use ApplicationRole, not IdentityRole:
        var role = new ApplicationRole(roleViewModel.Name);
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

 

Add Integer Type Argument to GetUserId() Calls

If we take a look at our Error list now, we see the preponderance of errors are related to calls to User.Identity.GetUserId(). If we take a closer look at this method, we find that once again, the default version of GetUserId() returns a string, and that there is an overload which accepts a type argument which determines the return type.

Sadly, calls to GetUserId() are sprinkled liberally throughout ManageController, and a few places in AccountController as well. We need to change all of the calls to reflect the proper type argument, and the most efficient way to do this is an old fashioned Find/Replace.

Fortunately, you can use Find/Replace for the entire document on both ManageController and AccountController, and get the whole thing done in one fell swoop. Hit Ctrl + H, and in the "Find" box, enter the following:

Find all instances of:
Identity.GetUserId()

 

Replace with:
Identity.GetUserId<int>()

 

If we've done this properly, most of the glaring red errors in our error list should now be gone. There are a few stragglers, though. In these cases, we need to counter-intuitively convert the int Id back into a string.

Return a String Where Required

There are a handful of methods which call to GetUserId(), but regardless of the type the Id represents (in our case, now, an int) want a string representation of the Id passed as the argument. All of these methods are found on ManageController, and in each case, we just add a call to .ToString().

First, in the Index() method of ManageController, we find a call to AuthenticationManager.TwoFactorBrowserRemembered() . Add the call to .ToString() after the call to GetUserId():

Add Call to ToString() to TwoFactorBrowserRemembered:
public async Task<ActionResult> Index(ManageMessageId? message)
{
    ViewBag.StatusMessage =
        message == ManageMessageId.ChangePasswordSuccess ? 
            "Your password has been changed."
        : message == ManageMessageId.SetPasswordSuccess ? 
            "Your password has been set."
        : message == ManageMessageId.SetTwoFactorSuccess ? 
            "Your two factor provider has been set."
        : message == ManageMessageId.Error ? 
            "An error has occurred."
        : message == ManageMessageId.AddPhoneSuccess ? 
            "The phone number was added."
        : message == ManageMessageId.RemovePhoneSuccess ? 
            "Your phone number was removed."
        : "";
  
    var model = new IndexViewModel
    {
        HasPassword = HasPassword(),
        PhoneNumber = await UserManager.GetPhoneNumberAsync(User.Identity.GetUserId<int>()),
        TwoFactor = await UserManager.GetTwoFactorEnabledAsync(User.Identity.GetUserId<int>()),
        Logins = await UserManager.GetLoginsAsync(User.Identity.GetUserId<int>()),
  
        // *** Add .ToString() to call to GetUserId():
        BrowserRemembered = await AuthenticationManager
            .TwoFactorBrowserRememberedAsync(User.Identity.GetUserId<int>().ToString())
    };
    return View(model);
}

 

Similarly, do the same for the RememberBrowser method, also on ManageController:

Add Call to ToString() to RememberBrowser Method:
[HttpPost]
public ActionResult RememberBrowser()
{
    var rememberBrowserIdentity = AuthenticationManager
        .CreateTwoFactorRememberBrowserIdentity(
            // *** Add .ToString() to call to GetUserId():
            User.Identity.GetUserId<int>().ToString());
    AuthenticationManager.SignIn(
        new AuthenticationProperties { IsPersistent = true }, 
        rememberBrowserIdentity);
    return RedirectToAction("Index", "Manage");
}

 

Lastly,the same for the LinkLogin() and LinkLoginCallback() methods:

Add Call to ToString() to LinkLogin():
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
    return new AccountController
        .ChallengeResult(provider, Url.Action("LinkLoginCallback", "Manage"), 
            // *** Add .ToString() to call to GetUserId():
            User.Identity.GetUserId<int>().ToString());
}

 

Add Call to ToString() to LinkLoginCallback():
public async Task<ActionResult> LinkLoginCallback()
{
    var loginInfo = await AuthenticationManager
        .GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId<int>().ToString());
    if (loginInfo == null)
    {
        return RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
    }
    var result = await UserManager
        // *** Add .ToString() to call to GetUserId():
        .AddLoginAsync(User.Identity.GetUserId<int>().ToString(), loginInfo.Login);
  
    return result.Succeeded ? RedirectToAction("ManageLogins") 
        : RedirectToAction("ManageLogins", new { Message = ManageMessageId.Error });
}

 

With that, we have addressed most of the egregious issues, and we basically taken a project built against a model set using all string keys and converted it to using integers. The integer types will be propagated as auto-incrementing integer primary keys in the database backend as well.

But there are still a few things to clean up.

Fix Null Checks Against Integer Types

Scattered throughout the primary identity controllers are a bunch of null checks against the Id values received as arguments in the method calls. If you rebuild the project, the error list window in Visual Studio should now contain a bunch of the yellow "warning" items about this very thing.

You can handle this in your preferred manner, but for me, I prefer to check for a positive integer value. We'll look at the Details() method from the UserAdminController as an example, and you can take it from there.

The existing code in the Details() method looks like this:

Existing Details() Method from UserAdminController:
public async Task<ActionResult> Details(int id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var user = await UserManager.FindByIdAsync(id);
    ViewBag.RoleNames = await UserManager.GetRolesAsync(user.Id);
    return View(user);
}

 

In the above, we can see that previously, the code checked for a null value for the (formerly) string-typed Id argument. Now that we are receiving an int, the check for null is meaningless. Instead, we want to check for a positive integer value. If the check is true, then we want to process accordingly. Otherwise, we want to return the BadRequest result.

In other words, we need to invert the method logic. Previously, if the conditional evaluated to true, we wanted to return the error code. Now, is the result is true, we want to proceed, and only return the error result if the conditional is false. So we're going to swap our logic around.

Replace the code with the following:

Modified Details() Method with Inverted Conditional Logic:
public async Task<ActionResult> Details(int id)
{
    if (id > 0)
    {
        // Process normally:
        var user = await UserManager.FindByIdAsync(id);
        ViewBag.RoleNames = await UserManager.GetRolesAsync(user.Id);
        return View(user);
    }
    // Return Error:
    return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}

 

We can do something similar for the other cases in UserAdminController, RolesAdminController, and AccountController. Think through the logic carefully, and all should be well.

Update Roles Admin Views

Several of the View Templates currently use the default IdentityRole model instead of our new, custom ApplicationRole. We need to update the Views in Views => RolesAdmin to reflect our new custom model.

The Create.cshtml and Edit.cshtml Views both depend upon the RoleViewModel, which is fine. However, the Index.cshtml, Details.cshtml, and Delete.cshtml Views all currently refer to IdentityRole. Update all three as follows

The Index.cshtml View currently expects an IEnumerable<IdentityRole> . We need to change this to expect an IEnumerable<ApplicationRole> . Note that we need to include the project Models namespace as well:

Update the RolesAdmin Index.cshtml View:
@model IEnumerable<IdentitySample.Models.ApplicationRole>
// ... All the view code ...

 

All we need to change here is the first line, so I omitted the rest of the View code.

Similarly, we need to update the Details.cshtml and Delete.cshtml Views to expect ApplicationRole instead of IdentityRole. Change the first line in each to match the following:

Update the Details.cshtml and Delete.cshtml Views:
@model IdentitySample.Models.ApplicationRole
// ... All the view code ...

 

Obviously, if your default project namespace is something other than IdentitySamples, change the above to suit.

Additional Extensions are Easy Now

Now that we have essentially re-implemented most of the Identity object models with our own derived types, it is easy to add custom properties to the ApplicationUser and/.or ApplicationRole models. All of our custom types already depend upon each other in terms of the interrelated generic type arguments, so we are free to simply add what properties we wish to add, and then update our Controllers, ViewModels, and Views accordingly.

To do so, review the previous post on extending Users and Roles, but realize all of the type structure stuff is already done. Review that post just to see what goes on with updating the Controllers, Views, and ViewModels.

A Note on Security

The basic Identity Samples application is a great starting point for building out your own Identity 2.0 application. However, realize that, as a demo, there are some things built in that should not be present in production code. For example, the database initialization currently includes hard-coded admin user credentials.

Also, the Email confirmation and two-factor authentication functionality currently circumvents the actual confirmation and two-factor process, by including links on each respective page which short-circuit the process.

The above items should be addressed before deploying an actual application based upon the Identity Samples project.

Wrapping Up

We've taken a rather exhaustive look at how to modify the Identity Samples application to use integer keys instead of strings. Along the way, we (hopefully) gained a deeper understanding of the underlying structure in an Identity 2.0 based application. There's a lot more there to learn, but this is a good start.

Additional Resources and Items of Interest

 

Posted on July 13 2014 10:45 AM by jatten     

Comments (12)

Creating A Basic Make File for Compiling C Code

Posted on July 6 2014 02:30 PM by jatten in Linux, Education, CodeProject, Learning   ||   Comments (3)

binary-blanket-240I recently began taking a Harvard computer science course. As pretentious as that sounds, it's not as bad as it seems. I am taking Harvard CS50 on-line, for free, in an attempt to push my knowledge and expand my understanding of this thing I love so much, programming.

The course uses Linux and C as two of the primary learning tools/environments. While I am semi-capable using Linux, and the syntax of C is vary familiar, the mechanics of C compilation, linking, and makefiles are all new.

Image by quimby  |  Some Rights Reserved

If you are an experienced C developer, or if you have worked extensively with Make before, there is likely nothing new for you here. However, if you wanted to let me know if and when I am passing bad information, please do! This article will (hopefully) be helpful to those who are just getting started compiling C programs, and/or using the GNU Make utility.

In the post, we discuss some things that are specific to the context of the course exercises. However, the concepts discussed, and the examples introduced are general enough that the post should be useful beyond the course specifics.

The course makes available an "appliance" (basically, a pre-configured VM which includes all the required software and files), which I believe can be downloaded by anyone taking even the free on-line course. However, since I already know how to set up/configure a Linux box (but can always use extra practice), I figured my learning would be augmented by doing things the hard way, and doing everything the course requires manually.

For better or for worse, this has forced me to learn about Make and makefiles (this is for the better, I just opened the sentence that way to sound good).

Make File Examples on Github

In this post we will put together a handful of example Make files. You can also find them as source at my Github repo:

Compiling and Linking - The Simple

This is by no means a deeply considered resource on compiling and linking source code. However, in order to understand how make works, we need to understand compiling and linking at some level.

Let's consider the canonical "Hello World!" program, written in the C programming language. Say we have the following source file, suitably named hello.c:

Basic Hello World Implementation in C:
#include <stdio.h>
  
int main(void)
{
    printf("Hello, World!\n");
}

 

In the above, the first line instructs the compiler to include the C Standard IO library, by making reference to the stdio.h header file. This is followed by our application code, which impressively prints the string "Hello World!" to the terminal window.

In order to run the above, we need to compile first. Since the Harvard course is using the Clang compiler, the most basic compilation command we can enter from the terminal might be:

Basic Compile Command for Hello World:
$ clang hello.c -o hello

 

When we enter the above command in our terminal window, we are essentially telling the Clang compiler to compile the source file hello.c, and the -o flag tells it to name the output binary file hello.

This works well enough for a simple task like compiling Hello World. Files from the C Standard Library are linked automatically, and the only compiler flag we are using is -o to name the output file (if we didn't do this, the output file would be named a.out, the default output file name).

Compiling and Linking - A Little More Complex

When I say "Complex" in the header above, it's all relative. We will expand on our simple Hello World example by adding an external library, and using some additional important compiler flags.

The Harvard CS50 course staff created a cs50 library for use by students in the course. The library includes some functions to ease folks into working with C. Among other things, the staff have added a number of functions designed to retreive terminal input from the user. For example, the cs50 library defines a GetString() function which will accept user input as text from the terminal window.

In addition to the GetString() function, the cs50 library also defines a string data type (which is NOT a native C data type!).

We can add the cs50 library to our machine by following the instructions from the cs50 site. During the process, the library will be compiled, and the output placed in the /usr/local/lib/ directory, and the all-important header files will be added to our usr/local/include/ directory.

NOTE: You don't need to focus on using this course-specific library specifically here - this is simply an example of adding an external include to the compilation process.

Once added, the various functions and types defined therein will be available to us.

We might modify our simple Hello World! example as follows by referencing the cs50 library, and making use of the GetString() function and the new string type:

Modified Hello World Example:
// Add include for cs50 library:
#include <cs50.h>
#include <stdio.h>
  
int main(void)
{
    printf("What is your name?");
    // Get text input from user:
    string name = GetString();
      
    // Use user input in output striing:
    printf("Hello, %s\n", name);
}

 

Now, if we try to use the same terminal command to compile this version, we will see some issues:

Run Original Compile Command:
$ clang hello.c -o hello

 

Terminal Output from Command:
/tmp/hello-E2TvwD.o: In function `main':
hello.c:(.text+0x22): undefined reference to `GetString'
clang: error: linker command failed with exit code 1 
    (use -v to see invocation)

 

From the terminal output, we can see that the compiler cannot find the GetString() method, and that there was an issue with the linker.

Turns out, we can add some additional arguments to our clang command to tell Clang what files to link:

$ clang hello.c -o hello -lcs50

 

By adding the -l flag followed by the name of the library we need to include, we have told Clang to link to the cs50 library.

Handling Errors and Warnings

Of course, the examples of using the Clang compiler and arguments above still represent a very basic case. Generally, we might want to direct the compiler to add compiler warnings, and/or to include debugging information in the output files. a simple way to do this from the terminal, using our example above, would be as follows:

Adding Additional Compiler Flags to clang Terminal Command:
clang hello.c -g -Wall -o hello -lcs50

 

Here, we have user the -g flag, which tells the compiler to include debugging information in the output files, and the -Wall flag. -Wall turns on most of the various compiler warnings in Clang (warnings do not prevent compilation and output, but warn of potential issues).

A quick skimming of the Clang docs will show that there is potential for a great many compiler flags and other arguments.

As we can see, though, our terminal input to compile even the still-simple hello application is becoming cumbersome. Now imagine a much larger application, with multiple source files, referencing multiple external libraries.

What is Make?

Since source code can be contained in multiple files, and also make reference to additional files and libraries, we need a way to tell the compiler which files to compile, which order to compile them, and how a link to external files and libraries upon which our source code depends. With the additional of various compiler options and such required to get our application running, combined with the frequency with which we are likely to use the compile/run cycle during development, it is easy to see how entering the compile commands manually could rapidly become cumbersome.

Enter the Make utility.

GNU Make was originally created by Richard M. Stallman ("RMS") and Roland McGrath. From the GNU Manual:

"The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them."

When we write source code in C, C++, or other compiled languages, creating the source is only the first step. The human-readable source code must be compiled into binary files in order that the machine can run the application.

Essentially, the Make utility utilizes structured information contained in a makefile in order to properly compile and link a program. A Make file is named either Makefile or makefile, and is placed in the source directory for your project.

An Example Makefile for Hello World

Our final example using the clang command in the terminal contained a number of compiler flags, and referenced one external library. The command was still doable manually, but using make, we can make like much easier.

In a simple form, a make file can be set up to essentially execute the terminal command from above. The basic structure looks like this:

Basic Makefile Structure:
# Compile an executable named yourProgram from yourProgram.c
all: yourProgram.c
<TAB>gcc -g -Wall -o yourProgram yourProgram.c

 

In a makefile, lines preceded with a hash symbol are comments, and will be ignored by the utility. In the structure above, it is critical that the <TAB> on the third line is actually a tab character. Using Make, all actual commands must be preceded by a tab.

For example, we might create a Makefile for our hello program like this:

Makefile for the Hello Program:
# compile the hello program with compiler warnings, 
# debug info, and include the cs50 library
all: hello.c
	clang -g -Wall -o hello hello.c -lcs50

 

This Make file, named (suitably) makefile and saved in the directory where our hello.c source file lives, will perform precisely the same as the final terminal command we examined. In order to compile our hello.c program using the makefile above, we need only type the following into our terminal:

Compiling Hello Using Make:
$ make

 

Of course, we need to be in the directory in which the make file and the hello.c source file are located.

A More General Template for Make Files

Of course, compiling our Hello World application still represents a pretty simplistic view of the compilation process. We might want to avail ourselves of the Make utilities strengths, and cook up a more general template we can use to create make files.

The Make utility allows us to structure a makefile in such a way as to separate the compilation targets (the source to be compiled) from the commands, and the compiler flags/arguments (called rules in a make file). We can even use what amount to variables to hold these values. 

For example, we might refine our current makefile as follows:

General Purpose Makefile Template:
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# the name to use for both the target source file, and the output file:
TARGET = hello
  
all: $(TARGET)
  
$(TARGET): $(TARGET).c
	$(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

As we can see in the above, we can make assignments to each of the capitalized variables, which are then used in forming the command (notice that once again, the actual command is preceded by a tab in the highlighted line). While this Make File is still set up for our Hello World application, we could easily change the assignment to the TARGET variable, as well as add or remove compiler flags and/or linked files for a different application.

Again, we can tell make to compile our hello application by simply typing:

Compile Hello.c Using the modified Makefile:
$ make

 

A Note on Tabs Vs. Spaces in Your Editor

If you, like me, follow the One True Coding Convention which states:

"Thou shalt use spaces, not tabs, for indentation"

Then you will have a problem with creating your make file. If you have your editor set to convert tabs to spaces, Make will not recognize the all-important Tab character in front of the command, because, well, it's not there.

Fortunately, there is a work-around. If you do not have tabs in your source file, you can instead separate the compile target from the command using a semi-colon. With this fix in place, our Make file might look like this:

Makefile with no Tab Characters:
# Compile an executable named yourProgram from yourProgram.c
all: yourProgram.c
<TAB>gcc -g -Wall -o yourProgram yourProgram.c
# compile the hello program with spaces instead of Tabs
  
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# require that an argument be provided at the command line for the target name:
TARGET = hello
  
all: $(TARGET)
$(TARGET): $(TARGET).c ; $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

In the above, we have inserted a semi-colon between the definition of dependencies definition of the target and the command statement structure (see highlighted line).

Passing the Compilation Target Name to Make as a Command Line Argument

Most of the time, when developing an application you will most likely need a application-specific Makefile for the application. At least, any substantive application which includes more than one source file, and/or external references.

However, for simple futzing about, or in my case, tossing together a variety of one-off example tidbits which comprise the bulk of the problem sets for the Harvard cs50 course, it may be handy to be able to pass the name of the compile target in as a command line argument. The bulk of the Harvard examples include the cs50 library created by the program staff (at least, in the earlier exercises), but otherwise would mostly require the same sets of arguments.

For example, say we had another code file, goodby.c in the same directory.

We could simply pass the target name like so:

Passing the Compilation Target Name as a Command Line Argument:
make TARGET=goodbye.c

 

As we can see, we assign the target name to the TARGET variable when we invoke Make. In this case, if we fail to pass a target name, by simply typing make as we have done previously, Make will compile the program hard-coded into the Makefile - in this case, hello. Despite our intention, the wrong file will be compiled.

We can make one more modification to our Makefile if we want to require that a target be specified as a command line argument:

Require a Command Line Argument for the Compile Target Name:
# compile the hello program with spaces instead of Tabs
  
# the compiler to use
CC = clang
  
# compiler flags:
#  -g    adds debugging information to the executable file
#  -Wall turns on most, but not all, compiler warnings
CFLAGS  = -g -Wall
  
#files to link:
LFLAGS = -lcs50
  
# require that an argument be provided at the command line for the target name:
TARGET = $(target)
  
all: $(TARGET)
$(TARGET): $(TARGET).c ; $(CC) $(CFLAGS) -o $(TARGET) $(TARGET).c $(LFLAGS)

 

With that change, we can now run make on a simple, single-file program like so:

Invoke Make with Required Target Name:
$ make target=hello

 

Of course, now things will go a little haywire if we forget to include the target name, or if we forget to explicitly make the assignment when invoking Make from the command line.

Only the Beginning

This is one of those posts that is mainly for my own reference. As I become more fluent with C, compilation, and Make, I expect my usage may change. For now, however, the above represents what I have figured out while trying to work with the examples in the on-line Harvard course.

If you see me doing anything idiotic in the above, or have suggestions, I am all ears! Please do comment below, or reach out at the email described in my "About the Author" blurb at the top of this page.

Additional Resources and Items of Interest

 

Posted on July 6 2014 02:30 PM by jatten     

Comments (3)

ASP.NET Identity 2.0: Customizing Users and Roles

Posted on June 22 2014 03:49 PM by jatten in ASP.Net, ASP.NET MVC, C#, CodeProject   ||   Comments (24)

3321550181_49277672cf_z-640x480The ASP.NET Identity team released the Identity 2.0 framework RTM back in march. The new release contained significant additions to the functionality found in the original 1.0 release, and introduced some breaking changes as well.

In a previous post, we took a high-level look at how Identity 2.0 works by digging in to the Identity Samples application (which was, and still is, in beta, so things may continue to change). We also took a detailed look at implementing Email Account Confirmation and Two Factor Authentication, which represent a couple of the sexier features of the Identity 2.0 RTM release.

Image By Herry Lawford  | Some Rights Reserved

We have previously explored explored Extending Identity Accounts and Implementing Role-Based Authentication under Identity 1.0, as well as Extending and Modifying Roles. However, things have changed since then. If you are using Identity 1.0, those posts are still applicable, and you should refer to them now. If you are looking to dig in to Identity 2.0, keep reading!

Many of the customizations we previously needed to add on our own under Identity Version 1.0 have now been incorporated into the Version 2.0 RTM. Specifically, Role administration, and the assignment of users to one or more roles is implemented out of the box in the Identity Samples project. Extending the basic IdentityUser and Role classes is a more flexible proposition, but is more complex than previously.

In this post we will dig in and see what we need to do to extend the basic ApplicationUser and ApplicationRole types by adding some custom properties to each.

UPDATE: If you are looking to use integer keys instead of strings, see ASP.NET Identity 2.0 Extending Identity Models and Using Integer Keys Instead of Strings.

I've also created a ready-to-use Easily Extensible Identity 2.0 project template which goes a little farther than the examples in this article.

We will walk through things step-by step here, so you can follow along. However, I have created a Github repo containing the source for the finished project. If you run into trouble, I strongly recommend cloning the source to get a closer look.

Bear in mind, the code here is minimal, in that we don't attempt to make any UI improvements, excess validations, or other things which may seem obvious from an application design standpoint. Instead, we try to keep things simple, so that we can focus on the topic at hand.

We'll look at all that in a minute. First, we are going to start by installing the Identity Samples project.

Installing the Identity 2.0 Sample Project

The Identity team has created a sample project which can be installed into an empty ASP.NET Web Project. Note that as of this writing this is an alpha release, so some things may change. However, most of the basic functionality is implemented, and in fact the sample project is a strong starting point for using Identity 2.0 in your own site.

The Identity Samples project is available on Nuget. First, create an empty ASP.NET Web Project (It is important that you use the "Empty" template here, not MVC, not Webforms, EMPTY). Then open the Package Manager console and type:

PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

 

This may take a minute or two to run. When complete, your will see a basic ASP.NET MVC project in the VS Solution Explorer. Take a good look around the Identity 2.0 Samples project, and become familiar with what things are and where they are at.

Re-Engineering the Identity Samples Project Using Custom Types

The Identity Samples project provides a solid platform to use as the basis for incorporating the Identity 2.0 framework into a new ASP.NET MVC project. However, the project itself assumes you will be using the default string keys (which translates into string-based primary keys in our database), and also assumes you will be using the default types included with Identity Samples out of the box.

As we will see, Identity 2.0 provides a great deal of flexibility for implementing custom types derived from the interfaces and generic base classes that form the Identity framework core. However, building a project similar to Identity Samples from scratch would be a large undertaking. We’re going to take advantage of the work done by the Identity team in creating the ample project, and instead of starting from scratch, we will tweak this excellent foundation to implement our own customizations.

Core Identity 2.0 Objects are Generic

The basic types implemented by the Identity team in the Identity Samples project represent an abstraction layer on top of a more flexible set of base classes which use generic type arguments. For example, if we look at the IdentityModels.cs code file, we can see the ApplicationUser is derived from IdentityUser:

Application User as Implemented in the Identity Samples Project:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager.CreateIdentityAsync(
            this, DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
}

 

IdentityUser in this case, belongs to the namespace Microsoft.AspNet.Identity.EntityFramework. We can use the VS “Go to Definition” function or a source decompiler (such as Just Decompile by Telerik or Reflector by Redgate) to take a closer look at IdentityUser:

The Identity User Class:
public class IdentityUser : 
    IdentityUser<string, IdentityUserLogin, IdentityUserRole, 
    IdentityUserClaim>, IUser, IUser<string>
{
    public IdentityUser()
    {
        this.Id = Guid.NewGuid().ToString();
    }
  
    public IdentityUser(string userName) : this()
    {
        this.UserName = userName;
    }
}

 

Here, we see that IdentityUser inherits from another base class, IdentityUser<TKey, TLogin, TRole, TClaim> as well as a couple interfaces. In this case the concrete IdentityUser passes specific type arguments to the generic base class. And this is where things begin to get interesting.

As it turns out, all of the basic types required to use Identity 2.0 begin life as generic base types, with similar type arguments allowing us to define custom implementations. Looking at the definitions for the core Identity components used to build up the Identity Samples project, we find the following classes, shown here in terms of the concrete type arguments used in the default Identity constructs:

Default Identity 2.0 Class Signatures with Default Type Arguments:
public class IdentityUserRole 
    : IdentityUserRole<string>
  
public class IdentityRole 
    : IdentityRole<string, IdentityUserRole>
  
public class IdentityUserClaim 
    : IdentityUserClaim<string>
  
public class IdentityUserLogin 
    : IdentityUserLogin<string>
  
public class IdentityUser 
    : IdentityUser<string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, IUser, IUser<string>
  
public class IdentityDbContext 
    : IdentityDbContext<IdentityUser, IdentityRole, string, 
        IdentityUserLogin, IdentityUserRole, IdentityUserClaim>
  
public class UserStore<TUser> 
    : UserStore<TUser, IdentityRole, string, IdentityUserLogin, 
        IdentityUserRole, IdentityUserClaim>, 
        IUserStore<TUser>, IUserStore<TUser, string>, IDisposable
    where TUser : IdentityUser
  
public class RoleStore<TRole> 
    : RoleStore<TRole, string, IdentityUserRole>, IQueryableRoleStore<TRole>, 
        IQueryableRoleStore<TRole, string>, IRoleStore<TRole, string>, IDisposable
    where TRole : IdentityRole, new()

 

In the above, we can see there is a progression of interdependency among the types. IdentityUserRole is derived from IdentityUserRole<TKey> with a string as the single required type argument. IdentityRole is derived from IdentityRole<TKey, TIdentityUserRole> with the concrete types of string and the default implementation of IdentityUserRole (which, as we’ve seen, specifies a string as the key type), respectively, and so on. As we move down the list, the interdependency between concrete type implementations increases.

We will see how this impacts our ability to customize the User and Role types in a bit. First, we can see that adding simple properties to the ApplicationUser implementation provided with the Identity Samples project is about as easy as it can get.

Extending Identity User - The Easy Part

If all we want to do is add some additional properties to the default ApplicationUser class defined in the Identity Samples project, life is simple enough - the Identity Samples team has set the project up with a sensible default implementation which can be extended with very little effort.

Recall from earlier the ApplicationUser class. Say we want to add some address properties as follows:

Extending the Default ApplicationUser Class:
public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> 
        GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        var userIdentity = await manager
            .CreateIdentityAsync(this, 
                DefaultAuthenticationTypes.ApplicationCookie);
        return userIdentity;
    }
  
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
  
    // Concatenate the address info for display in tables and such:
    public string DisplayAddress
    {
        get
        {
            string dspAddress = 
                string.IsNullOrWhiteSpace(this.Address) ? "" : this.Address;
            string dspCity = 
                string.IsNullOrWhiteSpace(this.City) ? "" : this.City;
            string dspState = 
                string.IsNullOrWhiteSpace(this.State) ? "" : this.State;
            string dspPostalCode = 
                string.IsNullOrWhiteSpace(this.PostalCode) ? "" : this.PostalCode;
                
            return string
                .Format("{0} {1} {2} {3}", dspAddress, dspCity, dspState, dspPostalCode);
        }
    }
}

 

From here, in this limited case, all we need to do is update the various ViewModels, Views, and Controllers to incorporate our new properties. We will add functionality for the new properties to our RegisterViewModel, the Register.cshtml View itself, and the Register method of the AccountsController.

We will also do the same for the UsersAdminController and associated ViewModels and Views.

Update the Register ViewModel to Include Address Information

The RegisterViewModel is defined in the AccountViewModels.cs file. We need to add our new properties to this VeiwModel in order that the Register view, by which new users can sign up, affords them the opportunity to input their address information:

Update the RegisterViewModel to Include Address Info:
public class RegisterViewModel
{
    [Required]
    [EmailAddress]
    [Display(Name = "Email")]
    public string Email { get; set; }
  
    [Required]
    [StringLength(100, ErrorMessage = 
        "The {0} must be at least {2} characters long.", MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(Name = "Password")]
    public string Password { get; set; }
  
    [DataType(DataType.Password)]
    [Display(Name = "Confirm password")]
    [Compare("Password", ErrorMessage = 
        "The password and confirmation password do not match.")]
    public string ConfirmPassword { get; set; }
  
    // Add the new address properties:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
}

 

Update the Register View to Include Address Information

Obviously, we want users to be able to input address information when they sign up. The Register.cshtml View is located in the Views => Accounts folder in the Solution Explorer. Update as follows:

Update the Register View with Address Information:
@model IdentitySample.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("Register", "Account", FormMethod.Post, 
    new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.City, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.State, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.PostalCode, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.PostalCode, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Register" />
        </div>
    </div>
}
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

We can see in the yellow highlighted area above where we have added the appropriate fields to our view template.

Update the Register Method on AccountController

Now we need to make sure the Address info is saved when the form data is submitted. Update the Register() method on the AccountController:

Update the Register Method on AccountController:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser 
        { 
            UserName = model.Email, 
            Email = model.Email 
        };
  
        // Add the Address properties:
        user.Address = model.Address;
        user.City = model.City;
        user.State = model.State;
        user.PostalCode = model.PostalCode;
 
        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            var code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id);
            var callbackUrl = Url.Action("ConfirmEmail", "Account", 
                new { userId = user.Id, code = code }, protocol: Request.Url.Scheme);
            await UserManager.SendEmailAsync(user.Id, 
                "Confirm your account", 
                "Please confirm your account by clicking this link: <a href=\"" 
                + callbackUrl + "\">link</a>");
            ViewBag.Link = callbackUrl;
            return View("DisplayEmail");
        }
        AddErrors(result);
    }
  
    // If we got this far, something failed, redisplay form
    return View(model);
}

 

Update the Users Admin Components to Use the New Properties

Now, the basic registration functionality has been updated to utilize the new Address properties. However, the Identity Samples project also provides some administrative functionality by which a member of the Admin role can view and edit user information.

We need to update a few ViewModels, Views, and Controller methods here as well.

Update the UsersAdmin/Create.cshtml User View

In the Views => UsersAdmin folder, the Create.cshtml View uses the now-familiar RegisterViewModel to allow system administrators to add new users to the system. We want to afford data entry of Address information here, too:

Update the UsersAdmin/Create.cshtml View:
@model IdentitySample.Models.RegisterViewModel
@{
    ViewBag.Title = "Create";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("Create", "UsersAdmin", FormMethod.Post, 
    new { @class = "form-horizontal", role = "form" }))
{
    @Html.AntiForgeryToken()
    <h4>Create a new account.</h4>
    <hr />
    @Html.ValidationSummary("", new { @class = "text-error" })
    <div class="form-group">
        @Html.LabelFor(m => m.Email, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
        </div>
    </div>
        <div class="form-group">
        @Html.LabelFor(m => m.Address, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.City, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.State, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.PostalCode, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.PostalCode, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.Password, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.ConfirmPassword, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.PasswordFor(m => m.ConfirmPassword, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <label class="col-md-2 control-label">
            Select User Role
        </label>
        <div class="col-md-10">
            @foreach (var item in (SelectList)ViewBag.RoleId)
            {
                <input type="checkbox" name="SelectedRoles" 
                    value="@item.Value" class="checkbox-inline" />
                @Html.Label(item.Value, new { @class = "control-label" })
            }
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Create" />
        </div>
    </div>
}
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

Again, we can see where we need to update the View by the highlighted area above.

Update the Edit User ViewModel

The EditUserViewModel is used by the UserAdminController and associated Views to support editing user information. We need to include any new properties here that we want to be able to edit. The EditUserViewModel is defined in the AdminViewModels.cs code file. Update as follows:

Update the EditUserViewModel:
public class EditUserViewModel
{
    public string Id { get; set; }
  
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "Email")]
    [EmailAddress]
    public string Email { get; set; }
  
    // Add the Address Info:
    public string Address { get; set; }
    public string City { get; set; }
    public string State { get; set; }
  
    // Use a sensible display name for views:
    [Display(Name = "Postal Code")]
    public string PostalCode { get; set; }
  
    public IEnumerable<SelectListItem> RolesList { get; set; }
}

 

Update the EditUser.cshtml View

Now that our EditUserViewModel has been updated, we again need to add the corresponding fields to the EditUser.cshtml View, also located in the Views => UsersAdmin folder:

Update the EditUser.cshtml View:
@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        <h4>Edit User Form.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
               @Html.TextBoxFor(m => m.Email, new { @class = "form-control" })
               @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Address, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.Address, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.City, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.City, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.State, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.State, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.PostalCode, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(m => m.PostalCode, new { @class = "form-control" })
            </div>
        </div>
        <div class="form-group">
            @Html.Label("Roles", new { @class = "control-label col-md-2" })
            <span class=" col-md-10">
                @foreach (var item in Model.RolesList)
                {
                    <input type="checkbox" name="SelectedRole" value="@item.Value" checked="@item.Selected" class="checkbox-inline" />
                    @Html.Label(item.Value, new { @class = "control-label" })
                }
            </span>
        </div>
  
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

Update the Users Admin Index, Delete, and Detail Views

For the UserAdmin Index, Delete, and Detail Views, we are going to do something a little different. We will use the DisplayAddress property to concatenate the address info into a single line suitable for display in a table or single form label. For the sake of brevity, we will only update the Index view here, also found in Views => UsersAdmin:

Update the UsersAdmin Index.cshtml View:
@model IEnumerable<IdentitySample.Models.ApplicationUser>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.UserName)
        </th>
        @*Add a table header for the Address info:*@
        <th>
            @Html.DisplayNameFor(model => model.DisplayAddress)
        </th>
        <th>
  
        </th>
    </tr>
  
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.UserName)
            </td>
            <td>
                @*Add table data for the Address info:*@
                @Html.DisplayFor(modelItem => item.DisplayAddress)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
  
</table>

 

We can see in the above that all we really did was add a table header element and a table data element to display the DisplayAddress data (which is a function masquerading as a property). The very same thing can be done for the Delete.cshtml View and the  Details.cshtml View, so we wont do that here.

Update the User Admin Controller

Now that we have updated the relevant ViewModel and Views, we also need to update the corresponding controller actions on the UserAdminController so that model data is properly passed to and from the Views. Specifically, we need to modify the Create() and Edit() methods.

Update the Create Method on UserAdminController

The create method allows an administrator to create a new system user. Add the functionality to include the new Address properties when the new user is created:

Modified Create method on UserAdminController:
[HttpPost]
public async Task<ActionResult> Create(RegisterViewModel userViewModel, params string[] selectedRoles)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser 
        { 
            UserName = userViewModel.Email, Email = 
            userViewModel.Email, 
            // Add the Address Info:
            Address = userViewModel.Address,
            City = userViewModel.City,
            State = userViewModel.State,
            PostalCode = userViewModel.PostalCode
        };
  
        // Add the Address Info:
        user.Address = userViewModel.Address;
        user.City = userViewModel.City;
        user.State = userViewModel.State;
        user.PostalCode = userViewModel.PostalCode;
  
        // Then create:
        var adminresult = await UserManager.CreateAsync(user, userViewModel.Password);
  
        //Add User to the selected Roles 
        if (adminresult.Succeeded)
        {
            if (selectedRoles != null)
            {
                var result = await UserManager.AddToRolesAsync(user.Id, selectedRoles);
                if (!result.Succeeded)
                {
                    ModelState.AddModelError("", result.Errors.First());
                    ViewBag.RoleId = new SelectList(await RoleManager.Roles.ToListAsync(), "Name", "Name");
                    return View();
                }
            }
        }
        else
        {
            ModelState.AddModelError("", adminresult.Errors.First());
            ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
            return View();
        }
        return RedirectToAction("Index");
    }
    ViewBag.RoleId = new SelectList(RoleManager.Roles, "Name", "Name");
    return View();
}

 

Next, update the Edit() method in a similar manner. First, we need to populate the EditUserViewModel with the Address info before we pass it to the View from the [Get] method:

Modified [Get] Implementation for UserAdmin Controller Edit Method:
public async Task<ActionResult> Edit(string id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var user = await UserManager.FindByIdAsync(id);
    if (user == null)
    {
        return HttpNotFound();
    }
  
    var userRoles = await UserManager.GetRolesAsync(user.Id);
  
    return View(new EditUserViewModel()
    {
        Id = user.Id,
        Email = user.Email,
        // Include the Addresss info:
        Address = user.Address,
        City = user.City,
        State = user.State,
        PostalCode = user.PostalCode,
        RolesList = RoleManager.Roles.ToList().Select(x => new SelectListItem()
        {
            Selected = userRoles.Contains(x.Name),
            Text = x.Name,
            Value = x.Name
        })
    });
}

 

Then, we need to update the [Post] override. Take careful note, though, to also include the additional bindings in the arguments:

Modified Edit Method on UserAdminController:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(Include = 
    "Email,Id,Address,City,State,PostalCode")] 
    EditUserViewModel editUser, params string[] selectedRole)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindByIdAsync(editUser.Id);
        if (user == null)
        {
            return HttpNotFound();
        }
 
        user.UserName = editUser.Email;
        user.Email = editUser.Email;
        user.Address = editUser.Address;
        user.City = editUser.City;
        user.State = editUser.State;
        user.PostalCode = editUser.PostalCode;
  
        var userRoles = await UserManager.GetRolesAsync(user.Id);
        selectedRole = selectedRole ?? new string[] { };
        var result = await UserManager.AddToRolesAsync(user.Id, 
            selectedRole.Except(userRoles).ToArray<string>());
  
        if (!result.Succeeded)
        {
            ModelState.AddModelError("", result.Errors.First());
            return View();
        }
        result = await UserManager.RemoveFromRolesAsync(user.Id, 
            userRoles.Except(selectedRole).ToArray<string>());
        if (!result.Succeeded)
        {
            ModelState.AddModelError("", result.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    ModelState.AddModelError("", "Something failed.");
    return View();
}

 

Role-Based Authorization is Already Implemented

When we looked at customizing Identity 1.0 in the article Extending Identity User and Implementing Role-Based Authorization, we needed significantly modify the basic project in order to assign users to roles. There was no provision in the default ASP.NET MVC project to directly manage user-role assignment.

The Identity Samples project addressed this deficiency, and has implemented user/role management out of the box. Where we previously needed to roll our own controller methods, models, and views in order to display and select roles for each user, this functionality is now included out of the box, in a manner very similar to what we had to do ourselves previously. However, what this means is that we need to make sure we initialize the application with a pre-built admin user.

Take Note of The IdentityConfig File

Within the Identity Samples project, database initialization and seeding is handled in the App_Start => IdentityConfig.cs file. For now, we don't need to make any changes to this file. However, note the ApplicationDbInitializer class, in which we define an initial Admin user, and initial Role, and a few other database configuration items:

The ApplicationDbInitializer Class in IdentityConfig.cs
public class ApplicationDbInitializer 
    : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
    protected override void Seed(ApplicationDbContext context) {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }
  
    //Create User=Admin@Admin.com with password=Admin@123456 in the Admin role        
    public static void InitializeIdentityForEF(ApplicationDbContext db) {
        var userManager = 
            HttpContext.Current.GetOwinContext()
                .GetUserManager<ApplicationUserManager>();
        var roleManager = 
            HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
        const string name = "admin@example.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";
  
        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);
        if (role == null) {
            role = new IdentityRole(roleName);
            var roleresult = roleManager.Create(role);
        }
  
        var user = userManager.FindByName(name);
        if (user == null) {
            user = new ApplicationUser { UserName = name, Email = name };
            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }
  
        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);
        if (!rolesForUser.Contains(role.Name)) {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}

 

Also note in the above that, in the default code the ApplicationDbInitializer class is derived from DropCreateDatabaseIfModelChanges<ApplicationDbContext> . As the name implies, this will cause a new database to be created, replacing the old, only when changes to the model impact the database schema. Often, this is sufficient. However, sometimes, it is handy to start with a fresh database each time the project is run. In these cases, we can simply change the class declaration to derive from DropCreateDatabaseAlways<ApplicationDbContext> which again, as the name implies, will cause a fresh database to be initialized each time the application is run.

For now, all we need to be aware of is the default admin user defined in the InitializeIdentityForEF() method, so we know how to log in for the first time.

Running the Project with the IdentityUser Modifications in Place

Thus far, all we have really done is add a few new properties to the existing implementation of the ApplicationUser model, and updated the corresponding ViewModels, Views, and Controllers. However, to extend IdentityUser in this manner, that is all that is needed. The project should now run, and the Address properties we have added should be properly represented within our application.

If we run the project, log in as the user defined in the IdentityConfig.cs file, we have admin access to the users and roles:

Logged In to the Identity Samples Application:

initial-login-modified-user

If we select the UsersAdmin tab, we find a list, which includes (to this point) only the seeded admin user, and in fact the Address information is blank. That's because we didn't seed any address values:

The Users Admin Tab of the Identity Samples Project with Empty Address:

select-users-admin-before-edit

We can now navigate to the Edit View, and update our initial user with some address information:

Edit the Default Admin User and Add Address Info:

edit-user-before-save

Once we have entered our address information and save, the list is updated, and the Users Admin Index view displays the updated info:

The Updated Users Admin Tab:

admin-user-index-after-edit

Things should work in a similar manner if we were to create a new user by navigating to the Create New link on the Admin Users tab, and also if we were to log out, and register as a new user.

Extending the basic IdentityUser implementation was simple enough, and it appears the Identity Samples project was fairly designed with this in mind. However, when it comes to extending or modifying the IdentityRole implementation, things become a little more complicated.

Extending Identity Role

As we noted earlier, the code Identity 2.0 framework was designed with a great deal of flexibility in mind, through the use of generic types and generic methods in the base classes used for key components. We saw previously how this creates a very flexible set of models, but working with them can be a little tricky when they become interdependent.

We want to use our existing, modified Identity Samples project, and add further customizations to the Identity Role implementation. Note that out of the box, Identity Samples does not define an ApplicationRole class - the project relies upon the basic IdentityRole provided by the framework itself.

The Identity Samples project simply uses the default implementation of the IdentityRole class defined in the namespace Microsoft.AspNet.Identity.EntityFramework. As we saw earlier, the default definition for IdentityRole looks like this:

The Default IdentityRole Implementation:
public class IdentityRole : IdentityRole<string, IdentityUserRole>
{
    public IdentityRole()
    {
        base.Id = Guid.NewGuid().ToString();
    }
  
    public IdentityRole(string roleName) : this()
    {
        base.Name = roleName;
    }
}

 

Again, as we discussed earlier, the Identity team has created a sensible default implementation by deriving from IdentityRole<TKey, TUserRole>, passing in a string and the Identity framework type IdentityUserRole as type arguments to the generic class definition.

If we wish to extend this implementation to include some custom properties, we will need to define our own. We can do this by inheriting directly from the default, and adding one or more of our own properties. We could, alternatively, start from the bottom and create our own implementation by deriving from IdentityRole<Tkey, TUserRole> but for our purposes here, we have no reason to start that low in the abstraction chain. We are sticking with the default string key type, and the basic IdentityUserRole.

A Note About IdentityRole and IdentityUserRole

Let's pause for a second to note a potential point of confusion. The Identity framework defines two seemingly similar classes, IdentityRole and IdentityUserRole. At the lowest framework implementation level, both are generic classes which implement specific interfaces. As suggested above, the generic implementation of IdentityRole looks like this:

Base implementation of the IdentityRole Class in Identity Framework 2.0:
public class IdentityRole<TKey, TUserRole> : IRole<TKey>
where TUserRole : IdentityUserRole<TKey>
{
    public TKey Id
    {
        get
        {
            return JustDecompileGenerated_get_Id();
        }
        set
        {
            JustDecompileGenerated_set_Id(value);
        }
    }
    public string Name
    {
        get;
        set;
    }
    public ICollection<TUserRole> Users
    {
        get
        {
            return JustDecompileGenerated_get_Users();
        }
        set
        {
            JustDecompileGenerated_set_Users(value);
        }
    }
    public IdentityRole()
    {
        this.Users = new List<TUserRole>();
    }
}

 

Meanwhile, the generic base IdentityUserRole class looks like this:

The Generic Base Implementation for IdentityUserRole:
public class IdentityUserRole<TKey>
{
    public virtual TKey RoleId
    {
        get;
        set;
    }
    public virtual TKey UserId
    {
        get;
        set;
    }
    public IdentityUserRole()
    {
    }
}

 

We don't need to worry overly much about these low-level details for our purposes here, other than to note that IdentityRole and IdentityUserRole are two different classes, with two different purposes. IdentityRole represents an actual Role entity in our application and in the database, while IdentityUserRole represents the relationship between a User and a Role.

With the similar names, it is easy to confuse one with the other in the midst of typing out code, and particularly when relying on VS intellisense. Of course, the compiler will let you know if you confuse the two, but it is still good to remain cognizant of the distinction, particularly when attempting more advanced customizations.

Adding a Customized Role to the Identity Samples Project

Bearing all of the above in mind, let's add a modified Role definition to our project. In keeping with the convention used for the project implementation of IdentityUser ("ApplicationUser"), we will add a class to the IdentityModels.cs file named ApplicationRole which inherits from IdentityRole and implements a custom Description property:

A Custom Implementation Derived from the Default IdentityRole Class:
public class ApplicationRole : IdentityRole
{
    public ApplicationRole() : base() { }
    public ApplicationRole(string name) : base(name) { }
    public string Description { get; set; }
}

 

Now, that wasn't too bad, right? Well, we're only just getting started here. Since we are no longer using the default IdentityRole implementation, if we want to actually USE our custom class in the Identity Samples project, we need to introduce some non-trivial changes in a number of places.

Re-Implementing RoleStore and ApplicationRoleManager

First off, if we take another look at the App_Start => IdentityConfig.cs file, we find a class definition for ApplicationRoleManager:

Existing Implementation of ApplicationRoleManager in Identity Samples Project:
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
    public ApplicationRoleManager(IRoleStore<IdentityRole,string> roleStore)
        : base(roleStore)
    {
    }
  
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new RoleStore<IdentityRole>(context.Get<ApplicationDbContext>()));
    }
}

 

As we can see, this class is rather heavily dependent upon the default framework type IdentityRole. We will need to replace all of the references to the IdentityRole type with our own ApplicationRole implementation.

Modified ApplicationRoleManager Class Depends on Custom ApplicationRole:
public class ApplicationRoleManager : RoleManager<ApplicationRole>
{
    public ApplicationRoleManager(
        IRoleStore<ApplicationRole,string> roleStore)
        : base(roleStore)
    {
    }
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, IOwinContext context)
    {
        return new ApplicationRoleManager(
            new RoleStore<ApplicationRole>(context.Get<ApplicationDbContext>()));
    }
}

 

Also, in the InitializeDatabaseForEF() method in our ApplicationDbInitializer class (also in the IdentityConfig.cs file), we need to initialize a new ApplicationRole instead of a new IdentityRole:

Initialize ApplicationRole in Database Set-Up:
public static void InitializeIdentityForEF(ApplicationDbContext db) {
    var userManager = 
        HttpContext.Current.GetOwinContext().GetUserManager<ApplicationUserManager>();
    var roleManager = 
        HttpContext.Current.GetOwinContext().Get<ApplicationRoleManager>();
    const string name = "admin@example.com";
    const string password = "Admin@123456";
    const string roleName = "Admin";
  
    //Create Role Admin if it does not exist
    var role = roleManager.FindByName(roleName);
    if (role == null) {
        role = new ApplicationRole(roleName);
        var roleresult = roleManager.Create(role);
    }
   
    var user = userManager.FindByName(name);
    if (user == null) {
        user = new ApplicationUser { UserName = name, Email = name };
        var result = userManager.Create(user, password);
        result = userManager.SetLockoutEnabled(user.Id, false);
    }
  
    // Add user admin to Role Admin if not already added
    var rolesForUser = userManager.GetRoles(user.Id);
    if (!rolesForUser.Contains(role.Name)) {
        var result = userManager.AddToRole(user.Id, role.Name);
    }
}

 

Update the Create Method on the Roles Admin Controller

Similar to the InitializeDatabaseForEF() method, we also need to properly initialize a new instance of ApplicationRole instead of IdentityRole in the Create() method on the RolesAdminController:

Update the Create Method on RolesAdminController:
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        // Initialize ApplicationRole instead of IdentityRole:
        var role = new ApplicationRole(roleViewModel.Name);
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

 

Now we need to make sure we can consume our new and improved Role implementation in our Views. As we did with the modified ApplicationUser class, we now need to accommodate or new Role implementation in our ViewModels and View, and make sure we are passing the property data between the controllers and views appropriately.

Add Extended Properties to the RoleViewModel

In the AdminViewModels.cs file, we need to update the definition for RoleViewModel by adding our new Description property:

The Updated RoleViewModel:
public class RoleViewModel
{
    public string Id { get; set; }
    [Required(AllowEmptyStrings = false)]
    [Display(Name = "RoleName")]
    public string Name { get; set; }
    public string Description { get; set; }
}

 

Next, we need to make sure the appropriate views make the Description property available for display and/or form entry.

Update the Roles Admin Create.cshtml View

The Views we need to update are in the Views => RolesAdmin folder in the VS Solution Explorer.

Similar to what we did with the Views for User Admin, we need to make the Description property available so that when administrators create new Roles, they can also enter and save the description. Add a form element to the Views => RolesAdmin => Create.cshtml view for the Description property:

The Updated Create.cshtml View for Role Admin:
@model IdentitySample.Models.RoleViewModel
  
@{
    ViewBag.Title = "Create";
}
  
<h2>Create.</h2>
  
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
  
    <div class="form-horizontal">
        <h4>Role.</h4>
        <hr />
        @Html.ValidationSummary(true)
  
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Description, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

Update the Roles Admin Edit.cshtml View

Next, we do a similar modification to the Views => RolesAdmin => Edit.cshtml View:

The Modified Roles Admin Edit.cshtml View:
@model IdentitySample.Models.RoleViewModel
  
@{
    ViewBag.Title = "Edit";
}
  
<h2>Edit.</h2>
  
@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()
      
    <div class="form-horizontal">
        <h4>Roles.</h4>
        <hr />
        @Html.ValidationSummary(true)
        @Html.HiddenFor(model => model.Id)
        <div class="form-group">
            @Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Name, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Name)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Description, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextBoxFor(model => model.Description, new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.Description)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}
  
<div>
    @Html.ActionLink("Back to List", "Index")
</div>
  
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

Update the Roles Admin Index.cshtml View

Unlike the Create and Edit Views, the Index, Delete, and Detail views need a few additional adjustments. Note that, in their existing form, these three View templates expect an instance of List<IdentityRole> as the model to be passed from the controller. We need to change the very first line of code to expect an IEnumerable<ApplicationRole> instead. Then, we make a relatively simple addition to the table header and table row elements in order to display the Description property:

The Updated Roles Admin Index.cshtml View
@model IEnumerable<IdentitySample.Models.ApplicationRole>
  
@{
    ViewBag.Title = "Index";
}
  
<h2>Index</h2>
  
<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.Name)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.Description)
        </th>
        <th>
        </th>
    </tr>
  
    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.Name)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.Description)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
                @Html.ActionLink("Details", "Details", new { id = item.Id }) |
                @Html.ActionLink("Delete", "Delete", new { id = item.Id })
            </td>
        </tr>
    }
  
</table>
    
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

 

The Delete.cshtml and the Details.cshtml Views can be modified in a similar fashion, so we won't do that here in the interest of brevity. Note, however, that for the Index View, the View expects an IEnnumerable<ApplicationRole> whereas the Details and Delete Views will expect a singular instance of ApplicationRole.

Updating The Roles Admin Controller

In order for all this to work, we now need to modify the Create() and Edit() methods on the RolesAdminController so that the proper data is passed to and from the corresponding Views, and properly persisted to the backing store.

Update the Create Method on the Roles Admin Controller

The create method receives an instance of ApplicationRole as form data and persists the new Role to the database. All we need to do here is make sure the new Description data is also saved:

The Updated Create Method on Roles Admin Controller:
[HttpPost]
public async Task<ActionResult> Create(RoleViewModel roleViewModel)
{
    if (ModelState.IsValid)
    {
        var role = new ApplicationRole(roleViewModel.Name);
  
        // Save the new Description property:
        role.Description = roleViewModel.Description;
        var roleresult = await RoleManager.CreateAsync(role);
        if (!roleresult.Succeeded)
        {
            ModelState.AddModelError("", roleresult.Errors.First());
            return View();
        }
        return RedirectToAction("Index");
    }
    return View();
}

 

Update the Edit Method on the Roles Admin Controller

There are a few additional items to tend to with the Edit() method. First off, we need to populate the form with the current data in the GET request, then, when the POST comes back, we need to bind the appropriate form data, and make sure the new Description property is saved along with everything else.

Update the Edit Method on Roles Admin Controller:
public async Task<ActionResult> Edit(string id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    var role = await RoleManager.FindByIdAsync(id);
    if (role == null)
    {
        return HttpNotFound();
    }
    RoleViewModel roleModel = new RoleViewModel 
    { 
        Id = role.Id, 
        Name = role.Name 
    };
  
    // Update the new Description property for the ViewModel:
    roleModel.Description = role.Description;
    return View(roleModel);
}
  
  
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Edit([Bind(
    Include = "Name,Id,Description")] 
    RoleViewModel roleModel)
{
    if (ModelState.IsValid)
    {
        var role = await RoleManager.FindByIdAsync(roleModel.Id);
        role.Name = roleModel.Name;
  
        // Update the new Description property:
        role.Description = roleModel.Description;
        await RoleManager.UpdateAsync(role);
        return RedirectToAction("Index");
    }
    return View();
}

 

Running the Project with Role Modifications in Place

If we have been careful as we updated all of our views and controllers, we should now be able to see our extended role in action. If we run the project, log in, and navigate to the RoleAdmin tab, we find a list containing a single Role. The description field is blank at this point, because we didn't add a description to the pre-defined initial role in our seed method.

The Roles Admin Tab at Startup:

role-admin-index

If we choose to Edit the existing role, we see we can type in a new description:

The Edit Role View - Data Entry:

edit-role-before-save

Once we save the new entry, we can see that the Index View now displays the Role Description:

The Roles Admin Index View After Edit:

role-admin-index-after-save

Wrapping It Up

In this article, we've taken a look at how to extend and modify the key IdentityUser and IdentityRole components of the Identity 2.0 framework. We have Done so in the context of the Identity Samples project, which provides a strong platform for both learning how to implement Identity 2.0 for basic authentication and authorization purposes, as well as a great foundation for the Identity portion of your own web application.

Items to keep in mind are:

The Identity Samples project is an alpha release, and is likely to evolve over time - there may be future changes which impact the specifics of this article.

Identity 2.0 RTM brings substantial flexibility and a host of additional capabilities to the ASP.NET platform, capabilities which until now had been notably missing. We have only scratched the surface here.

Identity 2.0 and the Identity Samples project present a simplified abstraction over a more flexible, and more complex underlying model. Customization is possible to a greater degree, with fewer hack-like work-arounds. However, the steps necessary are not necessarily immediately apparent. The framework of generically-typed components requires some additional thought when customizing due to the inter-dependent nature of the components.

As developers, it is within our power to dig in and explore, and figure things out. We get to do a lot of that as we set out to maximize the benefit of Identity 2.0 to our applications, and learn the new framework in general.

Additional Resources and Items of Interest

Identity 2.0 Resources:

 

Posted on June 22 2014 03:49 PM by jatten     

Comments (24)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by