简体   繁体   English

使用 Autofac Registration 中间件解析服务的一个版本

[英]Using Autofac Registration middleware to resolve a version of the service

I want to resolve the Named registration where the name is given at runtime.我想解决在运行时给出名称的命名注册。

Background story - I am exposing my endpoints with multiple versions, eg:背景故事 - 我用多个版本公开我的端点,例如:

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

The controller needs to invoke a different version of the injected service based on the version of the invoked endpoint. controller 需要根据调用端点的版本调用不同版本的注入服务。

To illustrate, I would expect when I invoke https://localhost/myservice/api/v1/blahblah it executes using MyServiceV1 and when I invoke https://localhost/myservice/api/v2/blahblah it executes using MyServiceV2为了说明,我希望当我调用https://localhost/myservice/api/v1/blahblah它使用MyServiceV1执行,当我调用https://localhost/myservice/api/v2/blahblah它使用MyServiceV2执行

I'm using .NET Core 3.1 API with Microsoft.AspNetCore.Mvc.Versioning , Autofac 6.我正在使用 .NET Core 3.1 API 和Microsoft.AspNetCore.Mvc.Versioning ,Autofac 6。

I can get what version was invoked via IHttpContextAccessor as contextAccessor.HttpContext.GetRequestedApiVersion() .我可以得到通过IHttpContextAccessor调用的版本作为contextAccessor.HttpContext.GetRequestedApiVersion() In this question I want to focus on resolving a specific version of the service at runtime.在这个问题中,我想专注于在运行时解决特定版本的服务。

My idea was to register the service as Named (or Keyed, doesn't matter) and using the Registration middleware registration intercept the resolution process and inject the proper version of the Named service.我的想法是将服务注册为Named (或键控,没关系)并使用注册中间件注册拦截解析过程并注入命名服务的正确版本。

Code:代码:

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

The registrations ( Edited for completeness)注册(为完整性而编辑

// ... 
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>();

And finally the implementation of RegisterVersioned extension where the question lies:最后是问题所在的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);
                    }
                });
            });
        }
}

Do you have any ideas?你有什么想法? Is my approach even on the right path?我的方法是否正确?

It seems like you could make this easier by using a lambda registration.您似乎可以通过使用 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();

This way, when you resolve an un-keyed IMyService , it'll go through the detection process and return the right keyed version.这样,当您解析未加密的IMyService时,它将通过检测过程 go 并返回正确的加密版本。 That keyed instance will be cached for the duration of the request.该键控实例将在请求期间被缓存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM