如何使用自定义工厂方法注册开放通用? [英] How to register open generic with custom factory method?
问题描述
TL; DR :我可以使用Autofac创建一个通用工厂,以便我可以注入IProduct<TModel>
而不是在需要的任何地方从IFactory
解析它吗?有没有办法将从工厂解决"任务移至合成根目录?
TL;DR: Can I create a generic factory with Autofac, so that I can inject IProduct<TModel>
rather than resolving it from IFactory
anywhere I need it? Is there a way to move the resolving-from-factory task to the composition root?
因此,我正在使用第三方库,该库公开了一些通过工厂创建的通用接口.出于演示目的,我们假设以下代码是该库:
So I'm using a third party library, which exposes some generic interfaces which are created through a factory. For demonstration purposes, we'll assume that the following code is the library:
第三方库模型:
public interface IFactory
{
IProduct<TModel> CreateProduct<TModel>(string identifier);
}
internal class Factory : IFactory
{
private readonly string _privateData = "somevalues";
public IProduct<TModel> CreateProduct<TModel>(string identifier)
{
return new Product<TModel>(_privateData, identifier);
}
}
public interface IProduct<TModel>
{
void DoSomething();
}
internal sealed class Product<TModel>: IProduct<TModel>
{
private readonly string _privateData;
private readonly string _identifier;
public Product(string privateData, string identifier)
{
_privateData = privateData;
_identifier = identifier;
}
public void DoSomething()
{
System.Diagnostics.Debug.WriteLine($"{_privateData} + {_identifier}");
}
}
我的代码:
还有我的TModel
:
public class Shoe { }
现在,让我们假设我要在MyService
中使用IProduct<Shoe>
.我需要在那里解决它:
Now, let's assume that I want an IProduct<Shoe>
in MyService
. I need to resolve it there:
public class MyService
{
public MyService(IFactory factory)
{
IProduct<Shoe> shoeProduct = factory.CreateProduct<Shoe>("theshoe");
}
}
但是如果我能像这样声明鞋子,那会不会更好:
But wouldn't it be nicer if I could declare shoe like this:
public class ProductIdentifierAttribute : System.Attribute
{
public string Identifier { get; }
public ProductIdentifierAttribute(string identifier)
{
this.Identifier = identifier;
}
}
[ProductIdentifier("theshoe")]
public class Shoe { }
然后像这样注入它?:
public class MyService
{
public MyService(IProduct<Shoe> shoeProduct) { }
}
借助Autofac,我可以使用工厂来创建常规的非泛型类,例如:
With Autofac I can use a factory to create regular non-generic classes like so:
builder
.Register<INonGenericProduct>(context =>
{
var factory = context.Resolve<INonGenericFactory>();
return factory.CreateProduct("bob");
})
.AsImplementedInterfaces();
但这不适用于泛型类.我必须使用RegisterGeneric
.不幸的是,您传递给RegisterGeneric
的类型是开放的 concrete 类型,而不是开放的 interface 类型.我提出了两种解决方法.
But this doesn't work for generic classes. I have to use RegisterGeneric
. Unfortunately, the type you pass to RegisterGeneric
is the open concrete type, rather than the open interface type. I've come up with two workarounds.
解决方法1 :反映IFactory
以提取_privateData
(在真实库中,这要复杂一些,涉及访问其他internal
方法和类等),然后提供作为OnPreparing
中的Autofac参数:
Workaround 1: Reflect IFactory
to extract _privateData
(in the real library this is somewhat more complicated, and involves accessing other internal
methods and classes, etc.) and then supply that as Autofac parameters in OnPreparing
:
Type factoryType = typeof(Factory);
Type factoryField = factoryType.GetField("_privateData", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Getfield);
Type productType = typeof(Product); // this is `internal` in the third party library, so I have to look it up from the assembly in reality
builder
.RegisterGeneric(productType)
.OnPreparing(preparing =>
{
var factory = preparing.Context.Resolve<IFactory>();
var privateFieldValue = factoryField.GetValue(factory);
var closedProductType = preparing.Component.Activator.LimitType;
var productModel = closedProductType.GetGenericArguments().Single();
var productIdentifier = productModel.GetGenericArgument<ProductIdentifierAttribute>().Identifier;
preparing.Parameters = new List<Parameter>()
{
new PositionalParameter(0, privateFieldValue),
new PositionalParameter(0, productIdentifier)
};
})
.AsImplementedInterfaces();
但是显然,由于许多原因,这是一个糟糕的解决方案,最重要的是,它很容易受到库中内部更改的影响.
But clearly this is a terrible solution for numerous reasons, the most significant being that it's vulnerable to internal changes within the library.
解决方法2 :创建一个虚拟类型并将其替换为OnActivating
:
Workaround 2: Create a dummy type and substitute it in OnActivating
:
public class DummyProduct<TModel> : IProduct<TModel>
{
public void DoSomething() => throw new NotImplementedException("");
}
因此,我们将其注册为开放泛型,并在注入之前替换其值:
So, we register that as the open generic, and substitute its value before injecting it:
MethodInfo openProductBuilder = this.GetType().GetMethod(nameof(CreateProduct), BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod);
builder
.RegisterGeneric(typeof(DummyProduct<>))
.OnActivating(activating =>
{
var productModel = activating.Instance.GetType().GetGenericArguments().First();
var productIdentifier = productModel.GetGenericArgument<ProductIdentifierAttribute>().Identifier;
var factory = activating.Context.Resolve<IFactory>();
var closedProductBuilder = openProductBuilder.MakeGenericMethod(productModel);
object productObject = closedProductBuilder.Invoke(this, new object[] { factory, productIdentifier });
handler.ReplaceInstance(productObject);
})
.AsImplementedInterfaces();
,我们有一个辅助方法,因此我们仅依赖于此Mongo模块类中的反映方法 :
and we have a helper method so that we're only reliant on reflecting methods in this Mongo module class:
private IProduct<TModel> CreateProduct<TModel>(IFactory factory, string identifier)
{
return factory.CreateProduct<TModel>(identifier);
}
现在,显然,这比第一种方法更好,并且不需要过多的反射.不幸的是,每当我们想要一个真实的对象时,它的确涉及创建一个虚拟对象.糟透了!
Now, clearly this is better than the first method, and doesn't rely on too much reflection. Unfortunately, it does involve creating a dummy object each time we want the real one. That sucks!
问题:还有其他方法可以使用Autofac做到这一点吗?我可以以某种方式创建Autofac可以使用的通用工厂方法吗?我的主要目标是删除创建虚拟类型的步骤,然后直接跳转到调用CreateProduct
代码.
Question: Is there another way to do this using Autofac? Can I somehow create a generic factory method that Autofac can use? My main goal is to cut out the creating the dummy type, and skip straight to calling the CreateProduct
code.
注意事项:我已经删去了很多错误检查等,通常我会尽力使这个问题尽可能短,同时仍然充分说明问题和当前的解决方案.
Notes: I've cut out a fair bit of error checking, etc. that I would normally do to make this question as short as possible whilst still adequately demonstrating the problem and my current solutions.
推荐答案
如果工厂中没有非通用的Create
方法,则需要调用MakeGenericMethod
.
If there is no non generic Create
method in your factory you will need a call to the MakeGenericMethod
.
您可以使用IRegistrationSource
组件代替OnActivating
事件,该组件的作用与解决方法2相同
Instead of OnActivating
event you can use a IRegistrationSource
component that will do the same as in your workaround 2
internal class FactoryRegistrationSource : IRegistrationSource
{
private static MethodInfo openProductBuilder = typeof(Factory).GetMethod(nameof(Factory.CreateProduct));
public Boolean IsAdapterForIndividualComponents => false;
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
IServiceWithType typedService = service as IServiceWithType;
if (typedService != null && typedService.ServiceType.IsClosedTypeOf(typeof(IProduct<>)))
{
IComponentRegistration registration = RegistrationBuilder.ForDelegate(typedService.ServiceType, (c, p) =>
{
IFactory factory = c.Resolve<IFactory>();
Type productModel = typedService.ServiceType.GetGenericArguments().First();
String productIdentifier = productModel.GetCustomAttribute<ProductIdentifierAttribute>()?.Identifier;
MethodInfo closedProductBuilder = openProductBuilder.MakeGenericMethod(productModel);
Object productObject = closedProductBuilder.Invoke(factory, new object[] { productIdentifier });
return productObject;
}).As(service).CreateRegistration();
yield return registration;
}
yield break;
}
}
这篇关于如何使用自定义工厂方法注册开放通用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!