Click here to Skip to main content
15,067,335 members
Articles / Programming Languages / C#
Tip/Trick
Posted 22 Jan 2015

Tagged as

Stats

19K views
17 bookmarked

MVC and Identity Framework 2.0 (Part 2)

Rate me:
Please Sign up or sign in to vote.
4.78/5 (9 votes)
22 Jan 2015CPOL8 min read
Extending Users

Introduction

If you've just started with MVC and IF, I suggest you read the first part to understand how to start working with this framework.

After setting the project and enabling the basic features, we will probably need to work for real on this project. It means a simple email and few more features are not enough to manage real users. In general, we will need at least users first name, last name, might be gender, birthday and some other fields. To make it short, I will only include First Name, Last Name, Birthday and Gender, because it represents most of the features I want to show in this section.

Extending users

If you want to go further and look into the details about how the Microsoft Team improved the model using generics and whole set of new classes, I will add a link at the end that's a great blog that lets me understand how everything is working underneath. In this article, I'm only trying to explain the basis of customizing users and roles.

IdentityUser class

This class is the key and honestly it's pretty straightforward to extend now. Despite the fact, we will need to update many views, models and controllers (all of them require to be updated to match our changes) there's no challenge on that.

The IdentityUser class is located into a file IdentityModel.cs in the Models folder. The class itself is not directly used, our sample project is created with an ApplicationUser class that extends it.

C#
public class ApplicationUser : IdentityUser

Extending this class is nearly the easiest job ever, just add new properties.

C#
public string FirstName { get; set; }

public string LastName { get; set; }

public DateTime Birthday { get; set; }

public string Gender { get; set; } 

Now we have new properties but none of these properties are displayed anywhere. The default project created by the sample package only includes the username. So we have to update every single model that creates, updates or lists information of this class. I will include a couple of examples, but it's not the objective of this article to go through all of them.

Updating the Registration

This is the first feature we want to update or at least the first that comes to our mind at the beginning of the project. All the view models are defined in the AccountViewModels.cs file into the same folder (honestly, not something I like too much, I tend to separate POCO models from view models, they're all mixed here in the same folder). Find below my class, some properties are going to be explained later, don't worry about them now.

C#
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; }

        [Required]
        [StringLength(50, ErrorMessage = "Names must be 50 characters long maximum")]
        [Display(Name = "First Name")]
        public string FirstName { get; set; }
        
        [Required]
        [StringLength(50, ErrorMessage = "Names must be 50 characters long maximum")]
        [Display(Name = "Last Name")]
        public string LastName { get; set; }

        [Required]
        [StringLength(10, ErrorMessage = "Please enter your gender")]
        [Display(Name = "Gender")]
        public string Gender { get; set; }

        [Required]
        [DataType(DataType.Date)]
        [DisplayName("Birthday")]
        [DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:dd/MM/yyyy}")]
        public DateTime Birthday { get; set; }

        public List<Gender> Genders { get; set; }  // * Will be explained later        
    }

