单例 ASP.NET Web API 控制器中时间驱动的生命周期范围
考虑通过包装需要调用令牌的下游服务来实现某些 API 的 Web 控制器。令牌已过期,因此我需要某种时间驱动的范围,以重新获取令牌并重新创建客户端,以防令牌过期:
MyController: Controller
{
IServiceAPI _downstreamServcie;
MyController (IServiceAPI downstreamService)
{
}
}
....
builder.Register(c => {
Token token = generateToken() ..
return new ServiceAPIClient(token) ;
})
.As<IServiceAPI>()
我不想注册 MyController
由于性能问题,使用每个请求范围。
有了 Spring 背景,这种强制依赖关系在 Spring 中通过注入单例动态代理来解决,该代理将调用转发到正确的作用域对象(请求/会话/自定义)。
使用 Autofac 实现相同功能的正确方法是什么?
谢谢
[更新]
深入研究 Autofac 文档,我发现了可用于动态创建/更改范围的 IResolveMiddleware
接口:
class TokenScopeResolverMiddleware : IResolveMiddleware {
private ISharingLifetimeScope _currentTokenScope;
private ISharingLifetimeScope _prevTokenScope;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next) {
if (null == _currentTokenScope) {
lock (this) {
if (null == _currentTokenScope) {
RolloverScope(context);
}
}
}
if (!CanUseCurrentToken()) {
lock (this) {
if (!CanUseCurrentToken()) {
RolloverScope(context);
}
}
}
context.ChangeScope(_currentTokenScope);
next(context);
}
private bool CanUseCurrentToken() {
AuthenticationResult authResult = _currentTokenScope.Resolve<AuthenticationResult>();
TimeSpan expiresIn = authResult.ExpiresOn - DateTime.Now;
return expiresIn > TimeSpan.FromSeconds(20);
}
private void RolloverScope(ResolveRequestContext context) {
if (null != _prevTokenScope) {
_prevTokenScope.Dispose();
}
_prevTokenScope = _currentTokenScope; // give another `expiration time` grace period before disposing token scope
_currentTokenScope =
context.ActivationScope.RootLifetimeScope.BeginLifetimeScope("token") as ISharingLifetimeScope;
}
public PipelinePhase Phase { get; } = PipelinePhase.ScopeSelection;
}
用法:
builder.Register(c => {
AuthenticationResult result = // acquire token
return result;
})
.InstancePerMatchingLifetimeScope("token");
builder.Register(c => {
return new Client(c.Resolve<AuthenticationResult>().Token)
})
.InstancePerMatchingLifetimeScope("token");
builder.RegisterServiceMiddleware<Client>(new TokenScopeResolverMiddleware());
有更好的建议吗?
Consider the web controller that implements some API by wrapping downstream service that requires token to be called. The token has the expiration, so I'm after some kind of time-driven scope that re-acquires the token and re-creates client in case the token is expired:
MyController: Controller
{
IServiceAPI _downstreamServcie;
MyController (IServiceAPI downstreamService)
{
}
}
....
builder.Register(c => {
Token token = generateToken() ..
return new ServiceAPIClient(token) ;
})
.As<IServiceAPI>()
I don't want to register MyController
with per-request-scope because of performance issues.
Having spring background, such kind of captive
dependency is resolved in spring by injecting singleton dynamic proxy that forwards the call to the right scoped-object (request/session/custom).
What would be the right way to implement the same with Autofac?
Thanks
[UPDATE]
Digging into Autofac documentation, I've found IResolveMiddleware
interface that can be used to dynamically create/change scope :
class TokenScopeResolverMiddleware : IResolveMiddleware {
private ISharingLifetimeScope _currentTokenScope;
private ISharingLifetimeScope _prevTokenScope;
public void Execute(ResolveRequestContext context, Action<ResolveRequestContext> next) {
if (null == _currentTokenScope) {
lock (this) {
if (null == _currentTokenScope) {
RolloverScope(context);
}
}
}
if (!CanUseCurrentToken()) {
lock (this) {
if (!CanUseCurrentToken()) {
RolloverScope(context);
}
}
}
context.ChangeScope(_currentTokenScope);
next(context);
}
private bool CanUseCurrentToken() {
AuthenticationResult authResult = _currentTokenScope.Resolve<AuthenticationResult>();
TimeSpan expiresIn = authResult.ExpiresOn - DateTime.Now;
return expiresIn > TimeSpan.FromSeconds(20);
}
private void RolloverScope(ResolveRequestContext context) {
if (null != _prevTokenScope) {
_prevTokenScope.Dispose();
}
_prevTokenScope = _currentTokenScope; // give another `expiration time` grace period before disposing token scope
_currentTokenScope =
context.ActivationScope.RootLifetimeScope.BeginLifetimeScope("token") as ISharingLifetimeScope;
}
public PipelinePhase Phase { get; } = PipelinePhase.ScopeSelection;
}
Usage :
builder.Register(c => {
AuthenticationResult result = // acquire token
return result;
})
.InstancePerMatchingLifetimeScope("token");
builder.Register(c => {
return new Client(c.Resolve<AuthenticationResult>().Token)
})
.InstancePerMatchingLifetimeScope("token");
builder.RegisterServiceMiddleware<Client>(new TokenScopeResolverMiddleware());
Any better suggestions ?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为您可能正在寻找
Func
关系或类似的关系,在其中注入一个工厂,根据需要动态解析客户端。只要同步运行,您的 lambda 可以是任何东西。不要忘记 DI 更多的是关于注入依赖项(对象构造),而不是管理应用程序的状态、编排逻辑或代表您执行工厂,尽管无可否认,尝试以这些方式实现多用途非常方便。
警告 - 您可能会遇到内存泄漏问题。
如果
IClient
实现也是IDisposable
,Autofac 将保留创建的每个IClient
直到生命周期范围被释放,因为容器负责创建对象...并释放它们。如果您的控制器是单例,则意味着Func
将从根生命周期范围(容器本身)解析,这进一步意味着您无法处置捕获的IClient< /code> 实例,无需处置整个应用程序容器。
您可以使用
ExternallyOwned
禁用该功能< /a> 但你也必须自己处理东西。最好稍微放松一下,尝试少用 DI 做事,多用自己的代码做事。例如,实际上创建您自己的客户端工厂,它知道何时刷新令牌、如何构建和处置客户端等。您甚至可能想查看诸如
IHttpClientFactory
专门用于此类内容。然后,不要注入客户端,而是注入工厂并根据需要使用工厂来获取客户端实例。也就是说,不要注入Func
,而是注入IHttpClientFactory
或类似的东西,从而减少尝试强制强制依赖项运行的需要,而是使用以下方法解决挑战可能更合适的解决方案。I think you're likely looking for the
Func<T>
relationship, or something like it, where you inject a factory that dynamically resolves the client as you need it.Your lambda could be just about anything as long as it runs synchronously. Don't forget DI is more about injecting dependencies (object construction) than it is about managing your application's state, orchestrating logic, or executing factories on your behalf, though admittedly it's pretty convenient to try to multipurpose it in those ways.
A word of warning - you may run into memory leak trouble.
If the
IClient
implementation is alsoIDisposable
, Autofac is going to hold onto everyIClient
created until the lifetime scope is disposed because the container is responsible for creating objects... and disposing them. If your controller is a singleton, that means theFunc<IClient>
will be resolving from the root lifetime scope (the container itself), which further means you can't dispose the capturedIClient
instances without disposing the whole application container.You can disable that with
ExternallyOwned
but then you also will have to dispose things yourself.It may be better to unwind things just a little and try to do less in DI, more with your own code. For example, actually create your own client factory that knows when to refresh the token, how to construct and dispose of clients, etc. You may even want to look at stuff like
IHttpClientFactory
which is specifically meant for stuff like this. Then instead of injecting the client, inject the factory and use the factory to get a client instance as you need it. That is, instead of injectingFunc<IClient>
, injectIHttpClientFactory
or something similar, thus reducing the need to try to force the captive dependency to behave and instead addressing the challenge with a solution possibly more appropriate.