ASP.NET MVC6 Identity Part 1 – The Database

About a month ago, I published a series of nine articles that tackled ASP.NET Identity in the ASP.NET MVC5 framework using Beta-3 of the framework. Well, Beta-4 is now out, along with Visual Studio 2015 RC and things have changed. As a result, the next five articles will go through the same process as before.

As a reminder, this is what we want to achieve:

  1. Setting up the Identity Database
  2. Logging in as a normal user
  3. Registering a new account
  4. Handling forgotten password requests
  5. Handling Social Logins

I didn’t do that last one the last time because the support from the framework wasn’t included yet. This time I’m definitely going to take a look at that. To start with, I have a base MVC5 project and you can download it at my GitHub Repository (Sorry – the newer versions of ASP.NET5 don’t allow this to compile, so I’ve deleted the repository to reduce confusion). This project currently has no identity provisions, except for a Sign In link that I haven’t filled in yet. You can run it and a Lorem Ipsum type home page will be displayed.

Setting up the Database

As before, I am going to need some packages. The new packages for supporting Identity are as follows:

  1. EntityFramework.Core
  2. EntityFramework.SqlServer
  3. Microsoft.AspNet.Identity
  4. Microsoft.AspNet.Identity.EntityFramework

As before, I’m going to be using Entity Framework v7 to handle the database functionality, and ASP.NET Identity for the authentication and authorization piece. There isn’t a whole lot of documentation about EF7 right now. Also, there are other packages that could be doing Identity – including Microsoft.AspNet.Security packages. This means that the end method hasn’t probably been settled yet and there may be more changes in the future.

Back to this release. I updated the Startup.cs file to include this:

        public Startup()
        {
            Configuration = new Configuration()
                .AddJsonFile("./config.json")
                .AddJsonFile("./config-local.json")
                .AddEnvironmentVariables();
        }

I’m going to check in the config.json file, but not the config-local.json file. If I include any credentials, I’ll put them in the config-local.json file. This allows me to check in something to show the structure but then override it with another configuration file. If you are following along, make sure you create a config-local.json file with a blank object in it.

I’ve added my database connection string to the config.json:

{
  "Mode": "Development",
  "Database": {
    "Connection": "Server=(localdb)\mssqllocaldb;Database=TestDb;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "DefaultUser": {
    "Username": "admin",
    "Password": "Chang3Me!"
  }
}

Last time, I deliberately modified the password to be non-secure. Not so this time – I’ve got my strong password right from the start. Again – you can override the default user password in your own config-local.json file.

The next task is to configure an application database context, linking it to Entity Framework and ASP.NET Identity as well. This is done in the ConfigureServices method inside Startup.cs:

        public void ConfigureServices(IServiceCollection services)
        {
            // Configure our application database context to be Entity Framework backed by SQL Server
            services.AddEntityFramework()
                .AddSqlServer()
                .AddDbContext(options =>
                    options.UseSqlServer(Configuration.Get("Database:Connection")));

            // Specify the configuration of our Application database context
            services.Configure(options =>
            {
                options.DefaultUsername = Configuration.Get("DefaultUser:Username");
                options.DefaultPassword = Configuration.Get("DefaultUSer:Password");
            });

            // Configure ASP.NET Identity to use our Identity-based application context
            services.AddIdentity()
                .AddEntityFrameworkStores()
                .AddDefaultTokenProviders();

            services.AddMvc();
        }

Finally, we need to set up the application to use identity and call into our context to initialize the database. This is done in the Configure method in Startup.cs:

        public void Configure(IApplicationBuilder app)
        {
            app.UseErrorPage(ErrorPageOptions.ShowAll);
            app.UseStaticFiles();
            app.UseIdentity();

            app.UseMvc(r ==>
            {
                r.MapRoute(name: "default", template: "{area=Public}/{controller=Home}/{action=Index}/{id?}");
            });

            ApplicationDbContext.InitializeDatabaseAsync(app.ApplicationServices).Wait();
        }

So far (aside from needing EntityFramework.Core as a package), this is all the same as the beta-3 framework. I need to write the ApplicationDbContext class and its associated options class, plus my ApplicationUse model for the identity database. All my database code is going to go in the Grumpy.Wizards.Database namespace – this includes all three of these files. First up is Database/ApplicationUser.cs:

using Microsoft.AspNet.Identity.EntityFramework;

namespace Grumpy.Wizards.Database
{
    public class ApplicationUser : IdentityUser
    {
    }
}

Next is the Database/ApplicationDbContextOptions.cs file:

namespace Grumpy.Wizards.Database
{
    public class ApplicationDbContextOptions
    {
        public string DefaultUsername { get; set; }

        public string DefaultPassword { get; set; }
    }
}

These are identical to the classes that I used in Beta-3. The last time around, I split the database context into a static initialization class and a non-static context class. I’ve combined them this time into a single file Database/ApplicationDbContext.cs:

using System;
using System.Threading.Tasks;

using Microsoft.AspNet.Identity;
using Microsoft.AspNet.Identity.EntityFramework;
using Microsoft.Data.Entity.SqlServer;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;

namespace Grumpy.Wizards.Database
{
    public class ApplicationDbContext : IdentityDbContext
    {
        const string adminRole = "admin";

        public static async Task InitializeDatabaseAsync(IServiceProvider serviceProvider)
        {
            using (var db = serviceProvider.GetRequiredService())
            {
                var sqlDb = db.Database as SqlServerDatabase;
                if (sqlDb != null)
                {
                    await sqlDb.EnsureCreatedAsync();
                    await CreateAdminUser(serviceProvider);
                }
            }
        }

        private static async Task CreateAdminUser(IServiceProvider serviceProvider)
        {
            var options = serviceProvider.GetRequiredService<IOptions>().Options;
            var userMgr = serviceProvider.GetRequiredService<UserManager>();
            var roleMgr = serviceProvider.GetRequiredService<RoleManager>();

            if (!await roleMgr.RoleExistsAsync(adminRole))
            {
                await roleMgr.CreateAsync(new IdentityRole(adminRole));
            }

            var user = await userMgr.FindByNameAsync(options.DefaultUsername);
            if (user == null)
            {
                user = new ApplicationUser { UserName = options.DefaultUsername };
                var userCreationResult = await userMgr.CreateAsync(user, options.DefaultPassword);
                if (userCreationResult.Succeeded)
                {
                    await userMgr.AddToRoleAsync(user, adminRole);
                }
            }
        }
    }
}

The actual code for setting up the database is identical to the code used in beta-3. At this point, you are probably wondering why I didn’t just refer people to the other article. Well, I like tutorials to stand alone, especially when there is a sequence of articles. I also wanted to show off that you can integrate the creation of the database with the identity database context – there is no need for multiple classes here. Since these two elements deal with the same thing (the database), it’s appropriate that they are together.

Testing Your Work

Run the project and wait for the site to load (which it should do). Then use the View menu to bring up the SQL Server Object Explorer. Add the (localdb)mssqllocaldb database instance. Open up the Databases, then TestDb and take a look at the dbo.AspNetUsers table.

blog-code-0529-1

If you right click and View the data, you should see the admin user has been created. That’s all we can hope for right now as we have not wired up the login form as yet. More on that in the next article.