Nozus Step 3: Adding ASP.Net Identity to MVC 6

In the previous post, we added some simple logging to our API using Serilog and simple middleware. In this installment, we’ll be adding identity management to that project using the standard ASP.Net Identity libraries.

First, let’s do a little project cleanup.  In Nozus.Data and Nozus.Domain, go ahead and delete the default Class1 that was added when we created those projects. Also, from the HomeController in Web.Api, make sure you removed the thrown exception added at the end of the previous post.

Now we’re going to add in packages needed for the identity system. Open your Package Manager Console (Tools –> Nuget Package Manager –> Package Manager Console).  In the console select your VNext package source and install the Identity.EntityFramework package into both the Data and Web.Api projects.  Make sure to includeprerelease which can be abbreviated to “-inc”.

PM> Install-Package Microsoft.AspNet.Identity.EntityFramework -inc
Installing NuGet package Microsoft.AspNet.Identity.EntityFramework.3.0.0-beta3.
PM> Install-Package Microsoft.AspNet.Identity.EntityFramework -inc
Installing NuGet package Microsoft.AspNet.Identity.EntityFramework.3.0.0-beta3.

We’re going to be using SQL Server as the data store, but you can use any applicable store.  From the Package Manager Console, install EntityFramework.SqlServer into the Web.Api project only.

PM> Install-Package EntityFramework.SqlServer -inc
Installing NuGet package EntityFramework.SqlServer.7.0.0-beta3.

Finally, in our Domain project, we’re going to add in a package to support basic Authentication classes.  We’ll derive from these later.

PM> Install-Package Microsoft.AspNet.Identity.Authentication -inc
Installing NuGet package Microsoft.AspNet.Identity.Authentication.3.0.0-alpha4.

Now that all packages are installed…let’s put identity into place.  We’re going to first add in our user and role classes into the Domain project.  In the Domain project, add an Entities folder and inside of that folder add an Identity folder. Inside of the Identity folder, add two classes, AppUser and AppRole.  For the moment, these classes require no implementation other than inheriting from the proper identity classes.  AppUser will inherit from IdentityUser<int> and AppRole will inherit from IdentityRole<int>.  The type specifies the type of the ID that will be used in the entities and the database.  By default, an int will be set up as an Identity column in SQL Server.

using Microsoft.AspNet.Identity;
namespace Nozus.Domain.Entities.Identity
{
    public class AppUser : IdentityUser<int>
    {}
}

using Microsoft.AspNet.Identity;
namespace Nozus.Domain.Entities.Identity
{
   public class AppRole : IdentityRole<int>
   {}
}

Now add a reference to the Domain project from the Data project.  Add a reference to both Domain and Data from the Web.Api.  In the Data project, add an AppDbContext class.  Inherit this class from IdentityDbContext<AppUser, AppRole, int>.


using Microsoft.AspNet.Identity.EntityFramework;
using Nozus.Domain.Entities.Identity;
namespace Nozus.Data
{
    public class AppDbContext 
        : IdentityDbContext<AppUser,AppRole,int>;
    {}
}

IdentityClassesWe’ll use this as our EF dbContext for the solution, and we can dual purpose it by inheriting from the IdentityDbContext.  We’re also specifying our User and Role types as well as the type for the IDs, which applies to both User and Role.  The project structure should now look similar to the image at the right.

Now lets fix up our Web.Api project to use this identity setup.  All the initial action here is going to be in the Startup class because, as expected, the identity functionality is set up using middleware.  In the ConfigureServices method, we’ll configure both Entity Framework and Identity as follows:


public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(Configuration);

// Add EF services to the services container.
services.AddEntityFramework(Configuration)
.AddSqlServer()
.AddDbContext<AppDbContext>();

// Add Identity services to the services container.
services.AddIdentity<AppUser, AppRole>(Configuration)
.AddEntityFrameworkStores<AppDbContext, int>();

services.AddMvc();
services.Configure<MvcOptions>(options =>
{
  options.OutputFormatters.RemoveAll(formatter => 
  formatter.Instance is XmlDataContractSerializerOutputFormatter);
});
}

You can  see that we’re adding EF, telling it to use SQL Server and then giving it the type of our DbContext.  Next we’re adding identity, telling it our user and role types, and letting it know to use an EF store with our DbContext and the type of IDs it will be using.  Next, we’ll do some more setup in the Configure method.


public void Configure(IApplicationBuilder app, 
  IHostingEnvironment env, ILoggerFactory loggerfactory)
{
  loggerfactory.AddSerilog(GetLoggerConfiguration());
  app.UseStaticFiles();

  //*** Tell the app to use Identity ***//
  app.UseIdentity();

  app.UseMiddleware<ErrorLoggingMiddleware>();
  app.UseMvc(routes =>
  {
    routes.MapRoute(
    name: "default",
    template: "{controller}/{action}/{id?}";,
    defaults: new { 
        controller = "Home", 
        action = "Index" });
  });

  //***  Initialize the DB ***//
  if(env.EnvironmentName == "Development")
    InitializeDb(app.ApplicationServices).Wait();
}

