ASP.NET MVC and Identity 2.0: Understanding the Basics

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

Kryha-crypto-machineOn March 20, 2014, the ASP.NET team released the RTM version 2.0 of the new Identity framework. The new release brings with it some long-awaited new features, and marks a substantial expansion of the security and authorization capabilities available to ASP.NET applications of all types.

The ASP.NET Identity framework was originally introduced in 2013 as the follow-on to the ASP.NET Membership system, a staple of MVC applications for several years, but which was beginning to show its age. Originally, ASP.NET Identity presented a useful, if somewhat minimal API for managing security and authorization in the context of a public-facing web application built using ASP.NET. The Identity framework introduced modern features such as social network log-in integration, and easily extensible user model definitions.

Image by Ryan Somma  |  Some Rights Reserved

The new RTM release introduces the following features, among others:

  • Extended User Account Definition, including Email and contact information
  • Two-Factor Authentication via email or SMS messaging, functionally similar to that used by Google, Microsoft, and others
  • Account Confirmation via email
  • Administrative management of Users and Roles
  • Account Lock-Out in response to invalid log-in attempts
  • Security Token Provider to regenerate a user's security token in response to changes in security settings.
  • Improved support for Social log-ins
  • Easy Integration of Claims-Based Authorization

Identity 2.0 represents a substantial revision from the original version introduced last year. With the numerous new features, comes some added complexity. If, like myself, you had just recently found your way through the first iteration of the Identity framework, be ready. While you won't be starting over from scratch with version 2.0, there is a lot to learn.

In this article, we're going to take a look around, get familiar with the major components of the system, and in general familiarize ourselves with the new features, and where they fit in the overall scheme of things. We won't go into too much detail yet. Think of this as a familiarization tour.

If you are looking for more detailed how-to's, I will be adding posts over the next few weeks examining specific implementation concerns here:

While we will be looking at a decent amount of code, it's not necessary yet to understand the details of what it all does - just get familiar with the general concepts, where the major components are located, and how things are structured.

Identity 2.0 Introduces Breaking Changes

Identity 2.0 does not slide smoothly into place for applications written using version 1. The additional capabilities appear to have required significant changes to the architecture, and the manner in which the Identity API is consumed from within the application. Upgrading an existing ASP.NET application from Identity 1.0 to the 2.0 version will require some new code, and is beyond the scope of this article. Be aware, though, that moving from Identity 1.0 to the 2.0 version is not a simple "plug-it-in-and-go" affair.

Getting Started - Get the Examples from Nuget

As of this writing, there is not a directly available ASP.NET MVC project template using Identity 2.0. In order to take Identity for a spin, you need to pull the example project libraries into an empty ASP.NET MVC project. First, Create a new ASP.NET project, and select the Empty Project template from the template options dialog:

Select the Empty ASP.NET Project Template:

select-empty-asp-net-project-template

Once you have create the new Empty project, you can get the Identity 2.0 sample project from Nuget by typing the following into the Package Manager Console:

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

 

Once Nuget has done its thing, you should see a folder structure in the Solutions Explorer that looks quite like a standard MVC project. Nuget has basically added everything needed to compose a complete ASP.NET MVC project, including Models, Views, Controllers, and various components required for this basic application to run.

While at first glance the project components look fairly similar, a closer look will reveal some significant changes, and some added complexity.

Identity 2.0 Configuration - Not So Simple Anymore

In my mind, one of the strengths of the original Identity framework was also its primary weakness (strange how THAT works in software, isn't it?). The simplicity of the Identity version 1.0 made it extraordinarily easy to use, and relatively intuitive to figure out. On the other hand, the utter simplicity also meant that the feature set available "out-of-the-box" was limited, and to some, insufficient.

Just to get an idea, we'll take a quick look at some of the configuration that runs when our application starts, and compare it to the comparable code in an application which used Identity Version 1.0.

In both flavors of project, we find a class file named Startup.cs at the root level of the project. In this file a class named Startup is defined, and makes a single call to the method ConfigureAuth(). What we DON'T see anywhere in this file is an actual method named ConfigureAuth(). This is because the rest of the code for the Startup class is defined in a partial class tucked away in the App_Start folder. The code file is named Startup.Auth.cs, but if we open that, we find a standard partial class definition, which contains the ConfigureAuth() method. In a project using the original version 1.0 of the Identity Framework, the standard code for ConfigureAuth() looks like this:

Standard ConfigureAuth() Method Using Identity 1.0:
public partial class Startup
{
    public void ConfigureAuth(IAppBuilder app)
    {
        // Enable the application to use a cookie to 
        // store information for the signed in user
        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login")
        });
        // Use a cookie to temporarily store information about a 
        // user logging in with a third party login provider
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
  
        // Uncomment the following lines to enable logging 
        // in with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");
  
        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");
  
        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");
  
        //app.UseGoogleAuthentication();
    }
}

 

In the above, we see some boilerplate code for configuring cookies, and some commented out code which can be uncommented, and then called to enable third-party logins from various social media providers.

In contrast, when we look at the ConfigureAuth() method in our project using Identity 2.0, we see a bit more code has been added:

ConfigureAuth() Method from Project Using Identity 2.0:
public partial class Startup {
  
    public void ConfigureAuth(IAppBuilder app) {
  
        // Configure the db context, user manager and role 
        // manager to use a single instance per request
        app.CreatePerOwinContext(ApplicationDbContext.Create);
        app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
        app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    
        // Enable the application to use a cookie to store information for the 
        // signed in user and to use a cookie to temporarily store information 
        // about a user logging in with a third party login provider 
        // Configure the sign in cookie
        app.UseCookieAuthentication(new CookieAuthenticationOptions {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider {
  
                // Enables the application to validate the security stamp when the user 
                // logs in. This is a security feature which is used when you 
                // change a password or add an external login to your account.  
                OnValidateIdentity = SecurityStampValidator
                    .OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) 
                    => user.GenerateUserIdentityAsync(manager))
            }
        });
  
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
  
        // Enables the application to temporarily store user information when 
        // they are verifying the second factor in the two-factor authentication process.
        app.UseTwoFactorSignInCookie(
            DefaultAuthenticationTypes.TwoFactorCookie, 
            TimeSpan.FromMinutes(5));
  
        // Enables the application to remember the second login verification factor such 
        // as phone or email. Once you check this option, your second step of 
        // verification during the login process will be remembered on the device where 
        // you logged in from. This is similar to the RememberMe option when you log in.
        app.UseTwoFactorRememberBrowserCookie(
            DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
  
        // Uncomment the following lines to enable logging in 
        // with third party login providers
        //app.UseMicrosoftAccountAuthentication(
        //    clientId: "",
        //    clientSecret: "");
  
        //app.UseTwitterAuthentication(
        //   consumerKey: "",
        //   consumerSecret: "");
  
        //app.UseFacebookAuthentication(
        //   appId: "",
        //   appSecret: "");
  
        //app.UseGoogleAuthentication();
    }
}

 