There're a couple of comments about my implementation. First, I needed to update the format string accepted by my application because it's Spanish and I need to fix the format, so you can see the Birthday property decorated with a very specific format. Second, feel free to change string lengths, fields required (in case you don't need to ask for birthday for example). They're all required in my case, removing [Required] from a field makes it not mandatory. Of course, it must match our database structure (we will talk about that later).

Our register model now supports new fields, they need to interact from the view so we're going to update the register view (Register.cshtml in Accounts folder)

HTML
@model IdentitySample.Models.RegisterViewModel
@{
    ViewBag.Title = "Register";
}
@section headerscripts
{

}

<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.FirstName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.FirstName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.LastName, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.LastName, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Birthday, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.EditorFor(m => m.Birthday, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        @Html.LabelFor(m => m.Gender, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.DropDownListFor(m => m.Gender, 
        new SelectList(Model.Genders, "Code", "Name", Model.Gender),
                new { @class = "form-control", id = "GenderDropDown" })
        </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")
}

Now the code is getting more interesting. Let's talk about it, you can see something very particular when I display a dropdown with Genders:

C#
@Html.DropDownListFor(m => m.Gender, new SelectList(Model.Genders, "Code", "Name", Model.Gender),
                new { @class = "form-control", id = "GenderDropDown" })

To be honest, there're several ways to display a dropdown, some people prefer to embed a SelectList in the controller and send it to the view so the view doesn't have to deal with anything, I don't think this is correct, a controller should only handle data, not visual elements even when they're related. If I send a SelectList from the controller, I'm binding my controller to a very specific control in the view which is incorrect. The best approach is to fill a list of elements in the controller and decide how to deal with them in the view.

Another concept here is I bind my list to the model. Again, I saw many times these lists passed to the view using the viewbag. I consider it also incorrect, if I have a list that's connected to the model I think it's mandatory to insert it into the model. We don't know in the future what's going to happen with some objects of the framework, like the ViewBag, that could mutate into something different or change the behavior so we should avoid it.

Updating the Controller

Once we update the view and the model, we need to bind it in the Controller. We have to update two methods, Register (Get) and Register (Post). The first method is very simple but we need to update it:

C#
public ActionResult Register()
{
    return View();
}

First, we need to bind our model, always, as we get Genders from the model.

C#
public ActionResult Register()
{
    var model = new RegisterViewModel();
    model.Genders = _userExtensionManager.GetGenders();

    return View(model);
}

The Gender class is just a model with Code and Name properties, we don't need to go into further details now, in fact, I will explain more about using NInject in a next article.

We also have to update the post to store in the ApplicationUser instance the new data that will be stored. Find below a piece of code that's the only one we need to update, the rest can remain still.

C#
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser
        {
            UserName = model.Email,
            Email = model.Email
        };
        user.FirstName = model.FirstName;
        user.LastName = model.LastName;
        user.Birthday = model.Birthday;
        user.Gender = model.Gender;

        var result = await UserManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        { ...

We just copied from the view model the new properties to the ApplicationUser instance... you could think here, wait! How's it going to be saved if we didn't do anything? Well, that's the magic of Generics used here, if you peep into the method CreateAsync of the UserManager, we see it's using a generic type.

C#
public virtual Task<IdentityResult> CreateAsync(TUser user, string password);

So it receives any kind of IdentityUser. The mapping to the database is simple, there're some fields this entity internally has and they're created at the time of creating the database. Our fields are mapped and created using a pattern that maps the best option for the C# type. I didn't follow this approach, I took the original table and created my own fields first, instead of relying on the framework, we will see it's not a good idea to do it and it's better to do it ourselves.

Even when it works if I let the framework do it for me, we don't need to recreate everything every time the structure changes. That's what the default installation does and we're going to change it in the future.

ApplicationDbContext

We're all familiar with database contexts, a very usual pattern we can find in many other frameworks. The context is instanced and initialized when it's required to handle requests to the database. The db context in IF 2 also initializes Users and Roles managers through OWin.

The default installation creates a class in IdentityModel.cs (it was very confusing to find it there at the beginning):

C#
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false)
    {
    }

    static ApplicationDbContext()
    {
        // Set the database initializer which is run once during application start
        // This seeds the database with admin user credentials and admin role
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }

    public static ApplicationDbContext Create()
    {
        return new ApplicationDbContext();
    }
}

The database initializer requires a IDatabaseInitializer<TContext> (again, flexibility through generics, it's a major change in the architecture). I won't get into details about it now.

The default initializer is built internally and it's the following:

C#
public class ApplicationDbInitializer : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 

Now we can intuit what's going to do, the name is very self-explanatory. Every time the model changes (it checks metadata created in the database), the database is recreated. So if we create more properties or update one, even the name, the first time the initializer is called, it will recreate the database. Not the best behavior, of course, but it works at least during the first stage of our development when we're just tweaking the model. Once we code more than few lines, we should change it and this is going to be the topic of the next article.

The initialization process let us update the model and basic data using simple code, in fact the default installation creates and admin user and an admin role.

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

It's easy to identify what this code does. It works pretty well for small projects and starting up but most of these features need to be customized in order to create a real application.

In general, we all have a set of schemas and conventions for our data, we don't want to use the default tables of the system so we're going to overwrite those names.

In the ApplicationDbContext, we have to override the method OnModelCreating in order to rename the tables.

C#
protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<ApplicationUser>().ToTable("[YourSchema].[User]");
            modelBuilder.Entity<IdentityRole>().ToTable("[YourSchema].[Role]");
            modelBuilder.Entity<IdentityUserRole>().ToTable("[YourSchema].[UserRole]");
            modelBuilder.Entity<IdentityUserLogin>().ToTable("[YourSchema].[UserLogin]");
            modelBuilder.Entity<IdentityUserClaim>().ToTable("[YourSchema].[UserClaim]");
}

We map every entity to a new name, it's better to leave at least the basic names to be able to identify them later (ApplicationUser --> User, IdentityRole --> Role...)

Summary

At this point, we have an MVC project with security integrated and mapped to our tables, we could extend the user and have our own properties. In this article, we just modified the registration process but there're many more references to the User in the administration area (list, edit, view, create) that need to be updated.

We also customized our tables overriding the table creation. Be careful about it, once they're created they're not going to be recreated unless we change the model. Changing the name doesn't modify the model itself.

In case you want to update the model at a different level, you can read the best article I've ever read about Identity Framework 2.0 following this link:

http://typecastexception.com/post/2014/07/13/ASPNET-Identity-20-Extending-Identity-Models-and-Using-Integer-Keys-Instead-of-Strings.aspx

Next Article

In the next article, we will go through database initialization and roles extension. Find the link below:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Maximiliano Rios
Technical Lead
Argentina Argentina
Software developer, amateur writer, sports fan. I like more travelling than breathing Smile | :) I play any existing sport on earth but I love soccer (forever). I'm working on bigdata now and the topic and future of this is more than exciting.

Comments and Discussions

 
Question_userExtensionManager.GetGenders() Pin
Clayton G19-Jul-15 15:19
professionalClayton G19-Jul-15 15:19 
AnswerRe: _userExtensionManager.GetGenders() Pin
Maximiliano Rios29-Nov-15 4:05
MemberMaximiliano Rios29-Nov-15 4:05 
GeneralRe: _userExtensionManager.GetGenders() Pin
Luis M. Teijón12-Oct-16 13:10
MemberLuis M. Teijón12-Oct-16 13:10 
GeneralMy vote of 5 Pin
Humayun Kabir Mamun25-Jan-15 20:12
MemberHumayun Kabir Mamun25-Jan-15 20:12 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.