Freigeben über


Tip: Entity Framework, Unity, and The ASP.NET RoleProvider – Read the MSDN Fine Print

A custom Role Provider is a very useful tool for adding custom authorization to your ASP.NET and/or ASP.NET MVC applications.  A common use for a custom Role Provider is when you have role information stored in a data store that differs from the schema that is used by the built-in ASP.NET Role Providers.  In this case, ASP.NET provides a abstract RoleProvider class that can be used to implement the custom provider.  This contains role-specific methods such as: CreateRole, DeleteRole, GetRolesForUser, etc.  In a custom Role Provider, you provide the implementation of these methods using your database access mechanism of choice.  The MSDN documentation for implementing a custom Role Provider is a good place to start if you want to learn more about this.

In this post, I am NOT going to detail the steps in creating a custom Role Provider.  Examples of this can be found all over the internet.  Rather, I am going to talk about a recent gotcha with implementing a custom Role Provider that was encountered on a project that I am working on. 

The following preconditions are used in our custom implementation:

  • We are using the Entity Framework as our data access and the Unity Application Block/Service Locator for dependency injection and inversion of control. 
  • We are abstracting away the Entity Framework context using the Repository pattern.
  • The IRoleRepository instance is managed by Unity using a per-request lifetime manager.

Here is a quick snippet of the initial custom provider that we implemented:

    1: public class CustomRoleProvider : RoleProvider
    2: {
    3:     private IRoleRepository roleRepository;
    4:  
    5:     public CustomRoleProvider()
    6:         : this(ServiceLocator.Current.GetInstance<IRoleRepository>())
    7:     {
    8:     }
    9:  
   10:     public CustomRoleProvider(IRoleRepository roleRepository)
   11:     {
   12:         this.roleRepository = roleRepository;
   13:     }
   14:  
   15:     // Role Provider Abstract members
   16:  
   17:     // ...
   18:  
   19:     public override void CreateRole(string roleName)
   20:     {
   21:         Role newRole = Role.CreateFromName(roleName);
   22:         this.roleRepository.Add(newRole);
   23:     }
   24:  
   25:     // ...
   26: }

At the outset, this seemed to be working great.  The application functioned.  The unit tests passed.  Our roles that were stored in the database were being managed by the custom Role Provider.  Then we started to notice some weird behavior. 

  • Direct modifications of the Role table in the database were not exposed by the role provider until a restart of Cassini/IIS.
  • Changes made using a per-request instance of IRoleRepository were not exposed by the role provider, but changes made using the System.Web.Security.Roles class were.

Blank-Facepalm

I had a facepalm moment and quickly realized that I had neglected to remember a very important part of implementing a custom Role Provider.  During the initialization of an ASP.NET application, a single instance of the RoleProvider class is instantiated and used for the life of the application.  Of course, this all made perfect sense now.  The Entity context for the role provider was effectively a singleton since is was only being injected at the beginning of the application.  It didn’t take long for this context to become stale if changes were made to the roles during other requests.

The MSDN fine print does talk about this:

 

For each role provider specified in the configuration for an application, ASP.NET instantiates a single role-provider instance that is used for all of the requests served by an HttpApplication object. As a result, you can have multiple requests executing concurrently. ASP.NET does not ensure the thread safety of calls to your provider. You will need to write your provider code to be thread safe. For example, creating a connection to a database or opening a file for editing should be done within the member that is called, such as AddUsersToRoles , rather than opening a file or database connection when the Initialize method is called.

“Implementing a Role Provider (Threading Section)", Microsoft Developer Network Library, https://msdn.microsoft.com/en-us/library/8fw7xh74.aspx

The quick fix for this problem was to change the custom role provider to use a new IRoleRepository without a lifetime manager (returning a new instance for every call to GetInstance()) for each call to a method on the provider:

    1: public class CustomRoleProvider : RoleProvider
    2: {
    3:     public CustomRoleProvider()
    4:     {
    5:     }
    6:  
    7:     // Role Provider Abstract members
    8:  
    9:     // ...
   10:  
   11:     public override void CreateRole(string roleName)
   12:     {
   13:         Role newRole = Role.CreateFromName(roleName);
   14:  
   15:         IRoleRepository roleRepository = ServiceLocator.Current.GetInstance<IRoleRepository>(“NoLifetime”);
   16:         roleRepository.Add(newRole);
   17:     }
   18:  
   19:     // ...
   20: }

So what have we learned? 

  • When using the Entity framework, make sure that you are handling your context with care.  Misuse can often cause bugs that are hard to find.
  • Read the MSDN documentation.  It may contain very important information that you might overlook or forget.
  • Integration testing is just as important as unit testing.
  • Remember that ASP.NET has some architectural paradigms that do not always behave the way that you think they should (more on this in a future post with the always fun ThreadStatic attribute).

Comments