CHAPTER 13

image

Getting Started with Identity

Identity is a new API from Microsoft to manage users in ASP.NET applications. The mainstay for user management in recent years has been ASP.NET Membership, which has suffered from design choices that were reasonable when it was introduced in 2005 but that have aged badly. The biggest limitation is that the schema used to store the data worked only with SQL Server and was difficult to extend without re-implementing a lot of provider classes. The schema itself was overly complex, which made it harder to implement changes than it should have been.

Microsoft made a couple of attempts to improve Membership prior to releasing Identity. The first was known as simple membership, which reduced the complexity of the schema and made it easier to customize user data but still needed a relational storage model. The second attempt was the ASP.NET universal providers, which I used in Chapter 10 when I set up SQL Server storage for session data. The advantage of the universal providers is that they use the Entity Framework Code First feature to automatically create the database schema, which made it possible to create databases where access to schema management tools wasn’t possible, such as the Azure cloud service. But even with the improvements, the fundamental issues of depending on relational data and difficult customizations remained.

To address both problems and to provide a more modern user management platform, Microsoft has replaced Membership with Identity. As you’ll learn in this chapter and Chapters 14 and 15, ASP.NET Identity is flexible and extensible, but it is immature, and features that you might take for granted in a more mature system can require a surprising amount of work.

Microsoft has over-compensated for the inflexibility of Membership and made Identity so open and so adaptable that it can be used in just about any way—just as long as you have the time and energy to implement what you require.

In this chapter, I demonstrate the process of setting up ASP.NET Identity and creating a simple user administration tool that manages individual user accounts that are stored in a database.

ASP.NET Identity supports other kinds of user accounts, such as those stored using Active Directory, but I don’t describe them since they are not used that often outside corporations (where Active Directive implementations tend to be so convoluted that it would be difficult for me to provide useful general examples).

In Chapter 14, I show you how to perform authentication and authorization using those user accounts, and in Chapter 15, I show you how to move beyond the basics and apply some advanced techniques. Table 13-1 summarizes this chapter.

Table 13-1. Chapter Summary

Problem

Solution

Listing

Install ASP.NET Identity.

Add the NuGet packages and define a connection string and an OWIN start class in the Web.config file.

14

Prepare to use ASP.NET Identity.

Create classes that represent the user, the user manager, the database context, and the OWIN start class.

58

Enumerate user accounts.

Use the Users property defined by the user manager class.

9, 10

Create user accounts.

Use the CreateAsync method defined by the user manager class.

1113

Enforce a password policy.

Set the PasswordValidator property defined by the user manager class, either using the built-in PasswordValidator class or using a custom derivation.

1416

Validate new user accounts.

Set the UserValidator property defined by the user manager class, either using the built-in UserValidator class or using a custom derivation.

1719

Delete user accounts.

Use the DeleteAsync method defined by the user manager class.

2022

Modify user accounts.

Use the UpdateAsync method defined by the user manager class.

2324

Preparing the Example Project

I created a project called Users for this chapter, following the same steps I have used throughout this book. I selected the Empty template and checked the option to add the folders and references required for an MVC application. I will be using Bootstrap to style the views in this chapter, so enter the following command into the Visual Studio Package Manager Console and press Enter to download and install the NuGet package:

Install-Package -version 3.0.3 bootstrap

I created a Home controller to act as the focal point for the examples in this chapter. The definition of the controller is shown in Listing 13-1. I’ll be using this controller to describe details of user accounts and data, and the Index action method passes a dictionary of values to the default view via the View method.

Listing 13-1.  The Contents of the HomeController.cs File

using System.Web.Mvc;
using System.Collections.Generic;
 
namespace Users.Controllers {
 
    public class HomeController : Controller {
 
        public ActionResult Index() {
            Dictionary<string, object> data
                = new Dictionary<string, object>();
            data.Add("Placeholder", "Placeholder");
            return View(data);
        }
    }
}

I created a view by right-clicking the Index action method and selecting Add View from the pop-up menu. I set View Name to Index and set Template to Empty (without model). Unlike the examples in previous chapters, I want to use a common layout for this chapter, so I checked the Use a Layout Page option. When I clicked the Add button, Visual Studio created the Views/Shared/_Layout.cshtml and Views/Home/Index.cshtml files. Listing 13-2 shows the contents of the _Layout.cshtml file.

Listing 13-2.  The Contents of the _Layout.cshtml File