Above, the first thing we notice are several calls to app.CreatePerOwinContext, wherein we register callbacks to be invoked to create instances of the type specified by the type arguments. Type instances created are then available using the context.Get() method.

What this tells us is that, at least for the purpose of the example project supplied by the Identity 2.0 team, Owin is now part of our application, and Identity 2.0 relies upon it to deliver the goods. I am not clear on whether Owin is REQUIRED in general for identity 2.0 to work, but for the purpose of our example project, it is.

We can also see some other new calls in the ConfigureAuth method body setting up Two-Factor Authentication, and some additional cookie authentication configuration code not present in the previous version. 

For our purposes here, we can assume that most of this stuff has been configured optimally for our basic use, and short of adding social media logins (which we're not going to look at in this article), we can leave this code alone. But bear in mind that this is where a lot of the Identity behavior in your application is set at runtime, based on the configuration components and helpers defined in another file in the App_Start folder, IdentityConfig.cs.

Before we go seeing what's up in IdentityConfig.cs, though, it will help us understand what's going on there is we first take a look at the ApplicationUser class, defined for us in the Models folder.

The New ApplicationUser Class in Identity 2.0

If you have built out an application using the previous version of Identity framework, you may have run into the situation where you found the core IdentityUser class rather limiting. Previously, Identity used a very simple IdentityUser implementation which represented a very minimal user profile:

The Original IdentityUser Class From Identity Version 1.0:
public class IdentityUser : IUser
{
    public IdentityUser();
    public IdentityUser(string userName);
  
    public virtual string Id { get; set; }
    public virtual string UserName { get; set; }
    public virtual ICollection<IdentityUserRole> Roles { get; }
  
    public virtual ICollection<IdentityUserClaim> Claims { get; }
    public virtual ICollection<IdentityUserLogin> Logins { get; }
    public virtual string PasswordHash { get; set; }
    public virtual string SecurityStamp { get; set; }
}

 

Of the properties available in the above, only the first three, Id, UserName, and Roles were of much use from the business perspective of our application. The other items are mainly used by the security logic, which, while important, does not help us maintain useful information about users.

In a previous article, we examined how to extend Identity Accounts and implement Role-Based Authentication under Identity 1.0 to add more useful data, such as a user email address and/or other information which might be needed by our application.

With the advent of Identity 2.0, the need to create such work-arounds is diminished somewhat. While it is still possible to extend the Identity 2.0 ApplicationUser class in a similar manner, the Identity team has taken care of some of the more common use-cases for us.

What we find is that the example project already contains a subclass ApplicationUser, which is derived from a more complex default IdentityUser implementation.

We find the definition for ApplicationUser in the Models folder, in a file named IdentityModels.cs. We can see that the class definition itself is as simple as can be:

The ApplicationUser Class in Identity 2.0:
public class ApplicationUser : IdentityUser {
    
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(
        UserManager<ApplicationUser> manager) {
        // Note the authenticationType must match the one 
        // defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = 
            await manager.CreateIdentityAsync(this, 
                DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
}

 

We see here that ApplicationUser is, as stated previously, a sub-class of IdentityUser. However, if we find the definition for IdentityUser (using VS Go to definition context menu item) we see that IdentityUser, as defined under the Identity 2.0 framework, is itself a sub-class of IdentityUser<TKey, TLogin, TRole, TClaim> . When we look at the definition of THAT, we find a significantly different animal from the version 1.0 implementation:

IdentityUser Implementation from Identity 2.0:
public class IdentityUser<TKey, TLogin, TRole, TClaim> : IUser<TKey>
    where TLogin : Microsoft.AspNet.Identity.EntityFramework.IdentityUserLogin<TKey>
    where TRole : Microsoft.AspNet.Identity.EntityFramework.IdentityUserRole<TKey>
    where TClaim : Microsoft.AspNet.Identity.EntityFramework.IdentityUserClaim<TKey>
{
    public IdentityUser();
    // Used to record failures for the purposes of lockout
    public virtual int AccessFailedCount { get; set; }
    // Navigation property for user claims
    public virtual ICollection<TClaim> Claims { get; }
    // Email
    public virtual string Email { get; set; }
    // True if the email is confirmed, default is false
    public virtual bool EmailConfirmed { get; set; }
    // User ID (Primary Key)
    public virtual TKey Id { get; set; }
    // Is lockout enabled for this user
    public virtual bool LockoutEnabled { get; set; }
    // DateTime in UTC when lockout ends, any 
    // time in the past is considered not locked out.
    public virtual DateTime? LockoutEndDateUtc { get; set; }
    // Navigation property for user logins
    public virtual ICollection<TLogin> Logins { get; }
    // The salted/hashed form of the user password
    public virtual string PasswordHash { get; set; }
    // PhoneNumber for the user
    public virtual string PhoneNumber { get; set; }
    // True if the phone number is confirmed, default is false
    public virtual bool PhoneNumberConfirmed { get; set; }
    // Navigation property for user roles
    public virtual ICollection<TRole> Roles { get; }
    // A random value that should change whenever a users 
    // credentials have changed (password changed, login removed)
    public virtual string SecurityStamp { get; set; }
    // Is two factor enabled for the user
    public virtual bool TwoFactorEnabled { get; set; }
    // User name
    public virtual string UserName { get; set; }
}

 

Note in the above, a whole lot of those properties are again related to authorization and security, and not to our business needs for user data. However, the Email and PhoneNumber fields definitely go a long way towards minimizing the need for additional customization of the ApplicationUser class.

But, what's with the weird generic type arguments in the class declaration?

The new version of IdentityUser implements generic type arguments to allow for additional flexibility. As an example, recall that in Identity Version 1.0, the Id property was a string. Here, the generic type argument TKey allows us to specify the type of the Id field. We can see in the above, the Id property declaration returns the type specified by TKey:

The Id Property Declaration:
public virtual TKey Id { get; set; }

 

Also of particular note, the Roles property, defined as follows:

The Roles Property:
public virtual ICollection<TRole> Roles { get; }

 

We can see the the Type TRole is left open at compile time, and in fact is specified in the generic declaration of the IdentityUser class. If we look at the type constraints in that declaration, we see the TRole is constrained to the type IdentityUserRole<TKey> which is not terribly different from the version 1.0 implementation. What IS different, and which represents a breaking change, is the definition of IdentityUserRole itself.

Previously, in version 1.0 of the Identity Framework, IdentityUserRole was defined as follows:

The IdentityUserRole Class from Identity 1.0:
public class IdentityUserRole 
{
      public IdentityUserRole();
      public virtual IdentityRole Role { get; set; }
      public virtual string RoleId { get; set; }
      public virtual IdentityUser User { get; set; }
      public virtual string UserId { get; set; }
}

 

Compare with the Identity 2.0 Implementation:

The IdentityUserRole Class from Identity 2.0:
public class IdentityUserRole<TKey> 
{
    public IdentityUserRole();
    public virtual TKey RoleId { get; set; }
    public virtual TKey UserId { get; set; }
}

 

See what happened there? The former contained references to an IdentityRole object, and an IdentityUser object. The version 2.0 implementation contains only Id values. If you had done any customization under the previous version, such as we discussed in Implementing Group-Based Permissions, this will break things.

We will take a closer look at the new flexibility created with the new, extended IdentityUser class in a subsequent post. For now, realize that while the basic user class definition has become more complex, it has also become significantly more flexible.

Since ApplicationUser sub-classes IdentityUser, all of the above properties are available to ApplicationUser, which is the basic implementation used in the example application.

Now that we have taken a quick look at the new ApplicationUser implementation, the configuration components and helpers we are about to look at will make more sense.

Identity 2.0 Configuration Components and Helpers

While the ConfigAuth() method of the Startup class is where the runtime configuration for Identity happens during startup, we actually use the components defined in the IdentityConfig.cs file to configure how most of the Identity 2.0 features behave in our application.

If we examine the content of the IdentityConfig.cs file, we find that there are a number of individual classes defined therein. We could split each one out into its own code file, but for now, we will just examine each class independently, despite the fact that they all share the same file location in our project. Not that all of these classes are enclosed in the ApplicationName.Models namespace.

Application User Manager and Application Role Manager

The first things we run into in the IdentityConfig.cs file are two helper classes, ApplicationUserManager and ApplicationRoleManager. Be ready - large blobs of code with generic types ahead!

The Identity 2.0 Application User Manager Class:
public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(IUserStore<ApplicationUser> store)
        : base(store)
    {
    }
  
    public static ApplicationUserManager Create(
        IdentityFactoryOptions<ApplicationUserManager> options, 
        IOwinContext context)
    {
        var manager = new ApplicationUserManager(
            new UserStore<ApplicationUser>(
                context.Get<ApplicationDbContext>()));
  
        // Configure validation logic for usernames
        manager.UserValidator = 
            new UserValidator<ApplicationUser>(manager)
        {
            AllowOnlyAlphanumericUserNames = false,
            RequireUniqueEmail = true
        };
  
        // Configure validation logic for passwords
        manager.PasswordValidator = new PasswordValidator
        {
            RequiredLength = 6, 
            RequireNonLetterOrDigit = true,
            RequireDigit = true,
            RequireLowercase = true,
            RequireUppercase = true,
        };
  
        // Configure user lockout defaults
        manager.UserLockoutEnabledByDefault = true;
        manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
        manager.MaxFailedAccessAttemptsBeforeLockout = 5;
  
        // Register two factor authentication providers. This application uses 
        // Phone and Emails as a step of receiving a code for verifying 
        // the user You can write your own provider and plug in here.
        manager.RegisterTwoFactorProvider("PhoneCode", 
            new PhoneNumberTokenProvider<ApplicationUser>
        {
            MessageFormat = "Your security code is: {0}"
        });
  
        manager.RegisterTwoFactorProvider("EmailCode", 
            new EmailTokenProvider<ApplicationUser>
        {
            Subject = "SecurityCode",
            BodyFormat = "Your security code is {0}"
        });
  
        manager.EmailService = new EmailService();
        manager.SmsService = new SmsService();
        var dataProtectionProvider = options.DataProtectionProvider;
        if (dataProtectionProvider != null)
        {
            manager.UserTokenProvider = 
                new DataProtectorTokenProvider<ApplicationUser>(
                    dataProtectionProvider.Create("ASP.NET Identity"));
        }
        return manager;
    }
  
  
    public virtual async Task<IdentityResult> AddUserToRolesAsync(
        string userId, IList<string> roles)
    {
        var userRoleStore = (IUserRoleStore<ApplicationUser, string>)Store;
  
        var user = await FindByIdAsync(userId).ConfigureAwait(false);
        if (user == null)
        {
            throw new InvalidOperationException("Invalid user Id");
        }
  
        var userRoles = await userRoleStore
            .GetRolesAsync(user)
            .ConfigureAwait(false);
  
        // Add user to each role using UserRoleStore
        foreach (var role in roles.Where(role => !userRoles.Contains(role)))
        {
            await userRoleStore.AddToRoleAsync(user, role).ConfigureAwait(false);
        }
        // Call update once when all roles are added
        return await UpdateAsync(user).ConfigureAwait(false);
    }
  
  
    public virtual async Task<IdentityResult> RemoveUserFromRolesAsync(
        string userId, IList<string> roles)
    {
        var userRoleStore = (IUserRoleStore<ApplicationUser, string>) Store;
  
        var user = await FindByIdAsync(userId).ConfigureAwait(false);
        if (user == null)
        {
            throw new InvalidOperationException("Invalid user Id");
        }
  
        var userRoles = await userRoleStore
            .GetRolesAsync(user)
            .ConfigureAwait(false);
  
        // Remove user to each role using UserRoleStore
        foreach (var role in roles.Where(userRoles.Contains))
        {
            await userRoleStore
                .RemoveFromRoleAsync(user, role)
                .ConfigureAwait(false);
        }
        // Call update once when all roles are removed
        return await UpdateAsync(user).ConfigureAwait(false);
    }
}

 

For what appears to be a large chunk of code, ApplicationUserManager actually only provides a handful of very important functions - Adding new Users, Adding Users to Roles, and Removing Users from Roles. However, ApplicationUserManager is derived from the UserManager<ApplicationUser> class, so all the functionality provided by UserManager is also available to ApplicationUserManager. Other than that, there is a static Create() method defined which returns an instance of ApplicationUserManager itself. It is in this method that much of your user configuration settings and default are set up.

Of particular note in the Create() method is the call to context.Get<ApplicationDBContext>(). Remember earlier, when we looked at the ConfigAuth() method with those calls to CreatePerOwinContext and we passed in a callback method? The call to context.Get<ApplicationDbContext>() executes that call back, in this case, the static method ApplicationDbContext.Create() . We'll see more of this shortly.

If you look closely, you can see that user authorization, authentication, and management settings and defaults are set up in the Create() method, before returning a new ApplicationUserManager instance to the caller. Also, this is where two-factor auth services are set up. We can see that most of the settings are fairly self-explanatory. However, the two services bear a closer look. We'll come back to that in a moment. First, a quick look at  the ApplicationRoleManager class:

The Application Role Manager Class:
public class ApplicationRoleManager : RoleManager<IdentityRole>
{
    public ApplicationRoleManager(IRoleStore<IdentityRole,string> roleStore)
        : base(roleStore)
    {
    }
  
    
    public static ApplicationRoleManager Create(
        IdentityFactoryOptions<ApplicationRoleManager> options, 
        IOwinContext context)
    {
        var manager = new ApplicationRoleManager(
            new RoleStore<IdentityRole>(
                context.Get<ApplicationDbContext>()));
  
        return manager;
    }
}

 

As with ApplicationUserManager, we can see that ApplicationRoleManager is derived from RoleManager<IdentityRole> and thus brings with it all of the functionality offered by that class as well. Once again, we see a static Create() method returning an instance of the class itself.

Email Service and SMS Service for Account Validation and Two-Factor Auth

Also in the IdentityConfig.cs file are two service classes, EmailService and SmsService. Out of the box, these two classes are basically empty wrappers, providing an abstraction within which you can implement Email and/or SMS services required for two-factor authentication and account validation.

The ASP.NET Identity Email Service Class:
public class EmailService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your email service here to send an email.
        return Task.FromResult(0);
    }
}

 

