【问题标题】:Accessing MVC DI Service inside other services在其他服务中访问 MVC DI 服务
【发布时间】:2018-03-18 00:14:54
【问题描述】:

我正在构建一个 HealthAPI 类库,它为我们的 HealthMonitor 服务提供统计信息列表。

我已经成功完成了这项工作,中间件正在记录服务启动时间和响应时间,我们的运行状况监视器能够通过调用 StatusController 来解析这些值,该 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&lt;HealthApiService&gt;(); 重构为services.AddHealthApi();(我们目前还没有做任何工作,但在回答这个问题时仍然可能是相关的)

2) 在 services.AddHealthApi(); 调用中加载我的 StatusController。

我尝试了以下方法:

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'.

【问题讨论】:

  • 你为什么还要通过Assembly.Load加载它?只需将它放在一个nuget包中,引用它,然后调用注册中间件的扩展方法。只要控制器继承自 Controller 基类或引用 Microsoft.AspNetCore.Mvc 包,ASP.NET Core MVC 就会自动检测控制器
  • 从服务中访问容器是可怕的服务定位器反模式。但是在阅读了您的问题之后,这甚至与您想要实现的目标还相差甚远。您真正追求的只是创建一个扩展方法以使您的库更易于注册。在应用程序启动时应该注册您的服务的扩展方法。
  • @Tseng,你能否提供更多细节,我在开发这个时做了一些测试,找不到任何关于 ASP.net Web 应用程序项目如何能够访问的信息位于类库中的控制器。
  • ASP.NET Core 按约定发现控制器。它查看主项目加载/引用的每个程序集。因此,如果您有一个带有控制器的类库并且这个类库引用了Microsoft.AspNetCore.Mvc / Microsoft.AspNetCore.MvcCore,它将查找从Controller 基类继承或具有@ 的所有类987654333@ 后缀(即MyController)并将它们注册为控制器。您需要做的就是引用该库
  • 正确,您不应该将属性路由与库一起使用,您应该使用基于约定的路由。通过这种方式,您可以更轻松地在应用程序启动时将其绑定到宿主应用程序。

标签: c# asp.net-core .net-core asp.net-core-webapi asp.net-core-2.0


【解决方案1】:

1.依赖注入

您收到异常是因为服务集合中没有注册IMvcBuilder。将此类型添加到集合中没有意义,因为它仅在启动期间使用。

2。扩展方法

你可以创建一个扩展方法来实现你想要的方法。

public static class AddHealthApiExtensions
{
    public static void AddHealthApi(this IServiceCollection services)
    {
        services.AddSingleton<HealthApiService>();
    }
}

3. Assembly.Load

看看@Tseng 的评论。

【讨论】:

  • 在您的代码片段 2. 扩展方法中,它引用了 services 但无论如何都没有声明。 IServiceCollection 应该在某处传递吗?
  • @tornup 我把IMvcBuilder 误认为IServiceCollection。我已经改变了答案。要了解如何应用构建器模式,您可以查看@NightOwl888 答案。
【解决方案2】:

据我所知,您正试图允许最终用户向您的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 Friendly Library by Mark Seemann

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-23
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-27
    • 2013-07-09
    相关资源
    最近更新 更多