CSharp Web API Concepts
Introduction
This pages is to capture some useful thing around the Web API.
Middleware
I was trying to match up my Typescript work with C# so I started off be looking a middleware. Not a difficult concept. So for .NET 8.0 for me it consisting of
- Making and Extension
- Applying the extension
- Implementing the middleware
Pretty straight forward
// Create an extension
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<CustomMiddleware>();
}
// Applying the extension
...
var app = builder.Build();
app.UseCustomMiddleware();
app.MapGet("/", (HttpContext context) =>
{
return "Hello World!";
});
...
// Implementing the middleware
public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
Console.WriteLine($"Request: {context.Request.Path}");
await _next(context);
}
}
Parse the Query Parameters
For Typescript it was quite easy to do this. Maybe a bad day but took me a while to grasp this. Anyway this is what I did, I made a class to hold the variables and parse the data. Does looks repeatable but feel I could do better
namespace Vehicles.Api.Requests;
using System.Reflection;
public class GetVehicleRequest
{
public string? Rego { get; set; }
public int? RC { get; set; }
public static ValueTask<GetVehicleRequest?> BindAsync(HttpContext context, ParameterInfo parameter)
{
string? rego = context.Request.Query["rego"].FirstOrDefault();
int.TryParse(context.Request.Query["rc"], out var rc);
var result = new GetVehicleRequest
{
Rego = rego,
RC = rc
};
return ValueTask.FromResult<GetVehicleRequest?>(result);
}
}
We can now put this in the endpoint and pass it to the service validated so SQL injections issues are reduced.
...
app.MapGet("/vehicles", async (GetVehicleRequest request, IVehicleService service) =>
{
var vehicleServiceDtos = await service.GetVehiclesAsync(request);
return TypedResults.Ok(vehicleServiceDtos);
})
Validation of Request
So I implement this with middleware in typescript too but watching youtube has led me to two approaches
- Fluent Validation (3rd party)
- Data Validation (Microsoft
Fluent Validation
Lets install it first
dotnet add package FluentValidation.DependencyInjectionExtensions
Add the service
...
builder.Services.AddValidatorsFromAssemblyContaining<Program>();
...
I am going to valid a request for a vehicle which can have a rego or a responsibility code. The hard part was getting the query parameters into the mix but the validation was easy
- Create a Validation Filter
- Create a Validator
- Add to Endpoint
Create a Validation Filter
This was generic and I guess I do not know much about how it works. I am unclear if this is something I need to know or this is what everyone else does. It looks a bit like middleware by another name to me.
using FluentValidation;
namespace Vehicles.Api.Filters
{
public class ValidationFilter<TRequest> : IEndpointFilter
{
private readonly IValidator<TRequest> validator;
public ValidationFilter(IValidator<TRequest> validator)
{
this.validator = validator;
}
public async ValueTask<object?> InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)
{
var request = context.Arguments.OfType<TRequest>().First();
var validationResult = await validator.ValidateAsync(request, context.HttpContext.RequestAborted);
if (!validationResult.IsValid)
{
return TypedResults.ValidationProblem(validationResult.ToDictionary());
}
return await next(context);
}
}
}
Create a Validator
This looks a lot like the express-validator in typescript and sure there are tons of options. Might make a custom one to show the old grey cells still work. For now
using FluentValidation;
using Vehicles.Api.Requests;
namespace Vehicles.Api.Validators;
public class GetVehicleValidator : AbstractValidator<GetVehicleRequest>
{
public GetVehicleValidator()
{
RuleFor(x => x.RC).InclusiveBetween(1, 9999);
RuleFor(x => x.Rego).MaximumLength(6);
}
}
Add to Endpoint
Ezzy pezzy lemon squezzy. Well some bits to learn for me. Loving extension but worried it is hard to navigate to them. Anyway this is how to add validation to the endpoint. Make an extension to provide the two operations
public static class RouteHandlerBuilderExtensions
{
public static RouteHandlerBuilder WithRequestValidator<TRequest>(this RouteHandlerBuilder builder)
{
return builder.AddEndpointFilter<ValidationFilter<TRequest>>()
.ProducesValidationProblem();
}
}
And add it to the endpoint
...
app.MapGet("/vehicles", async (GetVehicleRequest request, IVehicleService service) =>
{
var vehicleServiceDtos = await service.GetVehiclesAsync(request);
return TypedResults.Ok(vehicleServiceDtos);
})
.WithRequestValidator<GetVehicleRequest>()
.WithName("GetVehicles")
.WithOpenApi();
}