Export (0) Print
Expand All

Provider Design Pattern, Part 2

 

Rob Howard
Microsoft Corporation

April 29, 2004

Summary: Rob Howard continues his examination of the upcoming provider model in ASP.NET 2.0, and how you can use it in today's applications. In the process Rob builds an implementation of a provider for an ASP.NET Membership feature. (14 printed pages)

Download the source code for this article.

Related Books

Implementing for ASP.NET 1.1

In the previous column, we discussed the new Microsoft® ASP.NET 2.0 (formerly referred to as ASP.NET "Whidbey") provider design pattern, a pattern that allows for a pluggable Business Logic Layer (BLL) and Data Access Layer (DAL) for published APIs. This pattern is being used throughout the new ASP.NET 2.0 infrastructure features; it allows you to unplug our default implementation of an API and plug-in your own—a very powerful capability. To learn more about the pattern, make sure to read Part 1 of this article.

We received lots of positive e-mail about the previous article as well as a few questions, which I've attempted to address below:

The provider pattern doesn't solve the data model problem

A point brought up in the previous article discussed the conundrum the ASP.NET team faced while building the Personalization system used for ASP.NET 2.0. The problem was choosing the right data model: standard SQL tables versus a schema approach. Someone pointed out that the provider pattern doesn't solve this, which is 100% correct. What it does allow is the flexibility to choose which data model makes the most sense for your organization. An important note about the pattern: it doesn't solve how you store your data, but it does abstract that decision out of your programming interface.

Where the provider pattern differs from normal abstraction layers is that the class containing the implementation and DAL is not fixed and easily replaced, either at design time or at run time. One of the major points of the pattern is that the implementer can choose which model works for them, rather than the person who originally created the API, such as the ASP.NET team.

Extending configuration

Another question that arose from the pattern: Why use a NameValueCollection versus using an XmlNode as a parameter on the Initialize() method of ProviderBase class? Furthermore, why support only a flat attribute/value collection in the configuration section versus supporting richer hierarchical XML?

We chose to use a flat attribute/value collection for configuration data since this can be easily described through a schema used for configuration. If you need to specify additional properties for your provider, the recommendation is to create a separate configuration entry where this more complex configuration data should go—you could simply reference it from the provider's attribute/value section and the provider would know where to find the rest of its configuration data. As for why the Initialize() method doesn't support an XmlNode parameter (or other XML-related type): Put simply, it is unnecessary since the attribute list is flat (not to mention that a NameValueCollection is a lot easier to work with than a full XmlNode).

Correction

In the previous article, I referred to the new Membership base class from which new classes must be derived as MembershipProviderBase. This is incorrect, and is simply MembershipProvider.

Now that we're done with the housekeeping, let's investigate how you can implement the Provider Design Pattern today in your ASP.NET 1.1 applications in anticipation of ASP.NET 2.0.

Here a Pattern, There a Pattern, Everywhere a Pattern

It seems these days that software "patterns" are all the rage. Patterns are useful because they allow developers and architects to describe solutions to common problems with well-known terms. It's important to keep in mind, though, that while patterns are wonderfully helpful, they aren't always the right solution to every problem. Before using any pattern, take the time to decide if the pattern is right for the problem you are attempting to solve. You'll find that many times there is a good fit, but many times there is not.

The problem addressed by the provider pattern is the ability to publish APIs and support a pluggable implementation for those APIs. Something we find very desirable, and something many software vendors may also choose to use, but it is not necessary for every application.

Truly, the pattern itself only formalizes something that many developers have done previously: allow for a pluggable abstraction layer. For example, Commerce Server supports a pluggable layer for its personalization system, requiring developers to write a component implementing OleDb; something only a handful of people truly can accomplish, or for that matter desire to accomplish. The difference with the provider pattern is that it is a simple and approachable pattern for abstracting the APIs themselves.

A Better Mousetrap, Ahem, Pattern

