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)

Biggy: Interface Semantics and The Interface Segregation Principle

Posted on May 19 2014 05:12 AM by jatten in Biggy, C#, Database   ||   Comments (2)

139856309_e91043c010_nOver the past few months I've been working hard on Rob Conery's Biggy project, a high-performance, in-memory query and persistence tool for .NET.

Recently, the architecture of Biggy underwent a major overhaul, implementing an interface-driven structure proposed by K.Scott Allen. The new structure created a much cleaner separation between the core, in-memory list function of Biggy itself and the backing data store.

Image by Dennis Yang  |  Some Rights Reserved

At its core, Biggy provides an in-memory, synchronized abstraction over various types of backing store. The current architecture is such that store implementations are represented by one or more interfaces and injected into the memory list as a constructor argument. Abstracting the backing store behind a set of interfaces isolates the in-memory list implementation from changes to the backing store, and also allows the list to call into the backing store without worrying about store implementation. 

It all works pretty darn well. However, I wonder if we might be approaching things from the wrong perspective in designing our interface structure.

NOTE: For additional background on Biggy, see the following:

Abstracting Backing Stores - Some Background from Biggy

In its current form, the Biggy architecture present a set of interfaces to represent a backing data store:

Interfaces to Represent Backing Stores in Biggy:
public interface IBiggyStore<T>
{
    List<T> Load();
    void Clear();     
    T Add(T item);
    List<T> Add(List<T> items);
}
  
public interface IUpdateableBiggyStore<T> : IBiggyStore<T>
{
    T Update(T item);
    T Remove(T item);
    List<T> Remove(List<T> items);
}
  
public interface IQueryableBiggyStore<T> : IBiggyStore<T>
{
    IQueryable<T> AsQueryable();
}

 

The interfaces above represent the potential capabilities of different backing stores. In general, when we work with databases, file stores, and other persistence mechanisms, we generally seek to perform the basic CRUD functions: Create new records, Read records from the store, Update existing records, and Delete records.

Different Backing Stores Afford Different Levels of Compatibility

However, not all persistence engines afford all of the above capabilities directly. For example, with a file-based JSON store, we can read the records from the file by reading the file data into memory. However, the file store itself is not directly queryable in the same sense that a relational database might be.