The ASP.NET Identity SmsService Class:
public class SmsService : IIdentityMessageService
{
    public Task SendAsync(IdentityMessage message)
    {
        // Plug in your sms service here to send a text message.
        return Task.FromResult(0);
    }
}

 

Note that both of these classes implement a common interface, IIdentityMessageService. Also recall in the ApplicationUserManager.Create() method, the following lines:

Setting Up the Email Service and the SMS Service in the ApplicationUserManager Create Method:
// Register two factor authentication providers. This application uses 
// Phone and Emails as a step of receiving a code for verifying 
// the user You can write your own provider and plug in here.
manager.RegisterTwoFactorProvider("PhoneCode", 
    new PhoneNumberTokenProvider<ApplicationUser>
{
    MessageFormat = "Your security code is: {0}"
});
  
manager.RegisterTwoFactorProvider("EmailCode", 
    new EmailTokenProvider<ApplicationUser>
{
    Subject = "SecurityCode",
    BodyFormat = "Your security code is {0}"
});
  
manager.EmailService = new EmailService();
manager.SmsService = new SmsService();

 

We can see that during the course of the Create() method new instances of both EmailService and SmsService are initialized, and referenced by corresponding properties on the new ApplicationUserManager instance.

Sign-In Helper - The Basic Identity 2.0 Sign-In API

