Usage
public class AuthenticateController : Controller { private readonly IActionCommandSystemService _ActionCommandSystemService; public AuthenticateController(IActionCommandSystemService actionCommandSystemService) { this._ActionCommandSystemService = actionCommandSystemService; } public IActionResult Login() { //AuthenticateLoginGetCommand - our command class //ViewResult - the result type return this._ActionCommandSystemService .Execute<AuthenticateLoginGetCommand, ViewResult>(this); } } //Our Command Class public class AuthenticateLoginGetCommand : ActionCommandController { public override ViewResult Execute() { //more logic here //this.ControllerContext = using the same context as the invoker; //this.HttpContext = using the same context as the ; //returns the View with the name Login - same as the invoker return View(); } }
Description
Controllers may tend to be overcrowded when developing large scale applications. Controllers often become the space of multiple responsibilities and complex logic. A good approach would be to separate this logic into separate containers and access them from the controller. This is exactly what MediatR does (see Mediator Design Pattern).
MediatR provides a great approach to move action logic into their own containers (classes) while the job of the controller’s action is to only invoke the appropriate command and expect the result.
While this is a usefull approach, for my personal use I wanted to move all the logic of one action into a separate container, but in that container I wanted to be able to work as I was inside the controller. (I wanted access to the contexts and methos).
The first approach was to create a wrapper class and add the controller as a property (see image below), but it didn’t feel nice to always use this.controller.
My final approach : create a new controller that implements my IActionCommand interface
Code
The implementation will follow the schematics below:
ActionCommands:
public interface IActionCommand { TReturn Execute(); } public abstract class ActionCommandController : Controller, IActionCommand { public abstract TReturn Execute(); }
Command Client as a Service : This service will take the current controller’s context, and using the controller factory (this is provided by the framework using DI), it creates basically a clone of the controller but it uses as a type ActionCommandController<TReturn> : Controller, IActionCommand<TReturn>
public interface IActionCommandSystemService { Tout Execute<Tin, Tout>(Controller controller) where Tin : IActionCommand; } public class ActionCommandSystemService : IActionCommandSystemService { private readonly IControllerFactory _Factory; public ActionCommandSystemService(IControllerFactory factory) { this._Factory = factory; } public Tout Execute<Tin, Tout>(Controller controller) where Tin : IActionCommand { Type commandType = typeof(Tin); ControllerContext controllerContext = new ControllerContext() { ActionDescriptor = controller.ControllerContext.ActionDescriptor, HttpContext = controller.HttpContext, RouteData = controller.RouteData, ValueProviderFactories = controller.ControllerContext.ValueProviderFactories }; controllerContext.ActionDescriptor.ControllerTypeInfo = typeof(Tin).GetTypeInfo(); IActionCommand commandController = this._Factory.CreateController(controllerContext) as IActionCommand; if (commandController == null) { throw new ApplicationException("Invalid Command"); } Tout result = commandController.Execute(); return result; } }
Service Extensions : Just to register this service
public static class ServiceExtensions { public static IServiceCollection AddActionCommandSystemService(this IServiceCollection collection) { return collection.AddTransient<IActionCommandSystemService, ActionCommandSystemService>(); } }
In Startup.cs : add the following snippet in startup file to register this service
public static class ServiceExtensions /* * ActionCommandSystem - delegates action tasks to a command class */ services.AddActionCommandSystemService();