如何通过依赖注入传递Web API请求?
本文关键字:Web API 请求 何通过 依赖 注入 | 更新日期: 2023-09-27 18:09:08
我有一个Web API应用程序,其中控制器有服务/存储库等注入到它通过依赖注入(Unity)。让我们假设我有一个IStuffService
,它需要当前请求的IPrincipal
(或它周围的包装器)。
Web API的问题似乎是当前请求/用户的唯一可靠来源是ApiController
的实例上的Request
属性。由于Web API的同步特性,任何静态(无论是HttpContext.Current
、CallContext.Get/SetData
还是Thread.Get/SetData
)都不能保证在同一个线程上。
我如何可靠地确保特定于请求的上下文通过依赖关系传递,更重要的是,操作在整个操作过程中保持正确的IPrincipal
?
两个选择:
- 每个需要
IPrincipal
的方法都有它作为方法的参数-这是最可靠的方式,但它也要求我在每个方法签名中都有这个东西 - 将IPrincipal注入到服务的元素中,在每个请求上都启动一个对象图的新实例,使用Unity中的DependencyOverride:
container.Resolve(opType, new DependencyOverride(typeof(IPrincipal), principal))
选项2意味着我的方法签名是干净的,但它也意味着我需要确保所有依赖都使用TransientLifetimeManager,而不是Singleton或Per-Thread。
有比我看不到更好的解决办法吗?
的评论:
@MichaelStum,我相信HttpContext。用户应该正确流动跨async/await(在同一个HTTP请求中)。不是给你的吗?- Noseratio 17 hours ago@Noseratio参见其他答案-在。net 4.0中,它被绑定到当前线程没有得到适当的维护。在第4.5章中,这可能会被修复。也就是说,HttpContext。现在还不是在Web API中是合适的,因为在自托管的API中没有HttpContext.Current .
老实说,在ASP中没有适当的支持async/await
。无论如何。NET 4.0(您可能可以使用Microsoft.Bcl.Async
作为语言支持,但没有ASP. NET。. NET运行时支持,因此您必须求助于AsyncManager
来实现TAP模式)。
也就是说,我99%肯定Thread.CurrentPrincipal
仍然会在ASP中正确地流过await
延续。NET 4.0。这是因为它是作为ExecutionContext
流的一部分流动的,而不是通过同步上下文。至于HtttContext.Current.User
,我不确定它是否会在ASP中正确流动。. NET 4.0(尽管在ASP中确实如此)。NET 4.5)。
我重新阅读了你的问题,但可以找到一个关于Thread.CurrentPrincipal
没有正确流动的明确抱怨。您是否在现有代码中遇到此问题(如果是,它可能是ASP.NET中的错误)?
下面是一些相关的问题,Stephen Cleary给出了一些深刻的回答:
-
理解c# 5中async/await的上下文
-
为什么是' await Task.Yield() ';要求螺纹。CurrentPrincipal是否正确运行?
-
使用ASP。. NET Web API,我的ExecutionContext没有在异步操作中流动
Scott Hanselman的这篇博文也是相关的,尽管他讲的是WebForms:
- System.Threading.Thread。CurrentPrincipal vs. System.Web.HttpContext.Current.User或者为什么FormsAuthentication可以是微妙的
如果您担心自托管场景,我相信Thread.CurrentPrincipal
仍然会在那里正确地流动(一旦设置为正确的标识)。如果您想要流动任何其他属性(除了那些使用ExecutionContext
自动流动的属性),您可以推出自己的同步上下文。另一个选择(在我看来不是很好)是使用自定义侍者。
最后,如果你面临这样一种情况,你实际上需要跨await
延续的线程亲和性(很像在客户端UI应用程序中),你也有这样的选择(再次,使用自定义同步上下文):
- 如何在ASP中使用非线程安全的async/await api和模式。. NET Web API?
最终的答案是我们的IoC容器需要改变以更好地支持async/await。
背景: async/await的行为在。net 4和。net 4.5之间发生了变化。在。net 4.5中引入了SynchronizationContext
,它将正确地恢复HttpContext.Current
(参见http://msdn.microsoft.com/en-us/magazine/gg598924.aspx)。然而,使用.ConfigureAwait(false)
通常是最佳实践(参见"配置上下文")。在http://msdn.microsoft.com/en-us/magazine/jj991977.aspx)中,并且特别要求不保留上下文。在这种情况下,你仍然会遇到你所描述的问题。
答:
我在自己的代码中能够想出的最好的答案是确保在web请求的早期请求来自HttpContext.Current
(在您的情况下是IPrincipal
)的依赖项,以便将其加载到容器中。
我没有使用Unity的任何经验,但在Ninject中,这看起来像:
kernal.Bind<IPrincipal>().ToMethod(c => HttpContext.Current.User).InRequestScope();
那么我一定要在丢失上下文之前在web请求的早期加载IPrincipal
。要么在BeginRequest中,要么作为控制器的依赖项。这将导致IPrincipal
被加载到此请求的容器中。
注意:在某些情况下,这可能无法工作。我不知道Unity是否有这个问题,但我知道Ninject有。它实际上使用了HttpContext。当前,以确定活动的请求。因此,如果您稍后尝试从容器解析某些内容,例如服务定位器或工厂,则可能无法解析。
我知道这是一个老问题,我用选项一(在问题中)工作,它有效。
更新:我删除了我的答案,因为我意识到我已经发布了一些不起作用的东西。不便之处,敬请原谅。
我认为你不应该这样做。你的服务是一个比你的Api控制器低层。你的服务不应该依赖于任何与上层相关的类,否则你的服务将无法被重用,例如:当你需要在现有服务之上构建一个win forms应用程序时。如何可靠地确保传递特定于请求的上下文通过依赖关系,更重要的是,操作保留了依赖关系在整个操作过程中使用正确的IPrincipal ?
IPrincipal
不适合注入到我们的服务中,因为是与web应用相关的。当我们将此信息传递给较低层(服务)时,我们应该传递我们的中性类或者只是一个userId来将我们的服务与使用它的应用程序解耦。
你应该为用户定义你自己的类和任何与请求相关的在我们的服务层中使用,因为它更多的是与域相关的。有了这个,你的服务层是应用层(web, win forms, console…)不可知的:
public class AppPrincipal : IAppPrincipal
{
public int UserId { get; set; }
public string Role { get; set; }
//other properties
public AppPrincipal() {
}
public AppPrincipal(int userId, string role):this() {
UserId = userId;
Role = role;
}
}
然后你可以在你的web应用程序中注册IAppPrincipal
作为每个请求范围,并使用IPrincipal
填充所有属性。它将在任何await/async
调用之前初始化整个对象图的IAppPrincipal
。Unity示例代码:
public void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IAppPrincipal>(
new PerRequestLifetimeManager(),
new InjectionFactory(c => CreateAppPrincipal()));
}
public IAppPrincipal CreateAppPrincipal()
{
var principal = new AppPrincipal();
principal.UserId = //Retrieve userId from your IPrincipal (HttpContext.Current.User)
principal.Role = //Retrieve role from your IPrincipal (HttpContext.Current.User)
return principal;
}
这里的关键是我们已经将服务层与web分离了。如果您需要重用您的服务层来构建windows窗体或控制台应用程序,您可以将IAppPrincipal
注册为单例并以不同的方式填充它。
我们不需要处理与平台相关的问题,如async/await