The provider pattern introduced with ASP.NET 2.0 is much like other designs we've implemented for the Microsoft® .NET Framework; simplicity and approachability are high priorities. Implementing the provider pattern is simple, so simple it can be accomplished with ASP.NET 1.1 code. In fact, the original implementation was done in ASP.NET 1.1. The pattern itself is merely a set of rules dictating one way to build pluggable software.

These rules were detailed in part 1 of this column, which included content from the actual provider specification. We can summarize the pattern and state that at its most basic level the pattern is:

  • An abstract base class defining a contract. The abstract base class has all the abstract methods and properties required to implement the public API it supports. For example, the new ASP.NET 2.0 Membership feature defines an abstract base class, MembershipProvider. This class mirrors the methods and properties defined in the Membership API. Furthermore, the MembershipProvider derives from another base class, ProviderBase. ProviderBase is a class common to all providers. Developers create their own classes, derive from the MembershipProvider, and override methods and properties with their own implementation. This resulting class is known as the provider. Using the rules set forth in the provider specification, a custom class that implemented MembershipProvider and used Microsoft® SQL Server as the data store would likely be a class named SqlMembershipProvider.

    Aside: If you're wondering why we chose to use abstract base classes instead of interfaces: We did in fact use interfaces in the Alpha released at PDC. We chose to use abstract base classes since they are easier to version; interfaces are immutable. The interface versus abstract base class argument is saved for a future article, perhaps in the blogsphere @ http://weblogs.asp.net.

  • Configuration information. Once an implementation of the feature provider class is created, it must be described in the configuration section. For a given feature, an unlimited number of providers may be defined. The appropriate provider is instantiated at run time from information defined in the configuration file. It is possible to change providers at run time too, allowing for a dynamic change between, say, a SQL Server provider and an Oracle provider. The description of the provider within configuration provides all the information so that the provider can be instantiated in the running application.

The remainder of the specification, while important, is just window dressing to ensure all providers look, feel, and act similarly. Okay, I guess that means it is part of the pattern too!

Let's walk through a simple example demonstrating how this pattern can be implemented today in ASP.NET 1.1.

Nuts and Bolts: Provider Pattern with .NET 1.1

To demonstrate how a provider can be implemented, we'll create a simple implementation of one of the new ASP.NET 2.0 features: Membership. For our purposes, our Membership class will only be used to create users and validate credentials (definitely a simplified version of the ASP.NET 2.0 Membership system). Below is our class definition (we'll show the implementation momentarily):

public namespace NothinButAspNet {
  public class Membership {
    public static bool Validate (string username, string password)
    public static void CreateUser (string username, string password)
  }
}

The methods in the Membership class are static, so we can simply call Membership.Validate() versus having to instantiate the Membership class before use. While not part of the provider design pattern per se, use of static methods is a common design for classes implementing providers. In theory we could now start writing tests, or for that matter start planning applications to use this new Membership API.

Since we're implementing the provider pattern in .NET 1.1, we next need to create the base class, MembershipProvider. The MembershipProvider base class is our contract, which developers can use to implement the desired behaviors for the Membership API. Again, remember that in ASP.NET 2.0, this class is shipping with the Framework, but when we're building our own software today, we have to use our own implementation:

namespace System.Web.Security {

   public abstract class MembershipProvider : ProviderBase {
      public abstract bool Validate (string username, string password);
      public abstract void CreateUser (string username, string password);
   }
}

As you can see, the MembershipProvider class derives from ProviderBase, the base class common to all providers. Similar to the feature-specific base class used above (MembershipProvider), the ProviderBase class will be part of ASP.NET 2.0, but for now we need to create it ourselves:

namespace System.Configuration.Providers {

   public abstract class ProviderBase {
      public abstract void Initialize (string name, 
        NameValueCollection configValue);
      public abstract string Name { get; }
   }

}

The common ProviderBase base class is used to force some implementation detail, but also to allow applications to work with any provider in a generic manner. That is, any implementation of the provider specification should allow for the implemented class to be cast to ProviderBase. This behavior is useful mainly to tools that can work with various providers, for example, easily enumerate the list of providers displaying the name of each.

