Code First EF 4.1 with the Altairis Membership/Role Provider

Problem with Current Membership/Role Provider

I’ve always thought the default membership/role provider for ASP.Net is a bit heavy in that it is targeted by default to a different database than the main application database and takes several additional steps to set up and deploy. I found the Altairis Web Security Toolkit on CodePlex and it has a nice, simple schema that is easy to integrate that into your application database. It is also available from Nuget as Altairis.Web.Security.

Working with the EF Magic Unicorn Edition

1The new Entity Framework Code First, also known as the EF Magic Unicorn Edition” provides a nice way to generate the database and the ORM from POCO (Plain Old CLR Objects) classes that you write up front. This gives a nice “code first” approach where the domain objects drive the database rather than vice-versa (also known as “persistence ignorance”).

With the current edition of Magic Unicorn the database is constantly being regenerated during development, which interrupts the workflow to do the additional step of adding the membership/role tables. And even if that’s not too much trouble the membership/role tables aren’t accessible to the code first EF context for access by the application.

Code First Integration with the Drop-in Altairis Provider

One solution, besides writing a provider from scratch, is to create code first classes that generate the database schema that is expected by the drop-in membership provider.  That way the membership provider still works as expected without change, and the user/role classes are available to the application for doing administration and easily adding additional columns for the User table.

To implement this I did the following steps for an MVC3 project:

  1. Start a new MVC3 web application project.
  2. Use the NuGet console to download the following packages: Altairis.Web.Security EntityFramework
  3. Create User and Role classes and include them in a class that defines the EF context. In the context class I did a few special things to make sure that the many-to-many table for the roles was generated correctly:
public class ApplicationDB : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Role> Roles { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder) {
        // Maps to the expected many-to-many join table name for roles to users.
        modelBuilder.Entity<User>()
        .HasMany(u => u.Roles)
        .WithMany(r => r.Users)
        .Map(m =>
        {
            m.ToTable("RoleMemberships");
            m.MapLeftKey("UserName");
            m.MapRightKey("RoleName");
        });
    }
}

Data may also be seeded into the initial database, for example to create some default roles. The commented out class definition can be utilized during development to recreate the database if the model changes:

// Change the base class as follows if you want to drop and create the database during development:
// public class DBInitializer : DropCreateDatabaseIfModelChanges<ApplicationDB>
public class DBInitializer : CreateDatabaseIfNotExists<ApplicationDB>
{
    protected override void Seed(ApplicationDB context)
    {
        var roles = new List<Role>{
            new Role{RoleName = "Administrator"},
            new Role{RoleName = "User"},
            new Role{RoleName = "PowerUser"}
        };

        roles.ForEach(r => context.Roles.Add(r));
    }
}

The classes themselves are straightforward and can be decorated with attributes that integrate well with MVC:

public class User {
    [Key]
    [Required(ErrorMessage = "User Name is required")]
    [Display(Name="User Name")]
    [MaxLength(100)]
    public string UserName { get; set; }

    [Required]
    [MaxLength(64)]
    public byte[] PasswordHash { get; set; }

    [Required]
    [MaxLength(128)]
    public byte[] PasswordSalt { get; set; }

    [Required(ErrorMessage = "Email is required")]
    [MaxLength(200)]
    public string Email { get; set; }

    [MaxLength(200)]
    public string Comment { get; set; }

    [Display(Name = "Approved?")]
    public bool IsApproved { get; set; }

    [Display(Name = "Crate Date")]
    public DateTime DateCreated { get; set; }

    [Display(Name = "Last Login Date")]
    public DateTime? DateLastLogin { get; set; }

    [Display(Name = "Last Activity Date")]
    public DateTime? DateLastActivity { get; set; }

    [Display(Name = "Last Password Change Date")]
    public DateTime DateLastPasswordChange { get; set; }

    public virtual ICollection<Role> Roles { get; set; }

}
public class Role {
    [Key]
    [Display(Name = "Role Name")]
    [Required(ErrorMessage = "Role Name is required")]
    [MaxLength(100)]
    public string RoleName { get; set; }

    public virtual ICollection<User> Users { get; set; }
}

Additional properties could be added to the User class and they wouldn’t interfere with the operation of the membership provider as long a they are not “Required” properties.