private static async Task InitializeDb(
  IServiceProvider applicationServices)
{
  using (var dbContext = 
    applicationServices.GetService<AppDbContext>())
  {
    var sqlServerDatabase = 
      (SqlServerDatabase)dbContext.Database;
    await sqlServerDatabase.EnsureDeletedAsync();
    if (await sqlServerDatabase.EnsureCreatedAsync())
    {
      //We could add some test data...perhaps later
    }
  }
}

Here, we’re telling the app to use identity.  We’re also calling our InitializeDb method to drop and recreate our database. This is mainly in place for dev purposes, so you could call alternate database setups depending on whether you were running integration tests/developing/etc…  We’ll just have an empty database to start with for now.  We’re using the built in env.EnvironmentName to test that this is running in Dev before dropping and recreating our database.

When you installed the EF packaged into the Web.Api project, it should have automatically added a config section to your config.json file.  Take a look, it should look similar to the following:

{
 "Data": {
   "DefaultConnection": {
   "ConnectionString": 
    "Server=localhost;Database=Nozus;Trusted_Connection=True;
     MultipleActiveResultSets=true"
   }
 },
   "EntityFramework": {
     "AppDbContext": {
     "ConnectionString": "Data:DefaultConnection:ConnectionString"
     }
   }
}

The data section should contain a connection string.  By default it will set up a connection string to localDB with a random database name.  I’ve changed it to point to my local Sql Server Dev Edition, but localDB will work just fine.  You can explicitly set up which connection string EF uses, but by default, it’s going to find the entry under the EntityFramework section that matches the name of the DbContext.  So in this case, make sure that it’s named “AppDbContext”.

When exchanging data with our API, it’s important that we define contracts for data going in and out. We don’t want to use the domain classes, as they will have lots of properties that we don’t want to expose to the outside world.  If there isn’t already a Models folder in your Web.Api project, add one.  In this folder, create a new class called UserModel.  It should look like the following:

using System.ComponentModel.DataAnnotations;
namespace Nozus.Web.Api.Models
{
public class UserModel
{
   public int? Id { get; set; }
  [Required]
  [Display(Name = "User name")]
  public string UserName { 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; }
}
}

So now that we’ve got a model class, how to we map from our Domain AppUser class to this UserModel class. You can roll your own mappers, which is fine, but you might want to use a mapping library.  In .Net almost everybody uses AutoMapper. It is very versatile and works in a variety of environments.  So feel free to use AutoMapper if you like, but I’m going to use Mapster instead. It’s another mapper that I currently support.  I use it because it gives me everything I need from AutoMapper and is ~10-50x faster on average. The setup of these two mappers is very similar, so they translate easily in most cases.  Install the Mapster package into your Web.Api project, selecting nuget.org as your source.

PM> Install-Package Mapster
Installing NuGet package Mapster.1.14.0.

Now create a Mapping folder in your Web.Api project.  Create a class in this folder called UserMapping.  It should inherit from Mapster’s Registry class. A registry class can be scanned at startup to register all of the mappings.

using Mapster;
using Mapster.Registration;
using Nozus.Domain.Entities.Identity;
using Nozus.Web.Api.Models;
namespace Nozus.Web.Api.Mapping
{
public class UserMapping : Registry
{
   public override void Apply()
  {
    TypeAdapterConfig<AppUser, UserModel>.NewConfig()
    .Ignore(dest => dest.ConfirmPassword)
    .Ignore(dest => dest.Password);
  }
}
}

Here, we’re telling Mapster to map AppUser to UserModel and to ignore the destination ConfirmPassword and Password fields since we don’t want those returned.  Now in the Startup.cs constructor, we’ll add a call to find and register all of our Registry classes.  Of course right now there’s just the one, but it will grow as the application grows.

public Startup(IHostingEnvironment env)
{
  Configuration = new Configuration()
  .AddJsonFile("config.json")
  .AddEnvironmentVariables();
  //Scan for our mappings
  Mapster.Registration.Registrar
  .RegisterFromAssembly(Assembly.GetExecutingAssembly());
}

So now to finish up we’ll implement our AccountController. This will do things like register new users, reset passwords, inactivate users etc…  For right now we’ll just add methods to add a new users and to retrieve a user.  Add the below code to your AccountController class.

using System.Net;
using System.Threading.Tasks;
using Mapster;
using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Mvc;
using Nozus.Domain.Entities.Identity;
using Nozus.Web.Api.Models;