Nor can we (technically) Update or Delete a specific record in the middle of the file - the mechanics of the file system are such that, without essentially writing an actual database system (and dealing with some ugly, low-level file system stuff while we're at it). Since the reason for utilizing a file-based store is to keep things simple (and often, keep the data in a human-readable format), this is at cross purposes. We might as well scale up to a relational system at that point.

With a file-based store, we can Read the data from the file, and we can write data to the file, either in bulk, or by appending records to the end of the file. we can also clear the entire file. These functions are effectively represented by the IBiggyStore<T> interface. Lacking are methods to Update records, or to Remove (Delete) records. In order to Update or Remove an existing record, we are essentially stuck flushing the entire modified file back to disk.

Contrast this with a Relational store, which tends to include all of the CRUD functionality: We can query specific records or sets of records, we can INSERT, UPDATE, and DELTE records, etc. These functions are represented by the IBiggyStore<T> interface, an the additional IUpdateableBIggyStore<T> and (Potentially)IQueryableBiggyStore<T> interfaces used in conjunction.

Meaningful Backing Store Abstractions

In proposing the interface-driven architecture, K. Scott Allen indicates he was thinking in terms of the Interface Segregation Principle, as he explains in a discussion on a Github issue from the Biggy repo:

For example, Mongo [DB] is an IQueryable data source, so it might make sense to allow for effecient queries by "advertising" with an IQueryableBiggyStore. The only reason to implement this interface is if the behind the scenes store is IQueryable. A text file of data isn't IQueryable, so I would not implement the interface on a text file store. Same with the updateable interface - it makes sense for data stores that know how to update and persist individual items, which I think . . . would be tricky with a file full of JSON.

In Allen's view, different stores present different potential capabilities with respect to data access and manipulation, and the interfaces implemented should properly reflect (or "advertise") this to the client. This makes total sense, and is a solid example of the interface representing the actual capabilities available for a given store.

Under this scenario we might define a file-based store something like this:

Simplified Example of a File-Based Store Implementation:
public class ExampleFileStore<T> : IBiggyStore<T> {
  
    // ...
    // ... Local methods supporting the core file store functionality
    // ...
  
    List<T> IBiggyStore<T>.Load() {
        // ... Code to read all data from a file
    }
  
    void IBiggyStore<T>.Clear() {
        // ... Code to clear data from a file
    }
  
    T IBiggyStore<T>.Add(T item) {
        // ... Code to append a record to the end of a file
    }
  
    List<T> IBiggyStore<T>.Add(List<T> items) {
        // ... Code to append many records to the end of a file
    }
}

 

And we might similarly define a store against a relational database like so:

A Simplified Relational Store Implementation:
public class ExampleRelationalStore<T> 
    : IBiggyStore<T>, 
    IUpdateableBiggyStore<T>, 
    IQueryableBiggyStore<T> {
    // IBIGGY IMPLEMENTATION:
    List<T> IBiggyStore<T>.Load() {
        // ... Code to read all records from a table
    }
    void IBiggyStore<T>.Clear() {
        // ... Code to clear all records from a table
    }
    T IBiggyStore<T>.Add(T item) {
        // ... Code to INSERT a record into a table
    }
    List<T> IBiggyStore<T>.Add(List<T> items) {
        // ... Code to INSERT many records into a table
    }
    // UPDATEABLE IMPLEMENTATION:
    T IUpdateableBiggyStore<T>.Update(T item) {
        // ... Code to UPDATE a specific record in a table
    }
    T IUpdateableBiggyStore<T>.Remove(T item) {
        // ... Code to DELETE a specific record from a table
    }
    List<T> IUpdateableBiggyStore<T>.Remove(List<T> items) {
        // ... Code to DELETE a set of specific records from a table
    }
    // QUERYABLE IMPLMENTATION:
    IQueryable<T> IQueryableBiggyStore<T>.AsQueryable() {
        // ... Code to return an instance of IQueryable representing table records
    }
}

 

As we can see here, a relational store implementation will generally make use of all the available interface methods with the possible exception of IQueryableBiggyStore<T>. In order to properly return a queryable instance, we would need a provider which returns IQueryable<T> , such as LinqToSql, or we would need to write our own.

Our two example cases present interface-based APIs which accurately reflect the capabilities of each store, and client code can implement the functionality required by implementing the proper combination of interfaces.

A Closer Look at the Interface Segregation Principle

The Interface Segregation Principle (ISP), described as part of Robert "Uncle Bob" Martin's SOLID principles of Object-Oriented Design, states that:

"No client should be forced to depend on methods it does not use"

A potential corollary to ISP which is often cited, but for which I cannot find a direct source, is:

"Interfaces belong to the client, not the implementation"

From the perspective of defining an interface to abstract various data store implementations, the separations implied by IBiggyStore<T> , IUpdateableBiggyStore<T> , and IQueryableBiggyStore<T> make sense. Different clients can consume the featured represented by various store implementations, and the dependencies created between client and interface will indeed be consistent with ISP.

Within Biggy, though, our objective is to represent a data store as an in-memory list structure, and synchronize the data in the in-memory abstraction with that persisted in the actual store. Further, one of our primary goals in separating store implementation from in-memory list implementation is to isolate the memory list implementation from changes to the store implementation.

In other words, once we have built out an application which consumes Biggy as an in-memory data source, we want to be able to swap out (or more commonly, scale up) the backing store without impacting (or at least, minimally impacting) our application code.

Defining the BiggyList API

For the Biggy project, we have defined another interface, IBiggy<T> , which represents the core feature set required in any given BiggyList<T> implementation:

The IBiggy Interface:
public interface IBiggy<T> : IEnumerable<T>
{
    void Clear();
    int Count();
    T Update(T item);
    T Remove(T item);
    List<T> Remove(List<T> items);
    T Add(T item);
    List<T> Add(List<T> items);
    IQueryable<T> AsQueryable();
    bool InMemory { get; set; }
  
    event EventHandler<BiggyEventArgs<T>> ItemRemoved;
    event EventHandler<BiggyEventArgs<T>> ItemAdded;
    event EventHandler<BiggyEventArgs<T>> ItemsAdded;
  
    event EventHandler<BiggyEventArgs<T>> Changed;
    event EventHandler<BiggyEventArgs<T>> Loaded;
    event EventHandler<BiggyEventArgs<T>> Saved;
}

 

As we can see, any BiggyList implementation requires all of the CRUD features we saw defined previously in multiple BiggyStore interfaces. In other words, while not all store implementations will offer all required features, any Biggy list will expect some way to perform all of these actions anyway.

One of the assumptions behind the current interface architecture essentially is the injection of a store into the BiggyList implementation as a constructor argument. A simplified, generic example might look something like this:

Simplified BiggyList Implementation:
public class BiggyListExample<T> : IBiggy<T> {
  
    IBiggyStore<T> _store;
    IUpdateableBiggyStore<T> _updatableStore;
    IQueryableBiggyStore<T> _queryableStore;
  
    List<T> _items;
  
    // . . . Code for various Event Hooks . . . 
  
    // ... Other Implementation Code ...
  
    public BiggyListExample(IBiggyStore<T> store) {
        _store = store;
        _updatableStore = store as IUpdateableBiggyStore<T>;
        _queryableStore = store as IQueryableBiggyStore<T>;
  
        _items = _store.Load();
    }
  
    public void Clear() {
        _store.Clear();
        _items.Clear();
    }
  
    public int Count() {
        return _items.Count();
    }
  
    public T Update(T item) {
        return _updatableStore.Update(item);
    }
  
    public T Remove(T item) {
        _items.Remove(item);
        return _updatableStore.Remove(item);
    }
  
    public List<T> Remove(List<T> items) {
        foreach (var item in items) {
        _items.Remove(item);
        }
        _updatableStore.Remove(items);
        return items;
    }
  
    public T Add(T item) {
        _items.Add(item);
        return _store.Add(item);
    }
  
    public List<T> Add(List<T> items) {
        _items.AddRange(items);
        return _store.Add(items);
    }
  
    public IQueryable<T> AsQueryable() {
        return _queryableStore.AsQueryable();
    }
  
    public IEnumerator<T> GetEnumerator() {
        return _items.GetEnumerator();
    }
  
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
        return _items.GetEnumerator();
    }
}

 

