Chapter 3: Controllers

In CA a controller takes input, validates it and convert it to the format best used for the use-cases layer (especially the interactors).
This is where I did the compromises and went away from Clean Architecture. In my application the controllers are MVC-Controllers. They do the input-to-usecase-thing but also have to deliver the final IActionResult
(kind of the HTTP-Response). If I like to do a really clean architecture I need separate the web-stuff and the non-web-stuff for example by introducing a Use-Case-Controller (called by the MVC-Controller). I decided that this is so far will remain a web-application for the foreseeable future so I made the compromise to use the MVC-Controller as my controller.
A simple controller-action in my application looks like this:
[HttpGet] [Authorize(Policy = PolicyNames.RequireTauchboldeOrAdmin)] public async Task Publish(Guid id) { var publishLogbookEntry = new PublishLogbookEntry(id); var result = await mediator.Send(publishLogbookEntry); if (!result) { ShowErrorMessage("Fehler beim Publizieren des Logbucheintrages!"); } ShowSuccessMessage("Logbucheintrag erfolgreich publiziert."); return RedirectToAction("Detail", "Logbook", new {id}); }
Note that this is a controller-action with no further output besides a success message so I don’t use presenters etc. More on presenters and view-models later on.
What the above code does is create a use-case command object of type PublishLogbookEntry
which holds the data used for this simple use-case to publishing an article. In this case its just the ID of the LogbookEntry
to publish.
public class PublishLogbookEntry : IRequest<bool> { public PublishLogbookEntry(Guid logbookEntryId) { LogbookEntryId = logbookEntryId; } public Guid LogbookEntryId { get; } }
Notes:
- The class implements the
IRequest
interface of MediatR. MediatR is a in-process message-queue. It allows me to send requests to one recipients or broadcast notifications to multiple subscribers. I use it to decouple things further more. In this example the controller-action creates aPublishLogbookEntry
request and sends it using MediatR. The controller has not even a clue that there will be a use-case interactor or even a domain-entity. He just sends this request and expect a response from someone. -
The implementation of
ÌRequest
means that this is a “request” object that gets sent to one receiver and returns abool
result to the caller. -
I try to implement as many classes as immutable classes as possible. This is why there is no setter on the
LogbookEntryId
and it can only be set using the constructor and never be changed afterwards. For the “why immutable” you may read this blog from Eric Lippert on Immutability in C#. There will be other places in the code where I use newer C# language features to enforce immutability. -
In addition to the use-case command object I often also implement a validator (see Fluent Validations) for the command. These command validators get picked up by MediatR automatically (if configured correctly), validate the request object and return validation errors before any further processing of the command is done. These things are called pipeline behavior.
Let’s go one to one of the most important peaces of my implementation, the Use-Cases & Interactors …
Categories