ASP.NET Core MVC中的控制器激活和依赖项注入

2021-04-03  乐帮网

mvc netcore

原文:https://andrewlock.net/controller-activation-and-dependency-injection-in-asp-net-core-mvc/

我的最后一篇有关IDsiposable在ASP.NET Core中部署的文章中,马克·雷德尔Mark Rendle)指出,在请求结束时也部署了MVC控制器。乍一看,考虑到范围资源是在请求末尾分配的,这似乎很明显,但是MVC控制器实际上以与大多数服务稍有不同的方式进行处理。

在本文中,我将介绍如何使用IControllerActivator,可用的可用选项在ASP.NET Core MVC中创建控制器,以及它们在依赖项注入方面的区别。

默认值 IControllerActivator

在ASP.NET Core中,当接收到请求时MvcMiddleware,使用路由(常规路由或属性路由)选择要执行的控制器和操作方法。为了实际执行该动作,MvcMiddleware必须创建所选控制器的实例。

创建控制器的过程取决于许多不同的提供程序和工厂类,最终以的实例为准IControllerActivator。此类仅实现两种方法:

public interface IControllerActivator
{
    object Create(ControllerContext context);
    void Release(ControllerContext context, object controller);
}

如您所见,该IControllerActivator.Create方法传递了context,该方法ControllerContext定义了要创建的控制器。如何创建控制器取决于特定的实现。

现成的,ASP.NET Core使用DefaultControllerActivator,使用TypeActivatorCache来创建控制器。在TypeActivatorCache通过调用类的构造函数,并试图解决从DI容器所需的构造函数参数的依赖创建对象的实例。

这是重要的一点。在DefaultControllerActivator 不尝试从DI容器本身,只有控制器的依赖性解决控制器实例。

默认控制器激活器的示例

为了演示这种行为,我创建了一个简单的MVC应用程序,该应用程序由一个服务和一个控制器组成。服务实例具有一个名称属性,该名称属性是在构造函数中设置的。默认情况下,它将具有值"default"

public class TestService
{
    public TestService(string name = "default")
    {
        Name = name;
    }

    public string Name { get; }
}

HomeController应用的依赖于TestService,并返回Name属性:

public class HomeController : Controller
{
    private readonly TestService _testService;
    public HomeController(TestService testService)
    {
        _testService = testService;
    }

    public string Index()
    {
        return "TestService.Name: " + _testService.Name;
    }
}

难题的最后一部分是Startup文件。在这里,我TestService在DI容器中将其注册为范围服务,并设置MvcMiddleware和服务:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();

        services.AddScoped<TestService>();
        services.AddTransient(ctx =>
            new HomeController(new TestService("Non-default value")));
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}

您还会注意到,我已经定义了一个工厂方法来创建的实例HomeController。这会将HomeController类型注册到DI容器中,并注入TestService具有自定义Name属性的实例。

那么,如果运行该应用程序,您会得到什么?

img

如您所见,该TestService.Name属性具有默认值,表明该TestService实例直接来自DI容器。我们注册用于创建的工厂方法HomeController显然已被忽略。

当您记得DefaultControllerActivator正在创建控制器时,这很有意义。它不HomeController向DI容器请求,而仅请求其构造函数依赖项。

在大多数情况下,使用DefaultControllerActivator会很好,但是有时您可能想直接使用DI容器来创建控制器。当您使用具有拦截器或装饰器等功能的第三方容器时,尤其如此。

幸运的是,MVC框架包括一个IControllerActivator用于执行此操作的实现,甚至提供了方便的扩展方法来启用它。

ServiceBasedControllerActivator

如您所见,可以DefaultControllerActivator使用TypeActivatorCache来创建控制器,但是MVC包含替代实现,ServiceBasedControllerActivator可以用于直接从DI容器中获取控制器。实现本身很简单:

public class ServiceBasedControllerActivator : IControllerActivator
{
    public object Create(ControllerContext actionContext)
    {
        var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType();

        return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType);
    }

    public virtual void Release(ControllerContext context, object controller)
    {
    }
}

AddControllersAsServices()将MVC服务添加到应用程序时,可以使用扩展方法配置基于DI的激活器:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc()
                .AddControllersAsServices();

        services.AddScoped<TestService>();
        services.AddTransient(ctx =>
            new HomeController(new TestService("Non-default value")));
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvcWithDefaultRoute();
    }
}

将其放置在适当位置后,点击主页将通过从DI容器中加载控制器来创建一个控制器。由于我们已经为注册了工厂方法,因此HomeController我们TestService将遵循自定义配置,并使用替代Name方法:

img3

AddControllersAsServices方法有两件事-将应用程序中的所有Controller都注册到DI容器中(如果尚未注册),然后将IControllerActivator注册替换为ServiceBasedControllerActivator

public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder)
{
    var feature = new ControllerFeature();
    builder.PartManager.PopulateFeature(feature);

    foreach (var controller in feature.Controllers.Select(c => c.AsType()))
    {
        builder.Services.TryAddTransient(controller, controller);
    }

    builder.Services.Replace(ServiceDescriptor.Transient<IControllerActivator, ServiceBasedControllerActivator>());

    return builder;
}

如果您需要做一些深奥的事情,则可以随时实现IControllerActivator自己,但是我无法想到这两个实现不能满足您所有要求的任何原因!

汇总

  • 默认情况下,将ASP.NET Core MVCDefaultControllerActivator配置为IControllerActivator
  • DefaultControllerActivator用途TypeActivatorCache创建控制器。这将创建控制器的实例,并从DI容器中加载构造函数参数。
  • 您可以使用备用激活器,ServiceBasedControllerActivator直接从DI容器加载控制器。您可以通过AddControllersAsServices()在中的MvcBuilder实例上使用扩展方法来配置此激活器Startup.ConfigureServices

公众号二维码

关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com

庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。

欧阳修

付款二维码

如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力