Thumbnail image

Using HttpResults.Results for Minimal API Methods With MiniValidator in .NET Core

About the post

In this blog post, I will show you how to use HttpResults.Results for Minimal API Methods in .NET Core HttpResults.Results is a class that provides a set of typed results for common scenarios, such as Ok, BadRequest, Problem, ValidationProblem, etc. These results can be used to return consistent and meaningful responses from your API methods, without having to write a lot of boilerplate code.

To demonstrate the usage of HttpResults.Results, I will create a simple login method that takes a LoginInput object as a parameter, and returns a LoginPayload object if the login is successful, or an appropriate error result if not. The LoginInput object contains the email and password of the user, and the LoginPayload object contains an AuthToken object that has the JWT token and other information about the user.

The login method will use an IDataContext interface to access the database, and an ITokenService interface to generate the JWT token. These interfaces are injected into the method using dependency injection. The IDataContext interface has a Login method that takes the email and password, and returns a User object if the login is valid, or throws an exception if not. The ITokenService interface has a GenerateToken method that takes the user details and returns a JWT token.

The login method will also use a MiniValidator class to validate the LoginInput object, and return a ValidationProblem result if there are any validation errors. The MiniValidator class is a simple wrapper around FluentValidation that allows you to define validation rules using lambda expressions.

What is Minimal API in .NET Core?

Minimal API is a lightweight and minimalistic way of building HTTP APIs in .NET Core. It is designed to simplify the process of creating APIs without the overhead of traditional ASP.NET Core applications. With Minimal API, you can define your routes and handlers in a more concise and straightforward manner.

There is a example for Minimal API.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () =>
{
    return "hello world";
});

app.Run();

Using HttpResults.Results for Minimal API Methods in .NET Core

In the world of .NET, Minimal API has gained popularity for its simplicity and ease of use. One important aspect of building web applications is handling HTTP responses effectively. In this article, we will explore how to use HttpResults.Results to handle HTTP responses in Minimal API methods.

There is an example for HttpResults.Results with Minimal API.

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", async Task<Results<Ok, ProblemHttpResult, BadRequest, ValidationProblem>>
{
    var results = new List<HttpResult>
    {
        HttpResults.Results.Ok(new { message = "Hello, world!" }),
        HttpResults.Results.BadRequest(new { error = "Bad request" }),
        HttpResults.Results.ValidationProblem(new ValidationProblemDetails(new Dictionary<string, string[]>
        {
            { "username", new[] { "Username is required" } },
            { "password", new[] { "Password must be at least 8 characters long" } }
        })),
        HttpResults.Results.Created(new { message = "Resource created" })
    };

    return results;
});

app.Run();

Handling HTTP Responses with HttpResults.Results

In Minimal API methods, you often need to send appropriate HTTP responses back to clients based on the outcome of your operations. HttpResults.Results is a powerful tool that simplifies this task by providing a convenient way to create and return HTTP results.

Let’s take a look at an example Minimal API method for user login:

app.MapPost("/login", async Task<Results<Ok<LoginPayload>, ProblemHttpResult, BadRequest<RequestMessage>, ValidationProblem>>
    (LoginInput loginInput, IDataContext dataContext, ITokenService tokenService) =>
{
    // Validation using MiniValidator
    MiniValidator.TryValidate(loginInput, out var errors);
    if (errors.Any())
    {
        return Results.ValidationProblem(errors);
    }

    try
    {
        var user = await dataContext.Login(loginInput.Email, loginInput.Password);
        var token = new AuthToken(tokenService.GenerateToken(user.Email!, user.FullName!, user.Picture, user.UUID, user.RolesAsString));

        return Results.Ok(new LoginPayload(token));
    }
    catch (TaskCanceledException ex)
    {
        return Results.BadRequest(new RequestMessage(ex.Message, ProblemConstants.AuthTitle));
    }
    catch (Exception ex)
    {
        return Results.Problem(ex.Message, ProblemConstants.AuthTitle);
    }
});