In creating the Identity Sample project, the Identity team has added a handy helper class, also found in the IdentityConfig.cs file, which wraps commonly needed calls for sign-in and authentication into an efficient and easy to use API. We can see how these methods are consumed by examining the AccountController in the Controllers folder. First, though, let's take a look at the SignInHelper class itself.

As with the previous examples, we're not going to go into much detail here, other than familiarizing ourselves with the basic structure of the class, the methods available, and get a rough idea how we might use the methods in SignInHelper from within our application.

The Sign-In Helper Class:
public class SignInHelper
{
    public SignInHelper(
        ApplicationUserManager userManager, 
        IAuthenticationManager authManager)
    {
        UserManager = userManager;
        AuthenticationManager = authManager;
    }
  
  
    public ApplicationUserManager UserManager { get; private set; }
    public IAuthenticationManager AuthenticationManager { get; private set; }
  
  
    public async Task SignInAsync(
        ApplicationUser user, 
        bool isPersistent, 
        bool rememberBrowser)
    {
        // Clear any partial cookies from external or two factor partial sign ins
        AuthenticationManager.SignOut(
            DefaultAuthenticationTypes.ExternalCookie, 
            DefaultAuthenticationTypes.TwoFactorCookie);
        var userIdentity = await user.GenerateUserIdentityAsync(UserManager);
        if (rememberBrowser)
        {
            var rememberBrowserIdentity = 
                AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity, 
                rememberBrowserIdentity);
        }
        else
        {
            AuthenticationManager.SignIn(
                new AuthenticationProperties { IsPersistent = isPersistent }, 
                userIdentity);
        }
    }
   
  
    public async Task<bool> SendTwoFactorCode(string provider)
    {
        var userId = await GetVerifiedUserIdAsync();
        if (userId == null)
        {
            return false;
        }
    
        var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);
  
        // See IdentityConfig.cs to plug in Email/SMS services to actually send the code
        await UserManager.NotifyTwoFactorTokenAsync(userId, provider, token);
        return true;
    }
    
  
    public async Task<string> GetVerifiedUserIdAsync()
    {
        var result = await AuthenticationManager.AuthenticateAsync(
            DefaultAuthenticationTypes.TwoFactorCookie);
  
        if (result != null && result.Identity != null 
            && !String.IsNullOrEmpty(result.Identity.GetUserId()))
        {
            return result.Identity.GetUserId();
        }
        return null;
    }
  
  
    public async Task<bool> HasBeenVerified()
    {
        return await GetVerifiedUserIdAsync() != null;
    }
  
  
    public async Task<SignInStatus> TwoFactorSignIn(
        string provider, 
        string code, 
        bool isPersistent, 
        bool rememberBrowser)
    {
        var userId = await GetVerifiedUserIdAsync();
        if (userId == null)
        {
            return SignInStatus.Failure;
        }
  
        var user = await UserManager.FindByIdAsync(userId);
        if (user == null)
        {
            return SignInStatus.Failure;
        }
  
        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }
  
        if (await UserManager.VerifyTwoFactorTokenAsync(user.Id, provider, code))
        {
            // When token is verified correctly, clear the access failed 
            // count used for lockout
            await UserManager.ResetAccessFailedCountAsync(user.Id);
            await SignInAsync(user, isPersistent, rememberBrowser);
            return SignInStatus.Success;
        }
  
        // If the token is incorrect, record the failure which 
        // also may cause the user to be locked out
        await UserManager.AccessFailedAsync(user.Id);
        return SignInStatus.Failure;
    }
  
  
    public async Task<SignInStatus> ExternalSignIn(
        ExternalLoginInfo loginInfo, 
        bool isPersistent)
    {
        var user = await UserManager.FindAsync(loginInfo.Login);
        if (user == null)
        {
            return SignInStatus.Failure;
        }
  
        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }
  
        return await SignInOrTwoFactor(user, isPersistent);
    }
  
  
    private async Task<SignInStatus> SignInOrTwoFactor(
        ApplicationUser user, 
        bool isPersistent)
    {
        if (await UserManager.GetTwoFactorEnabledAsync(user.Id) &&
            !await AuthenticationManager.TwoFactorBrowserRememberedAsync(user.Id))
        {
            var identity = new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
            AuthenticationManager.SignIn(identity);
            return SignInStatus.RequiresTwoFactorAuthentication;
        }
        await SignInAsync(user, isPersistent, false);
        return SignInStatus.Success;
    }
  
  
    public async Task<SignInStatus> PasswordSignIn(
        string userName, 
        string password, 
        bool isPersistent, 
        bool shouldLockout)
    {
        var user = await UserManager.FindByNameAsync(userName);
        if (user == null)
        {
            return SignInStatus.Failure;
        }
  
        if (await UserManager.IsLockedOutAsync(user.Id))
        {
            return SignInStatus.LockedOut;
        }
  
        if (await UserManager.CheckPasswordAsync(user, password))
        {
            return await SignInOrTwoFactor(user, isPersistent);
        }
  
        if (shouldLockout)
        {
            // If lockout is requested, increment access failed 
            // count which might lock out the user
            await UserManager.AccessFailedAsync(user.Id);
            if (await UserManager.IsLockedOutAsync(user.Id))
            {
                return SignInStatus.LockedOut;
            }
        }
        return SignInStatus.Failure;
    }
}

 

