Ant*_*ift 27 c# dependency-injection .net-core asp.net-core
我需要能够将连接字符串传递给我的一些服务实现.我在构造函数中这样做.连接字符串可由用户配置,将ClaimsPrincipal添加为Claim.
到目前为止都很好.
不幸的是,我还希望能够充分利用ASP.NET Core中的依赖注入功能,并通过DI解决服务实现.
我有一个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
}
}
Run Code Online (Sandbox Code Playgroud)
这些服务已在启动期间注册,并INestedService已添加控制器的构造函数.
public HomeController(INestedService nestedService)
{
NestedService = nestedService;
}
Run Code Online (Sandbox Code Playgroud)
正如所料,我得到了错误 Unable to resolve service for type 'System.String' while attempting to activate 'Test.Dependency.Services.NestedService'.
我有什么选择?
Iva*_*nov 38
public void ConfigureServices(IServiceCollection services)
{
// Choose Scope, Singleton or Transient method
services.AddSingleton<IRootService, RootService>();
services.AddSingleton<INestedService, NestedService>(serviceProvider=>
{
return new NestedService("someConnectionString");
});
}
Run Code Online (Sandbox Code Playgroud)
如果您决定在appSettings.json中隐藏连接字符串,例如:
"Data": {
"ConnectionString": "someConnectionString"
}
Run Code Online (Sandbox Code Playgroud)
然后,如果您已经在ConfigurationBuilder中加载了appSettings.json(通常位于Startup类的构造函数中),那么您的ConfigureServices将如下所示:
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);
});
}
Run Code Online (Sandbox Code Playgroud)
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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
然后您的ConfigureServices方法将如下所示
public void ConfigureServices(IServiceCollection services)
{
var connectionString = Configuration["Data:ConnectionString"];
services.AddRootServices(connectionString);
}
Run Code Online (Sandbox Code Playgroud)
如果您需要更多参数,可以更进一步,创建一个传递给RootService构造函数的选项类.如果它变得复杂,您可以使用Builder模式.
Tse*_*eng 33
要传递应用程序启动时未知的运行时参数,必须使用工厂模式.你有两个选择
工厂方法
services.AddTransient<Func<string,INestedService>>((provider) =>
{
return new Func<string,INestedService>(
(connectionString) => new NestedService(connectionString)
);
});
Run Code Online (Sandbox Code Playgroud)
并在您的服务中注入工厂方法而不是INestedService.
public class RootService : IRootService
{
public INestedService NestedService { get; set; }
public RootService(Func<string,INestedService> nestedServiceFactory)
{
NestedService = nestedServiceFactory("ConnectionStringHere");
}
public void DoSomething()
{
// implement
}
}
Run Code Online (Sandbox Code Playgroud)
或者每次通话解决它
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
}
}
Run Code Online (Sandbox Code Playgroud)工厂类
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);
}
}
Run Code Online (Sandbox Code Playgroud)
并注入IRootServiceFactory而不是你的IRootService.
IRootService rootService = rootServiceFactory.CreateIntsance(connectionString);
Run Code Online (Sandbox Code Playgroud)Jay*_*ans 10
我设计了这个小模式来帮助我解析需要运行时参数的对象,但也具有 DI 容器能够解析的依赖项 - 我使用 WPF 应用程序的 MS DI 容器实现了这一点。
I already had a Service Locator (yes I know its a code smell - but I attempt to resolve that by the end of the example) that I used in specific scenarios to get access to objects in the DIC:
public interface IServiceFactory
{
T Get<T>();
}
Run Code Online (Sandbox Code Playgroud)
Its implementation takes a func<> in the constructor to decouple the fact it relies on MS DI.
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
public ServiceFactory(Func<Type, object> factory)
{
this.factory = factory;
}
// Get an object of type T where T is usually an interface
public T Get<T>()
{
return (T)factory(typeof(T));
}
}
Run Code Online (Sandbox Code Playgroud)
This was created in the composition root like so:
services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService));
Run Code Online (Sandbox Code Playgroud)
This pattern was extended to not only 'Get' objects of type T, but 'Create' objects of type T with parameters P:
public interface IServiceFactory
{
T Get<T>();
T Create<T>(params object[] p);
}
Run Code Online (Sandbox Code Playgroud)
The implementation took another func<> to decouple the creation mechanism:
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
private readonly Func<Type, object[], object> creator;
public ServiceFactory(Func<Type, object> factory, Func<Type, object[], object> creator)
{
this.factory = factory;
this.creator = creator;
}
// Get an object of type T where T is usually an interface
public T Get<T>()
{
return (T)factory(typeof(T));
}
// Create (an obviously transient) object of type T, with runtime parameters 'p'
public T Create<T>(params object[] p)
{
IService<T> lookup = Get<IService<T>>();
return (T)creator(lookup.Type(), p);
}
}
Run Code Online (Sandbox Code Playgroud)
The creation mechanism for the MS DI container is in the ActivatorUtilities extensions, here's the updated composition root:
services.AddSingleton<IServiceFactory>(
provider => new ServiceFactory(
provider.GetService,
(T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
Run Code Online (Sandbox Code Playgroud)
Now that we can create objects the problem becomes we have no way of determining the type of object we need without the DI container actually creating an object of that type, which is where the IService interface comes in:
public interface IService<I>
{
// Returns mapped type for this I
Type Type();
}
Run Code Online (Sandbox Code Playgroud)
This is used to determine what type we are trying to create, without actually creating the type, its implementation is:
public class Service<I, T> : IService<I>
{
public Type Type()
{
return typeof(T);
}
}
Run Code Online (Sandbox Code Playgroud)
So to pull it all together, in your composition root you can have objects that don't have runtime parameters which can be resolved by 'Get' and ones which do resolved by 'Create' e.g.:
services.AddSingleton<ICategorySelectionVM, CategorySelectionVM>();
services.AddSingleton<IService<ISubCategorySelectionVM>, Service<ISubCategorySelectionVM, SubCategorySelectionVM>>();
services.AddSingleton<ILogger, Logger>();
Run Code Online (Sandbox Code Playgroud)
The CategorySelectionVM has only dependencies that can be resolved via the DIC:
public CategorySelectionVM(ILogger logger) // constructor
Run Code Online (Sandbox Code Playgroud)
And this can be created by anyone with a dependency on the service factory like:
public MainWindowVM(IServiceFactory serviceFactory) // constructor
{
}
private void OnHomeEvent()
{
CurrentView = serviceFactory.Get<ICategorySelectionVM>();
}
Run Code Online (Sandbox Code Playgroud)
Where as the SubCategorySelectionVM has both dependencies that the DIC can resolve, and dependencies only known at runtime:
public SubCategorySelectionVM(ILogger logger, Category c) // constructor
Run Code Online (Sandbox Code Playgroud)
And these can be created like so:
private void OnCategorySelectedEvent(Category category)
{
CurrentView = serviceFactory.Create<ISubCategorySelectionVM>(category);
}
Run Code Online (Sandbox Code Playgroud)
Update : I just wanted to add a little enhancement which avoided using the service factory like a service locator, so I created a generic service factory which could only resolve objects of type B:
public interface IServiceFactory<B>
{
T Get<T>() where T : B;
T Create<T>(params object[] p) where T : B;
}
Run Code Online (Sandbox Code Playgroud)
The implementation of this depends on the original service factory which could resolve objects of any type:
public class ServiceFactory<B> : IServiceFactory<B>
{
private readonly IServiceFactory serviceFactory;
public ServiceFactory(IServiceFactory serviceFactory)
{
this.serviceFactory = serviceFactory;
}
public T Get<T>() where T : B
{
return serviceFactory.Get<T>();
}
public T Create<T>(params object[] p) where T : B
{
return serviceFactory.Create<T>(p);
}
}
Run Code Online (Sandbox Code Playgroud)
The composition root adds the original service factory for all the generic typed factories to depend on, and any of the typed factories:
services.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (T, P) => ActivatorUtilities.CreateInstance(provider, T, P)));
services.AddSingleton<IServiceFactory<BaseVM>, ServiceFactory<BaseVM>>();
Run Code Online (Sandbox Code Playgroud)
Now our main view model can be restricted to creating only objects that derive from BaseVM:
public MainWindowVM(IServiceFactory<BaseVM> viewModelFactory)
{
this.viewModelFactory = viewModelFactory;
}
private void OnCategorySelectedEvent(Category category)
{
CurrentView = viewModelFactory.Create<SubCategorySelectionVM>(category);
}
private void OnHomeEvent()
{
CurrentView = viewModelFactory.Get<CategorySelectionVM>();
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
26140 次 |
| 最近记录: |