<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
    <link href="∼/Content/bootstrap.min.css" rel="stylesheet" />
    <link href="∼/Content/bootstrap-theme.min.css" rel="stylesheet" />
    <style>
        .container {  padding-top: 10px; }
        .validation-summary-errors { color: #f00; }
    </style>
</head>
<body class="container">
    <div class="container">
        @RenderBody()
    </div>
</body>
</html>

Listing 13-3 shows the contents of the Index.cshtml file.

Listing 13-3.  The Contents of the Index.cshtml File

@{
    ViewBag.Title = "Index";
}
 
<div class="panel panel-primary">
    <div class="panel-heading">User Details</div>
    <table class="table table-striped">
        @foreach (string key in Model.Keys) {
            <tr>
                <th>@key</th>
                <td>@Model[key]</td>
            </tr>
        }
    </table>
</div>

To test that the example application is working, select Start Debugging from the Visual Studio Debug menu and navigate to the /Home/Index URL. You should see the result illustrated by Figure 13-1.

9781430265412_Fig13-01.jpg

Figure 13-1. Testing the example application

Setting Up ASP.NET Identity

For most ASP.NET developers, Identity will be the first exposure to the Open Web Interface for .NET (OWIN). OWIN is an abstraction layer that isolates a web application from the environment that hosts it. The idea is that the abstraction will allow for greater innovation in the ASP.NET technology stack, more flexibility in the environments that can host ASP.NET applications, and a lighter-weight server infrastructure.

OWIN is an open standard (which you can read at http://owin.org/spec/owin-1.0.0.html). Microsoft has created Project Katana, its implementation of the OWIN standard and a set of components that provide the functionality that web applications require. The attraction to Microsoft is that OWIN/Katana isolates the ASP.NET technology stack from the rest of the .NET Framework, which allows a greater rate of change.

OWIN developers select the services that they require for their application, rather than consuming an entire platform as happens now with ASP.NET. Individual services—known as middleware in the OWIN terminology—can be developed at different rates, and developers will be able to choose between providers for different services, rather than being tied to a Microsoft implementation.

There is a lot to like about the direction that OWIN and Katana are heading in, but it is in the early days, and it will be some time before it becomes a complete platform for ASP.NET applications. As I write this, it is possible to build Web API and SignalR applications without needing the System.Web namespace or IIS to process requests, but that’s about all. The MVC framework requires the standard ASP.NET platform and will continue to do so for some time to come.

The ASP.NET platform and IIS are not going away. Microsoft has been clear that it sees one of the most attractive aspects of OWIN as allowing developers more flexibility in which middleware components are hosted by IIS, and Project Katana already has support for the System.Web namespaces. OWIN and Katana are not the end of ASP.NET—rather, they represent an evolution where Microsoft allows developers more flexibility in how ASP.NET applications are assembled and executed.

image Tip  Identity is the first major ASP.NET component to be delivered as OWIN middleware, but it won’t be the last. Microsoft has made sure that the latest versions of Web API and SignalR don’t depend on the System.Web namespaces, and that means that any component intended for use across the ASP.NET family of technologies has to be delivered via OWIN. I get into more detail about OWIN in my Expert ASP.NET Web API 2 for MVC Developers book, which will be published by Apress in 2014.

OWIN and Katana won’t have a major impact on MVC framework developers for some time, but changes are already taking effect—and one of these is that ASP.NET Identity is implemented as an OWIN middleware component. This isn’t ideal because it means that MVC framework applications have to mix OWIN and traditional ASP.NET platform techniques to use Identity, but it isn’t too burdensome once you understand the basics and know how to get OWIN set up, which I demonstrate in the sections that follow.

Creating the ASP.NET Identity Database

ASP.NET Identity isn’t tied to a SQL Server schema in the same way that Membership was, but relational storage is still the default—and simplest—option, and it is the one that I will be using in this chapter. Although the NoSQL movement has gained momentum in recent years, relational databases are still the mainstream storage choice and are well-understood in most development teams.

ASP.NET Identity uses the Entity Framework Code First feature to automatically create its schema, but I still need to create the database into which that schema—and the user data—will be placed, just as I did in Chapter 10 when I created the database for session state data (the universal provider that I used to manage the database uses the same Code First feature).

image Tip  You don’t need to understand how Entity Framework or the Code First feature works to use ASP.NET Identity.

As in Chapter 10, I will be using the localdb feature to create my database. As a reminder, localdb is included in Visual Studio and is a cut-down version of SQL Server that allows developers to easily create and work with databases.

Select SQL Server Object Explorer from the Visual Studio View menu and right-click the SQL Server object in the window that appears. Select Add SQL Server from the pop-up menu, as shown in Figure 13-2.

9781430265412_Fig13-02.jpg

Figure 13-2. Creating a new database connection

Visual Studio will display the Connect to Server dialog. Set the server name to (localdb)v11.0, select the Windows Authentication option, and click the Connect button. A connection to the database will be established and shown in the SQL Server Object Explorer window. Expand the new item, right-click Databases, and select Add New Database from the pop-up window, as shown in Figure 13-3.

9781430265412_Fig13-03.jpg

Figure 13-3. Adding a new database

Set the Database Name option to IdentityDb, leave the Database Location value unchanged, and click the OK button to create the database. The new database will be shown in the Databases section of the SQL connection in the SQL Server Object Explorer.

Adding the Identity Packages

Identity is published as a set of NuGet packages, which makes it easy to install them into any project. Enter the following commands into the Package Manager Console:

Install-Package Microsoft.AspNet.Identity.EntityFramework –Version 2.0.0
Install-Package Microsoft.AspNet.Identity.OWIN -Version 2.0.0
Install-Package Microsoft.Owin.Host.SystemWeb -Version 2.1.0

Visual Studio can create projects that are configured with a generic user account management configuration, using the Identity API. You can add the templates and code to a project by selecting the MVC template when creating the project and setting the Authentication option to Individual User Accounts. I don’t use the templates because I find them too general and too verbose and because I like to have direct control over the contents and configuration of my projects. I recommend you do the same, not least because you will gain a better understanding of how important features work, but it can be interesting to look at the templates to see how common tasks are performed.

Updating the Web.config File

Two changes are required to the Web.config file to prepare a project for ASP.NET Identity. The first is a connection string that describes the database I created in the previous section. The second change is to define an application setting that names the class that initializes OWIN middleware and that is used to configure Identity. Listing 13-4 shows the changes I made to the Web.config file. (I explained how connection strings and application settings work in Chapter 9.)

Listing 13-4.  Preparing the Web.config File for ASP.NET Identity

<?xml version="1.0" encoding="utf-8"?>
<configuration>
 
  <configSections>
    <section name="entityFramework"        type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection,
        EntityFramework, Version=6.0.0.0, Culture=neutral,
        PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
 
  <connectionStrings>
    <add name="IdentityDb" providerName="System.Data.SqlClient"
         connectionString="Data Source=(localdb)v11.0;Initial
             Catalog=IdentityDb;Integrated Security=True;Connect
              Timeout=15;Encrypt=False;TrustServerCertificate=False;
              MultipleActiveResultSets=True"/>
  </connectionStrings>
  
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    <add key="owin:AppStartup" value="Users.IdentityConfig" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.1" />
    <httpRuntime targetFramework="4.5.1" />
  </system.web>
  <entityFramework>
    <defaultConnectionFactory
        type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory,
            EntityFramework">
      <parameters>
        <parameter value="v11.0" />
      </parameters>
    </defaultConnectionFactory>
    <providers>
      <provider invariantName="System.Data.SqlClient"
         type="System.Data.Entity.SqlServer.SqlProviderServices,
            EntityFramework.SqlServer" />
    </providers>
  </entityFramework>
</configuration>

image Caution  Make sure you put the connectionString value on a single line. I had to break it over several lines to make the listing fit on the page, but ASP.NET expects a single, unbroken string. If in doubt, download the source code that accompanies this book, which is freely available from www.apress.com.

OWIN defines its own application startup model, which is separate from the global application class that I described in Chapter 3. The application setting, called owin:AppStartup, specifies a class that OWIN will instantiate when the application starts in order to receive its configuration.

image Tip  Notice that I have set the MultipleActiveResultSets property to true in the connection string. This allows the results from multiple queries to be read simultaneously, which I rely on in Chapter 14 when I show you how to authorize access to action methods based on role membership.

Creating the Entity Framework Classes

If you have used Membership in projects, you may be surprised by just how much initial preparation is required for ASP.NET Identity. The extensibility that Membership lacked is readily available in ASP.NET Identity, but it comes with a price of having to create a set of implementation classes that the Entity Framework uses to manage the database. In the sections that follow, I’ll show you how to create the classes needed to get Entity Framework to act as the storage system for ASP.NET Identity.

Creating the User Class

The first class to define is the one that represents a user, which I will refer to as the user class. The user class is derived from IdentityUser, which is defined in the Microsoft.AspNet.Identity.EntityFramework namespace. IdentityUser provides the basic user representation, which can be extended by adding properties to the derived class, which I describe in Chapter 15. Table 13-2 shows the built-in properties that IdentityUser defines, which are the ones I will be using in this chapter.

Table 13-2. The Properties Defined by the IdentityUser Class

Name

Description

Claims

Returns the collection of claims for the user, which I describe in Chapter 15

Email

Returns the user’s e-mail address

Id

Returns the unique ID for the user

Logins

Returns a collection of logins for the user, which I use in Chapter 15

PasswordHash

Returns a hashed form of the user password, which I use in the “Implementing the Edit Feature” section

Roles

Returns the collection of roles that the user belongs to, which I describe in Chapter 14

PhoneNumber

Returns the user’s phone number

SecurityStamp

Returns a value that is changed when the user identity is altered, such as by a password change

UserName

Returns the username

image Tip  The classes in the Microsoft.AspNet.Identity.EntityFramework namespace are the Entity Framework–specific concrete implementations of interfaces defined in the Microsoft.AspNet.Identity namespace. IdentityUser, for example, is the implementation of the IUser interface. I am working with the concrete classes because I am relying on the Entity Framework to store my user data in a database, but as ASP.NET Identity matures, you can expect to see alternative implementations of the interfaces that use different storage mechanisms (although most projects will still use the Entity Framework since it comes from Microsoft).

What is important at the moment is that the IdentityUser class provides access only to the basic information about a user: the user’s name, e-mail, phone, password hash, role memberships, and so on. If I want to store any additional information about the user, I have to add properties to the class that I derive from IdentityUser and that will be used to represent users in my application. I demonstrate how to do this in Chapter 15.

To create the user class for my application, I created a class file called AppUserModels.cs in the Models folder and used it to create the AppUser class, which is shown in Listing 13-5.

Listing 13-5.  The Contents of the AppUser.cs File

using System;
using Microsoft.AspNet.Identity.EntityFramework;
 
namespace Users.Models {
    public class AppUser : IdentityUser {
        // additional properties will go here
    }
}

That’s all I have to do at the moment, although I’ll return to this class in Chapter 15, when I show you how to add application-specific user data properties.

Creating the Database Context Class

The next step is to create an Entity Framework database context that operates on the AppUser class. This will allow the Code First feature to create and manage the schema for the database and provide access to the data it stores. The context class is derived from IdentityDbContext<T>, where T is the user class (AppUser in the example). I created a folder called Infrastructure in the project and added to it a class file called AppIdentityDbContext.cs, the contents of which are shown in Listing 13-6.

Listing 13-6.  The Contents of the AppIdentityDbContext.cs File

using System.Data.Entity;
using Microsoft.AspNet.Identity.EntityFramework;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppIdentityDbContext : IdentityDbContext<AppUser> {
 
        public AppIdentityDbContext() : base("IdentityDb") { }
 
        static AppIdentityDbContext() {
            Database.SetInitializer<AppIdentityDbContext>(new IdentityDbInit());
        }
 
        public static AppIdentityDbContext Create() {
            return new AppIdentityDbContext();
        }
    }
 
    public class IdentityDbInit
            : DropCreateDatabaseIfModelChanges<AppIdentityDbContext> {
 
        protected override void Seed(AppIdentityDbContext context) {
            PerformInitialSetup(context);
            base.Seed(context);
        }
 
        public void PerformInitialSetup(AppIdentityDbContext context) {
            // initial configuration will go here
        }
    }
}

The constructor for the AppIdentityDbContext class calls its base with the name of the connection string that will be used to connect to the database, which is IdentityDb in this example. This is how I associate the connection string I defined in Listing 13-4 with ASP.NET Identity.

The AppIdentityDbContext class also defines a static constructor, which uses the Database.SetInitializer method to specify a class that will seed the database when the schema is first created through the Entity Framework Code First feature. My seed class is called IdentityDbInit, and I have provided just enough of a class to create a placeholder so that I can return to seeding the database later by adding statements to the PerformInitialSetup method. I show you how to seed the database in Chapter 14.

Finally, the AppIdentityDbContext class defines a Create method. This is how instances of the class will be created when needed by the OWIN, using the class I describe in the “Creating the Start Class” section.

image Note  Don’t worry if the role of these classes doesn’t make sense. If you are unfamiliar with the Entity Framework, then I suggest you treat it as something of a black box. Once the basic building blocks are in place—and you can copy the ones into your chapter to get things working—then you will rarely need to edit them.

Creating the User Manager Class

One of the most important Identity classes is the user manager, which manages instances of the user class. The user manager class must be derived from UserManager<T>, where T is the user class. The UserManager<T> class isn’t specific to the Entity Framework and provides more general features for creating and operating on user data. Table 13-3 shows the basic methods and properties defined by the UserManager<T> class for managing users. There are others, but rather than list them all here, I’ll describe them in context when I describe the different ways in which user data can be managed.

Table 13-3. The Basic Methods and Properties Defined by the UserManager<T> Class

Name

Description

ChangePasswordAsync(id, old, new)

Changes the password for the specified user.

CreateAsync(user)

Creates a new user without a password. See Chapter 15 for an example.

CreateAsync(user, pass)

Creates a new user with the specified password. See the “Creating Users” section.

DeleteAsync(user)

Deletes the specified user. See the “Implementing the Delete Feature” section.

FindAsync(user, pass)

Finds the object that represents the user and authenticates their password. See Chapter 14 for details of authentication.

FindByIdAsync(id)

Finds the user object associated with the specified ID. See the “Implementing the Delete Feature” section.

FindByNameAsync(name)

Finds the user object associated with the specified name. I use this method in the “Seeding the Database” section of Chapter 14.

UpdateAsync(user)

Pushes changes to a user object back into the database. See the “Implementing the Edit Feature” section.

Users

Returns an enumeration of the users. See the “Enumerating User Accounts” section.

image Tip  Notice that the names of all of these methods end with Async. This is because ASP.NET Identity is implemented almost entirely using C# asynchronous programming features, which means that operations will be performed concurrently and not block other activities. You will see how this works once I start demonstrating how to create and manage user data. There are also synchronous extension methods for each Async method. I stick to the asynchronous methods for most examples, but the synchronous equivalents are useful if you need to perform multiple related operations in sequence. I have included an example of this in the “Seeding the Database” section of Chapter 14. The synchronous methods are also useful when you want to call Identity methods from within property getters or setters, which I do in Chapter 15.

I added a class file called AppUserManager.cs to the Infrastructure folder and used it to define the user manager class, which I have called AppUserManager, as shown in Listing 13-7.

Listing 13-7.  The Contents of the AppUserManager.cs File

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppUserManager : UserManager<AppUser> {
 
        public AppUserManager(IUserStore<AppUser> store)
            : base(store) {
        }
 
        public static AppUserManager Create(
                IdentityFactoryOptions<AppUserManager> options,
                IOwinContext context) {
 
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(new UserStore<AppUser>(db));
 
            return manager;
        }
    }
}

