我正在构建一个HealthAPI类库,它为我们的HealthMonitor服务提供了统计信息列表。
我已经成功地让它工作,中间件正在记录服务启动时间和记录响应时间,我们的运行状况监视器能够通过调用我们的StatusController
来解析这些值,该具有许多返回 IActionResult JSON 响应的操作。
我们打算在所有服务中重用它,因此选择将 API 控制器与 DI 服务和中间件一起保留在类库中,以使控制器可访问 我最初做了以下操作。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().AddApplicationPart(Assembly.Load(new AssemblyName("HealthApiLibrary"))); //Bring in the Controller for HealthAPI;
services.AddSingleton<HealthApiService>();
}
但是,在重构阶段,我想通过执行以下操作来清理一下:
1)将services.AddSingleton<HealthApiService>();
重构为services.AddHealthApi();
(我们还没有做任何工作,但在回答这个问题时仍然可能是相关的)
2) 加载我的状态控制器作为services.AddHealthApi();
调用的一部分。
我尝试了以下方法:
public class HealthApiService
{
public HealthApiService(IMvcBuilder mvcBuilder)
{
mvcBuilder.AddApplicationPart(Assembly.Load(new AssemblyName("HealthApiLibrary"))); //Bring in the Controller for HealthAPI
ResponseTimeRecords = new Dictionary<DateTime, int>();
ServiceBootTime = DateTime.Now;
}
public DateTime ServiceBootTime { get; set; }
public Dictionary<DateTime,int> ResponseTimeRecords { get; set; }
public string ApplicationId { get; set; }
}
但是,这只会生成以下错误:
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.DependencyInjection.IMvcBuilder' while attempting to activate 'HealthApiLibrary.Services.HealthApiService'.
1. 依赖注入
您会收到异常,因为服务集合中没有注册IMvcBuilder
。我将这种类型添加到集合中是没有意义的,因为它仅在启动期间使用。
2. 扩展方法
可以创建扩展方法来实现所需的方法。
public static class AddHealthApiExtensions
{
public static void AddHealthApi(this IServiceCollection services)
{
services.AddSingleton<HealthApiService>();
}
}
3. 组装加载
看看@Tseng的评论。
据我收集,您正在尝试允许最终用户向您HealthApiService
提供自己的依赖项。这通常使用扩展方法和一个或多个构建器模式来完成。这不是 DI 问题,而是应用程序组合问题。
假设HealthApiService
有 2 个依赖项,IFoo
和IBar
,并且您希望用户能够为每个依赖项提供自己的实现:
public class HealthApiService : IHealthApiService
{
public HealthApiService(IFoo foo, IBar bar)
{
}
}
扩展方法
扩展方法有一个用于默认依赖项的重载和一个用于任何自定义依赖项的重载。
public static class ServiceCollectionExtensions
{
public static void AddHealthApi(this IServiceCollection services, Func<HealthApiServiceBuilder, HealthApiServiceBuilder> expression)
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (expression == null)
throw new ArgumentNullException(nameof(expression));
var starter = new HealthApiServiceBuilder();
var builder = expression(starter);
services.AddSingleton<IHealthApiService>(builder.Build());
}
public static void AddHealthApi(this IServiceCollection services)
{
AddHealthApi(services, builder => { return builder; });
}
}
建筑工人
构建器有助于一次构造一个依赖项HealthApiService
。它收集依赖项,然后在过程结束时,Build()
方法创建实例。
public class HealthApiServiceBuilder
{
private readonly IFoo foo;
private readonly IBar bar;
public HealthApiServiceBuilder()
// These are the default dependencies that can be overridden
// individually by the builder
: this(new DefaultFoo(), new DefaultBar())
{ }
internal HealthApiServiceBuilder(IFoo foo, IBar bar)
{
if (foo == null)
throw new ArgumentNullException(nameof(foo));
if (bar == null)
throw new ArgumentNullException(nameof(bar));
this.foo = foo;
this.bar = bar;
}
public HealthApiServiceBuilder WithFoo(IFoo foo)
{
return new HealthApiServiceBuilder(foo, this.bar);
}
public HealthApiServiceBuilder WithBar(IBar bar)
{
return new HealthApiServiceBuilder(this.foo, bar);
}
public HealthApiService Build()
{
return new HealthApiService(this.foo, this.bar);
}
}
用法
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
// Default dependencies
services.AddHealthApi();
// Custom dependencies
//services.AddHealthApi(healthApi =>
// healthApi.WithFoo(new MyFoo()).WithBar(new MyBar()));
}
奖金
如果默认IFoo
或IBar
实现具有依赖项,则可以为每个实现创建一个生成器类。例如,如果IFoo
具有依赖项IFooey
则可以为默认IFoo
实现创建生成器,然后使用表达式重载HealthApiServiceBuilder.WithFoo
方法:
public HealthApiServiceBuilder WithFoo(IFoo foo)
{
return new HealthApiServiceBuilder(foo, this.bar);
}
public HealthApiServiceBuilder WithFoo(Func<FooBuilder, FooBuilder> expression)
{
var starter = new FooBuilder();
var builder = expression(starter);
return new HealthApiServiceBuilder(builder.Build(), this.bar);
}
然后可以像这样使用
services.AddHealthApi(healthApi =>
healthApi.WithFoo(foo => foo.WithFooey(new MyFooey)));
更多
需要在应用程序启动时注册但不希望最终用户与之交互的任何其他服务(例如控制器)都可以在扩展方法中完成。
参考
DI友好图书馆由马克·西曼