使用Autofac注册中间件来解析服务的某个版本



我想解析在运行时给定名称的命名注册。

背景故事-我用多个版本公开我的端点,例如:

https://localhost/myservice/api/v1/allcities
https://localhost/myservice/api/v2/allcities
...

控制器需要根据被调用端点的版本调用不同版本的注入服务。

为了说明,我希望当我调用https://localhost/myservice/api/v1/blahblah时,它使用MyServiceV1执行,而当我调用https://localhost/myservice/api/v2/blahblah时,它则使用MyServiceV2执行

我使用的是带有Microsoft.AspNetCore.Mvc.Versioning、Autofac 6的.NET Core 3.1 API。

我可以得到通过IHttpContextAccessor调用的contextAccessor.HttpContext.GetRequestedApiVersion()版本。在这个问题中,我想集中讨论在运行时解决特定版本的服务。

我的想法是将服务注册为Named(或Keyed,无关紧要(,并使用注册中间件注册拦截解析过程并注入命名服务的正确版本。

代码:

public interface IMyService 
{ 
string GetImplementationVersion();
} 
[MyServiceVersioning("1.0")]
public class MyService1 : IMyService
{
public string GetImplementationVersion() => "Example 1.0";
}
[MyServiceVersioning("2.0")]
public class MyService2 : IMyService
{
public string GetImplementationVersion() => "Example 2.0";
}
public class MyMasterService
{
private IMyService _myService;
public MyMasterService(IMyService myService)
{
_myService = myService;
}
public string GetInjectedVersion() => _myService.GetImplementationVersion();
}

注册(为完整性编辑(

// ... 
builder.RegisterType<VersionService>().As<IVersionService>();
// next two lines registered using extension that's in the next code block example
builder.RegisterVersioned<MyServiceV1, IMyService>(); 
builder.RegisterVersioned<MyServiceV2, IMyService>();
builder.Register<MyMasterService>();

最后介绍了RegisterVersioned扩展的实现问题所在:

public static class AutofacVersioningExtensions
{
public static IRegistrationBuilder<T, ConcreteReflectionActivatorData, SingleRegistrationStyle>
RegisterVersioned<T, TInterface>(this ContainerBuilder builder) where T: class
{
var versioningAttribute = typeof(T).GetCustomAttribute<MyServiceVersionAttribute>();
if (versioningAttribute == null)
{
// no versioning exists, do it simply
return builder.RegisterType<T>().As<TInterface>();
}

return builder.RegisterType<T>().As<TInterface>().Named<TInterface>(versioningAttribute.Version).ConfigurePipeline(p =>
{
p.Use(PipelinePhase.RegistrationPipelineStart, (context, next) =>
{
var invokedVersion = context.Resolve<IVersionService>().CurrentVersion;
// HERE --> in the next line issue is that we have obvious circular resolution
// + it only resolves the last registration
// (I could do Resolve<IEnumerable<TInterface>> to get all registrations but that's an overhead that I'd like to avoid if possible).
var resolved = context.ResolveNamed<TInterface>(invokedVersion); 
if (resolved != null)
{
context.Instance = resolved;
}
else
{
next(context);
}
});
});
}
}

你有什么想法吗?我的方法是否正确?

似乎可以通过使用lambda注册来简化这一过程。

builder.RegisterType<MyServiceV1>().Keyed<IMyService>("1.0");
builder.RegisterType<MyServiceV2>().Keyed<IMyService>("2.0");
builder.Register<MyMasterService>();
builder.Register(ctx =>
{
var version = ctx.Resolve<IHttpContextAccessor>().HttpContext.GetRequestedApiVersion();
return ctx.ResolveKeyed<IMyService>(version);
}).As<IMyService>().InstancePerLifetimeScope();

这样,当您解析一个未键控的IMyService时,它将经过检测过程并返回正确的键控版本。该键控实例将在请求期间缓存。

最新更新