Given the example above is a very simplified implementation, it would appear that this class could actually represent a core, base-class type implementation. Except that, under our current assumptions about stores and interfaces discussed above, not all stores will offer up all of the features required for the class above to function properly. For example, if we passed in our ExampleFileStore class defined previously, this code will fail, since ExampleFileStore does not implement IUpdateableBiggyStore<T> or IQueryableBiggyStore<T>.

We could (and did, in the current iteration of Biggy) add null checks against the store instances, so that code won't fail if we try to execute an interface method that is not present:

Checking for Null Before Calling Potentially Missing Interface Method:
public T Update(T item) {
    if (_updatableStore != null) {
        return _updatableStore.Update(item);
    }
}

 

This is even more evil, though. Now we think we have updated some data, but nothing happened.

Originally, we had defined a SaveAll() method as part of the IBiggyStore<T> interface. This made some sense when working against a file-based store, in that updating a record essentially requires re-writing the entire file to disk. With a SaveAll() method defined as part of IBiggyStore<T> , the above could be re-written (roughly):

Modified Null Check Example:
public T Update(T item) {
    if (_updatableStore != null) {
        return _updatableStore.Update(item);
    }  else {
        _store.SaveAll(_items);
        return item;
    }
}

 

The code above assumes that, by passing the entire modified list back to the store, the store contents will be over-written reflecting any changes, including updates and deletions. This works well for the file-based store. For other stores where explicit Update and/or Delete methods are available, the first part of the conditional will execute.

The trouble with this is two-fold. First off, one of the core assumptions implied by the Biggy library is that the in-memory list and the backing store are kept in sync in "Real-time" (or it should appear that way to the client, in any case). Including a SaveAll() method as part of the public interface implies that it is possible to make a series of changes before saving, or "flushing" back to the store. In other words, NOT in real time.

Secondly, SaveAll() implies a "Unit of Work" architecture due to widespread use in popular ORMs. The project owner, Rob, has explicitly opted against Unit of Work in favor of a composed transaction model, and we would be prudent to avoid using this term as part of the public store API. The goal is that Biggy will present a more transactional persistence model for writes to the store, and "Save All" implies something else.

We could, of course, create implementations for IBiggyList<T> which take into account the limitations of certain stores, and accomplish through brute force what cannot be provided by the basic interface. But this, to me, limits the effectiveness of our interface architecture.