In this example, we have a Minimal API endpoint for user login. We use HttpResults.Results to create different types of HTTP responses based on the outcome of the login operation.

  • Results.ValidationProblem(errors) is used to return a validation problem response if there are input validation errors.

  • Results.Ok(new LoginPayload(token)) is used to return a successful response with a token payload.

  • Results.BadRequest(new RequestMessage(ex.Message, ProblemConstants.AuthTitle)) is used to return a bad request response in case of a TaskCanceledException.

  • Results.Problem(ex.Message, ProblemConstants.AuthTitle) is used to return a generic problem response for other exceptions.

MiniValidator

The LoginInput class is a simple class that represents the login request from the client. It has properties such as Email and Password.

Here is the code for the LoginInput class:

public class LoginInput
{
    [Required]
    [EmailAddress]
    public string Email { get; set; } = null!;
    
    [Required]
    [MinLength(6)]
    [MaxLength(20)]
    public string Password { get; set; } = null!;
}

The LoginInput class also has some data annotations to specify validation rules for its properties. For example, [Required] means that the property cannot be null or empty, [EmailAddress] means that the property must be a valid email address, etc.

The MiniValidator by Damian Edwards is a simple & awesome wrapper around FluentValidation that allows you to define validation rules using lambda expressions.

There is a example for validation.

MiniValidator.TryValidate(loginInput, out var errors);
if (errors.Any())
{
    return Results.ValidationProblem(errors);
}

Implementing the Login Operation

To provide a better understanding, let’s briefly review the implementation of the Login method, which is called within the Minimal API handler:

public async Task<User> Login(string email, string password)
{
    var user = await DatabaseContext.Users.Where(w => w.Email == email).FirstAsync();

    if (user is null)
    {
        throw new TaskCanceledException("Account not found!");
    }

    if (!user.IsMailValid)
    {
        throw new TaskCanceledException("Account not valid!");
    }

    var isPasswordCorrect = (user.Hash == HashService.HashString(password, user.Salt));

    if (!isPasswordCorrect)
    {
        throw new TaskCanceledException("Password or Email wrong!");
    }

    return user;
}

In the Login method, we perform various checks and validations and throw specific exceptions based on the outcome. These exceptions are then caught in the Minimal API handler, and appropriate HTTP responses are generated using HttpResults.Results.

JSON Result Examples

Here are some examples of the JSON responses that correspond to the HTTP results we discussed:

Validation Problem Response

{
    "errors": {
        "Email": [
            "The Email field is required.",
            "The Email field must be a valid email address."
        ]
    }
}

Successful Response Status: 200 OK

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJmdWxsbmFtZSI6IlVzZXIgTmFtZSIsInBpY3R1cmUiOiJodHRwczovL3d3dy5naXRodWIuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy9tYXN0ZXIvaW1hZ2VzL3VwbG9hZHMvMjAwMDAwMDAvcHVibGljL2ltYWdlL3VwbG9hZHMvMjAxOS8wMy9wbHVnLmpwZyIsInVVSUQiOiJ1c2VyLXVVSUQiLCJyb2xlcyI6WyJhZG1pbiJdfQ.ZPtUdajPYIQ_Rje9rDrUN8yYf5R2FtstY4Wi54TykLc"
}

Bad Request Response Status: 400 Bad Request

{
    "message": "Account not found!",
    "title": "Auth Title"
}

Problem Response Status: 500 Internal Server Error

{
    "message": "An unexpected error occurred.",
    "title": "Auth Title"
}

Conclusion

Handling HTTP responses effectively is crucial when building web APIs, even in Minimal API projects. HttpResults.Results simplifies the task of creating and returning HTTP responses, making your code more readable and maintainable. By using this approach, you can provide meaningful feedback to clients and ensure a smooth API experience.

In this article, we’ve explored how to use HttpResults.Results for Minimal API methods in .NET Core. We’ve also seen how it can be applied in a real-world scenario, such as handling user login operations.

To clarify briefly; With these two methods you can develop complex web services incredibly fast. The Minimal API, combined with MiniValidator, provides a great convenience, and when Results is used on top of them, we write more controllable code for the front-end, and at the same time, we minimize our error probability and check every possibility.

Thank you for reading.