DEV Community

Cover image for .Net 7 (Preview 4) - Minimal API - Multiple Result Type - Route Groups
Mohamad Lawand
Mohamad Lawand

Posted on

.Net 7 (Preview 4) - Minimal API - Multiple Result Type - Route Groups

In this article we will discover more feature which are coming to .Net 7 with Minimal API

The points we are going to cover today

  • Return multiple result types from minimal APIs
  • A self-documenting Todos API
  • Route Groups

You can watch the full video on YouTube

You can find the full source code on github
https://github.com/mohamadlawand087/Net7-MinimalApi-RouteGroup-MultipleResultType

We are going to continue working on the project from last article where we implemented filters on Minimal Api you can find the article here

https://dev.to/moe23/net-7-preview-4-minimal-api-filters-1812/

Starting project GitHub

https://github.com/mohamadlawand087/Net7-MinimalApi-Filters

Once we check out we will start refactoring our app to utilise the latest features within .Net 7 preview 4

The first item we will do is refactor our existing application, the first part will be the refactoring of our Todo CRUD operation

static class TodoApiV1
{
        // Static method to integrate with the .Net middleware 
        // Build the end route to integrate the different endpoints
    public static IEndpointRouteBuilder MapTodoApi(this IEndpointRouteBuilder routes)
    {
        routes.MapGet("/v1/items", GetAllItems);
        routes.MapGet("/v1/items/{id}", GetItem);
        routes.MapPost("/v1/items", CreateItem).AddFilter<ValidationFilter<Item>>();
        routes.MapPut("/v1/items/{id}", UpdateItem).AddFilter<ValidationFilter<Item>>();
        return routes;
    }

        // Get All Items
    public static async Task<Ok<List<Item>>> GetAllItems(ApiDbContext db)
    {
        return TypedResults.Ok(await db.Items.ToListAsync());
    }

        // Get a single item
    public static async Task<Results<Ok<Item>, NotFound>> GetItem(int id, ApiDbContext db)
    {
         return await db.Items.FirstOrDefaultAsync(x => x.Id == id) is Item item
         ? TypedResults.Ok(item)
         : TypedResults.NotFound();  
    }

        // Create a new item
    public static async Task<Results<Created<Item>, BadRequest>> CreateItem(Item item, ApiDbContext db)
    {
        if (await db.Items.FirstOrDefaultAsync(x => x.Id == item.Id) != null)
        {
            return TypedResults.BadRequest();
        }

        db.Items.Add(item);
        await db.SaveChangesAsync();
        return TypedResults.Created($"/Items/{item.Id}", item);
    }

        // Update the item
    public static async Task<Results<NoContent, NotFound>> UpdateItem(Item item, int id, ApiDbContext db)
    {
        var existItem = await db.Items.FirstOrDefaultAsync(x => x.Id == id);
        if(existItem == null)
        {
            return TypedResults.NotFound();
        }

        existItem.Title = item.Title;
        existItem.IsCompleted = item.IsCompleted;

        await db.SaveChangesAsync();
        return TypedResults.NoContent();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now we need to update the authentication mechanism

static class TodoAuthentication
{
    public static IEndpointRouteBuilder MapAuthenticationAPi(this IEndpointRouteBuilder routes)
    {
        routes.MapPost("/v1/accounts/login", Login);
        return routes;
    }

    public static async Task<Results<Ok<string>, UnauthorizedHttpResult>> Login(UserDto user, IConfiguration _config)
    {
        if(user.username == "admin@mohamadlawand.com" && user.password == "Password123")
        {
            var secureKey = Encoding.UTF8.GetBytes(_config["Jwt:Key"]);

            var issuer = _config["Jwt:Issuer"];
            var audience = _config["Jwt:Audience"];
            var securityKey = new SymmetricSecurityKey(secureKey);
            var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha512);

            var jwtTokenHandler = new JwtSecurityTokenHandler();

            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new [] {
                    new Claim("Id", "1"),
                    new Claim(JwtRegisteredClaimNames.Sub, user.username),
                    new Claim(JwtRegisteredClaimNames.Email, user.username),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                }),
                Expires = DateTime.Now.AddMinutes(5),
                Audience = audience,
                Issuer = issuer,
                SigningCredentials = credentials
            };

            var token = jwtTokenHandler.CreateToken(tokenDescriptor);
            var jwtToken = jwtTokenHandler.WriteToken(token);
            return TypedResults.Ok(jwtToken);  
        }

        return TypedResults.Unauthorized();
    }

}
Enter fullscreen mode Exit fullscreen mode

Once we have updated both we need to inform our middleware about these new endpoints

app.MapTodoApi();
app.MapAuthenticationAPi();
Enter fullscreen mode Exit fullscreen mode

Next we need to enable Authorisation on the endpoints

public static IEndpointRouteBuilder MapTodoApi(this IEndpointRouteBuilder routes)
{
    routes.MapGet("/v1/items", GetAllItems).RequireAuthorization();
    routes.MapGet("/v1/items/{id}", GetItem).RequireAuthorization();
    routes.MapPost("/v1/items", CreateItem)
                .AddFilter<ValidationFilter<Item>>()
                .RequireAuthorization();
    routes.MapPut("/v1/items/{id}", UpdateItem)
                .AddFilter<ValidationFilter<Item>>()
                .RequireAuthorization();
    return routes;
}
Enter fullscreen mode Exit fullscreen mode

Now we are going to enable route grouping for our endpoint, the first item we need to update the middleware integration to the following

app.MapGroup("/v1").MapTodoApi();
app.MapGroup("/v1").MapAuthenticationAPi();
Enter fullscreen mode Exit fullscreen mode

Next we update the Endpoint mapping for both our Todo and our Authorisation to the following

// Todo
public static GroupRouteBuilder MapTodoApi(this GroupRouteBuilder routes)
{
    routes.MapGet("/items", GetAllItems);
    routes.MapGet("/items/{id}", GetItem);
    routes.MapPost("/items", CreateItem)
                .AddFilter<ValidationFilter<Item>>();
    routes.MapPut("/items/{id}", UpdateItem)
                .AddFilter<ValidationFilter<Item>>();
    return routes;
}
Enter fullscreen mode Exit fullscreen mode
// Authorisation
public static IEndpointRouteBuilder MapAuthenticationAPi(this IEndpointRouteBuilder routes)
{
    routes.MapPost("/accounts/login", Login);
    return routes;
}
Enter fullscreen mode Exit fullscreen mode

Let us update our middleware

app.MapGroup("/v1").RequireAuthorization().MapCrudTodoApi();
app.MapGroup("/v1").MapAuthenticationForApi();
Enter fullscreen mode Exit fullscreen mode

For any questions please comment down below

Discussion (0)