In a perfect world, it seems we should be able to create a single, basic BiggyList implementation which is unaffected by moving from one store to the next. Special cases, such as stores and lists which are capable of using IQueryable, could then be created using specialized interfaces. But we should be able to assume core CRUD functionality, and compatibility within Biggy for all the various stores.

Re-thinking the Interface Structure from a Different Perspective

It seems like we have some decisions to make here. On the one hand, defining the store interfaces according to the available functionality makes 100% sense, and is probably in keeping with "proper" Object-Oriented Design principles. After all, if it is not technically possible (for example) to UPDATE a specific item in the store without re-writing the entire list back to disk, then should we be advertising that capability with and Update(item) method?

This would make sense if we were defining our stores as a straight-forward data-access library. However, we are explicitly defining store interfaces to provide compatibility with the Biggy library. Since "the interface belongs to the client, not the implementation" perhaps we should consider meeting the needs of the client first.

The over-arching premise of Biggy as a library is to provide an in-memory abstraction over a data store, along with all of the things we might expect to do with such a store. When we access data from any source, we expect some version of the basic CRUD functionality to be available.

Different stores may present other capabilities beyond the basic CRUD (the IQueryable returned by Mongo and/or LinqToSql is an example), but the basic ability to Create, Read, Update, and Delete records is a core function we expect to do from the context of a Biggy in-memory list. In cases where a certain store does not directly support one or more of the basic CRUD features, we might instead opt to implement the brute-force approach at the store implementation level. In other words, in order to be consumable by Biggy, and store must be compliant with the minimum feature set required by IBiggy<T> .

I propose combining the former IBiggyStore<T> and IUpdateableBiggyStore<T> :

Proposed Store Interface Organization:
public interface IBiggyStore<T> {
    List<T> Load();
    void Clear();
    T Add(T item);
    List<T> Add(List<T> items);
    T Update(T item);
    T Remove(T item);
    List<T> Remove(List<T> items);
}
  
    
public interface IQueryableBiggyStore<T> : IBiggyStore<T> {
    IQueryable<T> AsQueryable();
}

 

We can leave the IQueryableBiggyStore<T> interface as a distinct and separate item, since:

A. Most store options do not directly return an IQueryable anyway, and;

B. The premise of Biggy is that we are working with data directly in memory, and not querying directly against the backing store. IQueryable<T> is explicitly contrary to this notion, in that you are creating queries which will execute in a deferred manner against the backing store

C. Including AsQueryable<T> as part of the basic IBiggy<T> interface implies that it will always be possible to work with the store via an IQueryable instance, when in fact this is not necessarily possible (at least in any practical sense). This here might be considered a violation of the Interface Segregation Principle, in theory if not in fact.

While we can take a brute-force approach to implement (for example) an Update() method with a file-based store by writing the entire file contents to disk (including any updated data) then reading the updated file back from disk into memory, we can't, in a practical sense, write an effective IQueryable<T> provider against a file-based store that isn't doing exactly what the file-based store already does - read file contents into an in-memory list.

This doesn't mean an IQueryableBiggyStore is not useful, nor that using IQueryable from a BiggyList is not a desirable option. Simply, that to do so may be a case where one should "opt-in" through use of a specialized store, interface, and, potentially, a specialized implementation of IBiggy<T>.

In cases where the IQueryable option is desired, it almost makes sense to create a specialized IBiggy<T> implementation for this purpose, since the usage may differ significantly from the standard implementation.

In keeping with the above, we might then find the following Biggy List interfaces:

Modified IBiggy<T> with Separate IQueryableBiggy Interface
public interface IBiggy<T> : IEnumerable<T>
{
    void Clear();
    int Count();
    T Update(T item);
    T Remove(T item);
    List<T> Remove(List<T> items);
    T Add(T item);
    List<T> Add(List<T> items);
    bool InMemory { get; set; }
  
    event EventHandler<BiggyEventArgs<T>> ItemRemoved;
    event EventHandler<BiggyEventArgs<T>> ItemAdded;
    event EventHandler<BiggyEventArgs<T>> ItemsAdded;
  
