Wednesday, November 17, 2010

Windsor-managed MembershipProviders

I see many questions on Stackoverflow that are basically variants of this: "How can I integrate my custom MembershipProvider into my IoC container?"

Integrating your custom MembershipProvider to a IoC container has many advantages, since it would let you treat it just like any other service: you could manage its lifetime, dependencies, configuration, even proxy it if you wanted.

Problem is, MembershipProviders are one of those things that are managed by the ASP.NET runtime. You just configure it in your web.config and the runtime instantiates it when it's needed. You don't really get much control over its creation.

A cheap solution is to use the container as a service locator directly in your membership provider (using something like CommonServiceLocator), e.g.:

public class MyMembershipProvider : MembershipProvider {
    private IUserRepository repo {
        get { return ServiceLocator.Current.GetInstance<IUserRepository>(); }
    }

    public override string GetUserNameByEmail(string email) {
        return repo.First(u => u.Email == email);
    }

    ...
}

Using a service locator like this should be avoided as much as possible. Mark Seeman explains it thoroughly in this article. In a nutshell, you want to limit the usage of the service locator pattern to glue code (i.e. very low-level infrastracture), even there use it as little as possible, and never use it in application-level code.

As usual with this kind of problems, the solution is to write a wrapper/adapter/bridge/whatever-you-want-to-call-it that isolates the issue so that client code doesn't have to suffer it. It's similar in concept to the implementation of Windsor-managed HttpModules. It's actually simpler than that, we don't need a custom lifestyle manager here.

In fact, Spring.NET has had such an adapter for quite some time. The only problem with that implementation is that you can't change the lifetime of the custom provider, it's always a singleton. My implementation doesn't have this limitation: your provider can be transient, singleton, per web request, whatever. The price for this is that you can't use Initialize() (more precisely, it won't do anything), but since it's managed by the container, you can use the container to provide any configuration, which is much more flexible. The implementation is about 200 lines of boring, simple code so I'm not going to post it here. It does use Windsor as a service locator, but this is low-level infrastracture/glue code. The goal here is to keep your code clean.

The code is here, and here's how to use it:

  1. Write your custom MembershipProvider as a regular component, using constructor or property injection as you see fit.
  2. Implement IContainerAccessor in your global HttpApplication class. Use this article as reference.
  3. Register your custom provider in Windsor and assign a name to the component. E.g.:

    container.Register(Component.For<MyMembershipProvider>()
        .LifeStyle.Transient
        .Named("myProvider"));
  4. Register your custom provider in your web.config using the adapter and referencing the name of the corresponding Windsor component in a "providerId" attribute. E.g.:

        <membership defaultProvider="customProvider">
          <providers>
            <clear/>
            <add name="customProvider" type="ProviderInjection.WebWindsorMembershipProvider, ProviderInjection" providerId="myProvider"/>
          </providers>
        </membership>

That's it. Here's a sample app that you can use as reference. This can be easily ported to any IoC container, and for any provider like RoleProvider, ProfileProvider, etc. I haven't used this in anger so let me know if you have any problems with it.

17 comments:

  1. Hello,

    I found your article and followed it but the custom provider never gets initialized. Any ideas why? I used you code but for whatever reason it never gets called.

    ReplyDelete
  2. @Todd: use the sample app for reference. If you still have problems, post them in the Castle users mailing list.

    ReplyDelete
  3. Hi,

    How can i use Initialize parameter NameValueCollection in my custom membership provider? thnx

    ReplyDelete
  4. @Tomas: as I said in the post, Initialize() doesn't get called, but it's not really an issue, just use the standard Windsor configuration mechanisms

    ReplyDelete
  5. well, I'm fairly new to ioc containers and I think I should learn more to make it work. Thanks anyways.

    ReplyDelete
  6. I'm trying to pass in configuration in a windsor installer like such:

    Component.For().LifeStyle.Transient
    .Named("myProvider)")
    .DependsOn(Property.ForKey("requiresUniqueEmail").Eq(true))

    Is that the correct mechanism? When I unit test the installer, the property requiresUniqueEmail is not being set to true.

    ReplyDelete
  7. @kevin: blogger chew your angle brackets but I'll assume you used MyMembershipProvider. If so, then yes, it's correct. But did you add a setter to the RequiresUniqueEmail property? By default it's readonly, so Windsor can't set anything.

    ReplyDelete
  8. The RequiresUniqueEmail setter can not be overriden as it is not exposed by the base class.

    The RequiresUniqueEmail is passed to the WindsorMembershipProvider class from the web.config during the initialize call in the NameValueCollection argument. I have added a line of code in the WindsorMembershipProvider Initialize function

    WithProvider(p => p.Initialize(name, config));

    and added an Initialize function in my own custom MembershipProvider which caches the NameValueCollection in a private NameValue Collection called _config. I can then write the RequiresUniqueEmail getter as
    return _config[requiresUniqueEmail];

    As the Initialize function only gets called once I am not sure whether to change the lifestyle of the container to a Singleton or to write the cached private NameValueCollection as a static.

    Any advice?

    ReplyDelete
  9. @Marc: I see. In that case I'd try setting the property through a constructor parameter or use a different property to set RequiresUniqueEmail

    ReplyDelete
  10. Mauricio,

    You are a really intelligent guy!

    I took a look at the sample code you provided and managed to get a custom MembershipProvider working nicely with Windsor container and an Oracle database.

    Thanks for sharing your knowledge.

    Leniel

    ReplyDelete
  11. This comment has been removed by the author.

    ReplyDelete
  12. Thanks for the post. I ended-up using Spring.Net container. I wish though i used a more decoupled way, but now my custom membership provider depends on Spring.Net (and yours depends on Windsor)

    ReplyDelete
  13. @que0x : no, your membership code doesn't depend **at all** on Windsor. Only WebWindsorMembershipProvider has a dependency on Windsor, and that's ok because it's infrastructure code. It could very well be included in Castle.Windsor.dll itself.

    ReplyDelete
  14. Very clever... works really well. My favourite part is that my custom membership provider doesn't have to depend on Windsor in any way. The custom membership provider's dependencies are just magically injected into the constructor as normal.

    ReplyDelete
  15. You are great! Thank you so much! It works like a charm!

    ReplyDelete
  16. Fantastic! Thanks so much. This was exactly what I was thinking to work around the issue. Basically an implementation of the Proxy Design Pattern (I think).

    ReplyDelete