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:

Unknown said...

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.

Mauricio Scheffer said...

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

Tomas said...

Hi,

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

Mauricio Scheffer said...

@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

Tomas said...

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

kevin said...

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.

Mauricio Scheffer said...

@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.

Marc said...

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?

Mauricio Scheffer said...

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

Leniel Maccaferri said...

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

Mauricio Scheffer said...

@Leniel: glad you liked it!

Renan said...
This comment has been removed by the author.
Amr Ellafy said...

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)

Mauricio Scheffer said...

@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.

Jack Hughes said...

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.

Anonymous said...

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

Mark Whitfeld said...

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).