That's a lot of code to wade through, and like I said, we're not going to look too closely right now. We are mainly here to find our way around, get oriented. We can see that the methods in this class all appear to be related to sign-in and authorization responsibilities.

The methods available in the SignInHelper class all represent some of the new features introduces in Identity 2.0. We see a familiar SignInAsync() method, of course, but then we see a host of new methods related to two-factor authorization and external log-in. Further, there appears to be a lot more going on here in order to sign in than previously.

We can look at the Login method on AccountController in the example project for an example of how authentication is handled in Identity 2.0:

The Login Method on Account Controller Using Identity 2.0:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (!ModelState.IsValid)
    {
        return View(model);
    }
    
    // This doen't count login failures towards lockout only two factor authentication
    // To enable password failures to trigger lockout, change to shouldLockout: true
    var result = await SignInHelper.PasswordSignIn(
        model.Email, 
        model.Password, 
        model.RememberMe, 
        shouldLockout: false);
    
    switch (result)
    {
        case SignInStatus.Success:
            return RedirectToLocal(returnUrl);
        case SignInStatus.LockedOut:
            return View("Lockout");
        case SignInStatus.RequiresTwoFactorAuthentication:
            return RedirectToAction("SendCode", new { ReturnUrl = returnUrl });
        case SignInStatus.Failure:
        default:
            ModelState.AddModelError("", "Invalid login attempt.");
            return View(model);
    }
}

 

Signing in: Compare to Identity 1.0

If we take a quick look at how the log-in task was handled in an MVC project using Identity 1.0, we can go straight to the AccountController.Login method, and we find the following bit of code:

The Login Method on Account Controller Using Identity 1.0:
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Login(LoginViewModel model, string returnUrl)
{
    if (ModelState.IsValid)
    {
        var user = await UserManager.FindAsync(model.UserName, model.Password);
        if (user != null)
        {
            await SignInAsync(user, model.RememberMe);
            return RedirectToLocal(returnUrl);
        }
        else
        {
            ModelState.AddModelError("", "Invalid username or password.");
        }
    }
  
    // If we got this far, something failed, redisplay form
    return View(model);
}

 

Within the method above, we call into a UserManager class, similar to the code we saw for the SignInHelper. We also call the SignInAsync method, also defined directly in AccountController:

The SignInAsync Method on Account Controller Using Identity 1.0:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
    AuthenticationManager.SignOut(
        DefaultAuthenticationTypes.ExternalCookie);
 
    var identity = await UserManager.CreateIdentityAsync(
        user, DefaultAuthenticationTypes.ApplicationCookie);
 
    AuthenticationManager.SignIn(
        new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}

 

Obviously, with the added features related to security, authentication, and authorization comes additional complexity, even at the basic log-in level.

The Heart of it All - ApplicationDbContext

If you have spent any time at all working with ASP.NET MVC in general, and Identity in particular, you are likely familiar with the ApplicationDbContext. This is the default Entity Framework implementation class by which your application accesses and stores Identity-related data.

In the example project, the team has set this up a little differently than in the standard ASP.NET project using Identity 1.0. First, if we take a look in the IdentityModels.cs file, we find the ApplicationDbContext class defined thusly:

The ApplicationDbContext Class from Identity 2.0 Example Project:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser> {
    public ApplicationDbContext()
        : base("DefaultConnection", throwIfV1Schema: false) {
    }
  
    static ApplicationDbContext() {
        // Set the database intializer which is run once during application start
        // This seeds the database with admin user credentials and admin role
        Database.SetInitializer<ApplicationDbContext>(new ApplicationDbInitializer());
    }
  
    public static ApplicationDbContext Create() {
        return new ApplicationDbContext();
    }
}

 

The code above sets up two static methods, Create() , and another, ApplicationDbContext(), which sets a database initializer. This latter method is called during startup, and performs whatever database initialization is established in the ApplicationDbInitializer class.

If we go back to the IdentityConfig.cs file, we find the ApplicationDbInitializer defined like so:

The ApplicationDbInitializer Class from the IdentityConfig.cs File:
public class ApplicationDbInitializer 
    : DropCreateDatabaseIfModelChanges<ApplicationDbContext> 
{
  
    protected override void Seed(ApplicationDbContext context) 
    {
        InitializeIdentityForEF(context);
        base.Seed(context);
    }
  
  
    public static void InitializeIdentityForEF(ApplicationDbContext db) 
    {
        var userManager = HttpContext
            .Current.GetOwinContext()
            .GetUserManager<ApplicationUserManager>();
  
        var roleManager = HttpContext.Current
            .GetOwinContext()
            .Get<ApplicationRoleManager>();
  
        const string name = "admin@admin.com";
        const string password = "Admin@123456";
        const string roleName = "Admin";
  
        //Create Role Admin if it does not exist
        var role = roleManager.FindByName(roleName);
        if (role == null) 
        {
            role = new IdentityRole(roleName);
            var roleresult = roleManager.Create(role);
        }
  
        var user = userManager.FindByName(name);
        if (user == null) 
        {
            user = new ApplicationUser { UserName = name, Email = name };
            var result = userManager.Create(user, password);
            result = userManager.SetLockoutEnabled(user.Id, false);
        }
  
        // Add user admin to Role Admin if not already added
        var rolesForUser = userManager.GetRoles(user.Id);
        if (!rolesForUser.Contains(role.Name)) 
        {
            var result = userManager.AddToRole(user.Id, role.Name);
        }
    }
}

 

