在ASP.NET Core中,您可以使用Microsoft的依赖注入框架is bind "open generics"(未绑定到具体类型的泛型类型)执行以下操作之一,如下所示:
public void ConfigureServices(IServiceCollection services) {
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>))
}
您也可以使用the factory pattern to hydrate dependencies。这是一个人为的例子:
public interface IFactory<out T> {
T Provide();
}
public void ConfigureServices(IServiceCollection services) {
services.AddTransient(typeof(IFactory<>), typeof(Factory<>));
services.AddSingleton(
typeof(IRepository<Foo>),
p => p.GetRequiredService<IFactory<IRepository<Foo>>().Provide()
);
}
然而,我无法弄清楚如何将这两个概念结合在一起。看起来它会从这样的东西开始,但我需要用于水合IRepository<>
实例的具体类型。
public void ConfigureServices(IServiceCollection services) {
services.AddTransient(typeof(IFactory<>), typeof(Factory<>));
services.AddSingleton(
typeof(IRepository<>),
provider => {
// Say the IServiceProvider is trying to hydrate
// IRepository<Foo> when this lambda is invoked.
// In that case, I need access to a System.Type
// object which is IRepository<Foo>.
// i.e.: repositoryType = typeof(IRepository<Foo>);
// If I had that, I could snag the generic argument
// from IRepository<Foo> and hydrate the factory, like so:
var modelType = repositoryType.GetGenericArguments()[0];
var factoryType = typeof(IFactory<IRepository<>>).MakeGenericType(modelType);
var factory = (IFactory<object>)p.GetRequiredService(factoryType);
return factory.Provide();
}
);
}
如果我尝试将Func<IServiceProvider, object>
仿函数与开放式泛型一起使用,我会从dotnet CLI获取this ArgumentException
消息Open generic service type 'IRepository<T>' requires registering an open generic implementation type.
。它甚至没有达到lambda。
这种类型的绑定是否可以与Microsoft的依赖注入框架一起使用?
答案 0 :(得分:12)
net.core依赖项不允许您在注册开放泛型类型时提供工厂方法,但您可以通过提供将实现所请求接口的类型来解决此问题,但在内部它将充当工厂。伪装的工厂:
services.AddSingleton(typeof(IMongoCollection<>), typeof(MongoCollectionFactory<>)); //this is the important part
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>))
public class Repository : IRepository {
private readonly IMongoCollection _collection;
public Repository(IMongoCollection collection)
{
_collection = collection;
}
// .. rest of the implementation
}
//and this is important as well
public class MongoCollectionFactory<T> : IMongoCollection<T> {
private readonly _collection;
public RepositoryFactoryAdapter(IMongoDatabase database) {
// do the factory work here
_collection = database.GetCollection<T>(typeof(T).Name.ToLowerInvariant())
}
public T Find(string id)
{
return collection.Find(id);
}
// ... etc. all the remaining members of the IMongoCollection<T>,
// you can generate this easily with ReSharper, by running
// delegate implementation to a new field refactoring
}
当容器解析MongoCollectionFactory时,ti将知道T是什么类型,并将正确创建集合。然后我们将创建的集合保存在内部,并将所有调用委托给它。 (我们模仿c this=factory.Create()
,这在csharp中是不允许的。:))
更新: 正如Kristian Hellang所指出的,ASP.NET Logging使用了相同的模式
public class Logger<T> : ILogger<T>
{
private readonly ILogger _logger;
public Logger(ILoggerFactory factory)
{
_logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
}
void ILogger.Log<TState>(...)
{
_logger.Log(logLevel, eventId, state, exception, formatter);
}
}
原创讨论:
答案 1 :(得分:1)
我也不明白你的lambda表达的意思,所以我会向你解释我的做法。
我想您希望达到您分享的文章中所解释的内容
这允许我在向ASP.NET Core依赖注入系统提供依赖项之前检查传入的请求
我需要检查HTTP请求中的自定义标头,以确定哪个客户正在请求我的API。然后我可以稍后在管道中决定我的IDatabaseRepository
(链接到SQL数据库的文件系统或实体框架)的哪个实现来提供这个唯一的请求。
所以我首先编写一个中间件
public class ContextSettingsMiddleware
{
private readonly RequestDelegate _next;
public ContextSettingsMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
{
_next = next;
}
public async Task Invoke(HttpContext context, IServiceProvider serviceProvider, IHostingEnvironment env, IContextSettings contextSettings)
{
var customerName = context.Request.Headers["customer"];
var customer = SettingsProvider.Instance.Settings.Customers.FirstOrDefault(c => c.Name == customerName);
contextSettings.SetCurrentCustomer(customer);
await _next.Invoke(context);
}
}
我的SettingsProvider
只是一个为我提供相应客户对象的单身人士。
要让我们的中间件访问此ContextSettings
,我们首先需要在Startup.cs中的ConfigureServices
中注册它
var contextSettings = new ContextSettings();
services.AddSingleton<IContextSettings>(contextSettings);
在Configure
方法中,我们注册了我们的中间件
app.UseMiddleware<ContextSettingsMiddleware>();
既然我们的客户可以从其他地方访问,那就来写我们的工厂。
public class DatabaseRepositoryFactory
{
private IHostingEnvironment _env { get; set; }
public Func<IServiceProvider, IDatabaseRepository> DatabaseRepository { get; private set; }
public DatabaseRepositoryFactory(IHostingEnvironment env)
{
_env = env;
DatabaseRepository = GetDatabaseRepository;
}
private IDatabaseRepository GetDatabaseRepository(IServiceProvider serviceProvider)
{
var contextSettings = serviceProvider.GetService<IContextSettings>();
var currentCustomer = contextSettings.GetCurrentCustomer();
if(SOME CHECK)
{
var currentDatabase = currentCustomer.CurrentDatabase as FileSystemDatabase;
var databaseRepository = new FileSystemDatabaseRepository(currentDatabase.Path);
return databaseRepository;
}
else
{
var currentDatabase = currentCustomer.CurrentDatabase as EntityDatabase;
var dbContext = new CustomDbContext(currentDatabase.ConnectionString, _env.EnvironmentName);
var databaseRepository = new EntityFrameworkDatabaseRepository(dbContext);
return databaseRepository;
}
}
}
要使用serviceProvider.GetService<>()
方法,您需要在CS文件中包含以下内容
using Microsoft.Extensions.DependencyInjection;
最后,我们可以在ConfigureServices
方法
var databaseRepositoryFactory = new DatabaseRepositoryFactory(_env);
services.AddScoped<IDatabaseRepository>(databaseRepositoryFactory.DatabaseRepository);
因此,DatabaseRepository
的每个HTTP请求都可能因多个参数而异。我可以使用文件系统或SQL数据库,我可以获得与我的客户相对应的正确数据库。 (是的我每个客户有多个数据库,不要试图理解为什么)
我尽可能简化它,我的代码实际上更复杂,但你明白了(我希望)。现在您可以修改它以满足您的需求。
答案 2 :(得分:1)
我也不满意现有的解决方案。
这是使用内置容器的完整解决方案,它支持我们需要的所有内容:
IServiceProvider
。我们将注册一个我们真正想要使用的类型的代理。代理只是从预期的类型继承而来,但是通过单独注册的Options
类型来获取“困难”部分(复杂的依赖关系和配置)。
由于Options
类型是非泛型的,因此很容易照常进行自定义。
public static class RepositoryExtensions
{
/// <summary>
/// A proxy that injects data based on a registered Options type.
/// As long as we register the Options with exactly what we need, we are good to go.
/// That's easy, since the Options are non-generic!
/// </summary>
private class ProxyRepository<T> : Repository<T>
{
public ProxyRepository(Options options, ISubdependency simpleDependency)
: base(
// A simple dependency is injected to us automatically - we only need to register it
simpleDependency,
// A complex dependency comes through the non-generic, carefully registered Options type
options?.ComplexSubdependency ?? throw new ArgumentNullException(nameof(options)),
// Configuration data comes through the Options type as well
options.ConnectionString)
{
}
}
public static IServiceCollection AddRepositories(this ServiceCollection services, string connectionString)
{
// Register simple subdependencies (to be automatically resolved)
services.AddSingleton<ISubdependency, Subdependency>();
// Put all regular configuration on the Options instance
var optionObject = new Options(services)
{
ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString))
};
// Register the Options instance
// On resolution, last-minute, add the complex subdependency to the options as well (with access to the service provider)
services.AddSingleton(serviceProvider => optionObject.WithSubdependency(ResolveSubdependency(serviceProvider)));
// Register the open generic type
// All dependencies will be resolved automatically: the simple dependency, and the Options (holding everything else)
services.AddSingleton(typeof(IRepository<>), typeof(ProxyRepository<>));
return services;
// Local function that resolves the subdependency according to complex logic ;-)
ISubdependency ResolveSubdependency(IServiceProvider serviceProvider)
{
return new Subdependency();
}
}
internal sealed class Options
{
internal IServiceCollection Services { get; }
internal ISubdependency ComplexSubdependency { get; set; }
internal string ConnectionString { get; set; }
internal Options(IServiceCollection services)
{
this.Services = services ?? throw new ArgumentNullException(nameof(services));
}
/// <summary>
/// Fluently sets the given subdependency, allowing to options object to be mutated and returned as a single expression.
/// </summary>
internal Options WithSubdependency(ISubdependency subdependency)
{
this.ComplexSubdependency = subdependency ?? throw new ArgumentNullException(nameof(subdependency));
return this;
}
}
}
答案 3 :(得分:1)
请参见此issue on the dotnet (5) runtime git。 这将增加对通过工厂注册开放通用名称的支持。