Search code examples
c#asp.net-mvcmoqcastle-windsorservice-locator

How can I decouple my application from my membership service?


I'm working on an ASP.NET MVC 4 project that uses Castle Windsor. One of the controllers has a dependency on MembershipService:

public class FooController : Controller
{
    private readonly MembershipService MembershipService;

    public FooController( MembershipService membershipService )
    {
        MembershipService = membershipService;
    }

    [Authorize( Roles = "Administrator" )]
    public ActionResult DoSomething()
    {
        var user = MembershipService.GetUser( User.Identity.Name );

        // etc...
    }
}

When I started writing unit tests for this controller (using Moq), I ran into a problem:

private Mock<MembershipService> membershipServiceMock;

[TestInitialize]
public void MyTestInitialize()
{
    membershipServiceMock = new Mock<MembershipService>();
}

[TestMethod]
public void DoSomethingReturnsWhatever()
{
    var controller = new FooController( membershipServiceMock.Object );

    // etc...
}

The test fails because Castle throws an exception:

Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: MembershipService.
Could not find a parameterless constructor.

I decided I should make an interface that MembershipService can implement, because that's how our apps are normally designed anyways:

public interface IMembershipService
{
    User GetCurrentUser( string userName );
}

I updated the controller to use the interface, but now Castle throws another exception:

[HandlerException: Handler for IMembershipService was not found.]
   Castle.MicroKernel.Resolvers.DefaultDependencyResolver.TryGetHandlerFromKernel(DependencyModel dependency, CreationContext context) +210
   Castle.MicroKernel.Resolvers.DefaultDependencyResolver.ResolveFromKernelByType(CreationContext context, ComponentModel model, DependencyModel dependency) +38

[DependencyResolverException: Missing dependency.
Component FooController has a dependency on IMembershipService, which could not be resolved.
Make sure the dependency is correctly registered in the container as a service, or provided as inline argument.]

Obviously I'm not registering IMembershipService with Castle, so I changed my installer from this:

container.Register( Component.For<MembershipService>() );

To this:

container.Register( Component.For( typeof(IMembershipService) )
         .ImplementedBy( typeof(MembershipService) ) );

Now Castle throws yet another exception:

Configuration Error
Parser Error Message: Activation error occurred while trying to get instance of type MembershipService, key ""

Here's the relevant section of the web.config file. The offending line is the one that starts with add name="MyRoleProvider":

<authentication mode="Windows" />
<roleManager enabled="true" defaultProvider="MyRoleProvider">
  <providers>
    <clear />
    <add name="MyRoleProvider" type="MyRoleProvider" applicationName="MyApplication" autoCreateRole="true" autoCreateUser="true" />
    <add name="AspNetWindowsTokenRoleProvider" type="System.Web.Security.WindowsTokenRoleProvider" applicationName="/" />
  </providers>
</roleManager>

This is because MyRoleProvider has a reference to MembershipService as well:

public class MyRoleProvider : RoleProvider
{
    private MembershipService MembershipService;

    public override void Initialize( string name, NameValueCollection config )
    {
        MembershipService = ServiceLocator.Current.GetInstance<MembershipService>();

        // blah blah blah...
    }
}

I tried changing the type of the MembershipService field in MyRoleProvider like so:

public class MyRoleProvider : RoleProvider
{
    private IMembershipService MembershipService;

But it still throws the same exception above ("Activation error occurred while trying to get instance of type MembershipService..."). The only way I've managed so far to get everything to cooperate is by registering both IMembershipService and MembershipService with Castle like so:

container.Register( Component.For<MembershipService>() );
container.Register( Component.For( typeof(IMembershipService) )
         .ImplementedBy( typeof(MembershipService) ) );

I'm pretty sure I'm not supposed to register things twice. So how else can I fix this? At this point I'd be happy with leaving the application tightly coupled to the membership service if I can just get on with writing unit tests. But a decoupling solution would be nice too if it doesn't involve a major architecture change.

Edit

Stack trace:

at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance(Type serviceType, String key) in c:\Projects\CommonServiceLocator\main\Microsoft.Practices.ServiceLocation\ServiceLocatorImplBase.cs:line 53
at Microsoft.Practices.ServiceLocation.ServiceLocatorImplBase.GetInstance[TService]() in c:\Projects\CommonServiceLocator\main\Microsoft.Practices.ServiceLocation\ServiceLocatorImplBase.cs:line 90
at MyRoleProvider.Initialize(String name, NameValueCollection config) in d:\Code\MyApplication\MyRoleProvider.cs:line 51
at System.Web.Configuration.ProvidersHelper.InstantiateProvider(ProviderSettings providerSettings, Type providerType)

Solution

  • You already defined IMembershipService

    public interface IMembershipService
    {
        User GetCurrentUser( string userName );
    }
    

    Now change your FooController

    public class FooController : Controller
    {
        private readonly IMembershipService MembershipService;
    
        public FooController( IMembershipService membershipService )
        {
            MembershipService = membershipService;
        }
    
        [Authorize( Roles = "Administrator" )]
        public ActionResult DoSomething()
        {
            var user = MembershipService.GetUser( User.Identity.Name );
    
            // etc...
        }
    }
    

    Go and change everywhere in your code MembershipService to IMembershipService as shown in FooController. Now, register IMembershipService in Castle

    container.Register( Component.For( typeof(IMembershipService) )
             .ImplementedBy( typeof(MembershipService) ) );
    

    Now change Unit Tests

    private Mock<IMembershipService> membershipServiceMock;
    
    [TestInitialize]
    public void MyTestInitialize()
    {
        membershipServiceMock = new Mock<IMembershipService>();
    }
    
    [TestMethod]
    public void DoSomethingReturnsWhatever()
    {
        var controller = new FooController( membershipServiceMock.Object );
    
        // etc...
    }