namespace Nozus.Web.Api.Controllers
{
  [Route("[controller]")]
  public class AccountController : Controller
  {
   private readonly UserManager<AppUser> _userManager;

  public AccountController(UserManager<AppUser> userManager)
  {
    _userManager = userManager;
  }

 // POST api/Account/
 [AllowAnonymous]
 [HttpPost]
 public async Task<IActionResult> Post(
 [FromBody] UserModel userModel)
 {
   if (!ModelState.IsValid)
   {
     return new BadRequestObjectResult(ModelState);
   }
   var user = new AppUser {UserName = userModel.UserName};

   IdentityResult idResult = 
   await _userManager.CreateAsync(user, userModel.Password);

   IActionResult errorResult = GetErrorResult(idResult);
   if (errorResult != null)
   {
     return errorResult;
   }
   //Put together a response
   string url = 
    Url.RouteUrl("GetUserById", new { userId = user.Id },
   Request.Scheme, Request.Host.ToUriComponent());
   Context.Response.Headers["Location"] = url;
   Context.Response.StatusCode = (int)HttpStatusCode.Created;

   return new ObjectResult(TypeAdapter.Adapt<UserModel>(user));
}

//GET api/Account/1
[HttpGet("{userId:int}", Name = "GetUserById"]
public async Task<IActionResult> GetUserById(int userId)
{
  var user = 
   await _userManager.FindByIdAsync(userId.ToString());

  return new ObjectResult(TypeAdapter.Adapt<UserModel>(user));
} 

private IActionResult GetErrorResult(IdentityResult result)
{
  if (!result.Succeeded)
  {
   if (result.Errors != null)
   {
     foreach (var error in result.Errors)
     {
         ModelState.AddModelError(error.Code, error.Description);
     }
   }
   return new BadRequestObjectResult(ModelState);
  }
  return null;
}

}
}

So there’s a fair amount going on here.  The Post method accepts a UserModel, validates it using the annotations on the class and returns a BadRequest response with the model errors if validation fails.  If it succeeds, it uses the UserManager that is included with Identity to attempt to create an AppUser.  If the create fails, we will build an error response and return it. If the create is successful, it will store this new AppUser in our database and will also populate its assigned UserId.  We’ll create an object response and use Mapster to map the AppUser back to a response UserModel, but we’ll also add a header to the response that points to the Get uri that can be used to retrieve the user.

Now let’s try this out.  Start your project and the Home page should be displayed. If you don’t have it installed already, install Postman.  Fire up Postman and lets perform a Post to our API to create a new user.

CreateUserPostman

Select POST from the method dropdown.  Set the url to {your api url}/Account. Here, mine is http://localhost:13171/Account.  Add a a Content-Type header and set it to application/json.  Put some json in the payload that will pass password validation:

{
 "userName": "RicoSuave",
 "password": "MyPassword_123",
 "confirmPassword": "MyPassword_123"
}

Now click the Send button.  The tabs on the bottom should show the response.  A new user with ID should be returned.  If you examine the Headers tab, you will also see the Location header we added to the response.  Take note of the ID and try out the GET that we added to retrieve the user as well.  Note that because our DB setup method in Startup.cs deletes the DB on each restart, your users will be lost with each run of your project. Feel free to change this.

Before we end, there’s one more change I want to make.  Most consumers expect our response to come across as camel-cased json.  In addition, we don’t want to transmit null values.  In json, we should just skip that property to keep our payload concise.  Open the Startup.cs class and in the ConfigureService method, replace the code to set our MvcOptions with the following:

services.Configure<MvcOptions>(options =>
{
  options.OutputFormatters.RemoveAll(
    f => f.Instance is XmlDataContractSerializerOutputFormatter);

  var formatter = options.OutputFormatters.FirstOrDefault(
    f => f.Instance is JsonOutputFormatter);

  var jsonFormatter = formatter?.Instance as JsonOutputFormatter;
  if (jsonFormatter != null)
  {
    jsonFormatter.SerializerSettings.ContractResolver = 
      new CamelCasePropertyNamesContractResolver();
    jsonFormatter.SerializerSettings.NullValueHandling = 
      NullValueHandling.Ignore;
  }
});

This will drop null values from our json formatting and camel case all properties by default.  Try out the API again to see the difference.

Next time, we’ll take a look at setting up a basic Aurelia project and calling our basic API.

Advertisements

2 thoughts on “Nozus Step 3: Adding ASP.Net Identity to MVC 6

    1. Thanks for the shout Nikita, agreed that mapster isn’t compiled against core…frankly going forward I would take a look at ExpressMapper. I’m using it myself on some things. It’s got some key pieces missing but it’s a great alternative to AutoMapper and they are planning on adding core support (just not yet).

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s