How can I decouple my application from my membership service?

限于喜欢 提交于 2019-12-08 03:36:47

问题


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)

回答1:


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


来源:https://stackoverflow.com/questions/25516181/how-can-i-decouple-my-application-from-my-membership-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!