ЧАСТЬ 1: Создание вашего первого минимального API с помощью .NET 7
Здравствуйте, сегодня я хотел бы обсудить, как обрабатывать исключения в минимальном API .NET 7 с помощью специального промежуточного программного обеспечения исключений. Кроме того, мы углубимся в настройку Serilog для ведения журналов в рамках этой минимальной настройки API. Пример, который я буду использовать, основан на прошлом проекте, который также был создан с использованием минимального API .NET.
Начнем с изучения специального промежуточного программного обеспечения исключений. Промежуточное ПО служит мощным инструментом для глобального перехвата исключений в вашем приложении. Представьте, что у вас есть функция, которая использует стороннюю службу. Вы отправляете запрос на получение данных, но по какой-то причине не получаете ожидаемого ответа, что приводит к неожиданной ошибке. Хотя локальные методы обработки ошибок, такие как блоки try-catch или операторы if-else, могут быть эффективными, они не всегда доступны или правильно реализованы. Именно здесь в игру вступает глобальное промежуточное программное обеспечение исключений.
Промежуточное программное обеспечение глобальных исключений действует как простое расширение, предназначенное для обработки ошибок на протяжении всего конвейера вашего приложения. У вас есть возможность вернуть пользовательскую модель или напрямую написать сообщение об ошибке в ответе. В этом примере я создал класс ResponseModel.cs
специально для использования при генерации ответов об ошибках.
Модель ответа
public class ResponseModel { public bool Success { get; set; } public string Message { get; set; } public int StatusCode { get; set; } }
Создание ExceptionHandleMiddleware
public class ExceptionHandleMiddleware { private readonly RequestDelegate _next; public ExceptionHandleMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception ex) { await HandleException(ex, httpContext); } } private async Task HandleException(Exception ex, HttpContext httpContext) { if (ex is InvalidOperationException) { httpContext.Response.StatusCode = 400; //HTTP status code //httpContext.Response.WriteAsync("Invalid operation"); //httpContext.Response.WriteAsync("Invalid operation"); await httpContext.Response.WriteAsJsonAsync(new ResponseModel { Message = "Invalid operation", StatusCode = 400, Success = false }); } else if (ex is ArgumentException) { await httpContext.Response.WriteAsync("Invalid argument"); } else { await httpContext.Response.WriteAsync("Unknown error"); } } } // Extension method used to add the middleware to the HTTP request pipeline. public static class ExceptionHandleMiddlewareExtensions { public static IApplicationBuilder UseExceptionHandleMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware<ExceptionHandleMiddleware>(); } }
Теперь нам нужно добавить наше промежуточное ПО в класс program.cs. Нам нужно добавить перед методом app.Run(). Поэтому я добавил после app.MapControllers(); метод.
var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext<LiftDb>(opt => opt.UseInMemoryDatabase("LiftList")); //builder.Services.AddDatabaseDeveloperPageExceptionFilter(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.UseExceptionHandleMiddleware(); // Our middelware to handle exceptions var liftsEndPoint = app.MapGroup("/lifts"); liftsEndPoint.MapGet("/", GetAllLifts); liftsEndPoint.MapGet("/getSquats", GetLiftByName); liftsEndPoint.MapGet("/{id}", GetLift); liftsEndPoint.MapPost("/", CreateLift); liftsEndPoint.MapPut("/{id}", UpdateLift); liftsEndPoint.MapDelete("/{id}", DeleteLift); app.MapGet("/fakeError", FakeError); app.Run();
Далее нам нужно интегрировать наше специальное промежуточное программное обеспечение в файл Program.cs
. В частности, мы должны включить его перед вызовом метода app.Run()
, чтобы гарантировать, что он вступит в силу во время конвейера запросов приложения. В моем примере я добавил его сразу после метода app.MapControllers();
.
static Task<IResult> FakeError() { throw new InvalidOperationException("Fake error"); }
Следующим пунктом нашей повестки дня является внедрение Serilog для регистрации исключений. Serilog — очень популярная библиотека .NET, предназначенная для надежного и гибкого ведения журналов. Он предлагает различные уровни ведения журнала, такие как информация, ошибки и предупреждения, что делает его бесценным инструментом для мониторинга состояния вашего приложения.
Прежде чем углубиться, нам нужно установить несколько пакетов Serilog. Основные из них — Serilog
, Serilog.AspNetCore
и Serilog.Sinks.Console
. Если вы хотите войти в файл, вам также нужно добавить Serilog.Sinks.File
в свой список пакетов.
Вы можете легко установить эти пакеты через NuGet или, если вы предпочитаете использовать CLI dotnet, вам пригодятся следующие команды:
dotnet add package Serilog dotnet add package Serilog.AspNetCore dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File
После установки Serilog следующим шагом будет инкапсуляция кода в файл Program.cs
с помощью блоков try-catch-finally. Это позволит нам эффективно обрабатывать любые исключения, которые могут возникнуть. Кроме того, мы добавим в этот файл конфигурации ведения журнала Serilog. Ниже представлена окончательная версия файла Program.cs
:
using LearningCenter.WhatIsMinimalApi.Entity; using LearningCenter.WhatIsMinimalApi.Middleware; using LearningCenter.WhatIsMinimalApi.Repository; using Microsoft.EntityFrameworkCore; using Serilog; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); try { Log.Information("Starting web application"); var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog(); // <-- Add this line // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddDbContext<LiftDb>(opt => opt.UseInMemoryDatabase("LiftList")); //builder.Services.AddDatabaseDeveloperPageExceptionFilter(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.UseExceptionHandleMiddleware(); // Our middelware to handle exceptions var liftsEndPoint = app.MapGroup("/lifts"); liftsEndPoint.MapGet("/", GetAllLifts); liftsEndPoint.MapGet("/getSquats", GetLiftByName); liftsEndPoint.MapGet("/{id}", GetLift); liftsEndPoint.MapPost("/", CreateLift); liftsEndPoint.MapPut("/{id}", UpdateLift); liftsEndPoint.MapDelete("/{id}", DeleteLift); app.MapGet("/fakeError", FakeError); app.Run(); static async Task<IResult> GetAllLifts(LiftDb db) { return TypedResults.Ok(await db.Lifts.ToArrayAsync()); } static async Task<IResult> GetLiftByName(LiftDb db) { return TypedResults.Ok(await db.Lifts.Where(t => t.Name == Lift.LiftName.Squat).ToListAsync()); } static async Task<IResult> GetLift(int id, LiftDb db) { return await db.Lifts.FindAsync(id) is Lift todo ? Results.Ok(todo) : Results.NotFound(); } static async Task<IResult> CreateLift(Lift lift, LiftDb db) { db.Lifts.Add(lift); await db.SaveChangesAsync(); return TypedResults.Created($"/todoitems/{lift.Id}", lift); } static async Task<IResult> UpdateLift(int id, Lift inputLift, LiftDb db) { var lift = await db.Lifts.FindAsync(id); if (lift is null) return Results.NotFound(); lift.Name = inputLift.Name; lift.Weight = inputLift.Weight; lift.Reps = inputLift.Reps; await db.SaveChangesAsync(); return TypedResults.NoContent(); } static async Task<IResult> DeleteLift(int id, LiftDb db) { if (await db.Lifts.FindAsync(id) is Lift todo) { db.Lifts.Remove(todo); await db.SaveChangesAsync(); return Results.NoContent(); } return TypedResults.NotFound(); } static Task<IResult> FakeError() { throw new InvalidOperationException("Fake error"); } } catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); } finally { Log.CloseAndFlush(); }
Далее мы настроим Serilog для регистрации действий нашего приложения, направляя вывод на консоль. Чтобы регистрировать исключения специально в нашем специальном промежуточном программном обеспечении, мы также интегрируем туда Serilog. В моем примере я добавил Serilog в функцию HandleException
для захвата и регистрации любых возникающих исключений.
private async Task HandleException(Exception ex, HttpContext httpContext) { Log.Error(ex, "Error happend!"); // serilog if (ex is InvalidOperationException) { //httpContext.Response.WriteAsync("Invalid operation"); //httpContext.Response.WriteAsync("Invalid operation"); httpContext.Response.StatusCode = 400; await httpContext.Response.WriteAsJsonAsync(new ResponseModel { Message = "Invalid operation", StatusCode = 400, Success = false }); } else if (ex is ArgumentException) { await httpContext.Response.WriteAsync("Invalid argument"); } else { await httpContext.Response.WriteAsync("Unknown error"); } }
Вот и все! Наш API теперь полностью настроен и готов к использованию. Благодаря интеграции Serilog вы теперь можете отслеживать вывод консоли, чтобы увидеть, что происходит, когда в приложении возникают исключения.
Если вы также хотите включить ведение журнала файлов, вы можете легко сделать это, внеся небольшую корректировку в исходный код конфигурации Serilog в файле Program.cs
.
Log.Logger = new LoggerConfiguration() .WriteTo .Console() .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day) // add this .CreateLogger();
Спасибо за чтение. Надеюсь, вы нашли это руководство полезным для настройки обработки исключений и входа в минимальный API .NET 7 с использованием специального промежуточного программного обеспечения и Serilog.
https://github.com/fahricankacan/LearningCenter/tree/master/LearningCenter.WhatIsMinimalApi