Request Validation for Asp.NET API with Fluent Validation
Context
One of the most basic requirements for every API is to provide a solid and consistent request validation experience for the consumers, and the reason is, whoever is consuming your API for their platform, need to give their customers a smooth and meaningful experience as well.
As an example, if someone’s app depends on your API, there should be a difference for request failure when “they” send wrong or incomplete information, or the API simply malfunctions, or is temporarily unavailable. Now, as you can see, that accounts for three different types of response to a customer:
- Bad Data: End user must be informed about their input, and this can be fixed immediately.
- Internal Server Error: Consumers must inform their users that there is a problem with their system, and they are working on it. A notification needs to also be sent to you to fix your API’s underlying issue.
- Temporary Problem: Type of a problem which could happen from time to time like network latency and the user needs to try again shortly afterwards.
By the way, this topic belongs to the series to set up an Asp.NET API for production use.
- API Route Versioning
- Configuration Management
- Secret Management
- Monitoring
- Database
- Documentation
- CORS
- Request Validation
- Global Exception Handling
- URL Rewriting
- Deploy .NET API to Azure App Service
End Game
Now talking about the above experiences, the response to clients’ requests should be information rich, in a way to help them solve the problem, while not exposing too much detailed information that would violate some security non-negotiables. This is an example response for an invalid request sent by a client.
There are a bunch of information you can learn from this response.
- 404 Status Code: 404 means resource not found. However, resource could refer to the URL, or the customer in question. For that we can refer to the body.
- Payload: By looking at the payload, it is clear now the request was received by the server, and was processed. That means the URL is correct, and the customer-Id was wrong, for example. You can also have dedicated codes for each scenario, so if your client mentioned a code to you, you would know exactly what happened and what was the experience.
The other example below is about a 400 Bad Request error, which you can clearly understand and see that the identifiable information was missing in the request, and how you should fix it.
Although these are just a few situations which you need to cover amongst many, I hope it signifies the importance of how you respond to requests and what data you send back to your API customers, so they have all the information they need to provide that meaningful experience for their clients as well.
Now let’s dive in and cover all the elements at our disposal through which we can send a meaningful response.
1. HTTP Status Code
It is pretty much the first and one of the most important elements in the response that you use to let your clients know what is happening with their request. Below, you can find some of the most common status codes used on an API.
Success Responses
- 200 OK: The request has been a success. A 200 Get Request has returned the requested data in the body, a 200 HEAD request has returned the headers with no body, a 200 PUT/POST has the result of the action in the body.
- 201 Created: The request has been a success, and a new resource has been created.
- 202 Accepted: Used in batch processing and operations with delay, in a queue for example, the message has been received but will be actioned at a later time.
- 204: No Content: There is no content to send for this request. Perhaps headers might have some information.
Redirection Responses
- 301 Moved Permanently: The target URL has been permanently moved, and the replacement URL is given in the response.
- 302 Found: The target URL has been temporarily moved, and will be made available in future.
- 307 Temporary Redirect: The server asks the client to get the resource at another Url temporarily.
- 308 Permanent Redirect: The server asks the client to get the resource at another URL permanently, and the new location is sent in a response header called Location.
Client Error
- 400 Bad Request: The request contains invalid data.
- 401 Unauthorised: The client has not been authenticated yet, or you forgot to send the credentials such as Bearer token or Basic Auth header.
- 403 Forbidden: The client’s identity has been established, but the client doesn’t have any right to access the resource.
- 404 Not Found: The server cannot find the requested resource.
- 405 Method Not Allowed: The request URL and method are known but it is not supported by the server. As an example this happens when a Post route is called with a Get method.
- 408 Request Timeout: A response sent by servers to indicate it was shut down as it was idle for too long.
Server Error
- 500 Internal Server Error: The server has an internal error and doesn’t know how to handle it.
- 503: Service Unavailable: The server is not ready to handle the request, for example when it is down, stopped, or overloaded.
For a complete list of status errors, refer to mozilla.
2. HTTP Response Header
Which is the other aspect of a HTTP response, to send information as a response header to indicate a specific situation. An example could be how the browsers/servers handle CORS (Cross Origin Resource Sharing), through which they send a header in the response (such as Access-Control-Allow-Origin, or Access-Control-Allow-Methods) if a request method/origin is allowed to take place or not. For more information on CORS, refer to my post “All” You Need to Implement CORS for Asp.NET 5.0 Web APIs.
3. HTTP Response Body
Receiving information in the body, especially for GET, POST requests is perhaps why you made the call in the first place. However, I have a different use case for it here.
As an example, suppose you have an endpoint that searches for clients based on client Id, and returns the client information in the body.
However, when there is no client found, the server sends a 404 Not Found as the response. The challenge however is the client could receive the same 404 Not Found if the URL was wrong, and we need to find a way to differentiate between these two cases. The solution could be to include a code or indicator in the response body to indicate which is the case.
Request Validation
Now that we know the importance of providing users with enough information about their requests and how it was processed, let’s focus on how we can configure our API to help us do that.One aspect of such meaningful experience is Request Validation (400 Error Range). For that purpose I am using a library called Fluent Validation.
Configuration
Fluent Validation Library is a popular library which has been around for a while, and in terms of providing a flexible and comprehensive validation experience has a proven track record. In this post, we will cover the common scenarios for request validation, and how to configure it for your Asp.NET Web API.
1. Packages
Here is the package you need to continue with the rest of this post, which you can install from the below link:
2. Code
2.1 Central Implementation: One of the advantages of FluentValidation library is the fact that it will keep your controllers clean, so no more Model validation in there. It provides a central location which you can include all your model validations.
Although this method is covered using FluentValidation, there is no request validation logic behind the controller, which is pretty clean. Instead, what we do is, we store all our validation logic in one folder like below:
And each validator can contain a whole lot of Strongly Typed validation logic around your models.
2.2 It’s Fluent: As the name suggest the validation style is fluent, meaning you can chain all the validation rules together.
2.3 Injection: Next we need to inject our validation logic into our ecosystem, so it will be called when receiving requests which is done through the below snippets, and called in your API’s Configure Services method, that basically registers all the validators inside StartUp’s assembly.
And finally the optional attribute that I defined, because I needed all the Error responses to look consistent for our clients. They all have the same structure, and consist of three fields like mentioned in Figure 2. To achieve that, I have an Error class which I fill and return in every Error scenario, be it from 400 or 500 range. For that reason, I have defined the below attribute which I have injected in the above Validation Configuration as well.
Note
Configuration, plumbing and troubleshooting your software foundation take a considerable amount of time in your product development. Consider using Pellerex which is a complete foundation for your enterprise software products, providing source-included Identity and Payment functions across UI (React), API (.NET), Pipeline (Azure DevOps) and Infrastructure (Kubernetes).
Now even in other error scenarios such as below, I return a consistent Error object everywhere, so my clients can have a predictable experience.
Pellerex Foundation: For Your Next Enterprise Software
How are you building your current software today? Build everything from scratch or use a foundation to save on development time, budget and resources? For an enterprise software MVP, which might take 8–12 months with a small team, you might indeed spend 6 months on your foundation. Things like Identity, Payment, Infrastructure, DevOps, etc. they all take time, while contributing not much to your actual product. These features are needed, but they are not your differentiators.
Pellerex does just that. It provides a foundation that save you a lot development time and effort at a fraction of the cost. It gives you source-included Identity, Payment, Infrastructure, and DevOps to build Web, Api and Mobile apps all-integrated and ready-to-go on day 1.
Check out Pellerex and talk to our team today to start building your next enterprise software fast.