如何使用开放泛型类型注入集合以使用 Autofac 服务
How to inject collection with open generic types to service with Autofac
我已尽力解释我公认的复杂问题。让我知道是否有任何我可以补充说明的内容。
简要背景
我有一个 DbWrapperCollection
我用来存储 DbWrapper<TInput. TOutput>
(因为 TInput
和 TOutput
会有所不同,这个集合实际上只是一个非通用列表container” 包含作为对象的泛型以及作为 System.Types 的输入和输出 – 请参阅下面的实现)
另一方面,我有数量可变的服务,所有服务都有自己的 IDbWrapperCollection
,我想在启动时用 autofac 注入这些服务。
基本上我想做的是:
builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
.Named<string>("appleService");
builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
.As<IDbMapperService>();
我的问题
正如你在上面看到的,我在调用 ResolveNamed()
时特意省略了预期的类型参数。那是因为我是 autofac 的新手(在某种程度上,泛型),我特别不知道是否有任何策略来注入开放泛型列表 DbWrappers
并延迟关闭我的泛型类型。
我会在下面解释我研究过哪些策略来处理这个问题,以及我目前的实施情况
我自己的研究
在我看来,我可以为我的包装器创建一个非泛型基类并将它们保存为该基类,将解析原始泛型类型的责任委托给该基类,或者放弃我的包装器集合想法支持我的服务构造函数上的特定参数(无聊——并且与我的复合式实现不兼容)。
随着复合模式的流行,我想我不是第一个想使用 DI 和 IoC 的具有“通用叶子”的复合模式解决方案的人。
我打算这样使用我的 fruitService:
myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);
服务在它的 DbMapperCollection 中查找,找到具有提供的类型参数的映射器,并调用它的 Save() 实现;
到目前为止的实施
对于那些好奇的人,这是我的实现:
DbWrappers:
class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable
服务:
public abstract class DbMapperService : IDbMapperService
{
public IWrapperCollection Wrappers { get; set; }
protected BestandService(IWrapperCollection wrappers)
{
Wrappers = wrappers;
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
return Wrappers.GetWrapper<TInput, TResult>();
}
}
我的 WrapperCollection 助手 类:
public struct WrapperKey
{
public static WrapperKey NewWrapperKey <TInput, TResult>()
{
return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
}
public Type InputType { get; set; }
public Type ResultType { get; set; }
}
public struct WrapperContainer
{
public WrapperContainer(object wrapper) : this()
{
Wrapper= wrapper;
}
public object Wrapper{ get; set; }
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
return Wrapper as DbWrapper<TInput, TResult>;
}
}
还有我的 WrapperCollection:
public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
IDbWrapperCollection
{
public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
{
Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
var key = WrapperKey.NewWrapperKey<TInput, TResult>();
return this[key].GetWrapper<TInput, TResult>();
}
}
我看过的问题不走运
我看过的一些问题,none 其中似乎与我的案例相关(虽然我的问题可能可以通过通用委托来解决,但我认为这不是我的最佳解决方案问题。
- 使用 Autofac 注入通用类型参数
- Autofac。如何在构造函数中注入一个开放的通用委托
- 如何使用 Autofac 注入泛型工厂
- 带有嵌套开放泛型的 Autofac
我不认为你能做你想做的事。抱歉,可能不是你想要的答案。我将向您展示原因,也许还有一些解决方法,但是拥有一个任意的封闭泛型集合,在解决之前不会关闭并不是真正的事情。
让我们暂时忽略 DI,只考虑 FruitService
,我在问题中没有看到它,但我们在此处的用法中看到了它:
builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
.As<IDbMapperService>();
请注意,我们可以看到 FruitService
实现了 IDbMapperService
,因为它已注册为该接口。
此外,我们可以看到 FruitService
看起来应该需要某种东西的集合,因为在注册示例中有两个名称相同的东西。
builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.Named<string>("fruitService");
我注意到它们都实现了不同的泛型类型。我必须根据问题的其余部分假设它们没有共同的基础 class.
为了使其更具体并通过 Autofac 部分,我认为这与更大的问题无关,让我们这样考虑:
var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);
让我们假设 CreateWrapper
和 CreateHandler
都接受一个字符串,并神奇地创建适当的 wrapper/handler 类型。不管它如何发生。
这里有两个密切相关的问题需要考虑:
FruitService
构造函数中参数的类型是什么?
- 你对
CreateWrapper("appleService")
和 CreateHandler("appleService")
比 return 有什么期望?
我这里基本上只有两个选项。
选项 1:使用 object
.
如果没有共同的基础class,那么一切都必须是object
。
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public object GetWrapper<TInput, TResult>()
{
object foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
}
}
尚不清楚 DbWrapper<TInput, TResult>
是否可以转换为 IUcHandler<TInput, TResult>
,因此您甚至不能依赖它。没有共同点。
但是假设有共同的基础class。
选项 2:使用公共基数 class
好像已经有了DbWrapper<TInput, TResult>
的概念。重要的是要注意,即使您定义了泛型,一旦关闭它,它们就是两种不同的类型。 DbWrapper<AppleDto, SavedAppleDbEntity>
无法转换为 DbWrapper<OrangeDto, SavedOrangeDbEntity>
。泛型更像 "class templates" 而不是基础 classes。它们不是一回事。
例如,您不能这样做:
var collection = new DbWrapper<,>[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
但是,如果您有通用接口或基础 class,您可以...
var collection = new IDbWrapper[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
但这意味着您可以切换到那个,表面上使用通用界面。
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public IDbWrapper GetWrapper<TInput, TResult>()
{
IDbWrapper foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
// IDbWrapper could expose those `TInput` and `TResult`
// types as properties on the interface, so the reflection
// could be super simple and way more straight LINQ.
}
}
您的消费代码只需 IDbWrapper
并调用非泛型方法即可完成任务。
正在将其带回 Autofac...
记得我提到过关键是弄清楚 Create
方法应该 return;或者 FruitService
构造函数期望什么?那。毫无疑问。
您可以将所有内容注册为键控对象。
builder.RegisterType<SaveApplDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<object>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
.As<IDbMapperService>();
Autofac 中的 Resolve
操作 是我示例中的创建方法 。那里没有魔法;它只是在创建对象。您仍然需要知道您希望它提供什么类型。
或者您可以使用共同的基数 class。
builder.RegisterType<SaveApplDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<IDbWrapper>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
.As<IDbMapperService>();
如果您不介意将 DI 系统混入 FruitService
,您可以这样做:
public class FruitService
{
private readonly ILifetimeScope _scope;
public FruitService(ILifetimeScope scope)
{
this._scope = scope;
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
var type = typeof(DbWrapper<TInput, TResult>);
var wrapper = this._lifetimeScope.Resolve(type);
return wrapper;
}
}
你必须在没有命名和 As
一个 DbWrapper
的情况下注册东西,但如果一切都基于它,它会起作用。
builder.RegisterType<SaveApplDbWrapper>()
.As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
.As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
.As<DbWrapper<MelonDto, MelonDbEntity>>()
.As<IUcHandler<MelonDto, MelonDbEntity>>();
builder.RegisterType<FruitService>()
.As<IDbMapperService>();
当您解析 IDbMapperService
时,FruitService
构造函数将获得对解析它的生命周期范围的引用。所有包装器都将从同一范围解析。
人们通常不喜欢像这样将 IoC 引用混合到他们的代码中,但这是我能看到的唯一方法,您可以摆脱反射或上下投射的麻烦。
祝你好运!
我已尽力解释我公认的复杂问题。让我知道是否有任何我可以补充说明的内容。
简要背景
我有一个 DbWrapperCollection
我用来存储 DbWrapper<TInput. TOutput>
(因为 TInput
和 TOutput
会有所不同,这个集合实际上只是一个非通用列表container” 包含作为对象的泛型以及作为 System.Types 的输入和输出 – 请参阅下面的实现)
另一方面,我有数量可变的服务,所有服务都有自己的 IDbWrapperCollection
,我想在启动时用 autofac 注入这些服务。
基本上我想做的是:
builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveMelon>().As<IUcHandler<MelonDto, MelonDbEntity>>()
.Named<string>("appleService");
builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
.As<IDbMapperService>();
我的问题
正如你在上面看到的,我在调用 ResolveNamed()
时特意省略了预期的类型参数。那是因为我是 autofac 的新手(在某种程度上,泛型),我特别不知道是否有任何策略来注入开放泛型列表 DbWrappers
并延迟关闭我的泛型类型。
我会在下面解释我研究过哪些策略来处理这个问题,以及我目前的实施情况
我自己的研究
在我看来,我可以为我的包装器创建一个非泛型基类并将它们保存为该基类,将解析原始泛型类型的责任委托给该基类,或者放弃我的包装器集合想法支持我的服务构造函数上的特定参数(无聊——并且与我的复合式实现不兼容)。
随着复合模式的流行,我想我不是第一个想使用 DI 和 IoC 的具有“通用叶子”的复合模式解决方案的人。
我打算这样使用我的 fruitService:
myFruitService.GetDbMapper<MyFruitDto, DbEntityForThatSaidFruit(myOrange);
服务在它的 DbMapperCollection 中查找,找到具有提供的类型参数的映射器,并调用它的 Save() 实现;
到目前为止的实施
对于那些好奇的人,这是我的实现:
DbWrappers:
class SaveApplebWrapper : DbWrapper<TInput, TOutput>
// and plenty more wrapppers for any fruit imaginable
服务:
public abstract class DbMapperService : IDbMapperService
{
public IWrapperCollection Wrappers { get; set; }
protected BestandService(IWrapperCollection wrappers)
{
Wrappers = wrappers;
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
return Wrappers.GetWrapper<TInput, TResult>();
}
}
我的 WrapperCollection 助手 类:
public struct WrapperKey
{
public static WrapperKey NewWrapperKey <TInput, TResult>()
{
return new WrapperKey { InputType = typeof(TInput), ResultType = typeof(TResult) };
}
public Type InputType { get; set; }
public Type ResultType { get; set; }
}
public struct WrapperContainer
{
public WrapperContainer(object wrapper) : this()
{
Wrapper= wrapper;
}
public object Wrapper{ get; set; }
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
return Wrapper as DbWrapper<TInput, TResult>;
}
}
还有我的 WrapperCollection:
public class UcWrapperCollection : Dictionary<WrapperKey, WrapperContainer>,
IDbWrapperCollection
{
public void AddWrapper<TInput, TResult>(UcHandler<TInput, TResult> handler)
{
Add(WrapperKey.NewWrapperKey<TInput, TResult>(), new WrapperContainer(handler));
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
var key = WrapperKey.NewWrapperKey<TInput, TResult>();
return this[key].GetWrapper<TInput, TResult>();
}
}
我看过的问题不走运
我看过的一些问题,none 其中似乎与我的案例相关(虽然我的问题可能可以通过通用委托来解决,但我认为这不是我的最佳解决方案问题。
- 使用 Autofac 注入通用类型参数
- Autofac。如何在构造函数中注入一个开放的通用委托
- 如何使用 Autofac 注入泛型工厂
- 带有嵌套开放泛型的 Autofac
我不认为你能做你想做的事。抱歉,可能不是你想要的答案。我将向您展示原因,也许还有一些解决方法,但是拥有一个任意的封闭泛型集合,在解决之前不会关闭并不是真正的事情。
让我们暂时忽略 DI,只考虑 FruitService
,我在问题中没有看到它,但我们在此处的用法中看到了它:
builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
.As<IDbMapperService>();
请注意,我们可以看到 FruitService
实现了 IDbMapperService
,因为它已注册为该接口。
此外,我们可以看到 FruitService
看起来应该需要某种东西的集合,因为在注册示例中有两个名称相同的东西。
builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.Named<string>("fruitService");
我注意到它们都实现了不同的泛型类型。我必须根据问题的其余部分假设它们没有共同的基础 class.
为了使其更具体并通过 Autofac 部分,我认为这与更大的问题无关,让我们这样考虑:
var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);
让我们假设 CreateWrapper
和 CreateHandler
都接受一个字符串,并神奇地创建适当的 wrapper/handler 类型。不管它如何发生。
这里有两个密切相关的问题需要考虑:
FruitService
构造函数中参数的类型是什么?- 你对
CreateWrapper("appleService")
和CreateHandler("appleService")
比 return 有什么期望?
我这里基本上只有两个选项。
选项 1:使用 object
.
如果没有共同的基础class,那么一切都必须是object
。
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public object GetWrapper<TInput, TResult>()
{
object foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
}
}
尚不清楚 DbWrapper<TInput, TResult>
是否可以转换为 IUcHandler<TInput, TResult>
,因此您甚至不能依赖它。没有共同点。
但是假设有共同的基础class。
选项 2:使用公共基数 class
好像已经有了DbWrapper<TInput, TResult>
的概念。重要的是要注意,即使您定义了泛型,一旦关闭它,它们就是两种不同的类型。 DbWrapper<AppleDto, SavedAppleDbEntity>
无法转换为 DbWrapper<OrangeDto, SavedOrangeDbEntity>
。泛型更像 "class templates" 而不是基础 classes。它们不是一回事。
例如,您不能这样做:
var collection = new DbWrapper<,>[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
但是,如果您有通用接口或基础 class,您可以...
var collection = new IDbWrapper[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
但这意味着您可以切换到那个,表面上使用通用界面。
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public IDbWrapper GetWrapper<TInput, TResult>()
{
IDbWrapper foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
// IDbWrapper could expose those `TInput` and `TResult`
// types as properties on the interface, so the reflection
// could be super simple and way more straight LINQ.
}
}
您的消费代码只需 IDbWrapper
并调用非泛型方法即可完成任务。
正在将其带回 Autofac...
记得我提到过关键是弄清楚 Create
方法应该 return;或者 FruitService
构造函数期望什么?那。毫无疑问。
您可以将所有内容注册为键控对象。
builder.RegisterType<SaveApplDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<object>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
.As<IDbMapperService>();
Autofac 中的 Resolve
操作 是我示例中的创建方法 。那里没有魔法;它只是在创建对象。您仍然需要知道您希望它提供什么类型。
或者您可以使用共同的基数 class。
builder.RegisterType<SaveApplDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<IDbWrapper>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
.As<IDbMapperService>();
如果您不介意将 DI 系统混入 FruitService
,您可以这样做:
public class FruitService
{
private readonly ILifetimeScope _scope;
public FruitService(ILifetimeScope scope)
{
this._scope = scope;
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
var type = typeof(DbWrapper<TInput, TResult>);
var wrapper = this._lifetimeScope.Resolve(type);
return wrapper;
}
}
你必须在没有命名和 As
一个 DbWrapper
的情况下注册东西,但如果一切都基于它,它会起作用。
builder.RegisterType<SaveApplDbWrapper>()
.As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
.As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
.As<DbWrapper<MelonDto, MelonDbEntity>>()
.As<IUcHandler<MelonDto, MelonDbEntity>>();
builder.RegisterType<FruitService>()
.As<IDbMapperService>();
当您解析 IDbMapperService
时,FruitService
构造函数将获得对解析它的生命周期范围的引用。所有包装器都将从同一范围解析。
人们通常不喜欢像这样将 IoC 引用混合到他们的代码中,但这是我能看到的唯一方法,您可以摆脱反射或上下投射的麻烦。
祝你好运!