如何在ASP.NET MVC中使Controller成为每个应用程序的单个实例?

6op*_*puc 9 asp.net-mvc controller

随着时间的推移,控制器会产生大量的依赖关系,并且为每个请求创建一个控制器实例会变得过于昂贵(特别是对于DI).是否有任何解决方案来制作控制器单例?

Ser*_*kiy 19

创建控制器实例非常简单快捷.变得太昂贵的是为每个请求创建依赖项.所以,你真正需要的是许多控制器,它们共享相同的依赖实例.

例如,您有以下控制器

public class SalesController : Controller
{
    private IProductRepository productRepository;
    private IOrderRepository orderRepository;

    public SalesController(IProductRepository productRepository,
                           IOrderRepository orderRepository)
    {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
    }

    // ...       
}
Run Code Online (Sandbox Code Playgroud)

您应该配置依赖项注入框架,以便为所有应用程序使用相同的存储库实例(请记住,您可能遇到同步问题).现在创建依赖关系并不昂贵.所有依赖项仅实例化一次,并重用于所有请求.

如果您有许多依赖项,并且您担心引用每个依赖项的实例并将这些引用提供给控制器实例(我认为这将不会非常昂贵)的成本,那么您可以对依赖项进行分组(类似于引入参数对象重构):

public class SalesController : Controller
{
    private ISalesService salesService;

    public SalesController(ISalesService salesService)
    {
        this.salesService = salesService;
    }

    // ...       
}

public class SalesService : ISalesService
{
    private IProductRepository productRepository;
    private IOrderRepository orderRepository;

    public SalesService(IProductRepository productRepository,
                           IOrderRepository orderRepository)
    {
        this.productRepository = productRepository;
        this.orderRepository = orderRepository;
    }

    // ...       
}
Run Code Online (Sandbox Code Playgroud)

现在你有一个依赖,将很快注入.如果要将依赖项注入框架配置为使用singleton SalesService,则所有SalesControllers都将重用相同的服务实例.创建控制器和提供依赖关系将非常快.


mor*_*wai 6

因此,首先要回答原始问题:

public void ConfigureServices(IServiceCollection services) {
    // put other services bindings here

    // bind all Controller classes as singletons
    services.AddSingleton<HomeController, HomeController>();

    // tell framework to obtain Controller instances from ServiceProvider.
    services.AddMvc().AddControllersAsServices();
}
Run Code Online (Sandbox Code Playgroud)

如原始问题所述,如果控制器具有主要由请求范围或瞬态依赖项组成的大依赖关系树,则为每个请求单独创建它们可能会占用应用程序的可伸缩性(例如,在Java中,Servlet实例默认情况下是单例的)这个原因)。尽管通常创建一个大的依赖关系树所需的CPU和实时性可以忽略不计(除非您在组件的构造函数中进行了大量的计算或网络通信,这对于瞬态或请求范围内的组件来说几乎不是一个好主意),但是内存的使用情况足迹是不容忽视的。对于常见的DB-Web应用程序,内存是限制单个机器节点可以处理的并发请求数的主要因素。如果每个请求都有一个独立的大依赖关系树副本,

公认的答案1220560也可以解决该问题,但我认为这是一个丑陋的破解,它有一些缺点:您需要创建此人工单例服务,该服务将由控制器用作服务定位器或其他服务的代理。如果您的所有控制器只有一个这样的单例对象,那么您实际上是在隐藏控制器的实际依赖关系:例如,如果某人想为控制器编写单元测试,则需要仔细分析其实现以查看其实际依赖关系使用,以便他知道需要在测试设置中提供哪些模拟/伪造。如果以后更改了控制器,并且由于更改而导致控制器也使用更改的服务子集,很容易忘记也更新测试设置。有时这可能会导致难以跟踪的错误。与此相反,如果将依赖项显式声明为构造函数参数,则测试设置中将立即出现编译器错误。您可以做的另一件事是为每个控制器单独设置这样的单例代理/服务定位器,但是基本上这很麻烦。

无论您使用的是我提出的解决方案还是答案#1220560的解决方案,都要按照https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals中的说明将请求范围的依赖项注入到单例对象中时必须小心/ dependency-injection#注册您自己的服务,位于“注册您自己的服务”部分的末尾。您可以在此处找到解决此问题的可能解决方案:如何在C#/ ASP中的单例中使用作用域依赖关系 另一个要注意的问题是并发问题:处理多个并发请求的多个线程可能会并发访问单例对象,因此请确保添加与您单例使用的任何非线程安全资源正确同步。

编辑:

我刚刚意识到最初的问题是关于ASP.NET的,而这个答案是针对ASP.NET Core的,因此它可能不适用于“非核心”。