Project Description

This is another approach to inheriting and implementing the abstract MembershipProvider class. It is adapted instead of inherited. MembershipUser related calls are isolated in a IUserRepository implementing class (supplied by the developer) and instance of the class is injected in the MembershipProviderAdapter after it is initialized.

Provider Design Pattern

This is the main idea behind all the adapters in the family: Provider Model Design Pattern... It is flexible, relying mostly on configuration files, but when it comes to inheriting and implementing a new, customized Membership provider it is not this straightforward.

The reason lies in the tight coupling with MembershipUser class. (This is not true for RoleProvider only because a Role is simply a string.) This class is complex enough and deserves attention by itself. Despite this, when FormsAuthentication is used what’s needed is very little – only username actually matters and is kept in the authentication cookie. Obviously things are not so smooth here and they have to be handled accordingly.

The ProfileProvider was used to extend the MembershipUser so that it is more adequate to the developers’ needs. Although a solution it is by far not the best one. To make things worse other means for storing/manipulating/retrieving user data exist today which are modern and much more elegant.

The requirement for the provider implementation to define a default, parameterless constructor makes the things even more complicated – injecting code to handle MembershipUser related functionality becomes another hurdle.

Adapter Design Pattern

The Problem

Clients of class A, knowing only class A’s interface, need to use functionality exposed by class B, despite they know nothing about B’s interface.

The Solution

By creating a descendent of class A, say class C, and aggregating an instance of class B inside it we can give access to the functionality “buried” in B as if clients are granted access to B’s functionality. This is called adapting B’s functionality to A’s clients.

This is in brief the essence of the Adapter Design Pattern. (A description of the Adapter Design Pattern here.)

With some not so very complicated substitutions we can adapt MembershipProvider to whatever we need just by creating an descendent of it an injecting an instance of a class handling all MembershipUser related calls.

Here's the class diagram of the Adapter DP with concrete classes implementing the MembershipAdapter (only a couple of methods left from MembershipProvider for clarity):

AdapterDP

What we achieve is:

  • separation of concerns – when implementing MembershipProvider descendent we do not care how MembershipUser functionality is about to be handled,
  • clear responsibility – MembershipProviderAdapter does not to “worry” about MembershipUser and all related stuff – it simply delegates what its clients need to the MembershipUser adapter,
  • legacy needs no change whatsoever – in fact the legacy code are MembershipProvider’s clients – they are as happy as if they access the MembershipProvider itself (or its descendent class),
  • simplicity– by implementing fake (or just as simple as possible) MembershipUser adapters code for handling back-end development would be greatly simplified,
  • testability – MembershipUser adapters maybe unit tested alone, without coupling them with the MembershipProvider.

By implementing different MembershipUser adapters various user repository schemes might be used – from a very simple one – in memory repository for simplest purposes or just testing other features under implementation, thru file based user repositories, to specialized database schema repositories, like ones created with ORMs, say NHibernate or Entity Framework. The overall effect is that what the Provider Design Patter was designed to solve now is solved thru various implementations of the MembershipUser adapter (IUserRepository implementing classes). The responsibility is transferred, hopefully for good.

The Source Code

The MembershipUser Adapter

This is the IUserRepository implementing class handling all calls to MembershipUser like CreateUser, ChangePasword, etc.

Coding the MembershipProvider descendent

This is the MembershipProviderAdapter class. Its implementation is as simple as deriving a class from MembershipProvider. Instances of this class are initialized much like SqlMembershipProvider’s instances are.

Calls to MembershipProviderAdapter’s methods just delegate to corresponding methods of the IUserRepository implementer class. They even have the same name, parameters and return types.

Points of Interest

During the initialization all settings are collected in MembershipProviderSettings struct which is then passed to the adapter’s constructor. This is done since most of the settings relate to how certain features of MembershipUser are handled, like number of login attempts before the account is banned, password length, encryption, and format, etc.

Actual type implementing IUserRepository is injected after all settings in the config file are read and assigned to Adaptee property of MembershipProviderAdapter class.

Config file changes

The same configuration settings are used as in SqlMembershipProvider with two exceptions:

1. Connection string is no longer supplied and an error is reported if it is added to configuration settings. This is because everything that relates to users is moved to IUserRepository implementer class and it is its responsibility to handle all user stuff,

2. The implementer’s type is specified as a userRepositoryType attribute. This is required so that the implementing class could be injected after all provider settings are read from the congig file.

An example of a configuration file:

<?xml version="1.0" encoding="utf-8"?>
<!--
  For more information on how to configure your ASP.NET application, please visit
  http://go.microsoft.com/fwlink/?LinkId=152368
  -->
<configuration>
  <appSettings>
    <add key="webpages:Version" value="1.0.0.0" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.0">
      <assemblies>
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>
    </compilation>
    <authentication mode="Forms">
      <forms loginUrl="~/Account/LogOn" timeout="2880" />
    </authentication>
    <membership defaultProvider="MembershipProviderAdapter">
      <providers>
        <clear />
        <!-- NOTE: Change userRepositoryType to whatever the implementation of IUserRepository is -->
        <add name="MembershipProviderAdapter" type="MembershipAdapter.MembershipProviderAdapter, MembershipAdapter" userRepositoryType="Test.Samples.SampleUserRepository, Test" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" requiresUniqueEmail="false" maxInvalidPasswordAttempts="5" minRequiredPasswordLength="6" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" applicationName="Test" />
      </providers>
    </membership>
    <roleManager defaultProvider="RoleProviderAdapter" enabled="true">
      <providers>
        <clear />
        <add name="RoleProviderAdapter" type="MembershipAdapter.RoleProviderAdapter, MembershipAdapter" roleRepositoryType="Test.Samples.SampleRoleRepository, Test" applicationName="Test" />
      </providers>
    </roleManager>
    <pages>
      <namespaces>
        <add namespace="System.Web.Helpers" />
        <add namespace="System.Web.Mvc" />
        <add namespace="System.Web.Mvc.Ajax" />
        <add namespace="System.Web.Mvc.Html" />
        <add namespace="System.Web.Routing" />
        <add namespace="System.Web.WebPages" />
      </namespaces>
    </pages>
  </system.web>
  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true" />
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Installation

This is a class library project so just download it and add it to your project, then add a reference to it. Or, if you prefer, add it as a NuGet package from the online NuGet package repository. The package is named MembershipAdapter.

It is also available as a NuGet package.

Examples

An example how to use both providers here and here.

Last edited Dec 8, 2012 at 2:11 PM by ac2008, version 10

Comments

No comments yet.