如何配置Startup类构造函数可用的DI服务

本文关键字:构造函数 服务 DI Startup 何配置 配置 | 更新日期: 2023-09-27 18:14:33

当我为ASP.NET Core应用程序创建Web主机时,我可以指定Startup类,但不能指定实例。

您自己的Startup类的构造函数可以接受通过DI提供的参数。我知道如何在ConfigureServices中为DI注册服务,但由于DI是该类的成员,这些服务对于我的启动类的构造函数不可用。

如何注册将作为Startup类的构造函数参数可用的服务?

原因是我必须提供一个对象实例,该实例必须在创建webhost之前在外部创建,并且我不想以类似的全局样式传递它。

创建IWebHost:的代码

this.host = new WebHostBuilder()
    .UseConfiguration(config)
    .UseKestrel()
    .UseIISIntegration()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseStartup<WebStartup>()
log.Debug("Run webhost");
this.host.Start();

WebStartup:的构造函数

public WebStartup(IHostingEnvironment env, MyClass myClass)
{
    var config = new ConfigurationBuilder()
        .SetBasePath(env.ContentRootPath)
        .AddEnvironmentVariables()
        .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
        .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true)
        .Build();
    ...
}

那么具体地说,在这个例子中,我如何注册MyClass(这显然必须在WebStartupIWebHost初始化之前完成(?

如何配置Startup类构造函数可用的DI服务

尽管Steven的担忧是有效的,您应该注意到它们,但从技术上讲,配置用于解决Startup类的DI容器是可能的。

ASP.NET宿主使用依赖项注入来连接Startup类的实例,还允许我们使用IWebHostBuilder:上的ConfigureServices扩展方法将自己的服务添加到该容器中

var host = new WebHostBuilder()
    .UseKestrel()
    .UseContentRoot(Directory.GetCurrentDirectory())
    .UseIISIntegration()
    .ConfigureServices(services => services.AddSingleton<IMyService, MyService>())
    .UseStartup<Startup>()
    .Build();
host.Run();

和:

public Startup(IMyService myService)
{
    myService.Test();
}

事实上,UseStartup<WebStartup>所做的只是将其作为IStartup的服务实现添加到托管DI容器中(请参阅此部分(。

请注意,您的服务实例将在应用程序容器中再次解析,因为将生成IServiceProvider的新实例。但是,服务的注册将传递给Startup类中的应用程序IServiceCollection

这是一个"鸡还是蛋"的问题:在配置对象图之前,不能让DI容器解析对象图。

但是,您的问题不应该存在,因为正如您应该严格地将容器的注册阶段与解析阶段分开一样(ASP.NET Core强制要求您这样做(,您也应该以同样的方式将注册阶段与加载配置值之前的阶段分开。

这意味着,在容器的注册阶段所需的类不应由容器解析。因为这可能会导致难以追踪的常见问题。

相反,您应该手动创建类。例如:

public class Startup
{
    private static MyDependency dependency;
    public Startup(IHostingEnvironment env)
    {
        dependency = new MyDependency();
        var instance = new MyClass(dependency);
        var builder = new ConfigurationBuilder()
            .SetBasePath(instance.ContentRootPath)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();
        Configuration = builder.Build();
    }
    public IConfigurationRoot Configuration { get; }
    public void ConfigureServices(IServiceCollection services)
    {
        // Register the dependency if it is required by other components.
        services.AddSingleton<MyDependency>(dependency);
        // Add framework services.
        services.AddMvc();
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
    }
    // etc.
}

您有一个名为myClassMyClass实例,需要将该实例注入到Startup类中。我也有类似的要求,经过一些实验,我得出了以下结论。

首先,我们在调用UseStartup:之前注入myClass实例

var host = new WebHostBuilder()
    // .... other code
    .ConfigureServices(services => services.AddSingleton(myClass)) // Inject myClass instance
    .UseStartup<WebStartup>()

现在我们需要获取Startup中的对象(在您的情况下为WebStartup(。我找不到从构造函数访问它的方法,但这并不重要,因为它可以从启动类"ConfigureServices()"中访问,如果需要,可以从那里保存到字段或属性(稍后调用Startup.Configure()(。以下是我的想法:

    // This goes into Startup.cs/WebStartup.cs
    public virtual void ConfigureServices(IServiceCollection services)
    {
        var myClass = services.Single(s => s.ServiceType == typeof(MyClass)).ImplementationInstance as MyClass;
        if (myClass == null)
            throw new InvalidOperationException(nameof(MyClass) + " instance is null");
        // Now do all the things
    }

我怀疑框架中可能有一些东西可以更容易地检索注入的实例,但如果没有,或者直到我找到它,我可以确认上面的方法非常有效!