Now that we've laid the foundation classes for our Membership feature, let's go ahead and create a SqlMembershipProvider class. This class will be our implementation of the Membership APIs for a Microsoft SQL Server database. That is, rather than our application logic residing within the Membership class we created earlier, all of our Data Access Layer (DAL) and Business Logic Layer (BLL) code is put into this SqlMembershipProvider class. (We've not included the implementation for the sake of readability.)

public namespace NothinButAspNet {
  public abstract class SqlMembershipProvider : MembershipProviderBase {
    public override bool Validate (string username, string password) 

    public override bool CreateUser (string username, string password)

    // Provider base methods & properties
    public override void Initialize (string name, 
      configValue NameValueCollection);
    public override Name { get; }
  }
}

Finally, we need to compile these classes into an assembly, such as SqlMembership.dll, and deploy it to our application's \bin directory. We could create instances of SqlMembershipProvider and call its methods to create and validate users. The problem now is that we're dependent upon that implementation. Remember, we want to use the generic Membership API, which decouples us from any one particular implementation and allows us to replace our Microsoft SQL Server implementation with an Oracle implementation or maybe a different SQL Server implementation.

Wait for It, Wait for It... the AH HA!

Okay, so far we've taken a fairly simple requirement and now have 4 classes. Seems like a lot of work just to create users and validate credentials—and that doesn't even include any of the stored procedure logic or data schema! True, but keep in mind that we're currently also having to build a lot of infrastructure that is simply part of ASP.NET 2.0.

What's still missing is how we connect the SqlMembership.dll assembly into our application so when Membership.ValidateUser() is called, it forwards it to an instance of SqlMembershipProvider's ValidateUser() method.

The linkage between our 'provider' implementation and our API is accomplished through the configuration system. Again, while it's simply part of ASP.NET 2.0 to use the provider pattern in .NET 1.1, there are several things we have to build. To begin with, we need to create a configuration section and configuration section handler for our Membership feature.

The configuration section describes the providers available:

<configuration>

  <configSections>
      <sectionGroup name="membership">
          <section name="forums" 
            type=
"NothinButAspNet.Configuration.MembershipConfigurationHandler, 
            MembershipConfiguration" />
      </sectionGroup>
  </configSections>

  <system.web>
    <membership defaultProvider="SqlMembership">
      <providers>
        <add name="SqlMembership" 
            type="NothinButAspNet.SqlMembershipProvider, SqlMembership " 
connectionString="server=.;database=MyUsers;uid=mbrshp;pwd=&k12^"/>
      </providers>
    </membership>
  </system.web>
</configuration>

This configuration tells us that:

  • A configuration section handler is used for managing the custom <membership> configuration settings.
  • There is a <providers> section used to define the providers available for Membership; we've defined only one. Note, while not demonstrated here, other provider entries may be inherited from parent configuration applications.
  • The default provider for Membership is SqlMembership. (SqlMembership is the friendly name for our SqlMembershipProvider class.)
  • The implementation exists in a provider class named SqlMembershipProvider in the namespace, NothinButAspNet, in the assembly, SqlMembership.dll.
  • A connection string is specified for connecting to the appropriate database.

Now that we've defined the provider, let's look at how it gets loaded and made available to the Membership API.

To load the provider we require a configuration section handler for our <membership> configuration settings. A configuration section handler is a class capable of reading the XML configuration information from the configuration system. This class must implement IConfigurationSectionHandler:

public namespace NothinButAspNet {
  internal class MembershipConfigurationHandler : 
                 IConfigurationSectionHandler  
  {
    public virtual object Create(Object parent, 
                                 Object context, XmlNode node) 
    {
      MembershipConfiguration config = new MembershipConfiguration();
      config.LoadValuesFromConfigurationXml(node);
      return config;
    }
  }

  public class ForumConfiguration {
    string defaultProvider;
    Hashtable providers = new Hashtable();

    public static ForumConfiguration GetConfig() 
    {
      return (ForumConfiguration) 
        ConfigurationSettings.GetConfig("membership");
    }  

    void LoadValuesFromConfigurationXml(XmlNode node) 
    {
      // Get the default provider
      defaultProvider = attributeCollection["defaultProvider"].Value;

      // Read child nodes
      foreach (XmlNode child in node.ChildNodes) {
        if (child.Name == "providers")
          GetProviders(child);
      }
    }

    void GetProviders(XmlNode node) 
    {
      foreach (XmlNode provider in node.ChildNodes) {
        switch (provider.Name) {
          case "add" :
            providers.Add(provider.Attributes["name"].Value, 
              new Provider(provider.Attributes) );
            break;

          case "remove" :
            providers.Remove(provider.Attributes["name"].Value);
            break;

          case "clear" :
            providers.Clear();
            break;
        }
    }
  }
}

In addition to our MembershipConfigurationHandler class, we also have created a public MembershipConfiguration class and a public Provider class. At run time we ask the configuration system to retrieve the configuration data for Membership. What it gives us back is an instance of MembershipConfiguration. Through the MembershipConfiguration class we can then access the Providers property. The MembershipConfiguration.Providers property returns a Hashtable containing instances of Provider classes. The Provider class instance in turn contains information about entries in the <providers> section for Membership's configuration:

public namespace System.Web.Configuration {
   public class Provider {
      string name;
      string providerType;
      NameValueCollection providerAttributes = new NameValueCollection();

      public Provider (XmlAttributeCollection attributes) {

         // Set the name of the provider

         name = attributes["name"].Value;

         // Set the type of the provider
         providerType = attributes["type"].Value;

         // Store all the attributes in the attributes bucket
         foreach (XmlAttribute attribute in attributes) {

            if ( (attribute.Name != "name") && 
              (attribute.Name != "type") )
               providerAttributes.Add(attribute.Name, attribute.Value);

         }

      }

      public string Name {
         get {
            return name;
         }
      }

      public string Type {
         get {
            return providerType;
         }
      }

      public NameValueCollection Attributes {
         get {
            return providerAttributes;
         }
      }

   }

So at this point we've built a set of class libraries for reading data out of the XML configuration file and exposing that data through a strongly typed class. Next, let's revisit our Membership class—the class that we program against to create users or validate credentials—and examine the implementation of its methods:

public namespace NothinButAspNet {
  public class Membership {
    public static bool Validate (string username, string password) {  
      MembershipProvider provider = MembershipProvider.Instance();
      return provider.Validate (username, password);
    }

    public static void CreateUser (string username, string password) { 
      MembershipProvider provider = MembershipProvider.Instance();
      return provider.CreateUser (username, password);
    }
  }
}

When calling routines on the Membership class, internally it will always forwards those calls to an instance of the MembershipProvider—first creating an instance of MembershipProvider using a factory method, Instance(), and then calling the appropriate base class method on the retrieved instance. The Instance() method doesn't really return an instance of MembershipProvider. It is an abstract class; rather, a class that derives from MembershipProvider, such as SqlMembershipProvider.

Note   the actual implementation of how the provider class is instantiated is different in ASP.NET 2.0. Use of an Instance() factory method is simply one technique and instantiates the class on demand.

Let's look at the MembershipProvider.Instance() method:

""""""""'""""""""""""
public static MembershipProvider Instance() {

  // Use the cache because the reflection used later is expensive
  Cache cache = HttpRuntime.Cache;
  Type type = null;
  string cacheKey = null;

  // Get the names of the providers
  MembershipConfiguration config = MembershipConfiguration.GetConfig();

  // Read the configuration specific information
  // for this provider
  Provider membershipProvider = (Provider) config.Providers[config.DefaultProvider];

  // In the cache?
  cacheKey = "Membership::" + config.DefaultProvider;
  if ( cache[cacheKey] == null ) {

    // The assembly should be in \bin or GAC, so we simply need
    // to get an instance of the type
    try {

      type = Type.GetType( membershipProvider.Type );

      // Insert the type into the cache
      Type[] paramTypes = new Type[1];
      paramTypes[0] = typeof(string);
      cache.Insert( cacheKey, type.GetConstructor(paramTypes) );

    } catch (Exception e) {
      throw new Exception("Unable to load provider", e);
    }

  }

  // Load the configuration settings
  object[] paramArray = new object[1];
  paramArray[0] = membershipProvider.Attributes["connectionString"];

  return (MembershipProvider)(  ((ConstructorInfo)cache[cacheKey]).Invoke(paramArray) );
}

What the Instance() method does is really the heart of the provider pattern in our example. It uses reflection to dynamically load the class marked as the defaultProvider from configuration, caches a reference to the constructor of the class (if it has not already been loaded), and then returns an instance of the provider class.

Note   This trick, caching an instance to the constructor, yields an enormous performance benefit over having to use reflection on each call. Brady Gaster actually ran some numbers on this a while back: http://weblogs.asp.net/tatochip/archive/2003/11/26/39958.aspx. This modification is an extension of the Dynamic Assembly Loader pattern, and an extension I would strongly encourage using for the sake of performance.

Where We Are

We've left out some details for brevity, namely implementing the Providers collection on our Membership class or the Provider property. In a complete implementation, we would implement these properties to add behaviors for accessing the current provider as well as any other providers that may be configured for the system. The Provider and Providers property give us runtime access to any providers added in the configuration <providers> section and further allow us to cast the instance back to its original type, allowing us to access methods or properties that are not part of the base class the provider class overrides.

Here's a short version of what's happening in our article:

We created our own class that derived from MembershipProvider and overrode the methods of the base class with our own functionality. We added an entry into the <membership> configuration section describing the class we created. When we use the Membership API, it internally uses our custom class, logic, and SQL Server data store. If someone wanted to customize the behavior further, say add support for Oracle, they would simply create an OracleMembershipProvider, add it to configuration, and then Membership APIs would internally use an Oracle database instead of SQL Server.

We created several helper classes along the way, which added complexity to the examples. It's worth noting that to accomplish the same example in ASP.NET 2.0, all we would have to do is create a class deriving from MembershipProvider and then create an entry in the <membership> <providers> section of web.config. The provider pattern is incredibly powerful; in ASP.NET 2.0 it truly opens new enterprise-level accessibility doors that were not possible before. Systems architects can design and implement providers for common ASP.NET features from session state to new areas, such as membership or personalization. Developers familiar with ASP.NET will use the well-documented public APIs, such as Membership or Session, but unbeknownst to them, internally the APIs may be communicating with a mainframe and executing all manner of custom logic.

Two applications today that implement this pattern include the ASP.NET Forums and DotNetNuke, both of which are source projects that publish their code and allow external contributions. If you are building componentized infrastructure today, I would strongly encourage you to follow the provider design pattern. Not only will it allow you to make the system pluggable, but it will better prepare you for the move to ASP.NET 2.0.

Conclusion

The power of the provider design pattern is the ability to dynamically replace or extend the behavior of published APIs without needing source access to the API itself. The provider pattern is not just a data abstraction layer, but a full API abstraction layer. For the ASP.NET team, it means we can create implementations that ship with ASP.NET, but it also in turn means you can extend or replace our implementation with your own while still supporting a public API. Beautiful, isn't it?

In our next article, we're going to look at how to write an implementation of SQL Server database cache invalidation using the same model employed by ASP.NET 2.0. There are many solutions available for this today, ranging from extended stored procedures that callback to the server (the first of which was written by yours truly), to having SQL Server write to a common file that can be monitored for changes by the ASP.NET Cache. We'll look at all these techniques, as well as point out their shortcomings and why you shouldn't be using them when it comes to writing large-scale applications. We'll also look at the techniques you should be using.

Related Books

 

Nothin' But ASP.NET

Rob Howard is a program manager on the Microsoft Web Platform and Tools team, and helped design many of the new ASP.NET 2.0 infrastructure features (including the new provider design pattern). When he's not working, he plays hockey on the Dallas, TX Microsoft hockey team and spends time with his family. You can read more about what Rob's up to by visiting his blog at http://weblogs.asp.net/rhoward, or send him an e-mail message at rhoward@devadvice.com.

Show:
© 2014 Microsoft