As it is currently configured, this initializer will drop and re-create the database is the model schema (as defined by our code-first model objects) has changed. Otherwise, it will continue using the existing database.

If we want to drop and re-create the database every time our application is run we could change the base class from which it inherits to DropCreateDatabaseAlways<ApplicationDbContext>. We might want to do this during development if we wanted to start with an empty (or nearly so) data set every time for testing, for example.

Also, take note of the InitializeIdentityForEF() method. This method performs a function similar to that of the Seed() method when we use EF Migrations, allowing us to initialize the database with some data. In this case, the example project is set up with a pre-defined admin user, password, and role.

Ok John, That's All Great. Now What?

In this article we have looked broadly at where some of the new features and configuration items live in an ASP.NET MVC project using the Identity 2.0 framework. There is a lot more to it, and we will look at specifics in several upcoming posts.

For now, explore and run the example project, and get more familiar with how things work.

Additional Resources and Items of Interest

The Following Focus on Using the Identity 1.0 Framework:

 

 

Posted on April 20 2014 04:55 AM by jatten     

Comments (11)

ASP.NET MVC: Keep Private Settings Out of Source Control

Posted on April 6 2014 06:24 AM by jatten in C#, CodeProject, ASP.Net, ASP.NET MVC   ||   Comments (4)

Locked240It is just too easy to accidentally push confidential information up to a publicly hosted source repository such as Github. Also, when managing a project with multiple developers, it can become messy managing multiple configuration files between team members.

How often do you pull the latest changes down from source control, and then need to reset a database connection string after someone else accidentally pushed their own modified App.config or Web.config file up?

Even when the settings or connection strings are not critically private, this can be a pain.

Image by Rina Pitucci  |  Some Rights Reserved

Consider a typical Web.config file from an ASP.NET MVC web application (non-relevant content removed for clarity):

ASP.NET Web.config File Example:
<?xml version="1.0" encoding="utf-8"?>
<!--
  A bunch of ASP.NET MVC web config stuff goes here . . . 
  -->
<configuration>
  <connectionStrings>
    <add name="DefaultConnection" value="YourConnectionStringAndPassword"/>
  </connectionStrings>
  <appSettings file="PrivateSettings.config">
    <add key="owin:AppStartup" value="AspNetIdentity2ExtendingApplicationUser.Startup,AspNetIdentity2ExtendingApplicationUser" />
    <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="EMAIL_PASSWORD" value="YourEmailPassword"/>
  </appSettings>
</configuration>

 

In the above, there is a database connection string we likely don't want to push to a public repo, and/or which may differ from developer to developer on a team, even internally if they are working against different or individual development versions of the application database.

Also, there is an email password, likely used to send email from within the application, which also may differ amongst team members during development, and which also should not be published publicly.

At the same time, there is a bunch of other stuff which is global to the application, so keeping the entire Web.config file out of source control is not an attractive option, either.

Fortunately, the .NET ConfigurationManager affords us a couple of handy ways to deal with this.

Use configSource Attribute to move an Entire Configuration Section to Its Own File

We can use the configSource attribute to move an entire Configuration Section to an external file. For example, database connection strings are one of the most common items we need to keep in our App.config or Web.config files, but which we also (usually) don't want to publish to a publicly hosted source control repository.

