Modèle d’usine avec génériques ouverts

Dans ASP.NET Core, l’une des choses que vous pouvez faire avec l’infrastructure d’dependency injection de Microsoft est de lier les “génériques ouverts” (types génériques non liés à un type concret), comme suit:

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

Vous pouvez également utiliser le modèle d’usine pour hydrater les dépendances . Voici un exemple artificiel:

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

Cependant, je n’ai pas été capable de comprendre comment combiner les deux concepts. Il semble que cela commence par quelque chose comme cela, mais j’ai besoin du type concret utilisé pour hydrater une instance de 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(); } ); } 

Si j’essaie d’utiliser le foncteur Func avec un générique ouvert, j’obtiens cette Open generic service type 'IRepository' requires registering an open generic implementation type. ArgumentException avec le message Open generic service type 'IRepository' requires registering an open generic implementation type. de la CLI dotnet. Cela n’arrive même pas au lambda.

Ce type de liaison est-il possible avec le framework d’dependency injection de Microsoft?

La dépendance net.core ne vous permet pas de fournir une méthode de fabrique lors de l’enregistrement d’un type générique ouvert, mais vous pouvez contourner ce problème en fournissant un type qui implémentera l’interface demandée, mais en interne, il agira comme une fabrique. Une usine déguisée:

 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(ssortingng 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 } 

Lorsque le conteneur aura résolu MongoCollectionFactory, ti saura quel type est T et créera la collection correctement. Ensuite, nous prenons cette collection créée, la sauvegardons en interne et nous lui déléguons tous les appels. (Nous imitons this=factory.Create() ce qui n’est pas autorisé dans csharp. :))

Mise à jour: comme l’a souligné Kristian Hellang, le même modèle est utilisé par la journalisation ASP.NET.

 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

discussion originale ici:

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

Je ne comprends pas non plus le sens de votre expression lambda, je vais donc vous expliquer ma façon de le faire.

Je suppose que ce que vous souhaitez, c’est accéder à ce qui est expliqué dans l’article que vous avez partagé

Cela m’a permis d’inspecter la demande entrante avant de fournir une dépendance dans le système d’dependency injection ASP.NET Core.

J’avais besoin d’inspecter un en-tête personnalisé dans la requête HTTP pour déterminer quel client demandait mon API. Je pourrais ensuite un peu plus tard dans le pipeline décider quelle implémentation de mon IDatabaseRepository (système de fichiers ou Entity Framework lié à une firebase database SQL) à fournir pour cette requête unique.

Alors je commence par écrire un middleware

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

Mon SettingsProvider est juste un singleton qui me fournit l’object client correspondant.

Pour permettre à notre middleware d’accéder à ce ContextSettings nous devons d’abord l’enregistrer dans ConfigureServices dans Startup.cs.

 var contextSettings = new ContextSettings(); services.AddSingleton(contextSettings); 

Et dans la méthode Configure , nous enregistrons notre middleware

 app.UseMiddleware(); 

Maintenant que notre client est accessible d’ailleurs, écrivons notre usine.

 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.ConnectionSsortingng, _env.EnvironmentName); var databaseRepository = new EntityFrameworkDatabaseRepository(dbContext); return databaseRepository; } } } 

Pour utiliser la méthode serviceProvider.GetService<>() , vous devez inclure les éléments suivants dans votre fichier CS.

 using Microsoft.Extensions.DependencyInjection; 

Enfin, nous pouvons utiliser notre méthode Factory in ConfigureServices

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

Ainsi, chaque requête HTTP unique que mon DatabaseRepository sera différente en fonction de plusieurs parameters. Je pourrais utiliser un système de fichiers ou une firebase database SQL et obtenir la firebase database appropriée correspondant à mon client. (Oui, j’ai plusieurs bases de données par client, n’essayez pas de comprendre pourquoi)

Je l’ai simplifié le plus possible, mon code est en réalité plus complexe mais vous en avez l’idée (j’espère). Vous pouvez maintenant modifier cela pour répondre à vos besoins.