工厂模式与开放的generics

在ASP.NET Core中,您可以使用Microsoft的dependency injection框架执行的操作之一是绑定“open generics” (未绑定到具体类型的generics类型),如下所示:

public void ConfigureServices(IServiceCollection services) { services.AddSingleton(typeof(IRepository), typeof(Repository)) } 

您还可以使用工厂模式来水合依赖项 。 这是一个人为的例子:

 public interface IFactory { T Provide(); } public void ConfigureServices(IServiceCollection services) { services.AddTransient(typeof(IFactory), typeof(Factory)); services.AddSingleton( typeof(IRepository), p => p.GetRequiredService<IFactory<IRepository>().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 when this lambda is invoked. // In that case, I need access to a System.Type // object which is IRepository. // ie: repositoryType = typeof(IRepository); // If I had that, I could snag the generic argument // from IRepository and hydrate the factory, like so: var modelType = repositoryType.GetGenericArguments()[0]; var factoryType = typeof(IFactory<IRepository>).MakeGenericType(modelType); var factory = (IFactory)p.GetRequiredService(factoryType); return factory.Provide(); } ); } 

如果我尝试使用带有开放generics的Func functor,我会得到带有消息的ArgumentException Open generic service type 'IRepository' requires registering an open generic implementation type. 来自dotnet CLI。 它甚至没有到达lambda。

这种类型的绑定是否可以与Microsoft的dependency injection框架一起使用?

net.core依赖关系不允许您在注册开放generics类型时提供工厂方法,但是您可以通过提供将实现所请求接口的类型来解决此问题,但在内部它将充当工厂。 伪装工厂:

 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 : IMongoCollection { private readonly _collection; public RepositoryFactoryAdapter(IMongoDatabase database) { // do the factory work here _collection = database.GetCollection(typeof(T).Name.ToLowerInvariant()) } public T Find(string id) { return collection.Find(id); } // ... etc. all the remaining members of the IMongoCollection, // you can generate this easily with ReSharper, by running // delegate implementation to a new field refactoring } 

当容器解析MongoCollectionFactory时,ti将知道T是什么类型并将正确创建集合。 然后我们将创建的集合保存在内部,并将所有调用委托给它。 (我们模仿this=factory.Create() ,这在csharp中是不允许的。:))

更新:正如Kristian Hellang所指出的,ASP.NET Logging使用了相同的模式

 public class Logger : ILogger { private readonly ILogger _logger; public Logger(ILoggerFactory factory) { _logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T))); } void ILogger.Log(...) { _logger.Log(logLevel, eventId, state, exception, formatter); } } 

https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/LoggerOfT.cs#L29

原创讨论在这里:

https://twitter.com/khellang/status/839120286222012416

我也不明白你的lambda表达的意思所以我会向你解释我的做法。

我想您希望达到您分享的文章中所解释的内容

这允许我在向ASP.NET Coredependency injection系统提供依赖项之前检查传入的请求

我需要检查HTTP请求中的自定义标头,以确定哪个客户正在请求我的API。 然后我可以稍后在管道中决定我的IDatabaseRepository (链接到SQL数据库的文件系统或entity framework)的哪个实现来提供这个唯一的请求。

所以我从编写中间件开始

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

Configure方法中,我们注册了我们的中间件

 app.UseMiddleware(); 

现在我们的客户可以从其他地方访问,让我们来写我们的工厂。

 public class DatabaseRepositoryFactory { private IHostingEnvironment _env { get; set; } public Func DatabaseRepository { get; private set; } public DatabaseRepositoryFactory(IHostingEnvironment env) { _env = env; DatabaseRepository = GetDatabaseRepository; } private IDatabaseRepository GetDatabaseRepository(IServiceProvider serviceProvider) { var contextSettings = serviceProvider.GetService(); 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方法中使用Factory

 var databaseRepositoryFactory = new DatabaseRepositoryFactory(_env); services.AddScoped(databaseRepositoryFactory.DatabaseRepository); 

因此,根据几个参数,我的DatabaseRepository可能会有不同的每个HTTP请求。 我可以使用文件系统或SQL数据库,我可以获得与我的客户相对应的正确数据库。 (是的我每个客户有多个数据库,不要试图了解原因)

我尽可能简化它,我的代码实际上更复杂,但你明白了(我希望)。 现在您可以修改它以满足您的需求。