The static Create method will be called when Identity needs an instance of the AppUserManager, which will happen when I perform operations on user data—something that I will demonstrate once I have finished performing the setup.

To create an instance of the AppUserManager class, I need an instance of UserStore<AppUser>. The UserStore<T> class is the Entity Framework implementation of the IUserStore<T> interface, which provides the storage-specific implementation of the methods defined by the UserManager class. To create the UserStore<AppUser>, I need an instance of the AppIdentityDbContext class, which I get through OWIN as follows:

...
AppIdentityDbContext db =context.Get<AppIdentityDbContext>();
...

The IOwinContext implementation passed as an argument to the Create method defines a generically typed Get method that returns instances of objects that have been registered in the OWIN start class, which I describe in the following section.

Creating the Start Class

The final piece I need to get ASP.NET Identity up and running is a start class. In Listing 13-4, I defined an application setting that specified a configuration class for OWIN, like this:

...
<add key="owin:AppStartup" value="Users.IdentityConfig" />
...

OWIN emerged independently of ASP.NET and has its own conventions. One of them is that there is a class that is instantiated to load and configure middleware and perform any other configuration work that is required. By default, this class is called Start, and it is defined in the global namespace. This class contains a method called Configuration, which is called by the OWIN infrastructure and passed an implementation of the Owin.IAppBuilder interface, which supports setting up the middleware that an application requires. The Start class is usually defined as a partial class, with its other class files dedicated to each kind of middleware that is being used.

