Validating API input is crucial for ensuring data integrity, security, and the overall functionality of your application. When designing a RESTful API (or any kind of web service), it's important to validate the inputs before processing them
We will implement 3 ways to validate input requests.
1. Custom Middleware for Validating Input
We will be creating custom middleware to add validation for all input requests and that will be centralize code for all api endpoints.
Below will be flow diagram for .net core api request processing and we added middleware which will work like if Validation fails then middleware will send response back to User.
Steps for implementations
a. Create RequestValidationMiddleware class
b. Inject RequestDelegate object in contructor : this will take care to pass request to next middleware in pipeline.
c. Add logic in InvokeAsync method which will take httpcontext as input request and now we will have all data which input request should have.
d. Register middleware in statup.cs.
public class RequestValidationMiddleware { private readonly RequestDelegate _next; private readonly ILogger<RequestValidationMiddleware> _logger; public RequestValidationMiddleware(RequestDelegate next, ILogger<RequestValidationMiddleware> logger) { _next = next; _logger = logger; } public async Task InvokeAsync(HttpContext httpContext) { // Check if the request is JSON (Content-Type: application/json) if (httpContext.Request.ContentType?.ToLower() == "application/json") { // Read the body content var body = await new StreamReader(httpContext.Request.Body).ReadToEndAsync(); if (string.IsNullOrWhiteSpace(body)) { httpContext.Response.StatusCode = 400; // Bad Request await httpContext.Response.WriteAsync("Request body cannot be empty."); return; } // Here you would usually deserialize the body into a specific object (e.g., User model) // Let's simulate checking for a required field (for example, Name is required) if (!body.Contains("Name")) { httpContext.Response.StatusCode = 400; // Bad Request await httpContext.Response.WriteAsync("Name is required."); return; } } // Proceed to the next middleware or controller if validation passes await _next(httpContext); } }
API end point code
[HttpPost] public IActionResult CreateUser([FromBody] User user) { return Ok($"User {user.Name} created successfully!"); } /// ///Model class public class User { public string Name { get; set; } public string Email { get; set; } }
Execution and verification:
It is required to add Name in parameter but i am only adding email in input so we should get error.
//INPUT { "email": "sanjay@gmail.com" }
When we execute this request middleware will call and below code will execute: we can see we are getting JSON data which we are passing from user input.
As this is invalid input we are getting status 400 in response.
2. Model Validation with Data Annotations
One of the most common and clean ways to validate input in ASP.NET Core is using data annotations. This technique uses attributes to define validation rules on model properties.
Let's have a code below, we are going to create user model and will add some validation attributes.
public class UserModel { [Required] [StringLength(50, MinimumLength = 5)] public string Name { get; set; } [EmailAddress] public string Email { get; set; } [Range(18, 100)] public int Age { get; set; } }
We have defined Range for Age, Emailaddresss and Name and mandatory.
Validation Code at API end point
public class UserController : Controller { [HttpPost] public IActionResult Post(UserModel user) { if (!ModelState.IsValid) { return BadRequest(ModelState); // Returns validation errors to the client } return Ok(); } }
We have used Modelstate which is defined on controller base class and it take care for all validations.
Testing: below is running screen of API and we have added valid input.
Negative scenario Testing.
a. I have removed name from input, now we should get error message as we made it required.
{ "email": "user@example.com", "age": 34 }
Error as expected.
b. Let's make Age as out of range like 150.
{ "name": "devesh", "email": "user@example.com", "age": 150 }
Output
we can see we are getting expected validation messsage when we pass invalid input to API.
3. Validation Requests using Action Attributes
we have one problem in above approach, we have to add code to validate input inside controller action.
so if we have 100 of controllers then we need to add similar code on each endpoint or function which some time difficult to manage.
in this case we will create centralize place from where request get validate.
We are going to create Actionfilter class via below way and will validate all input data inside OnActionExecuting method.
public class ValidateInputActionFilter : ActionFilterAttribute { public ValidateInputActionFilter() { } public override void OnActionExecuting(ActionExecutingContext context) { // Check if the model state is valid if (!context.ModelState.IsValid) { // If the model state is invalid, log the error and return a bad request response context.Result = new BadRequestObjectResult(context.ModelState); return; // Prevents the action method from being called } base.OnActionExecuting(context); } }
Apply Attribute
[ValidateInputActionFilter] [HttpPost] public IActionResult Post(UserModel user) { return Ok(); }
Testing
1. Invalid Email
{ "name": "string", "email": "usera@@@@@@example.com", "age": 100 }
Result. we can see this is working and getting validation message.
Conclusion:
We have learnt 3 approaches but Best approaches will be custom middleware because by using middleware for validation, you can centralize all your validation logic in one place, making it easier to maintain and update.
Copyrights © 2024 letsupdateskills All rights reserved