Preventing Redundant DI Code in ASP.NET Core
By FoxLearn 2/3/2025 8:17:39 AM 52
For instance, you may find yourself repeatedly injecting the same services into various controllers. This redundancy violates the DRY (Don’t Repeat Yourself) principle, making your code harder to maintain and more error-prone.
Understanding ASP.NET Core’s Base Controller Classes
In ASP.NET Core, there are two primary base controller classes: ControllerBase
and Controller
.
- ControllerBase: This class is designed for API controllers. It implements the
IController
interface and provides methods for routing,HttpContext
integration, and managing things likeViewData
andTempData
. It is intended for API-centric applications where views aren’t needed. - Controller: This class extends
ControllerBase
and includes additional functionality for working with views (likeView()
andRedirect()
). It’s typically used in ASP.NET Core MVC applications.
For API projects, you should derive your controllers from ControllerBase
. For MVC projects, use Controller
.
Create a Custom Base Controller to Avoid Redundancy
To reduce redundancy in dependency injection across controllers, you can create your own base controller that encapsulates common services. Here's how to implement it.
1. Define Your Entity Class
Let's start with a simple Order
entity:
public class Order { public int Id { get; set; } public int CustomerId { get; set; } public string Address { get; set; } }
2. Create an Interface for Service Logic
Define an interface that declares the service methods needed by your controllers:
public interface IOrderManager { void Process(Order order); }
3. Implement the Service
Now, implement the interface in a concrete service class:
public class OrderManager : IOrderManager { public void Process(Order order) { // Implementation here... } }
4. Define the Custom Base Controller
Create a base controller class that handles common service injections. This will help eliminate the need to inject these services into each individual controller.
[Route("api/[controller]")] [ApiController] public class BaseController : ControllerBase { protected readonly ILogger<BaseController> _logger; protected readonly IOrderManager _orderManager; public BaseController(ILogger<BaseController> logger, IOrderManager orderManager) { _logger = logger; _orderManager = orderManager; } }
5. Create a Derived Controller
Now, create a controller that derives from the custom BaseController
. This allows you to access the injected services without repeating the DI code in each controller.
[Route("api/[controller]")] [ApiController] public class OrderController : BaseController { public OrderController(ILogger<OrderController> logger, IOrderManager orderManager) : base(logger, orderManager) { } [HttpGet] public string Get() { _logger.LogInformation("Getting order details"); return "OrderController: Get method"; } [HttpPost] public IActionResult Process(Order order) { _orderManager.ProcessOrder(order); return Ok("Order processed"); } }
The code won’t compile because you need to implement a constructor in the derived controller with the same arguments as the base controller. This would defeat the purpose of using a base controller to reduce redundancy.
To solve this, you can use HttpContext.RequestServices.GetService
to access the services within the controller. However, note that HttpContext
will be null if accessed in the constructor, so services should be resolved during the request lifecycle instead.
6. Resolve Services Using HttpContext.RequestServices
This allows you to resolve services dynamically during the request lifecycle.
Here’s how to modify your base controller to use HttpContext.RequestServices
:
public abstract class BaseController<T> : ControllerBase where T : BaseController<T> { private ILogger<T> _logger; protected ILogger<T> Logger => _logger ?? (_logger = HttpContext.RequestServices.GetService<ILogger<T>>()); }
And update your derived controller like this:
[Route("api/[controller]")] [ApiController] public class OrderController : BaseController<OrderController> { private readonly IOrderManager _orderManager; public OrderController(IOrderManager orderManager) { _orderManager = orderManager; } [HttpGet] public string Get() { Logger.LogInformation("Inside Get method"); return "OrderController: Get method"; } [HttpPost] public void ProcessOrder(Order order) { _orderManager.ProcessOrder(order); } }
7. Register Services in ConfigureServices
Finally, ensure that your services are properly registered in the ConfigureServices
method of your Startup
class:
public void ConfigureServices(IServiceCollection services) { services.AddTransient<IOrderManager, OrderManager>(); services.AddControllers(); }
Benefits of Using a Base Controller
By using a custom base controller:
- Code Reusability: You centralize common functionality (like dependency injection) in one place, making your code cleaner and easier to maintain.
- Adherence to DRY Principle: You avoid repetitive DI code in each controller.
- Easier Testing: With dependencies injected into the base controller, unit testing becomes more straightforward.
Avoiding redundancy in DI code in ASP.NET Core can greatly improve your project's maintainability. By implementing a base controller, you can inject common dependencies once and reuse them across multiple controllers, reducing boilerplate code and making your application easier to manage.
- Options Pattern In ASP.NET Core
- Implementing Rate Limiting in .NET
- IExceptionFilter in .NET Core
- Repository Pattern in .NET Core
- CRUD with Dapper in ASP.NET Core
- How to Implement Mediator Pattern in .NET
- How to use AutoMapper in ASP.NET Core
- How to fix 'asp-controller and asp-action attributes not working in areas'