I freely ignore this convention, given that the only OWIN middleware that I use in MVC framework applications is Identity. I prefer to use the application setting in the Web.config file to define a single class in the top-level namespace of the application. To this end, I added a class file called IdentityConfig.cs to the App_Start folder and used it to define the class shown in Listing 13-8, which is the class that I specified in the Web.config folder.

Listing 13-8.  The Contents of the IdentityConfig.cs File

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;
using Users.Infrastructure;
 
namespace Users {
    public class IdentityConfig {
        public void Configuration(IAppBuilder app) {
 
            app.CreatePerOwinContext<AppIdentityDbContext>(AppIdentityDbContext.Create);
            app.CreatePerOwinContext<AppUserManager>(AppUserManager.Create);
            
            app.UseCookieAuthentication(new CookieAuthenticationOptions {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                LoginPath = new PathString("/Account/Login"),
            });
        }
    }
}

The IAppBuilder interface is supplemented by a number of extension methods defined in classes in the Owin namespace. The CreatePerOwinContext method creates a new instance of the AppUserManager and AppIdentityDbContext classes for each request. This ensures that each request has clean access to the ASP.NET Identity data and that I don’t have to worry about synchronization or poorly cached database data.

The UseCookieAuthentication method tells ASP.NET Identity how to use a cookie to identity authenticated users, where the options are specified through the CookieAuthenticationOptions class. The important part here is the LoginPath property, which specifies a URL that clients should be redirected to when they request content without authentication. I have specified /Account/Login, and I will create the controller that handles these redirections in Chapter 14.

Using ASP.NET Identity

Now that the basic setup is out of the way, I can start to use ASP.NET Identity to add support for managing users to the example application. In the sections that follow, I will demonstrate how the Identity API can be used to create administration tools that allow for centralized management of users. Table 13-4 puts ASP.NET Identity into context.

Table 13-4. Putting Content Caching by Attribute in Context

Question

Answer

What is it?

ASP.NET Identity is the API used to manage user data and perform authentication and authorization.

Why should I care?

Most applications require users to create accounts and provide credentials to access content and features. ASP.NET Identity provides the facilities for performing these operations.

How is it used by the MVC framework?

ASP.NET Identity isn’t used directly by the MVC framework but integrates through the standard MVC authorization features.

Enumerating User Accounts

Centralized user administration tools are useful in just about all applications, even those that allow users to create and manage their own accounts. There will always be some customers who require bulk account creation, for example, and support issues that require inspection and adjustment of user data. From the perspective of this chapter, administration tools are useful because they consolidate a lot of basic user management functions into a small number of classes, making them useful examples to demonstrate the fundamental features of ASP.NET Identity.

I started by adding a controller called Admin to the project, which is shown in Listing 13-9, and which I will use to define my user administration functionality.

Listing 13-9.  The Contents of the AdminController.cs File

using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;
 
namespace Users.Controllers {
    public class AdminController : Controller {
 
        public ActionResult Index() {
            return View(UserManager.Users);
        }
 
        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
    }
}

The Index action method enumerates the users managed by the Identity system; of course, there aren’t any users at the moment, but there will be soon. The important part of this listing is the way that I obtain an instance of the AppUserManager class, through which I manage user information. I will be using the AppUserManager class repeatedly as I implement the different administration functions, and I defined the UserManager property in the Admin controller to simplify my code.

The Microsoft.Owin.Host.SystemWeb assembly adds extension methods for the HttpContext class, one of which is GetOwinContext. This provides a per-request context object into the OWIN API through an IOwinContext object. The IOwinContext isn’t that interesting in an MVC framework application, but there is another extension method called GetUserManager<T> that is used to get instances of the user manager class.

image Tip  As you may have gathered, there are lots of extension methods in ASP.NET Identity; overall, the API is something of a muddle as it tries to mix OWIN, abstract Identity functionality, and the concrete Entity Framework storage implementation.

I called the GetUserManager with a generic type parameter to specify the AppUserManager class that I created earlier in the chapter, like this:

...
return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
...

Once I have an instance of the AppUserManager class, I can start to query the data store. The AppUserManager.Users property returns an enumeration of user objects—instances of the AppUser class in my application—which can be queried and manipulated using LINQ.

In the Index action method, I pass the value of the Users property to the View method so that I can list details of the users in the view. Listing 13-10 shows the contents of the Views/Admin/Index.cshtml file that I created by right-clicking the Index action method and selecting Add View from the pop-up menu.

Listing 13-10.  The Contents of the Index.cshtml File in the /Views/Admin Folder

