How to make a controller a single instance for each application in ASP.NET MVC?

Time controllers develop many dependencies, and instantiating a controller becomes too expensive for each request (especially with DI). Is there any solution for creating controller singlets?

+6
source share
2 answers

Creating controller instances is pretty quick and easy. What becomes too expensive is to create dependencies for each request. So, you really need a lot of controllers that use the same dependency instances.

eg. you have a controller

public class SalesController : Controller { private IProductRepository productRepository; private IOrderRepository orderRepository; public SalesController(IProductRepository productRepository, IOrderRepository orderRepository) { this.productRepository = productRepository; this.orderRepository = orderRepository; } // ... } 

You must configure the dependency injection infrastructure to use the same repository instances for all applications (remember that you may have synchronization problems). Now creating dependencies is no longer expensive. All dependencies are created only once and reused for all queries.

If you have many dependencies, and you are worried about the cost of getting a link to an instance of each dependency and providing these links to an instance of the controller (which, in my opinion, will be very expensive), then you can group your dependencies (for example, enter refactoring of parameter objects):

 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; } // ... } 

Now you have one dependency that will be introduced very quickly. If you configure your dependency injection infrastructure to use singleton SalesService, then all SalesControllers will use the same service instance. Creating controllers and providing dependencies will be very fast.

+16
source

So, first the answer to the original question:

 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(); } 

As pointed out in the original question, if the controllers have large dependency trees, consisting mainly of Scoped or Transient queries, then creating them individually for each query may have some trace in the scalability of your application (in Java, for example, servlet instances are single by default exactly because of this reason). Although usually the processor and the real time required to create even a large dependency tree are insignificant (unless you have heavy computing or network communication in the constructors of your components, which is almost never a good idea for transient or requested components), memory usage Trace is something to be reckoned with. In the case of using a common database of DB-Web applications, the main factor is the limitation on the number of simultaneous queries that can be processed by one node machine. If each request has a separate copy of a large dependency tree, together they can consume a significant amount of memory (by the way, another thing to watch out for is the initial stack size for the new thread).

The accepted answer 1220560 also solves the problem, but I consider it an ugly hack, and it has some disadvantages: you need to create this artificial single-user service, which will be used by your controllers either as a service locator or as a proxy server for other services. If you have only one such singleton object for all your controllers, then you effectively hide the real dependencies of your controller: for example, if someone wants to write a unit test for your controller, he needs to carefully analyze its implementation to see what dependencies are on actually uses it so that he knows what bullying / fakes he should provide in his test setup. If you later change your controller and, as a result of your change, a subset of the services in which your controller also uses the changes, it is very easy to forget to also update the test setup. This can sometimes lead to errors that are difficult to track. In contrast, if your dependencies are explicitly declared as constructor parameters, you will immediately receive a compiler error in the test setup. Another thing you can do is to have a separate proxy / service syntax for each controller, but then it is very difficult.

Regardless of whether you use the solution I proposed or the answer from answer # 1220560, you should be careful when injecting Scoped dependencies in single-user objects, as described in https://docs.microsoft.com/en-us/aspnet/core/ fundamentals / dependency-injection # registering-your-own-services at the end of the "Registering Your Own Services" section. You can find possible solutions to this problem here: how to use scope-dependent single-element mode in C # / ASP Another thing that you should pay attention to is the concurrency problem: several threads can handle Singleton objects at the same time, processing different simultaneous requests, therefore, be sure to add the correct synchronization with any insecure resources that your singleton uses.

change

I just realized that the original question was about ASP.NET, and this answer is for ASP.NET Core, so it probably won't work for non-core ones.

+1
source

Source: https://habr.com/ru/post/953004/


All Articles