В этой статье мы откроем для себя новые возможности, которые появятся в .Net 7 с Minimal API.
Вопросы, которые мы рассмотрим сегодня
- Возврат нескольких типов результатов из минимальных API
- Самодокументирующийся Todos API
- Группы маршрутов
Вы можете посмотреть полное видео на YouTube
Вы можете найти полный исходный код на github
https://github.com/mohamadlawand087/Net7-MinimalApi-RouteGroup-MultipleResultType
Мы собираемся продолжить работу над проектом из прошлой статьи, где мы реализовали фильтры на Minimal Api, вы можете найти статью здесь
https://dev.to/moe23/net-7-preview-4-minimal-api-filters-1812/
Начало проекта GitHub
https://github.com/mohamadlawand087/Net7-MinimalApi-Filters
После регистрации мы начнем рефакторинг нашего приложения, чтобы использовать последние возможности .Net 7 preview 4.
Первое, что мы сделаем, это рефакторинг нашего существующего приложения, первой частью будет рефакторинг нашей CRUD-операции Todo.
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();
}
}
Теперь нам нужно обновить механизм аутентификации
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();
}
}
После обновления обоих механизмов нам нужно сообщить нашему промежуточному ПО о новых конечных точках
app.MapTodoApi();
app.MapAuthenticationAPi();
Далее нам нужно включить авторизацию на конечных точках
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;
}
Теперь мы собираемся включить группировку маршрутов для нашей конечной точки, первым пунктом нам нужно обновить интеграцию промежуточного ПО до следующего состояния
app.MapGroup("/v1").MapTodoApi();
app.MapGroup("/v1").MapAuthenticationAPi();
Далее мы обновим связку конечных точек для Todo и авторизации следующим образом
// 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;
}
// Authorisation
public static IEndpointRouteBuilder MapAuthenticationAPi(this IEndpointRouteBuilder routes)
{
routes.MapPost("/accounts/login", Login);
return routes;
}
Давайте обновим наше промежуточное ПО
app.MapGroup("/v1").RequireAuthorization().MapCrudTodoApi();
app.MapGroup("/v1").MapAuthenticationForApi();
По всем вопросам обращайтесь в комментарии ниже