March 25, 2010

Inserting a MembershipProvider into the MembershipProviderCollection programmatically

The Membership.Providers MembershipProviderCollection is supposed to be maintained through the web.config. However there are times when you don't wish to use the web.config - perhaps for unit testing, or even just for prototyping something where you want to test a provider in isolation.

If you attempt to inject a MembershipProvider into the collection programmatically, you will have come across the exception 'Collection is read-only'. This is to prevent you from injecting membership providers willy nilly and not following the workflow that the .NET team would prefer you use.

Disclaimer: The code I'm using below is a hack to allow you to bypass the proper channels of adding your provider to the collection and should be used carefully. This code should not be used in a production system. Just because you can do something, doesn't always mean you should.

Okay, now that you've read and understood the disclaimer, you still want to know how to inject your provider into the collection for whatever reason you have. Here is how:

The membership provider collection has an internal field that sets it as read-only. This is set to coerce [and by coerce I mean force] the programmer to use the web.config to add your provider. All we need to do to inject our provider programmatically is set this to read-write. The field is on the ProviderBase base class which means that we can't get at it directly from the MembershipProviderCollection type, we have to traverse up to the base class to get at it; don't worry, it's not complicated.

We're going to use reflection [so don't forget to add the reference to System.Reflection] to grab the MembershipProviderCollection type, and traverse up to the base type to override the private field _ReadOnly and set it to false. Normally this field is set to true, preventing you from writing to the collection.

public static void Main()
{
    //Instantiate my custom provider that 
    //inherits MembershipProvider and set 
    //up a reference to the memberhip 
    //providers collection.
    MembershipProvider mp = 
      new MyProvider();

    MembershipProviderCollection mpc = 
      Membership.Providers;

    //Grab the base type from our collection by 
    //grabbing the type and then grabbing the 
    //type's base type like so:
    Type t = mpc.GetType();
    Type tbt = t.BaseType;

    //Okay, now we've got our base type we can 
    //grab the read only field.
    BindingFlags bindingFlags =
      BindingFlags.Instance | 
      BindingFlags.NonPublic;

    FieldInfo fi = 
      tbt.GetField("_ReadOnly", bindingFlags);

    //Having grabbed a reference to the field, we 
    //can override its value
    fi.SetValue(mpc, false);

    //Now our MembershipProviderCollection is no 
    //longer read-only
    mpc.Add(mp);
}

I guess you could easily create a method to do the injection. I've condensed the code in the following method but it doesn't function any differently than the code above.

public static void InjectProvider(MembershipProvider providerToInject)
{
    MembershipProviderCollection mpc = 
      Membership.Providers;

    mpc.GetType().BaseType.GetField("_ReadOnly", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(mpc, false);

    mpc.Add(mp);
}

public static void Main()
{
    MembershipProvider mp = new MyProvider();
    InjectProvider(mp);

    //If you check your Membership.Providers 
    //in your watch window at this point you 
    //will see that your provider exists in 
    //the collection.

}

So there you have it, you can now programmatically inject your provider into the provider collection. Remember though, Microsoft set this as read-only for a reason. They want you to use the proper channel for adding your provider to the collection, use this only under carefully considered circumstances.

No comments:

Post a Comment