    event EventHandler<BiggyEventArgs<T>> Changed;
    event EventHandler<BiggyEventArgs<T>> Loaded;
    event EventHandler<BiggyEventArgs<T>> Saved;
}
  
  
public interface IQueryableBiggy<T> : IBiggy<T> {
    IQueryable<T> AsQueryable();
}

 

Summing Up

If we were creating our stores and attendant interfaces in the context of designing a straight-forward data-access/querying library, for direct consumption by client code, the original interface structure would likely be the right choice. The interface semantics would correctly describe the functionality each store implementation is capable of providing.

In Biggy, we have created and additional abstraction layer, the in-memory list representation of a store, and we might better define our basic store interface in terms of what a minimal BiggyList implementation will require. Then, we can build out store implementations to meet the needs of the client – in this case, the BiggyList.

I would be fascinated to here contrary views on this in the comments, or you can email me at the address in the “About the Author” section.

Additional Resources and Items of Interest

 

Posted on May 19 2014 05:12 AM by jatten     

Comments (2)

ASP.NET Identity 2.0: Setting Up Account Validation and Two-Factor Authorization

Posted on April 20 2014 04:56 AM by jatten in C#, ASP.NET MVC, CodeProject, ASP.Net   ||   Comments (4)

4097092685_cd75ff7679_zWith the release of the Identity 2.0 framework in March of 2014, the Identity team has added a significant set of new features to the previously simple, but somewhat minimal ASP.NET Identity system. Some of the most visible, and in-demand features introduced with the new release are account validation and two-factor authorization.

The Identity team has created a terrific sample project/template which can effectively serve as the starting point for just about any ASP.NET MVC project you wish to build out with Identity 2.0 features. In this post, we will look at implementing email account validation as part of the account creation process, and two-factor authorization.

Image by Lucas | Some Rights Reserved

Previously, we took a very high-level tour of some of the new features available in Identity 2.0, and how key areas differ from the previous version 1.0 release. In this article, we'll take a close look at implementing Email Account Validation, and Two-Factor Authentication.

Getting Started: Create the Sample Project Using Nuget

To get started and follow along, create an empty ASP.NET project in Visual Studio (not an MVC project, use the Empty project template when creating a new project). Then, open the Package Manager Console and type:

Install the Sample Project from Nuget:
PM> Install-Package Microsoft.AspNet.Identity.Samples -Pre

 

Once Nuget has done its thing, you should see a familiar ASP.NET MVC project structure in the Solution Explorer:

ASP.NET Identity 2.0 Sample Project in Solution Explorer:installed-project-in-solution-explorer

 

This structure should look mostly familiar if you have worked with a standard ASP.NET MVC project before. There are, however, a few new items in there, if you look closely. For our purposes in this article, we are primarily concerned with the IdentityConfig.cs file, located in the App_Start folder.

If we open the IdentityConfig.cs file and scroll through, we find two services classes defined, EmailService and SmsService:

The Email Service and SMS Service Classes:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        return Task.FromResult(0);
    }
}
  
public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        return Task.FromResult(0);
    }
}

 

Notice the both EmailService and SmsService implement the common interface, IIdentityMessageService. We can use IIdentityMessageService to create any number of Mail, SMS, or other messaging implementations. We'll se how this works momentarily.

Set Up A Default Admin Account

Before you go too much further, we need to set up a default Admin account that will be deployed when the site runs. The example project is set up so that the database is initialized when the project is run, and/or anytime the Code-First model schema changes. We need a default Admin user so that once the site runs, you can log in and do admin-type things, and you can't do THAT until you have a registered admin account.

As with the previous version of Identity, the default user account created during on-line registration has no admin privileges. Unlike the previous version, the first user created when the site is run is NOT an admin user. We need to seed the database during initialization.

Take a look at ApplicationDbInitializer class, also defined in IdentityConfig.cs:

The Application Db Initializer 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@admin.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);
        }
    }
}

 

The lines highlighted in yellow define the default admin user that will be created when the application is run for the first time. Before we proceed, change these to suit your own needs, and ideally, include a live, working email address.

Account Validation: How it works

You are no doubt familiar with email-based account validation. You create an account at some website, and a confirmation email is sent to your email address with a link you need to follow in order to confirm your email address and validate your account.

If we look at the AccountController in the example project, we find the Register method:

The Register Method on Account Controller:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Register(RegisterViewModel model)
{
    if (ModelState.IsValid)
    {
        var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
        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);
}

 

