1 Dec
2022

Refactoring Bloated Controllers with [FromService]

Category:General Post

In my previous article, Refactoring Bloated Controllers with IServiceProvider, I explored using the Service Locator Pattern to reduce the number of services injected into an ASP.NET MVC controller constructor. While a convenient way to reduce the number of constructor arguments, using IServiceProvider does have some potential downsides.

  1. It obfuscates the services needed by the controller.
  2. When a service is needed in an action method, it must be deliberatively retrieved from the service provider.
  3. One must really dig into the class to see the entirety of each service’s surface area. That is, it can be hard to get a clear picture of which action methods use which services.

It has been a few years since I dug deeply into an ASP.NET MVC app and my chops are a bit dull. Nicely, Dean Ward and Aashish Koirala hit me up on Mastodon (a gratuitous link to my account) and pointed me toward the [FromService] attribute which I can use to decorate arguments to my action methods, passing in a registered service as a method argument.

This takes the code in my action methods from this:

[HttpPost]
public IActionResult ReverseWords(string wordsToReverse)
{
    var wordReversalService = _serviceProvider.GetService<IWordReversalService>() ?? throw new ArgumentNullException("_serviceProvider.GetService<IWordReversalService>()");

    var reversedWords = wordReversalService.ReverseWords(wordsToReverse);

    var model = new ReversedArrayModel()
    {
        InitialArray = wordsToReverse,
        ReversedArray = reversedWords
    };

    return View(model);
}

To this:

[HttpPost]
public IActionResult ReverseWords(
    [FromServices] IWordReversalService wordReversalService, 
    string wordsToReverse)
{
    var reversedWords = wordReversalService.ReverseWords(wordsToReverse);

    var model = new ReversedArrayModel()
    {
        InitialArray = wordsToReverse,
        ReversedArray = reversedWords
    };

    return View(model);
}

Benefits of using this technique

I know I am late to the game on this one, but this feature is simply brilliant. This technique of method injection helps me in a several ways.

  1. Allows me to see almost at a glance what the service dependencies are for a given action method.
  2. Keeps my controller constructor clean except for those dependencies I actually want to inject at a class level, like ILogger.
  3. #2 matters double as it makes it much simpler to test each method because I don’t have to pass in all those null or stubbed constructor arguments.
  4. I don’t need to treat services returned from IServiceProvider as nullable (or use null checks).

I far prefer this to the original refactoring technique in the first iteration of this article. I would propose this method injection technique is better than constructor injection in general. We can save constructor injection for those service instances where we will use them across many methods.

3 thoughts on “Refactoring Bloated Controllers with [FromService]

  1. Regarding number 4, you can use GetRequiredService() instead to get something that is not nullable.

  2. Major reason i used this for years now with huge success is that any HTTP Context (User) is not available at class initialization but is available at the moment of the execution of the Action methods. This way, you no longer need complex and hard to debug approaches.

Comments are closed.