I wrote an example ASP.Net MVC3 application and it is available for download at Github: Download Code

Update: I found an issue with database initialization if the model is changed and the EF data access is not initialized before the classic ADO.Net access provided by Altairis. See this post for more information.


14 thoughts on “Code First EF 4.1 with the Altairis Membership/Role Provider”

Commenter
raj
April 14, 2011 at 12:56 am

How to identify the User table from the Membership provider?


Commenter
James Culbertson
April 14, 2011 at 7:10 am

The membership provider in this example (Altairis) will automatically identify the User table as that’s how it has been defined. It’s possible that a different provider will look for a different table and columns so please note that this one is specific to Altairis. The Membership provider itself is defined in the web.config (see download code).


Commenter
RWBrad
April 24, 2011 at 9:51 pm

How would you create some initial user in the DBInitializer routine?


Commenter
James Culbertson
April 24, 2011 at 10:02 pm

@RWBrad: That’s a great question and it is definitely a chicken/egg thing as you’d like to create a user with an admin role to get started. I was working on a related issue on the follow-up post on how to force the DB re-creation before the membership provider is accessed as it can cause an error otherwise. It could be done by calling the membership provider directly in that method. I’ll try that and do a follow-up on it.
EDIT: I did find that you could do this in the “Seed” class for the initializer. Here is an example:

MembershipCreateStatus status = new MembershipCreateStatus();
Membership.CreateUser(admin, password, admin@user.com);
if (status == MembershipCreateStatus.Success) {
    // Add the role to it.
    User user = context.Users.Find(admin);
    Role role = context.Roles.Find(Administrator);
    user.Roles = new List();
    user.Roles.Add(role);
}

Commenter
Adam Nagle
April 26, 2011 at 8:29 am

I dug into the Altairis TableMembershipProvider Source Code and found another way to Seed user data. I needed to do it this way because my initializer lives down in my data access layer.

using (var hmac = new System.Security.Cryptography.HMACSHA512())
{
    passwordSalt = hmac.Key;
    passwordHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));

    var users = new List
    {
        new User
        { 
            UserName = testuser, Email = testuser@gmail.com,
            PasswordHash = passwordHash,
            PasswordSalt = passwordSalt,
            IsApproved = true,
            DateCreated = DateTime.Now,
            DateLastPasswordChange = DateTime.Now,
            Roles = context.Roles.Where(x=>x.RoleName == User).ToList()
        }
    };
    users.ForEach(x => context.Users.Add(x));

Commenter
James Culbertson
April 26, 2011 at 8:36 am

@Adam,
Thanks for sharing that more direct way to create the user!


Commenter
dc
May 11, 2011 at 4:49 am

Can we have add extra properties with Required attribute onto the User entity?


Commenter
James Culbertson
May 12, 2011 at 8:45 pm

@dc: Not on the User entity because it would cause the Membership provider to break on the create method. However, a workaround could be to add the properties as not required and then use a ViewModel for the controller/view to make the property required with an attribute in the ViewModel for the purpose of validation in the view. The ViewModel could then be mapped to the User Entity in the controller. There are tools such as Automapper that can help automate that mapping.


Commenter
Michiel
August 3, 2011 at 8:23 am

Great code. I made a NuGet package based on this code, check it out here:

http://www.nuget.org/List/Packages/quickstart.mvc3.unity.ef.altairiswebsecurity


Commenter
James Culbertson
August 3, 2011 at 1:31 pm

@Michiel, Thanks for packaging that up, looks very cool! Great to have nice packages like that to start up a new project.


Commenter
Stuart
November 3, 2011 at 1:07 am

Adding role(s) to user(s) when seeding the DB…. stuck on this one for a bit.
Just what I was after:
user.Roles = new List();
Thanks.


Commenter
Tim
December 31, 2011 at 3:13 pm

finally i found your post. it does help me a lot, thanks a lot, but i have one more question? how to init the database with .mdf format instead of scf. i played around with this few hours, can’t figure out, please help me.


Commenter
Tim
December 31, 2011 at 3:56 pm

dudu, i figured out myself, thank ya


Commenter
James Culbertson
December 31, 2011 at 7:55 pm

Glad you got it figured out OK. You can control the database it creates or connects to in the connection string.