Looking close at the above, we see a call to UserManager.SendEmailAsync where we pass in some arguments. Within AccountController, UserManager is a property which returns an instance of type ApplicationUserManager. If we take a look in the IdentityConfig.cs file in the App_Start folder, we find the definition for ApplicationUserManager. On this class, the static Create() method initializes and returns a new instance of ApplicationUserManager, and this is where our messaging services are configured:

The Create() Method Defined on the ApplicationUserManager Class:
public static ApplicationUserManager Create(
    IdentityFactoryOptions<ApplicationUserManager> options, 
    IOwinContext context)
{
    var manager = new ApplicationUserManager(
        new UserStore<ApplicationUser>(
            context.Get<ApplicationDbContext>()));
  
    // Configure validation logic for usernames
    manager.UserValidator = new UserValidator<ApplicationUser>(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.
    manager.RegisterTwoFactorProvider(
        "PhoneCode", 
        new PhoneNumberTokenProvider<ApplicationUser>
    {
        MessageFormat = "Your security code is: {0}"
    });
  
    manager.RegisterTwoFactorProvider(
        "EmailCode", 
        new EmailTokenProvider<ApplicationUser>
    {
        Subject = "SecurityCode",
        BodyFormat = "Your security code is {0}"
    });
  
    manager.EmailService = new EmailService();
    manager.SmsService = new SmsService();
    var dataProtectionProvider = options.DataProtectionProvider;
  
    if (dataProtectionProvider != null)
    {
        manager.UserTokenProvider = 
            new DataProtectorTokenProvider<ApplicationUser>(
                dataProtectionProvider.Create("ASP.NET Identity"));
    }
    return manager;
}

 

The lines highlighted in yellow show where we set the Email and SMS services on the ApplicationUserManager instance. Just above the highlighted lines, we see how we register two-factor providers for Email and SMS messages.

From the above, we can see that the Register() method of AccountController calls the SendEmailAsync method of ApplicationUserManager, which has been configured with an Email and SMS service at the time it is created.

Email validation of new user accounts, two-factor authentication via email, and two-factor authentication via SMS Text all depend upon working implementations for the EmailService and SmsService classes.

Implementing the Email Service Using Your Own Mail Account

Setting up the email service for our application is a relatively simple task. You can use your own email account, or a mail service such as Sendgrid to create and send account validation or two-factor sign-in email. For example, if I wanted to use an Outlook.com email account to send confirmation email, I might configure my Email Service like so:

The Mail Service Configured to Use an Outlook.com Host:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Credentials:
        var credentialUserName = "yourAccount@outlook.com";
        var sentFrom = "yourAccount@outlook.com";
        var pwd = "yourApssword";
  
        // Configure the client:
        System.Net.Mail.SmtpClient client = 
            new System.Net.Mail.SmtpClient("smtp-mail.outlook.com");
  
        client.Port = 587;
        client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
        client.UseDefaultCredentials = false;
  
        // Creatte the credentials:
        System.Net.NetworkCredential credentials = 
            new System.Net.NetworkCredential(credentialUserName, pwd);
  
        client.EnableSsl = true;
        client.Credentials = credentials;
  
        // Create the message:
        var mail = 
            new System.Net.Mail.MailMessage(sentFrom, message.Destination);
  
        mail.Subject = message.Subject;
        mail.Body = message.Body;
  
        // Send:
        return client.SendMailAsync(mail);
    }
}

 

The precise details for your SMTP host may differ, but you  should be able to find documentation. As a general rule, though, the above is a good starting point.

Implementing the Email Service Using Sendgrid

There are numerous email services available, but Sendgrid is a popular choice in the .NET community. Sendgrid offers API support for multiple languages as well as an HTTP-based Web API. Additionally, Sendgrid offers direct integration with Windows Azure.

If you have a standard Sendgrid account (you can set up a free developer account at the Sendgrid site), setting up the Email Service is only slightly different than in the previous example. First off, your network credentials will simply use your Sendgrid user name and password. Note that in this case, your user name is different than the email address you are sending from. in fact, you can use any address as your sent from address.

