CSharp Web API Concepts

From bibbleWiki
Jump to navigation Jump to search

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();
    }