@using Users.Models
@model IEnumerable<AppUser>
@{
    ViewBag.Title = "Index";
}
 
<div class="panel panel-primary">
    <div class="panel-heading">
        User Accounts
    </div>
    <table class="table table-striped">
        <tr><th>ID</th><th>Name</th><th>Email</th></tr>
        @if (Model.Count() == 0) {
            <tr><td colspan="3" class="text-center">No User Accounts</td></tr>
        } else {
            foreach (AppUser user in Model) {
                <tr>
                    <td>@user.Id</td>
                    <td>@user.UserName</td>
                    <td>@user.Email</td>
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "Create", null, new { @class = "btn btn-primary" })

This view contains a table that has rows for each user, with columns for the unique ID, username, and e-mail address. If there are no users in the database, then a message is displayed, as shown in Figure 13-4.

9781430265412_Fig13-04.jpg

Figure 13-4. Display the (empty) list of users

I included a Create link in the view (which I styled as a button using Bootstrap) that targets the Create action on the Admin controller. I’ll implement this action shortly to support adding users.

RESETTING THE DATABASE

When you start the application and navigate to the /Admin/Index URL, it will take a few moments before the contents rendered from the view are displayed. This is because the Entity Framework has connected to the database and determined that there is no schema defined. The Code First feature uses the classes I defined earlier in the chapter (and some which are contained in the Identity assemblies) to create the schema so that it is ready to be queried and to store data.

You can see the effect by opening the Visual Studio SQL Server Object Explorer window and expanding entry for the IdentityDB database schema, which will include tables with names such as AspNetUsers and AspNetRoles.

To delete the database, right-click the IdentityDb item and select Delete from the pop-up menu. Check both of the options in the Delete Database dialog and click the OK button to delete the database.

Right-click the Databases item, select Add New Database (as shown in Figure 13-3), and enter IdentityDb in the Database Name field. Click OK to create the empty database. The next time you start the application and navigate to the Admin/Index URL, the Entity Framework will detect that there is no schema and re-create it.

Creating Users

I am going to use MVC framework model validation for the input my application receives, and the easiest way to do this is to create simple view models for each of the operations that my controller supports. I added a class file called UserViewModels.cs to the Models folder and used it to define the class shown in Listing 13-11. I’ll add further classes to this file as I define models for additional features.

Listing 13-11.  The Contents of the UserViewModels.cs File

using System.ComponentModel.DataAnnotations;
 
namespace Users.Models {
 
    public class CreateModel {
        [Required]
        public string Name { get; set; }
        [Required]
        public string Email { get; set; }
        [Required]
        public string Password { get; set; }
    }
}

The initial model I have defined is called CreateModel, and it defines the basic properties that I require to create a user account: a username, an e-mail address, and a password. I have used the Required attribute from the System.ComponentModel.DataAnnotations namespace to denote that values are required for all three properties defined in the model.

I added a pair of Create action methods to the Admin controller, which are targeted by the link in the Index view from the previous section and which uses the standard controller pattern to present a view to the user for a GET request and process form data for a POST request. You can see the new action methods in Listing 13-12.

Listing 13-12.  Defining the Create Action Methods in the AdminController.cs File

using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;
using Users.Models;
using Microsoft.AspNet.Identity;
using System.Threading.Tasks;
 
namespace Users.Controllers {
    public class AdminController : Controller {
 
        public ActionResult Index() {
            return View(UserManager.Users);
        }
 
        public ActionResult Create() {
            return View();
        }
 
        [HttpPost]
        public async Task<ActionResult> Create(CreateModel model) {
            if (ModelState.IsValid) {
                AppUser user = new AppUser {UserName = model.Name, Email = model.Email};
                IdentityResult result = await UserManager.CreateAsync(user,
                    model.Password);
                if (result.Succeeded) {
                    return RedirectToAction("Index");
                } else {
                    AddErrorsFromResult(result);
                }
            }
            return View(model);
        }
 
        private void AddErrorsFromResult(IdentityResult result) {
            foreach (string error in result.Errors) {
                ModelState.AddModelError("", error);
            }
        }
 
        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
 
    }
}

The important part of this listing is the Create method that takes a CreateModel argument and that will be invoked when the administrator submits their form data. I use the ModelState.IsValid property to check that the data I am receiving contains the values I require, and if it does, I create a new instance of the AppUser class and pass it to the UserManager.CreateAsync method, like this:

...
AppUser user = new AppUser {UserName = model.Name, Email = model.Email};
IdentityResult result = await UserManager.CreateAsync(user, model.Password);
...

The result from the CreateAsync method is an implementation of the IdentityResult interface, which describes the outcome of the operation through the properties listed in Table 13-5.

Table 13-5. The Properties Defined by the IdentityResult Interface

Name

Description

Errors

Returns a string enumeration that lists the errors encountered while attempting the operation

Succeeded

Returns true if the operation succeeded

USING THE ASP.NET IDENTITY ASYNCHRONOUS METHODS

You will notice that all of the operations for manipulating user data, such as the UserManager.CreateAsync method I used in Listing 13-12, are available as asynchronous methods. Such methods are easily consumed with the async and await keywords. Using asynchronous Identity methods allows your action methods to be executed asynchronously, which can improve the overall throughput of your application.

However, you can also use synchronous extension methods provided by the Identity API. All of the commonly used asynchronous methods have a synchronous wrapper so that the functionality of the UserManager.CreateAsync method can be called through the synchronous UserManager.Create method. I use the asynchronous methods for preference, and I recommend you follow the same approach in your projects. The synchronous methods can be useful for creating simpler code when you need to perform multiple dependent operations, so I used them in the “Seeding the Database” section of Chapter 14 as a demonstration.

I inspect the Succeeded property in the Create action method to determine whether I have been able to create a new user record in the database. If the Succeeded property is true, then I redirect the browser to the Index action so that list of users is displayed:

...
if (result.Succeeded) {
    return RedirectToAction("Index");
} else {
    AddErrorsFromResult(result);
}
...

If the Succeeded property is false, then I call the AddErrorsFromResult method, which enumerates the messages from the Errors property and adds them to the set of model state errors, taking advantage of the MVC framework model validation feature. I defined the AddErrorsFromResult method because I will have to process errors from other operations as I build the functionality of my administration controller. The last step is to create the view that will allow the administrator to create new accounts. Listing 13-13 shows the contents of the Views/Admin/Create.cshtml file.

Listing 13-13.  The Contents of the Create.cshtml File

@model Users.Models.CreateModel
@{ ViewBag.Title = "Create User";}
<h2>Create User</h2>
@Html.ValidationSummary(false)
@using (Html.BeginForm()) {
    <div class="form-group">
        <label>Name</label>
        @Html.TextBoxFor(x => x.Name, new { @class = "form-control"})
    </div>
    <div class="form-group">
        <label>Email</label>
        @Html.TextBoxFor(x => x.Email, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Password</label>
        @Html.PasswordFor(x => x.Password, new { @class = "form-control" })
    </div>
    <button type="submit" class="btn btn-primary">Create</button>
    @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default"})
}

There is nothing special about this view—it is a simple form that gathers values that the MVC framework will bind to the properties of the model class that is passed to the Create action method.

Testing the Create Functionality

To test the ability to create a new user account, start the application, navigate to the /Admin/Index URL, and click the Create button. Fill in the form with the values shown in Table 13-6.

Table 13-6. The Values for Creating an Example User

Name

Value

Name

Joe

Email

[email protected]

Password

secret

image Tip  Although not widely known by developers, there are domains that are reserved for testing, including example.com. You can see a complete list at https://tools.ietf.org/html/rfc2606.

Once you have entered the values, click the Create button. ASP.NET Identity will create the user account, which will be displayed when your browser is redirected to the Index action method, as shown in Figure 13-5. You will see a different ID value because IDs are randomly generated for each user account.

9781430265412_Fig13-05.jpg

Figure 13-5. The effect of adding a new user account

Click the Create button again and enter the same details into the form, using the values in Table 13-6. This time when you submit the form, you will see an error reported through the model validation summary, as shown in Figure 13-6.

9781430265412_Fig13-06.jpg

Figure 13-6. An error trying to create a new user

Validating Passwords

One of the most common requirements, especially for corporate applications, is to enforce a password policy. ASP.NET Identity provides the PasswordValidator class, which can be used to configure a password policy using the properties described in Table 13-7.

Table 13-7. The Properties Defined by the PasswordValidator Class

Name

Description

RequiredLength

Specifies the minimum length of a valid passwords.

RequireNonLetterOrDigit

When set to true, valid passwords must contain a character that is neither a letter nor a digit.

RequireDigit

When set to true, valid passwords must contain a digit.

RequireLowercase

When set to true, valid passwords must contain a lowercase character.

RequireUppercase

When set to true, valid passwords must contain a uppercase character.

A password policy is defined by creating an instance of the PasswordValidator class, setting the property values, and using the object as the value for the PasswordValidator property in the Create method that OWIN uses to instantiate the AppUserManager class, as shown in Listing 13-14.

Listing 13-14.  Setting a Password Policy in the AppUserManager.cs File

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppUserManager : UserManager<AppUser> {
 
        public AppUserManager(IUserStore<AppUser> store) : base(store) {
        }
 
        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager>
                options, IOwinContext context) {
 
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(
                new UserStore<AppUser>(db));
 
            manager.PasswordValidator = new PasswordValidator {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };
 
            return manager;
        }
    }
}