We can add a separate configuration file named (for example) connectionStrings.config, and then use the configSource attribute within our Web.config file to refer to it. To do so, add a new Web Configuration file, name it ConnectionStrings.config, and then put only the following in the new file (no xml header, nothing but the <connectionStrings> section tags, and the <add> element(s):

ConnectionStrings.config File Example:
<connectionStrings>
  <add name="DefaultConnection" value="YourConnectionStringAndPassword"/>
</connectionStrings>

 

Then, we can modify our original Web.config file, removing the <add> element from the <connectionStrings> section, and instead, using the configSource attribute to refer to the new ConnectionStrings.config file:

Modified Web.config File Using configSource:
<connectionStrings configSource="ConnectionStrings.config">
</connectionStrings>

 

Now, we can still access our connection string the same as always:

Accessing Connection String By Name:
var conn = ConfigurationManager.ConnectionStrings["DefaultConnection"];
string connString = conn.ConnectionString;
// Etc...

 

In the above, accessing the connection string by name like that returns a ConnectionStringSettings object.

When we use the configSource attribute, the Configuration Section to which it is applied can contain no actual elements. The entire section will be referred to from the external file. Note that the configSource attribute can be used in this manner with any Configuration Section.

Use the File Attribute to Move Select Application Settings to an External File

You may have a case, such as our example We.config file above, in which most of the values in the <appSettings> Configuration Section are global to the project, but also include a handful of settings which should remain private, and kept out of source control.

In these cases, there is a special file attribute available specifically to the <appSettings> section which essentially allows us to extend <appSettings> to an external file. In other words, ConfigurationManager will recognize the contents in both locations when referring to <appSettings> and make all transparently available within the application.

In our example case, we have an email password we would like to keep private. We might add another Web Configuration file named PrivateSettings.config. Once again, there should be no XML header. The only thing this file should contain  will be a set of <appSettings> elements, and within those, the special settings we wish to define privately.

Special PrivateSettings.config File Extends AppSettings Section:
<appSettings>
  <add key="MAIL_PASSWORD" value="xspbqmurkjadteck"/>
</appSettings>

 

No, we remove the email password element from Web.config, and add the file attribute to the <appSettings> section element, pointing to the new PrivateSettings.config file:

Add File Attribute to Web.config AppSettings:
<appSettings file="PrivateSettings.config">
  <add key="owin:AppStartup" value="AspNetIdentity2ExtendingApplicationUser.Startup,AspNetIdentity2ExtendingApplicationUser" />
  <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" />
</appSettings>

 

Again, as before we can access any of our settings in the standard manner - externalizing the email password setting to a separate file is transparent to client code:

Accessing Settings:
var pwd = ConfigurationManager.AppSettings["MAIL_PASSWORD"];

 

Add Special Files to .gitignore

Now we can add our Web.config file to source and commit, and add the two special files, ConnectionStrings.config and PrivateSettings.config to our .gitignore file, and commit away. When it's time to push to a shared repo, our private information will stay private.

Documentation is Key

Of course, when we take this type of approach, it will be helpful to other developers if our documentation clearly indicates what is going on here. We might do this in our project README file, and/or add some XML comments at each point in our modified Web.config informing others that they will need to add the proper files to their local version of the project, and what those files should contain.

Additional Resources and Items of Interest

 

Posted on April 6 2014 06:24 AM by jatten     

Comments (4)

Biggy: Evolving the Architecture

Posted on April 2 2014 07:29 AM by jatten in Biggy, C#, Database   ||   Comments (0)

10927753_aebf894396_zRecently, K. Scott Allen proposed a re-thinking of the fundamental architecture for Rob Conery's Biggy project. Over the past month, the project has grown from a simple, flat-file, in-memory JSON store into a more ambitious, LINQ-compliant high-performance document/relational query tool. The existing structure worked, but there are clearly places where it is beginning to become brittle.

Biggy is a high-performance, synchronized in-memory document/relational query tool for .NET. The project attempts to combine the most desirable features of document and relational data stores, along with some ORM-like features.

Image by Ian Kershaw | Some Rights Reserved

As of this writing, the existing implementation is heavily inheritance-driven. K. Scott Allen's solution proposes an interface-centric approach, and more cleanly separates the concerns of data access from the backing store, versus managing the list-based, in-memory representation of the data with domain models. Of the existing architecture, Mr. Allen states, and I agree, that:

Looking over the Biggy implementation, every different data store becomes coupled to an InMemoryList<T> class through inheritance. The coupling isn’t necessarily wrong, but it does complicate the implementation of each new data store.

Having worked rather extensively with the code base on the relational database implementation, I found exactly this to be the case. Not that the current architecture was bad, but simply that the project had grown outward from Rob's early implementations, to the point where what was once simple was becoming increasingly complex.

Hiding Implementation with Interface Abstraction

Under the proposed architecture, Biggy would utilize a few interfaces to take some of the pain out of extending the implementation to support various data stores.

In his post, Scott Allen proposes, first and foremost, separating Lists from stores through interface implementation. The strength of Biggy as a library results from delivering an in-memory list representation of application data as domain objects.

K. Scott’s approach cleanly separates the responsibilities of the Store (fetch/push data to the back-end database) from the in-memory list (query/manipulate data within the domain model), and proposes to abstract the store functionality behind a set of interfaces, IBiggyStore<T> such that that different backing store implementations can be easily ported into the library. Further, the in-memory list implementation will be abstracted behind its own interface, IBiggy<T>, into which an instance of IBiggStore<T> is injected, thereby completing the de-coupling of store from list.

As I undertook to implement the proposed structure, I made a few minor adjustments, finally arriving at the following interface for the in-memory IBiggy<T> abstraction:

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

 

The primary differences between my implementation above and K. Scott’s proposed structure is the addition of a Remove(List<T>) method to remove a range of items from the list, and changing everything from IEnumerable<T> to List<T>. The reason for this last was that we seemed to have variations between List<T>, IEnumerable<T>, IList<T>, and others scattered about the API. While I can see a case coming to move back to IEnumerable, I was having to do a whole lot of myEnumerable.ToList() and such. We’ll see what happens. For now, I made everything I could List<T>.

 

Abstracting the Backing Store

One of the principle drivers behind the architectural changes was to separate the responsibilities of in-memory list management from those of data transfer to and from the backing store. Per K. Scott’s original proposal, the core IBiggyStore<T> interface is a simple, brute-force affair. Again, I have made some minor modifications which may be reversed  before this reaches production, but for now, the basic Interface looks like this, with the additional IUpdateable and IQueryable variants as well:

The Biggy Store Interfaces:
public interface IBiggyStore<T>
{
    List<T> Load();
    void SaveAll(List<T> items);
    void Clear();     
    T Add(T item);
    List<T> Add(List<T> items);
}
  
public interface IUpdateableBiggyStore<T> : IBiggyStore<T>
{
    T Update(T item);
    T Remove(T item);
    List<T> Remove(List<T> items);
}
  
public interface IQueryableBiggyStore<T> : IBiggyStore<T>
{
    IQueryable<T> AsQueryable();
}

 

In the above, we have abstracted the basic store functionality behind a set of interfaces. This allows us to swap backing stores with ease, while ensuring code which consumes instances of IBiggy will continue to function properly.

In my current implementation, I have defined a base class BiggyRelationalStore which contains code which will be common to any relational database implementation. Then, there are abstract methods which require concrete implementation in platform-specific subclasses.

As we can see, a concrete implementation of IBiggy through BiggyList can call out to the backing store through the various store interface method. As K. Scott says in his post:

"…the implementation of an actual data store doesn’t need to call into a base class or worry about raising events. The store only does what it is told…"

A Single Concrete List Implementation

The injection of the data store as an instance of IBiggyStore<T> allows us to create a single implementation class for the basic BiggyList. BiggyList is, of course, the business end of the Biggy library – its raison d’etre if you will. By injecting an abstract IBiggyStore instance into the constructor of the BiggyList class, we are able to get all of the code which previously managed database platform-specific store interaction out of the BiggyList class and safely stick it behind the IBiggyStore interface.

Sometimes, Abstraction Comes with a Price

On the whole, K. Scott’s new architecture cleaned up the Biggy code base significantly. However, this does not come without a price. Store abstraction and injection now requires the following to initialize a new BiggyList<T> instance:

Initializing a new BiggyList Instance with Store Injection:
// Initialize a Store Instance:
IBiggyStore<Artist> _artistStore = new SQLServerStore<Artist>("chinook");
  
// Inject the store into the BiggyList Constructor:
IBiggy<Artist> _artists = new BiggyList<Artist>(_artistStore);

 

We have added a bit of ceremony on the front side, in that we have to “new up” a store instance (in this case, a SQL Server store) for injection into our list constructor. Of course, we could do this in-line:

Initializing a new BiggyList Instance with Inline Store Injection:
// Inject a new store into the BiggyList Constructor:
IBiggy<Artist> _artists = new BiggyList<Artist>(new SQLServerStore<Artist>("chinook"));

 

But there is still some additional cognitive overhead (and a lot of repetitive type arguments to deal with!).

Also, because each concrete BiggyStore<T> is tied to a specific type argument <T> (and by extension, a specific database table, depending upon the backing store), we need to initialize a new store for each type-specific list we wish to consume:

Initializing Multiple BiggyList Instances:
// Initialize aseveral Store Instances:
IBiggyStore<Artist> _artistStore = new SQLServerStore<Artist>("chinook");
IBiggyStore<Album> _albumStore = new SQLServerStore<Album>("chinook");
IBiggyStore<Track> _trackStore = new SQLServerStore<Track>("chinook");
// Inject a new store into each BiggyList Constructor:
IBiggy<Artist> _artists = new BiggyList<Artist>(_artistStore);
IBiggy<Album> _albums = new BiggyList<Album>(_albumStore);
IBiggy<Track> _tracks = new BiggyList<Track>(_trackStore);

 

Hmmmm …

Now here we are looking at some repetitive coding, particularly since we need to specify the type argument <T> for each no less than five times . . .

However, since the whole purpose of Biggy is to get your data into memory for fast performance, while keeping things in sync with the backing store, you should mostly be able to do this once within your application, and then you’re off and running.

We shall see how things evolve. As I am learning, there is a balance between a friendly, easy-to-use API and “proper architecture” that is not always clear-cut.

Cache Schema Information

Biggy has a relatively sophisticated system for matching domain objects and properties with database tables and columns. Also, specific to relational database stores, there is the issue of primary keys, and whether or not these are auto-incrementing (“Identity” columns in SQL Server, and “serial” column types under Postgres).

Since Biggy relies heavily upon mapping database object names to domain object names to do its job, it made sense to pull as much schema information from the actual database as possible, then map objects and properties accordingly. We can accomplish this by hitting INFORMATION_SCHEMA once for a list of tables in the database, and again for a list of all the columns in the database. We then map columns to tables in memory, and make these mappings available for comparison to object names and properties.

This cache of schema information can be retrieved during initialization of an IBiggyStore<T> instance, or passed to a constructor override, depending on application requirements.If we needed just a single table’s data, we might just initialize our store using the database connection string name, as previously:

IBiggyStore<Artist> _artistStore = new SQLServerStore<Artist>("chinook");
IBiggy<Artist> artists = new BiggyList<Artist>(_artistStore);

 

No harm no foul above. It still seems a little clunkier than previous versions of Biggy, but not too bad.

Behind the scenes, during store initialization, the cache is still being retrieved, just specific for this instance. In the constructor for the sub-class SQLServerStore<T> we find:

public SQLServerStore(DbCache dbCache) : base(dbCache) { }
public SQLServerStore(string connectionString) 
    : base(new SQLServerCache(connectionString)) { }

 

As we can see, the constructor override is simply initializing a new SQLServerCache instance and passing it to the constructor of the base class, BiggyRelationalStore.

If, on the other hand, we need to spin up several tables (as in our earlier example above) it will make more sense to grab our schema stuff at the start, and pass references in to our store objects:

var schema = new SQLServerCache("chinook");
  
// Initialize several Store Instances, but pass the cached schema info in:
IBiggyStore<Artist> _artistStore = new SQLServerStore<Artist>(schema);
IBiggyStore<Album> _albumStore = new SQLServerStore<Album>(schema);
IBiggyStore<Track> _trackStore = new SQLServerStore<Track>(schema); 
  
IBiggy<Artist> artists = new BiggyList<Artist>(_artistStore);
IBiggy<Album> _albums = new BiggyList<Album>(_albumStore);
IBiggy<Track> _tracks = new BiggyList<Track>(_trackStore);

 

Yup. That looks pretty clunky.

All the abstraction/injection has made the Biggy code base more robust (much more, in my mind), but has made it less friendly from an API perspective.

What to do?

Simpler, More Friendly API or Stronger, More Flexible Library Structure?

The architecture proposed by K. Scott Allen most definitely improved the code organization, created better separation of concerns between the BiggyList and the backing store, and in general has created a code base which is more extensible. However, it has also introduced a good deal more ceremony from an API usage standpoint.

Do we decide, from a project standpoint, to wrap it all up somehow such that the API is simplified, but less extensible? Or do we provide the library as-is, and allow the consumer to decide how to best wrap it up in the context of their project.

I love the new structure, and while there is room for it to evolve (and I am CERTAIN I have missed some easy ways to make it more friendly!), I think the basics are there, and for the moment, the tradeoff is worth it. But that’s just my opinion, and the simple, no-ceremony API Biggy was born with is no longer so simple. And utter simplicity is one of Biggy’s strong points.

Wrap it Up in a Factory of Sorts . . .

Of course, depending on your application requirements, solutions to the simplicity conundrum might be easy to find. For example, If I were whipping up an application today, I might add a thingamajig like so:

Example Biggy Implementation Wrapper:
public class MyDatabase 
{
    DbCache _cache;
    public MyDatabase(string connectionStringName) 
    {
        _cache = new SQLServerCache(connectionStringName);
    }
  
    public IBiggyStore<T> CreateStoreFor<T>() where T : new()
    {
        return new SQLServerStore<T>(_cache);
    }
  
    public IBiggy<T> CreateBiggyList<T>() where T : new() 
    {
        return new BiggyList<T>(CreateStoreFor<T>());
    }
}

 

The above could then be called like so:

Consuming the Example Wrapper:
_db = new MyDatabase("chinook");
  
var artists = _db.CreateBiggyList<Artist>();
foreach (var artist in artists) 
{
    Console.WriteLine(artist.Name);
}

 

. . . Or Wrap it Up in a Context

Alternatively, one could borrow a page from Entity Framework, and go the “context” route:

Example Biggy Context Wrapper:
public class MyDatabaseContext : MyDatabase 
{
    public MyDatabaseContext(string connectionStringName) 
        : base(connectionStringName) 
    {
        this.Artists = this.CreateBiggyList<Artist>();
        this.Albums = this.CreateBiggyList<Album>();
        this.Tracks = this.CreateBiggyList<Track>();
    }
    public IBiggy<Artist> Artists { get; set; }
    public IBiggy<Album> Albums { get; set; }
    public IBiggy<Track> Tracks { get; set; }
}

 

 

The above makes possible the materialization of your data store into memory immediately upon initialization. Data can then be consumed directly, like so:

Consuming the Example Context Wrapper:
_db = new MyDatabaseContext("chinook");
foreach (var artist in _db.Artists) 
{
    Console.WriteLine(artist.Name);
}

 

The above are two similar, but slightly different ways one might consume Biggy within an application. While the new architecture adds a degree of (probably) undesirable ceremony to the simplest use case, it does enable some flexibility and extensibility which may have been more difficult previously.

It could be we wrap the new architecture into something like the above as part of the API for the library. I'm interested to see what others come up with.

Pushed Breaking Changes

At the suggestion of the project owner, I've just pushed my changes to the master repo. This is going to break things for anyone who has built out around the previous structure.

I've revised/Adapted the tests and the perf demos to work with the new architecture, and as of now, await feedback from the project owner as to what is acceptable, and what may need additional work.

More will be revealed . . .

Additional Resources and Items of Interest

 

Posted on April 2 2014 07:29 AM by jatten     

Comments (0)

About the author

My name is John Atten, and my "handle" on many of my online accounts is xivSolutions. I am Fascinated by all things technology and software development. I work mostly with C#, JavaScript/Node, and databases of many flavors. Actively learning always. I dig web development. I am always looking for new information, and value your feedback (especially where I got something wrong!). You can email me at:

jatten at typecastexception dot com

Web Hosting by