2016-08-19 2 views
15

В ASP.NET Core, одна из вещей, которые вы можете сделать с каркасом Microsoft в зависимости инжекционного is bind "open generics" (общие типы несвязанный к конкретному типу), как так:Factory Pattern с открытым Дженерики

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 с сообщением Open generic service type 'IRepository<T>' requires registering an open generic implementation type. из CLI Dotnet. Он даже не добирается до лямбды.

Возможно ли такое связывание с каркасом внедрения зависимостей Microsoft?

+0

Что такое преимущество registerin g лямбда, которая разрешает фабрику, которая разрешает требуемую услугу? – Steven

+0

Хороший вопрос. Он перенаправляет сложность условной гидратации. Вам не нужна явная фабрика, поскольку лямбда действует как одна (ее переменная даже называется «реализацияFactory»), но если вам нужно несколько служб, чтобы принять решение о том, какой экземпляр вы хотите увлажнить, вы будете иметь лямбда, сложная и трудная для тестирования. Сообщение в блоге, приведенное выше, имеет хороший пример: http://dotnetliberty.com/index.php/2016/05/09/asp-net-core-factory-pattern-dependency-injection/ – Technetium

+0

Вы когда-нибудь находили хорошее Ответ для этого? У меня такая же проблема, но ни один из ответов здесь не является хорошим решением проблемы. –

ответ

1

Я также не понимаю точку вашего выражения лямбды, поэтому я объясню вам мой способ сделать это.

Я полагаю, что вы хотите, чтобы достичь того, что объясняется в статье вы поделились

Это позволило мне проверить входящий запрос перед подачей зависимости в систему впрыска зависимости ASP.NET ядра

Мне нужно было проверить пользовательский заголовок в HTTP-запросе, чтобы определить, какой клиент запрашивает мой API. Затем я мог бы немного позже в конвейере решить, какая реализация моего IDatabaseRepository (Файловая система или Entity Framework связана с базой данных 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 сначала необходимо зарегистрировать его в ConfigureServices в Startup.cs

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

Таким образом, каждый один HTTP-запрос my DatabaseRepository может отличаться в зависимости от нескольких параметров. Я мог бы использовать файловую систему или базу данных SQL, и я могу получить соответствующую базу данных, соответствующую моему клиенту. (Да, у меня есть несколько баз данных для каждого клиента, не пытайтесь понять почему)

Я упростил его, насколько это возможно, мой код на самом деле более сложный, но вы получаете идею (я надеюсь). Теперь вы можете изменить это в соответствии с вашими потребностями.

+0

Это похоже немного отличается от моей проблемы, хотя это очень хороший пример того, как отложить определения для 'IServiceCollection' на per- запрос basis. Вы откладываете решение, для которого используется реализация известного типа ('IDatabaseRepository'). В моей ситуации 'IRepository <>' открыт/неизвестен, потому что у меня нет информации о типе, необходимой для того, как ее закрыть в lambda 'implementationFactory' для' ServiceProvider'. – Technetium

+0

Просто чтобы прояснить некоторые вещи. Вы можете ВСЕГДА избавиться от лямбда-выражений.Они просто здесь, чтобы упростить жизнь разработчиков и позволяют быстрее кодировать код. Но в этом случае мне не нравилось иметь этот большой кусок кода внутри моего файла Startup.cs. Вот почему я решил избавиться от него и реализовать свою логику в «DatabaseRepositoryFactory». Вы также можете сделать то же самое, если хотите. –

+0

Также мои репозитории базы данных фактически реализуют 'IDatabaseRepository ', интерфейс, который на самом деле наследует от 'IRepository '. Таким образом, моя реальная инъекция зависимостей - это 'services.AddScoped > (databaseRepositoryFactory.DatabaseRepository)'. Я думал, что было бы неплохо просто, что в моем примере, но, возможно, вам нужна полная выборка ... Не имеет значения, что я не выбрал, какую реализацию «базы данных» я использую. Мой репозиторий просто должен уважать его контракт и принимает или возвращает некоторую «базу данных». –

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 ти будет знать, какой тип Т и будет правильно создать коллекцию. Затем мы берем созданную коллекцию, сохраняем ее внутренне и делегируем на нее все вызовы. (Мы имитируя 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); 
    } 
} 

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

оригинальное обсуждение здесь:

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