The EmailService Configured to Use Sendgrid:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Credentials:
        var sendGridUserName = "yourSendGridUserName";
        var sentFrom = "whateverEmailAdressYouWant";
        var sendGridPassword = "YourSendGridPassword";
  
        // Configure the client:
        var client = 
            new System.Net.Mail.SmtpClient("smtp.sendgrid.net", Convert.ToInt32(587));
  
        client.Port = 587;
        client.DeliveryMethod = System.Net.Mail.SmtpDeliveryMethod.Network;
        client.UseDefaultCredentials = false;
  
        // Creatte the credentials:
        System.Net.NetworkCredential credentials = 
            new System.Net.NetworkCredential(credentialUserName, pwd);
  
        client.EnableSsl = true;
        client.Credentials = credentials;
  
        // Create the message:
        var mail = 
            new System.Net.Mail.MailMessage(sentFrom, message.Destination);
  
        mail.Subject = message.Subject;
        mail.Body = message.Body;
  
        // Send:
        return client.SendMailAsync(mail);
    }
}

 

In the above, we see all we really had to change were our credentials, and the SMTP host string.

With that done, we can give our email service a quick trial run.

Testing Account Confirmation Using the Email Service

First, let's run our application, and attempt to register a new user. User a working email address to which you have access. If all goes well you will be redirected to a view resembling the following:

Redirect to Confirmation Sent View:

redirect-to-confirmation-view

As you can see, there is some language there indicating that you can click the link in the view to confirm account creation for the purpose of the demo. However, an actual confirmation email should have been sent to the address you specified when creating the account, and following the confirmation link will, in fact, confirm the account and allow you to log in.

Once we have our site working properly, we will obviously want to do away with the demo link, and also change the text on this view. We'll look at that shortly.

Implementing the SMS Service

To use two-factor authentication with SMS/Text, you will need an SMS host provider, such as Twilio. Like Sendgrid, Twilio is quite popular in the .NET community, and offers a comprehensive C# API. You can sign up for a free account at the Twilio Site.

When you create a Twilio account, you will be issued an SMS phone number, an account SID, and an Auth Token. You can find your Twilio SMS phone number by logging into your account, and navigating to the Numbers tab:

Locate Twilio SMS Number:

twilio-sms-number

Likewise, you can find your SID and Auth Token on the Dashboard Tab:

Locate Twilio SID and Auth Token:

twilio-sid-and-auth-token

In the above, see the little padlock icon next to the Auth Token? If you click that, your token will be visible, and can be copied for pasting into your code.

Once you have created an account, in order to use Twilio from within your application, you will need to get the Twilio Nuget package:

Add the Twilio Nuget Package Using Package Manager Console:
PM> Install-Package Twilio

 

That done, we can add Twilio to the using statements at the top of our IdentityConfig.cs file, and then implement the SMS service as follows:

The SMS Service Using Twilio:
public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        string AccountSid = "YourTwilioAccountSID";
        string AuthToken = "YourTwilioAuthToken";
        string twilioPhoneNumber = "YourTwilioPhoneNumber";
  
        var twilio = new TwilioRestClient(AccountSid, AuthToken);
  
        twilio.SendSmsMessage(twilioPhoneNumber, message.Destination, message.Body); 
  
        // Twilio does not return an async Task, so we need this:
        return Task.FromResult(0);
    }
}

 

Now that we have both an email service and an SMS service configured, we can see if our two-factor authentication works as expected.

Testing Two-Factor Authentication

The example project is set up so that two-factor authentication is an "opt-in" feature per user. To see if everything is working properly, log in using either your default admin account, or the account you created previously when testing out the email validation feature. Once logged in, navigate to the user account area by clicking on the user name displayed top right in the browser view. You should see something like this:

The Manage User Account View:

enable-two-factor-auth

In the above window, you will want to enable two-factor authentication, and add your phone number. When you go to add your phone number, you will be sent an SMS message to confirm. If we have done everything correctly so far, this should only take a few seconds (sometimes it can be more than a few seconds, but in general, if 30 or more seconds goes by, something is probably wrong . . .).

Once two-factor auth is enabled, you can log out, and try logging in again. You will be offered a choice, via a drop-down menu, where you can choose to receive your two-factor code via email or SMS:

Select Two-Factor Authentication Method:

two-factor-choose-medium

Once you make your selection above, the two-factor code will be sent to your selected provider, and you can complete the login process.

Remove the Demo Shortcuts