I used the PasswordValidator class to specify a policy that requires at least six characters and a mix of uppercase and lowercase characters. You can see how the policy is applied by starting the application, navigating to the /Admin/Index URL, clicking the Create button, and trying to create an account that has the password secret. The password doesn’t meet the new password policy, and an error is added to the model state, as shown in Figure 13-7.

9781430265412_Fig13-07.jpg

Figure 13-7. Reporting an error when validating a password

Implementing a Custom Password Validator

The built-in password validation is sufficient for most applications, but you may need to implement a custom policy, especially if you are implementing a corporate line-of-business application where complex password policies are common. Extending the built-in functionality is done by deriving a new class from PasswordValidatator and overriding the ValidateAsync method. As a demonstration, I added a class file called CustomPasswordValidator.cs in the Infrastructure folder and used it to define the class shown in Listing 13-15.

Listing 13-15.  The Contents of the CustomPasswordValidator.cs File

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
 
namespace Users.Infrastructure {
 
    public class CustomPasswordValidator : PasswordValidator {
        public override async Task<IdentityResult> ValidateAsync(string pass) {
            IdentityResult result = await base.ValidateAsync(pass);
            if (pass.Contains("12345")) {
                var errors = result.Errors.ToList();
                errors.Add("Passwords cannot contain numeric sequences");
                result = new IdentityResult(errors);
            }
            return result;
        }
    }
}

I have overridden the ValidateAsync method and call the base implementation so I can benefit from the built-in validation checks. The ValidateAsync method is passed the candidate password, and I perform my own check to ensure that the password does not contain the sequence 12345. The properties of the IdentityResult class are read-only, which means that if I want to report a validation error, I have to create a new instance, concatenate my error with any errors from the base implementation, and pass the combined list as the constructor argument. I used LINQ to concatenate the base errors with my custom one.

Listing 13-16 shows the application of my custom password validator in the AppUserManager class.

Listing 13-16.  Applying a Custom Password Validator in the AppUserManager.cs File

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppUserManager : UserManager<AppUser> {
 
        public AppUserManager(IUserStore<AppUser> store) : base(store) {
        }
 
        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager>
                options, IOwinContext context) {
 
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(
                new UserStore<AppUser>(db));
 
            manager.PasswordValidator = new CustomPasswordValidator {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };
 
            return manager;
        }
    }
}

To test the custom password validation, try to create a new user account with the password secret12345. This will break two of the validation rules—one from the built-in validator and one from my custom implementation. Error messages for both problems are added to the model state and displayed when the Create button is clicked, as shown in Figure 13-8.

9781430265412_Fig13-08.jpg

Figure 13-8. The effect of a custom password validation policy

Validating User Details

More general validation can be performed by creating an instance of the UserValidator class and using the properties it defines to restrict other user property values. Table 13-8 describes the UserValidator properties.

Table 13-8. The Properties Defined by the UserValidator Class

Name

Description

AllowOnlyAlphanumericUserNames

When true, usernames can contain only alphanumeric characters.

RequireUniqueEmail

When true, e-mail addresses must be unique.

Performing validation on user details is done by creating an instance of the UserValidator class and assigning it to the UserValidator property of the user manager class within the Create method that OWIN uses to create instances. Listing 13-17 shows an example of using the built-in validator class.

Listing 13-17.  Using the Built-in user Validator Class in the AppUserManager.cs File

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppUserManager : UserManager<AppUser> {
 
        public AppUserManager(IUserStore<AppUser> store) : base(store) {
        }
 
        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager>
                options, IOwinContext context) {
 
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(
                new UserStore<AppUser>(db));
 
            manager.PasswordValidator = new CustomPasswordValidator {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };
 
            manager.UserValidator = new UserValidator<AppUser>(manager) {
                AllowOnlyAlphanumericUserNames = true,
                RequireUniqueEmail = true
            };
 
            return manager;
        }
    }
}

