RequestContext.Principal的Autofac DI在单元测试中使用WebAPI2

时间:2017-01-04 07:10:47

标签: c# asp.net-web-api dependency-injection owin autofac

我在WebAPI项目中使用Autofac和OWIN,该项目是从头开始构建的(与VS2015中提供的完整WebAPI模板相对应)。不可否认,我是这样做的新手。

在单元测试项目中,我在单元测试开始时设置了一个OWIN Startup类:

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

Startup类如下:

[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);
        }
    }
}

附注:理想情况下,我想将Principle设置为测试的一部分,以便能够将不同的用户传递给控制器​​,但是如果我必须在启动时保持CurrentPrincipal / User的设置上课,我可以解决它。

使用DI访问我的控制器时,启动类工作正常,但RequestContext.Principal中的Principal永远不会设置。它始终为空。我打算使用Request上下文的方式如下:

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

我还尝试将模拟的主要类注入到我的Controller的构造函数中作为一种解决方法 - 我使用与通用方法中显示的相同方法来使用DI设置我的服务。但是,我的构造函数中只有null。

此时我已经和这个问题坐了大约一天,拉了我的头发。任何帮助,将不胜感激。提前谢谢。

1 个答案:

答案 0 :(得分:2)

我避免使用DI执行此操作。您需要在请求上下文中设置主体,而不是将主体注入构造函数。

以下是我做的事情,如果是我的话:

首先,我不会嘲笑那些不需要嘲笑的事情。也就是说,您的IIdentity实现实际上可能是真实对象。

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

接下来,您需要在每个&#34;请求&#34;上运行设置。您通过测试应用程序进行处理。我猜这是更多&#34;集成测试&#34;比单元测试&#34;因为你使用了整个启动课程和所有内容,所以你不能只设置一次校长并完成。它必须在每个请求上完成,就像真正的身份验证操作一样。

最简单的方法是使用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处理程序并获得分配的主体。