2017-01-04 4 views
1

Я использую Autofac и OWIN в проекте WebAPI, который был создан с нуля (в соответствии с полным шаблоном WebAPI, доступным в VS2015). По общему признанию, я новичок в этом.Autofac DI для RequestContext.Principal с использованием WebAPI2 в единичном тесте

В модульном тестировании проекта, я создал класс Owin запуска в начале модульного тестирования:

WebApp.Start<Startup>("http://localhost:9000/") 

Класс запуска выглядит следующим образом:

[assembly: OwinStartup(typeof(API.Specs.Startup))] 

namespace API.Specs 
{ 
    public class Startup 
    { 
     public void Configuration(IAppBuilder appBuilder) 
     { 
      var config = new HttpConfiguration(); 
      //config.Filters.Add(new AccessControlAttribute()); 
      config.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver()); 

      config.Formatters.JsonFormatter.SerializerSettings = Serializer.Settings; 
      config.MapHttpAttributeRoutes(); 

      // Autofac configuration 
      var builder = new ContainerBuilder(); 

      // Unit of Work 
      var unitOfWork = new Mock<IUnitOfWork>(); 
      builder.RegisterInstance(unitOfWork.Object).As<IUnitOfWork>(); 

      // Principal 
      var principal = new Mock<IPrincipal>(); 
      principal.Setup(p => p.IsInRole("admin")).Returns(true); 
      principal.SetupGet(p => p.Identity.Name).Returns('test.user'); 
      principal.SetupGet(p => p.Identity.IsAuthenticated).Returns(true); 

      Thread.CurrentPrincipal = principal.Object; 
      if (HttpContext.Current != null) 
      { 
       HttpContext.Current.User = new GenericPrincipal(principal.Object.Identity, null); 
      } 

      builder.Register(c => principal).As<IPrincipal>(); 

      . 
      . 
      . 
      // Set up dependencies for Controllers, Services & Repositories 
      . 
      . 
      . 

      var container = builder.Build(); 
      config.DependencyResolver = new AutofacWebApiDependencyResolver(container); 
      config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; 

      appBuilder.UseWebApi(config); 
     } 

     private static void RegisterAssemblies<TModel, TController, TService, TRepoClass, TRepoInterface>(ref ContainerBuilder builder, ref Mock<IUnitOfWork> unitOfWork) 
      where TModel : class 
      where TRepoClass : class 
      where TService : class 
     { 
      RegisterController<TController>(ref builder); 
      var repositoryInstance = RegisterRepository<TRepoClass, TRepoInterface>(ref builder); 
      RegisterService<TService>(ref builder, ref unitOfWork, repositoryInstance); 
     } 

     private static void RegisterController<TController>(ref ContainerBuilder builder) 
     { 
      builder.RegisterApiControllers(typeof(TController).Assembly); 
     } 

     private static object RegisterRepository<TRepoClass, TRepoInterface>(ref ContainerBuilder builder) 
      where TRepoClass : class 
     { 
      var constructorArguments = new object[] { DataContexts.Instantiate }; 
      var repositoryInstance = Activator.CreateInstance(typeof(TRepoClass), constructorArguments); 
      builder.RegisterInstance(repositoryInstance).As<TRepoInterface>(); 

      return repositoryInstance; 
     } 

     private static void RegisterService<TService>(ref ContainerBuilder builder, ref Mock<IUnitOfWork> unitOfWork, object repositoryInstance) 
      where TService : class 
     { 
      var constructorArguments = new[] { repositoryInstance, unitOfWork.Object}; 
      var serviceInstance = Activator.CreateInstance(typeof(TService), constructorArguments); 

      builder.RegisterAssemblyTypes(typeof(TService).Assembly) 
       .Where(t => t.Name.EndsWith("Service")) 
       .AsImplementedInterfaces().InstancePerRequest(); 

      builder.RegisterInstance(serviceInstance); 
     } 
    } 
} 

Side Примечание: В идеале я хотел бы установить Принцип как часть теста, чтобы иметь возможность передавать разные пользователи контроллеру, но если мне абсолютно необходимо сохранить настройку CurrentPrincipal/User в классе запуска, я могу обойти его.

Начальный класс работает отлично с доступом к моим контроллерам с использованием DI, однако Принципал в RequestContext.Principal никогда не устанавливается. Он всегда равен нулю. То, как я намерен использовать контекст запроса выглядит следующим образом:

[HttpGet] 
[Route("path/{testId}")] 
[ResponseType(typeof(Test))] 
public IHttpActionResult Get(string testId) 
{ 
    return Ok(_service.GetById(testId, RequestContext.Principal.Identity.Name)); 
} 

Я также попытался инъекции высмеивал главный класс в конструктор моего контроллера в качестве обходного пути - я использовал тот же метод, как показано в родовом методе для настройки моих сервисов с использованием DI. Опять же, я только получил null в моем конструкторе.

На данный момент я сижу около дня с этой проблемой и вытаскиваю волосы. Любая помощь будет оценена по достоинству. Заранее спасибо.

ответ

1

Я бы не стал делать это с DI. Вам нужно что-то, чтобы установить принципала в контексте запроса, а не вводить принципал в конструктор.

Вот что бы я сделал, если это был я:

Во-первых, я не буду издеваться вещи, которые не нуждаются в насмешливый. То есть ваша реализация IIdentity может быть реальными объектами.

private static IPrincipal CreatePrincipal() 
{ 
    var identity = new GenericIdentity("test.user", "test"); 
    var roles = new string[] { "admin" }; 
    return new GenericPrincipal(identity); 
} 

Далее вам необходимо запустить программу установки на каждом «запрос» вы обрабатываете через тест приложения. Я предполагаю, что это более «интеграционный тест», чем «модульный тест», поскольку вы используете целый класс запуска и все такое, поэтому вы не можете просто назначить основную единицу времени и сделать это. Это нужно делать по каждому запросу, как это делает реальное действие аутентификации.

Самый простой способ сделать это с помощью a simple delegating handler.

public class TestAuthHandler : DelegatingHandler 
{ 
    protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
    // Set the principal. Whether you set the thread principal 
    // is optional but you should really use the request context 
    // principal exclusively when checking permissions. 
    request.GetRequestContext().Principal = CreatePrincipal(); 

    // Let the request proceed through the rest of the pipeline. 
    return await base.SendAsync(request, cancellationToken); 
    } 
} 

Наконец, добавьте обработчик в HttpConfiguration трубопровода в вашем Startup классе.

config.MessageHandlers.Add(new TestAuthHandler()); 

Это должно быть сделано. Запросы должны теперь пройти через этот обработчик auth и получить назначенный главный.

+0

Hi Travis. Оглядываясь назад, ваше решение совершенно очевидно. Спасибо, что так хорошо объяснили. Приносим извинения за то, что вы вернулись к вам сейчас, мне пришлось переходить к проекту с помощью обходного пути и только теперь вернулись к исправлению. – Ebbs

Смежные вопросы