The UserValidator class takes a generic type parameter that specifies the type of the user class, which is AppUser in this case. Its constructor argument is the user manager class, which is an instance of the user manager class (which is AppUserManager for my application).

The built-in validation support is rather basic, but you can create a custom validation policy by creating a class that is derived from UserValidator. As a demonstration, I added a class file called CustomUserValidator.cs to the Infrastructure folder and used it to create the class shown in Listing 13-18.

Listing 13-18.  The Contents of the CustomUserValidator.cs File

using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using Users.Models;
 
namespace Users.Infrastructure {
 
    public class CustomUserValidator : UserValidator<AppUser> {
 
        public CustomUserValidator(AppUserManager mgr)
            : base(mgr) {
        }
 
        public override async Task<IdentityResult> ValidateAsync(AppUser user) {
            IdentityResult result = await base.ValidateAsync(user);
 
            if (!user.Email.ToLower().EndsWith("@example.com")) {
                var errors = result.Errors.ToList();
                errors.Add("Only example.com email addresses are allowed");
                result = new IdentityResult(errors);
            }
            return result;
        }
    }
}

The constructor of the derived class must take an instance of the user manager class and call the base implementation so that the built-in validation checks can be performed. Custom validation is implemented by overriding the ValidateAsync method, which takes an instance of the user class and returns an IdentityResult object. My custom policy restricts users to e-mail addresses in the example.com domain and performs the same LINQ manipulation I used for password validation to concatenate my error message with those produced by the base class. Listing 13-19 shows how I applied my custom validation class in the Create method of the AppUserManager class, replacing the default implementation.

Listing 13-19.  Using a Custom User Validation Class in the AppUserManager.cs File

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.AspNet.Identity.Owin;
using Microsoft.Owin;
using Users.Models;
 
namespace Users.Infrastructure {
    public class AppUserManager : UserManager<AppUser> {
 
        public AppUserManager(IUserStore<AppUser> store) : base(store) {
        }
 
        public static AppUserManager Create(IdentityFactoryOptions<AppUserManager>
                options, IOwinContext context) {
 
            AppIdentityDbContext db = context.Get<AppIdentityDbContext>();
            AppUserManager manager = new AppUserManager(
                new UserStore<AppUser>(db));
 
            manager.PasswordValidator = new CustomPasswordValidator {
                RequiredLength = 6,
                RequireNonLetterOrDigit = false,
                RequireDigit = false,
                RequireLowercase = true,
                RequireUppercase = true
            };
 
            manager.UserValidator = new CustomUserValidator(manager) {
                AllowOnlyAlphanumericUserNames = true,
                RequireUniqueEmail = true
            };
 
            return manager;
        }
    }
}

You can see the result if you try to create an account with an e-mail address such as [email protected], as shown in Figure 13-9.

9781430265412_Fig13-09.jpg

Figure 13-9. An error message shown by a custom user validation policy

Completing the Administration Features

I only have to implement the features for editing and deleting users to complete my administration tool. In Listing 13-20, you can see the changes I made to the Views/Admin/Index.cshtml file to target Edit and Delete actions in the Admin controller.

Listing 13-20.  Adding Edit and Delete Buttons to the Index.cshtml File

@using Users.Models
@model IEnumerable<AppUser>
@{ ViewBag.Title = "Index"; }
<div class="panel panel-primary">
    <div class="panel-heading">
        User Accounts
    </div>
    <table class="table table-striped">
        <tr><th>ID</th><th>Name</th><th>Email</th><th></th></tr>
        @if (Model.Count() == 0) {
            <tr><td colspan="4" class="text-center">No User Accounts</td></tr>
        } else {
            foreach (AppUser user in Model) {
                <tr>
                    <td>@user.Id</td>
                    <td>@user.UserName</td>
                    <td>@user.Email</td>
                    <td>
                        @using (Html.BeginForm("Delete", "Admin",
                            new { id = user.Id })) {
                            @Html.ActionLink("Edit", "Edit", new { id = user.Id },
                                    new { @class = "btn btn-primary btn-xs" })
                            <button class="btn btn-danger btn-xs"
                                    type="submit">
                                Delete
                            </button>
                        }
                    </td>
                </tr>
            }
        }
    </table>
</div>
@Html.ActionLink("Create", "Create", null, new { @class = "btn btn-primary" })

image Tip  You will notice that I have put the Html.ActionLink call that targets the Edit action method inside the scope of the Html.Begin helper. I did this solely so that the Bootstrap styles will style both elements as buttons displayed on a single line.

Implementing the Delete Feature

The user manager class defines a DeleteAsync method that takes an instance of the user class and removes it from the database. In Listing 13-21, you can see how I have used the DeleteAsync method to implement the delete feature of the Admin controller.

Listing 13-21.  Deleting Users in the AdminController.cs File

using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;
using Users.Models;
using Microsoft.AspNet.Identity;
using System.Threading.Tasks;
 
namespace Users.Controllers {
    public class AdminController : Controller {
 
        //...other action methods omitted for brevity...
 
        [HttpPost]
        public async Task<ActionResult> Delete(string id) {
            AppUser user = await UserManager.FindByIdAsync(id);
            if (user != null) {
                IdentityResult result = await UserManager.DeleteAsync(user);
                if (result.Succeeded) {
                    return RedirectToAction("Index");
                } else {
                    return View("Error", result.Errors);
                }
            } else {
                return View("Error", new string[] { "User Not Found" });
            }
        }
 
        private void AddErrorsFromResult(IdentityResult result) {
            foreach (string error in result.Errors) {
                ModelState.AddModelError("", error);
            }
        }
 
        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
    }
}

My action method receives the unique ID for the user as an argument, and I use the FindByIdAsync method to locate the corresponding user object so that I can pass it to DeleteAsync method. The result of the DeleteAsync method is an IdentityResult, which I process in the same way I did in earlier examples to ensure that any errors are displayed to the user. You can test the delete functionality by creating a new user and then clicking the Delete button that appears alongside it in the Index view.

There is no view associated with the Delete action, so to display any errors I created a view file called Error.cshtml in the Views/Shared folder, the contents of which are shown in Listing 13-22.

Listing 13-22.  The Contents of the Error.cshtml File

@model IEnumerable<string>
@{ ViewBag.Title = "Error";}
 
