Je dois pouvoir passer une chaîne de connexion dans certaines de mes implémentations de service. Je fais cela dans le constructeur. La chaîne de connexion est configurable par l'utilisateur sera ajouté le ClaimsPrincipal en tant que revendication.
Très bien jusqu'à présent.
Malheureusement, je souhaite également pouvoir utiliser au maximum les fonctionnalités d'injection de dépendances dans ASP.NET Core et résoudre l'implémentation du service via DI.
J'ai une implémentation POC:
public interface IRootService
{
INestedService NestedService { get; set; }
void DoSomething();
}
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(INestedService nestedService)
{
NestedService = nestedService;
}
public void DoSomething()
{
// implement
}
}
public interface INestedService
{
string ConnectionString { get; set; }
void DoSomethingElse();
}
public class NestedService : INestedService
{
public string ConnectionString { get; set; }
public NestedService(string connectionString)
{
ConnectionString = connectionString;
}
public void DoSomethingElse()
{
// implement
}
}
Ces services ont été enregistrés au démarrage et INestedService
a été ajouté le constructeur d'un contrôleur.
public HomeController(INestedService nestedService)
{
NestedService = nestedService;
}
Comme prévu, j'obtiens l'erreur Unable to resolve service for type 'System.String' while attempting to activate 'Test.Dependency.Services.NestedService'.
Quelles sont mes options ici?
public void ConfigureServices(IServiceCollection services)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(serviceProvider=>
{
return new NestedService("someConnectionString");
});
}
Si vous décidez de masquer votre chaîne de connexion dans appSettings.json, par exemple:
"Data": {
"ConnectionString": "someConnectionString"
}
À condition que vous ayez chargé votre appSettings.json dans ConfigurationBuilder (généralement situé dans le constructeur de la classe Startup), vos ConfigureServices ressembleraient à ceci:
public void ConfigureServices(IServiceCollection services)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(serviceProvider=>
{
var connectionString = Configuration["Data:ConnectionString"];
return new NestedService(connectionString);
});
}
namespace Microsoft.Extensions.DependencyInjection
{
public static class RootServiceExtensions //you can pick a better name
{
//again pick a better name
public static IServiceCollection AddRootServices(this IServiceCollection services, string connectionString)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(_ =>
new NestedService(connectionString));
}
}
}
Ensuite, votre méthode ConfigureServices ressemblerait à ceci
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["Data:ConnectionString"];
services.AddRootServices(connectionString);
}
Si vous avez besoin de plus de paramètres, vous pouvez aller plus loin et créer une classe d'options que vous passez au constructeur de RootService. S'il devient complexe, vous pouvez utiliser le modèle Builder.
Pour passer un paramètre d'exécution inconnu au démarrage de l'application, vous devez utiliser le modèle d'usine. Vous avez deux options ici
méthode d'usine
services.AddTransient<Func<string,INestedService>>((provider) =>
{
return new Func<string,INestedService>(
(connectionString) => new NestedService(connectionString)
);
});
et injectez la méthode d'usine dans votre service au lieu de INestedService
.
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(Func<string,INestedService> nestedServiceFactory)
{
NestedService = nestedServiceFactory("ConnectionStringHere");
}
public void DoSomething()
{
// implement
}
}
ou le résoudre par appel
public class RootService : IRootService
{
public Func<string,INestedService> NestedServiceFactory { get; set; }
public RootService(Func<string,INestedService> nestedServiceFactory)
{
NestedServiceFactory = nestedServiceFactory;
}
public void DoSomething(string connectionString)
{
var nestedService = nestedServiceFactory(connectionString);
// implement
}
}
classe d'usine
public class RootServiceFactory : IRootServiceFactory
{
// in case you need other dependencies, that can be resolved by DI
private readonly IServiceCollection services;
public RootServiceCollection(IServiceCollection services)
{
this.services = services;
}
public CreateInstance(string connectionString)
{
// instantiate service that needs runtime parameter
var nestedService = new NestedService(connectionString);
// resolve another service that doesn't need runtime parameter
var otherDependency = services.GetService<IOtherService>()
// pass both into the RootService constructor and return it
return new RootService(otherDependency, nestedDependency);
}
}
et injectez IRootServiceFactory
au lieu de votre IRootService
.
IRootService rootService = rootServiceFactory.CreateIntsance(connectionString);