RequestContext.Principal的Autofac DI在单元测试中使用WebAPI2(Autofac DI for RequestContext.Principal using Web

编程入门 行业动态 更新时间:2024-10-27 20:28:25
RequestContext.Principal的Autofac DI在单元测试中使用WebAPI2(Autofac DI for RequestContext.Principal using WebAPI2 in a unit test)

我在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。

在这一点上,我已经坐了大约一天这个问题并拔出我的头发。 任何帮助,将不胜感激。 提前致谢。

I am using Autofac and OWIN in a WebAPI project which was build from scratch (as apposed to the complete WebAPI template available in VS2015). Admittedly I am new to doing it this way.

In the unit test project, I set up an OWIN Startup class at the start of unit testing:

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

The Startup class is as follows:

[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 note: Ideally I would like to set the Principle as part of the test, so as to be able to pass different users to the controller, but if I absolutely have to keep the setting of the CurrentPrincipal/User in the startup class, I can work around it.

The startup class works fine w.r.t accessing my controllers using DI, however the Principal in RequestContext.Principal is never set. It is always null. The way I intent to use the Request context is as follows:

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

I have also tried injecting the mocked principal class into the constructor of my Controller as a workaround - I used the same method as displayed in the generic method for setting up my services using DI. Again however, I only got null in my constructor.

At this point I have been sitting for about a day with this problem and pulling out my hair. Any help would be appreciated. Thanks in advance.

最满意答案

我不会用DI做这件事。 您需要在请求上下文中设置主体,而不是将主体注入构造函数。

如果是我,那就是我要做的事情:

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

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

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

最简单的方法是使用简单的委托处理程序 。

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

最后,将该处理程序添加到 Startup类中的HttpConfiguration管道

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

应该这样做。 请求现在应该通过该auth处理程序并获得分配的主体。

I'd avoid doing this with DI. You need something to set the principal in the request context, not inject the principal into a constructor.

Here's what I'd do if it was me:

First, I won't mock things that don't need mocking. Which is to say, your IIdentity implementation could actually be real objects.

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

Next, you need to run the setup on each "request" you process through your test app. I'm guessing this is more "integration test" than "unit test" since you're using a whole startup class and everything, so you can't just set the principal one time and be done. It has to be done on every request, just like a real authentication action would do.

The easiest way to do that is with 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); } }

Finally, add that handler to the HttpConfiguration pipeline in your Startup class.

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

That should do it. Requests should now go through that auth handler and get the principal assigned.

更多推荐

本文发布于:2023-04-28 00:05:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1329232.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:单元   测试中   Autofac   RequestContext   Principal

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!