<div class="alert alert-danger">
    @switch (Model.Count()) {
        case 0:
            @: Something went wrong. Please try again
            break;
        case 1:
            @Model.First();
            break;
        default:
            @: The following errors were encountered:
            <ul>
                @foreach (string error in Model) {
                    <li>@error</li>
                }
            </ul>
            break;
    }
</div>
@Html.ActionLink("OK", "Index", null, new { @class = "btn btn-default" })

image Tip  I put this view in the Views/Shared folder so that it can be used by other controllers, including the one I create to manage roles and role membership in Chapter 14.

Implementing the Edit Feature

To complete the administration tool, I need to add support for editing the e-mail address and password for a user account. These are the only properties defined by users at the moment, but I’ll show you how to extend the schema with custom properties in Chapter 15. Listing 13-23 shows the Edit action methods that I added to the Admin controller.

Listing 13-23.  Adding the Edit Actions in the AdminController.cs File

using System.Web;
using System.Web.Mvc;
using Microsoft.AspNet.Identity.Owin;
using Users.Infrastructure;
using Users.Models;
using Microsoft.AspNet.Identity;
using System.Threading.Tasks;
 
namespace Users.Controllers {
    public class AdminController : Controller {
 
       //...other action methods omitted for brevity...
 
        public async Task<ActionResult> Edit(string id) {
            AppUser user = await UserManager.FindByIdAsync(id);
            if (user != null) {
                return View(user);
            } else {
                return RedirectToAction("Index");
            }
        }
 
        [HttpPost]
        public async Task<ActionResult> Edit(string id, string email, string password) {
            AppUser user = await UserManager.FindByIdAsync(id);
            if (user != null) {
                user.Email = email;
                IdentityResult validEmail
                    = await UserManager.UserValidator.ValidateAsync(user);
                if (!validEmail.Succeeded) {
                    AddErrorsFromResult(validEmail);
                }
                IdentityResult validPass = null;
                if (password != string.Empty) {
                    validPass
                        = await UserManager.PasswordValidator.ValidateAsync(password);
                    if (validPass.Succeeded) {
                        user.PasswordHash =
                            UserManager.PasswordHasher.HashPassword(password);
                    } else {
                        AddErrorsFromResult(validPass);
                    }
                }
                if ((validEmail.Succeeded&&validPass == null) || ( validEmail.Succeeded
                        &&password != string.Empty&&validPass.Succeeded)) {
                    IdentityResult result = await UserManager.UpdateAsync(user);
                    if (result.Succeeded) {
                        return RedirectToAction("Index");
                    } else {
                        AddErrorsFromResult(result);
                    }
                }
            } else {
                ModelState.AddModelError("", "User Not Found");
            }
            return View(user);
        }
 
        private void AddErrorsFromResult(IdentityResult result) {
            foreach (string error in result.Errors) {
                ModelState.AddModelError("", error);
            }
        }
 
        private AppUserManager UserManager {
            get {
                return HttpContext.GetOwinContext().GetUserManager<AppUserManager>();
            }
        }
    }
}

The Edit action targeted by GET requests uses the ID string embedded in the Index view to call the FindByIdAsync method in order to get an AppUser object that represents the user.

The more complex implementation receives the POST request, with arguments for the user ID, the new e-mail address, and the password. I have to perform several tasks to complete the editing operation.

The first task is to validate the values I have received. I am working with a simple user object at the moment—although I’ll show you how to customize the data stored for users in Chapter 15—but even so, I need to validate the user data to ensure that I don’t violate the custom policies defined in the “Validating User Details” and “Validating Passwords” sections. I start by validating the e-mail address, which I do like this:

...
user.Email = email;
IdentityResult validEmail = await UserManager.UserValidator.ValidateAsync(user);
if (!validEmail.Succeeded) {
    AddErrorsFromResult(validEmail);
}
...

image Tip  Notice that I have to change the value of the Email property before I perform the validation because the ValidateAsync method only accepts instances of the user class.

The next step is to change the password, if one has been supplied. ASP.NET Identity stores hashes of passwords, rather than the passwords themselves—this is intended to prevent passwords from being stolen. My next step is to take the validated password and generate the hash code that will be stored in the database so that the user can be authenticated (which I demonstrate in Chapter 14).

Passwords are converted to hashes through an implementation of the IPasswordHasher interface, which is obtained through the AppUserManager.PasswordHasher property. The IPasswordHasher interface defines the HashPassword method, which takes a string argument and returns its hashed value, like this:

...
if (password != string.Empty) {
    validPass = await UserManager.PasswordValidator.ValidateAsync(password);
    if (validPass.Succeeded) {
        user.PasswordHash = UserManager.PasswordHasher.HashPassword(password);
    } else {
        AddErrorsFromResult(validPass);
    }
}
...

Changes to the user class are not stored in the database until the UpdateAsync method is called, like this:

...
if ((validEmail.Succeeded && validPass == null)
    || ( validEmail.Succeeded && password != string.Empty &&

        validPass.Succeeded)) {
    IdentityResult result = await UserManager.UpdateAsync(user);
    if (result.Succeeded) {
        return RedirectToAction("Index");
    } else {
        AddErrorsFromResult(result);
    }
}
...

Creating the View

The final component is the view that will render the current values for a user and allow new values to be submitted to the controller. Listing 13-24 shows the contents of the Views/Admin/Edit.cshtml file.

Listing 13-24.  The Contents of the Edit.cshtml File

@model Users.Models.AppUser
@{ ViewBag.Title = "Edit"; }
@Html.ValidationSummary(false)
<h2>Edit User</h2>
 
<div class="form-group">
    <label>Name</label>
    <p class="form-control-static">@Model.Id</p>
</div>
@using (Html.BeginForm()) {
    @Html.HiddenFor(x => x.Id)
    <div class="form-group">
        <label>Email</label>
        @Html.TextBoxFor(x => x.Email, new { @class = "form-control" })
    </div>
    <div class="form-group">
        <label>Password</label>
        <input name="password" type="password" class="form-control" />
    </div>
    <button type="submit" class="btn btn-primary">Save</button>
    @Html.ActionLink("Cancel", "Index", null, new { @class = "btn btn-default" })
}

There is nothing special about the view. It displays the user ID, which cannot be changed, as static text and provides a form for editing the e-mail address and password, as shown in Figure 13-10. Validation problems are displayed in the validation summary section of the view, and successfully editing a user account will return to the list of accounts in the system.

9781430265412_Fig13-10.jpg

Figure 13-10. Editing a user account

Summary

In this chapter, I showed you how to create the configuration and classes required to use ASP.NET Identity and demonstrated how they can be applied to create a user administration tool. In the next chapter, I show you how to perform authentication and authorization with ASP.NET Identity.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.52.7