Я работаю над проектом ASP.NET MVC 4, который использует Castle Windsor. Один из контроллеров имеет зависимость от 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...
}
}
Когда я начал писать модульные тесты для этого контроллера (используя Moq), у меня возникла проблема:
private Mock<MembershipService> membershipServiceMock;
[TestInitialize]
public void MyTestInitialize()
{
membershipServiceMock = new Mock<MembershipService>();
}
[TestMethod]
public void DoSomethingReturnsWhatever()
{
var controller = new FooController( membershipServiceMock.Object );
// etc...
}
Тест терпит неудачу, потому что Castle выдает исключение:
Castle.DynamicProxy.InvalidProxyConstructorArgumentsException: Can not instantiate proxy of class: MembershipService.
Could not find a parameterless constructor.
Я решил, что должен сделать интерфейс, который может реализовать MemberhipService, потому что как обычно разрабатываются наши приложения:
public interface IMembershipService
{
User GetCurrentUser( string userName );
}
Я обновил контроллер, чтобы использовать интерфейс, но теперь Castle выдает еще одно исключение:
[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.]
Очевидно, я не регистрирую IMembershipService с Castle, поэтому я изменил свой установщик:
container.Register( Component.For<MembershipService>() );
К этому:
container.Register( Component.For( typeof(IMembershipService) )
.ImplementedBy( typeof(MembershipService) ) );
Теперь замок бросает еще одно исключение:
Configuration Error
Parser Error Message: Activation error occurred while trying to get instance of type MembershipService, key ""
Здесь находится соответствующий раздел файла web.config. Оскорбительная строка - это та, которая начинается с 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>
Это связано с тем, что у MyRoleProvider также есть ссылка на MembershipService:
public class MyRoleProvider : RoleProvider
{
private MembershipService MembershipService;
public override void Initialize( string name, NameValueCollection config )
{
MembershipService = ServiceLocator.Current.GetInstance<MembershipService>();
// blah blah blah...
}
}
Я попытался изменить тип поля MembershipService в MyRoleProvider следующим образом:
public class MyRoleProvider : RoleProvider
{
private IMembershipService MembershipService;
Но он по-прежнему выдает одно и то же исключение выше ("Ошибка активации при попытке получить экземпляр типа MembershipService..."). Единственный способ, которым я до сих пор работал, чтобы получить все, чтобы сотрудничать, - зарегистрировать как IMembershipService, так и MembershipService с замком так:
container.Register( Component.For<MembershipService>() );
container.Register( Component.For( typeof(IMembershipService) )
.ImplementedBy( typeof(MembershipService) ) );
Я почти уверен, что я не должен регистрировать вещи дважды. Так как же я могу это исправить? В этот момент я был бы счастлив оставить приложение тесно связанным с сервисом членства, если я смогу просто продолжить тестирование модульных тестов. Но решение для развязки было бы неплохо, если бы оно не сопровождалось значительным изменением архитектуры.
Трассировки стека:
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)
Вы уже определили IMembershipService
public interface IMembershipService
{
User GetCurrentUser( string userName );
}
Теперь измените свой 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...
}
}
Перейдите и измените всюду в своем коде MembershipService на IMembershipService, как показано в FooController. Теперь зарегистрируйтесь IMembershipService в замке
container.Register( Component.For( typeof(IMembershipService) )
.ImplementedBy( typeof(MembershipService) ) );
Теперь измените тесты модуля
private Mock<IMembershipService> membershipServiceMock;
[TestInitialize]
public void MyTestInitialize()
{
membershipServiceMock = new Mock<IMembershipService>();
}
[TestMethod]
public void DoSomethingReturnsWhatever()
{
var controller = new FooController( membershipServiceMock.Object );
// etc...
}
ServiceLocator.CurrentGetInstance<MembershipService>
на ServiceLocator.CurrentGetInstance<IMembershipService>
после изменения типа поля в MyRoleProvider :)
private readonly IMembershipService MembershipService;
ServiceLocator.CurrentGetInstance<MembershipService>
наServiceLocator.CurrentGetInstance<IMembershipService>
по некоторым причинам.