I have a asp.net Core web API with the following structure:
View Layer: API endpoints
|
V
Controller Layer: Controller classes implementing endpoints
|
V
Business Logic Layer: Services
|
V
Data Access Layer: Proxy classes to our backend
There are a series of endpoints that all follow this structure. Most of the APIs are purely data queries to a backend, but some of the APIs do allow for the calling client to submit data, as well.
What I'm struggling with is finding a clean way to consolidate the various validations that must occur on this submitted data.
My original intent was that the Controller layer would be very simple and hand off all real "work" (including validation) to the Services layer, and the Controller layer would only be responsible for sending the appropriate HTTP response. My issue, though, is that with the service doing all the real work, how best to communicate back up to the Controller what it should be returning.
I can capture some of the basic validations using data annotations, and in most cases that might actually be enough in terms of data validation. But that doesn't cover other issues like more complex validations, or failures in the service/data access layer WRT read/writes, etc.
Some ideas I've mulled over:
Make the services layer aware of the IActionResult interface and make it responsible for determining what to return to the calling client. This then mixes the Controller and Service layer, but makes the Controller layer pretty lean.
Create various "OperationResults" objects for the various Services calls which would encapsulate any error messages, exceptions, error codes, etc, to be interpreted by the Controller layer to determine what http response to send back to the client. This is a cleaner separation since the Service layer wouldn't mix with the http code at all, but does then turn the Controller classes into a big set of switches to determine what to send back.
Split up the validation based on what makes sense to capture at the Controller vs. Services level. For example, a controller being passed a null object to save should clearly just reject it and return BadRequest(), but it seems redundant to add the check there when the services layer would have its own reasons for wanting to check that, too.
I'm admittedly somewhat new to .net web API and .net core in general, so if there's some obvious functionality available I'm not taking advantage of, I'd love to know.
Any advice would be appreciated, thanks.
The main idea is correct - you need to separate normal and error flows in your code. One of the main approaches is to use the .NET Exceptions to indicate that normal flow is not possible.
For input validation use ActionFilter
. You may have global filters for all controllers or define specific per action. See Filters section in documentation.
During controller action execution you should raise exceptions as soon as possible and stop further execution. And yes, the Exception may be raised on any of the levels (Service/Business layer, DA layer, etc).
How to handle raised exception?
Using provided by ASP.NET Core error handling approaches (like ExceptionHandler, or Exception Filters), you may to analyze exceptions and generate appropriate/different responses accordingly. See related SO Error handling in ASP.NET Core 1.0 Web API (Sending ex.Message to the client) question for example. There is also the error-handling section in documentation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With