Modifying ASP.Net Providers at Runtime

Modifying ASP.Net Providers at Runtime

  • Comments 2

How to add providers at runtime programmatically

So if you are like me, you are a fan of not reinventing the wheel. This is especially true of components like Membership, Roles, Authentication, Personalization and whatnot. While I take some issue with the choice of API and can think of many improvements that should be made, the benefits of leveraging what work has already been done instead of reinventing my own way of getting things done far outweighs any issues I find with the Provider model. One area that I cannot abide is the reliance on configuration files however. Don't get me wrong, the .Net web.config file is a great thing provided it isn't the only way to get stuff done. This holds true of configuration in general IMHO. You mustalways supply a programmatic API for configuration but if you so desire, supply an XML configuration API and in turn supply a component that understands how to interact with the specifically the System.Configuration subsystem in .Net framework. Go take a look at how the Castle team tackled configuration in Windsor. That's how you should get it done. The issue is that while the approach that the ASP.Net team took for configuration of the Provider system works for basic use cases, it totally lacks for any ability to modify or manage the Provider model at runtime. What this means is that you must set up ALL Providers in your web.config file ahead of time! This clearly doesn't work for systems wherein you need to add Providers at runtime or are unable to know the Providers at deployment time.

You might be tempted to think that "hey Jimmy, yeah I get that you have to configure all these Providers in the config file but haven't you noticed that the static provider specific API class has a collection of the providers?". Yes it does. Let me show you using the Membership system one of these collections:

var allProviders = Membership.Providers;
allProviders.ForEach(provider => Trace.Write(provider.GetType().Name);

Yuppers there's a big fat collection of them. And you might be thinking that because this is a collection then you can simply call the Add() method and be done with it. You would think; and it woudl be wrong. You see the collection happens to be read only. Try it out yourself:

Debug.Assert(Membership.Providers.IsReadOnly);

But never fear, you CAN get around this, albeit in a totally unsupported fashion leveraging a wee bit of reflection, get around this problem (with 1 caveat to be explained later). So if you want to build these guys up at runtime you totally can as nothing stops you from creating new instances of a provider class. The catch is that the Provider infrastructure doesn't know anything about your newly created instance. So the question is, if I can create them and I somehowget these instances into the Provider runtime, will they work? The pleasant answer is Yes they will. For the life of me, after reading the Provider code (one of the benefits of being a Microsoft employee +D), I cannot figure out why they decided to make this a configure time only feature. The only idea I can come up with is that the Provider model requires the provider implementation to be thread safe (no biggie there, just use local variables and keep no state and you are fine) and that if you are going to open the Provider runtime into being mutable, you are going to require those operations to be thread safe or done in a thread safe manner. Well it is obvious then that the .Net team thinks we are too dumb to do this correctly so they had better protect us from ourselves.

I've said it a million times and I will say it again: YOU MUST BE THIS SMART TO RIDE THIS RIDE [holds hand up in the air]. Document the requirements of the API and if I am a dumbass and use it wrong and it blows up in my face, that's MY PROBLEM. Stop trying to protect me; it doesn't work out anyways. You can not fix stupid so stop trying.

So here's the totally unsupported but functional, work around here (note that I am using a container in this example but how you get a reference to your provider instances is up to you; I care not how):

private void Application_Start(Object sender, EventArgs e)
{
    var field = typeof(ProviderCollection).GetField("_ReadOnly", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetField | BindingFlags.GetField);
    lock (Membership.Providers.SyncRoot)
    {
        field.SetValue(Membership.Providers, false);

        foreach (var provider in Container.GetAllInstances<MembershipProvider>())
        {
            var name = CreateUniqueName(); // Make up a unique name for the dictionary. Again this is up to you to figure out what makes sense
            Membership.Providers.Add(provider);
        }

        field.SetValue(Membership.Providers, true);
    }
}

Yup, that's it. Simply rinse and repeat for each provider type you want to hijack. Everything will actually work fine from there on with 1 catch. You actually MUST have 1 provider configured previously as the default provider in the web.config or the whole thing stops working as without defaults, the provider collections are not accessible. Strange logic but true. But never fear, there's a pattern that can help this situation out: The Null Object pattern. So my suggestion is that you create a special NullMembershipProvider, NullProfileProvider, Null yada yada yada Provider and use those pre configured as default providers. What you do in them is up to your use case (i prefer to simply throw a NotSupportedException explaining that the "use of the such and such default provider is not supported") but the specifics are left to you to figure out. An an aside, one idea is a default provider that uses some ambient context to select another specific provider instance from the collection. Hrrrmmmmm based on URL munging perhaps? Guess that's for another post.

BTW: If you want to replace the default provider you can. Simply remove it from the collection and use the same pattern to replace the required private field with your choice of reference. I figure that is also one way you can get around the need for a default provider, set it via reflection first and then run the indicated routines.

Well hopefully that helps you out.

 

Comments
  • Hi Jimmy,

    you saved me lot of strife. I had a strange requirement where I needed to add providers at run time. Yes I agree that .NET framework should allow this feature which will help lot of people.

  • Maybe I am not smart enough to ride this ride, but what framework does 'Container' belong to?

Page 1 of 1 (2 items)
Leave a Comment
  • Please add 7 and 1 and type the answer here:
  • Post