2016-08-19 132 views
15

在ASP.NET核心,你可以用微軟的依賴注入框架做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>函子以開放通用的,我得到this ArgumentException從DOTNET CLI消息Open generic service type 'IRepository<T>' requires registering an open generic implementation type.。它甚至沒有達到拉姆達。

這種類型的綁定可能與微軟的依賴注入框架?

+0

registerin的優點是什麼g解析解決所需服務的工廠的lambda表達式? – Steven

+0

好問題。它改變了條件水合的複雜性。你不需要一個明確的工廠,因爲lambda作爲一個(它的變量甚至稱爲「implementationFactory」),但是如果你需要幾個服務來決定你想要保存什麼樣的實例,你將會擁有一個複雜且難以測試的lambda。該博客文章中,我上面鏈接有一個很好的例子:http://dotnetliberty.com/index.php/2016/05/09/asp-net-core-factory-pattern-dependency-injection/ – Technetium

+0

你有沒有找到一個很好的爲此回答?我有同樣的問題,但這裏沒有一個答案似乎是解決問題的好方法 –

回答

1

我也不明白你的lambda表達式的意思,所以我會向你解釋我的做法。

我想你的願望是要達到什麼是你共享

文章這讓我供應的依賴到ASP.NET核心的依賴注入系統之前檢查傳入請求解釋

我的需求是檢查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

var contextSettings = new ContextSettings(); 
services.AddSingleton<IContextSettings>(contextSettings); 

註冊它在ConfigureServices而在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); 

所以每到包括根據幾個參數,單個HTTP請求我的DatabaseRepository可能會有所不同。我可以使用文件系統或SQL數據庫,並且可以獲得與我的客戶相對應的正確數據庫。 (是的,我有每個客戶的多個數據庫,不要試圖去理解爲什麼)

我簡化它有可能,我的代碼是在現實中更復雜,但是你的想法(我希望)。現在你可以修改它來適應你的需求。

+0

這似乎是我的問題有點不同,雖然它是如何拖延以每個爲'IServiceCollection'定義一個很好的例子請求基礎。您推遲決定使用已知類型('IDatabaseRepository')的哪個實現。在我的情況下,IRepository <>'是開放的/未知的,因爲我沒有必要的類型信息來告訴如何在'ServiceProvider'的'implementationFactory' lambda中關閉它。 – Technetium

+0

只是爲了澄清一些事情。你總是可以擺脫lambda表達式。他們只是爲了簡化開發人員的生活,並讓我們更快地編寫代碼。但是在那種情況下,我不喜歡在Startup.cs文件中包含這麼大的代碼。這就是爲什麼我選擇擺脫它,並在'DatabaseRepositoryFactory'中實現我的邏輯。如果你願意,你也可以做同樣的事情。 –

+0

另外我的數據庫存儲庫實際上實現'IDatabaseRepository ',實際上從'IRepository '繼承的接口'。所以我真正的依賴注入是'services.AddScoped >(databaseRepositoryFactory.DatabaseRepository)'。我認爲這是一個好主意,只是在我的例子中,但也許你需要完整的樣本......沒關係,我沒有選擇我使用的'IDatabase'的實現。我的存儲庫只需要遵守它的合同並接受或返回一些'IDatabase'。 –

4

的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是什麼類型的,並會正確地創建集合。然後我們將創建的集合保存在內部,並將所有調用委託給它。 (我們在模仿this=factory.Create()未在允許CSHARP :))。

更新: 正如指出的克里斯蒂安Hellang相同的模式是使用ASP.NET記錄

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

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

原創討論:

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