As mentioned previously, the example project comes with some shortcuts built-in so that, for development and testing purposes, it is not necessary to actually send or retreive the two-factor code (or follow the account validation link from an actual email) from an email or SMS account.

Once we are ready to deploy, we will want to remove these shortcuts from the corresponding views, and also the code which passes the links/codes to the ViewBag. For example, if we take another look at the Register() method on AccountController, we can see the following code in the middle of the method:

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>");
  
    // This should not be deployed in production:
    ViewBag.Link = callbackUrl;
    return View("DisplayEmail");
}
AddErrors(result);

 

The highlighted lines should be commented out or deleted in production.

Likewise, the View that is returned by the Register() method, DisplayEmail.cshtml, will need some adjustment as well:

The DisplayEmail View:
@{
    ViewBag.Title = "DEMO purpose Email Link";
}
<h2>@ViewBag.Title.</h2>
<p class="text-info">
    Please check your email and confirm your email address.
</p>
<p class="text-danger">
    For DEMO only: You can click this link to confirm the email: <a href="@ViewBag.Link">link</a>
    Please change this code to register an email service in IdentityConfig to send an email.
</p>

 

In a similar manner, the controller method VerifyCode() also pushes the two-factor code out into the view for ease of use during development, but we absolutely don't want this behavior in production:

The Verify Code Method on AccountController:
[AllowAnonymous]
public async Task<ActionResult> VerifyCode(string provider, string returnUrl)
{
    // Require that the user has already logged in via username/password or external login
    if (!await SignInHelper.HasBeenVerified())
    {
        return View("Error");
    }
  
    var user = 
        await UserManager.FindByIdAsync(await SignInHelper.GetVerifiedUserIdAsync());
  
    if (user != null)
    {
        ViewBag.Status = 
            "For DEMO purposes the current " 
            + provider 
            + " code is: " 
            + await UserManager.GenerateTwoFactorTokenAsync(user.Id, provider);
    }
    return View(new VerifyCodeViewModel { Provider = provider, ReturnUrl = returnUrl });
}

 

Also, as with the Register() method, we want to make suitable adjustments to the VerifyCode.cshtml View:

The VerifyCode.cshtml View:
@model IdentitySample.Models.VerifyCodeViewModel
@{
    ViewBag.Title = "Enter Verification Code";
}
  
<h2>@ViewBag.Title.</h2>
  
@using (Html.BeginForm("VerifyCode", "Account", new { ReturnUrl = Model.ReturnUrl }, FormMethod.Post, new { @class = "form-horizontal", role = "form" })) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary("", new { @class = "text-danger" })
    @Html.Hidden("provider", @Model.Provider)
    <h4>@ViewBag.Status</h4>
    <hr />
    <div class="form-group">
        @Html.LabelFor(m => m.Code, new { @class = "col-md-2 control-label" })
        <div class="col-md-10">
            @Html.TextBoxFor(m => m.Code, new { @class = "form-control" })
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <div class="checkbox">
                @Html.CheckBoxFor(m => m.RememberBrowser)
                @Html.LabelFor(m => m.RememberBrowser)
            </div>
        </div>
    </div>
    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" class="btn btn-default" value="Submit" />
        </div>
    </div>
}

 

Again, the highlighted line could prove troublesome, and should be removed.

Keep Mail and SMS Account Settings Secure

The examples in this article have account user names and passwords, Auth tokes, and such hard-coded into the methods in which they are used. It is unlikely we would want to deploy this way, and equally unlikely we would even want to push our code into source control with these things exposed in such a manner (ESPECIALLY to Github!!).

Far better to put any private settings in a secure location. See Keep Private Settings Out of Source Control for more information.

The Tip of the Iceberg

ASP.NET Identity 2.0 has introduced a number of exciting new features previously not available as "out-of-the-box" features available to ASP.NET developers. Email account validation and two-factor authentication are but two of the most visible, and easily implemented (at least, within the framework of the example project provided by the Identity team!).

New features such as these have added greatly to the security arsenal available to the average developer, but have also added complexity to development of sites which utilize Identity 2.0. The Identity 2.0 team has provided some powerful security tools, but it may be easier to introduce new and harder to find security holes if we are not careful.

Additional Resources and Items of Interest

The Following Focus on Identity 1.0:

 

 

Posted on April 20 2014 04:56 AM by jatten     

Comments (4)

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