پکیجهای Nuget زیادی با پیادهسازی الگوی Result وجود دارد، اجازه دهید چند مورد از محبوبترینها را به شما معرفی کنم: 📦
🔹 FluentResults
🔹 CSharpFunctionalExtensions
🔹 error-or
🔹 Ardalis.Result
مورد علاقه من error-or است، بیایید آن را بررسی کنیم.
الگوی Result با Error-Or ✨
همانطور که نویسنده پکیج بیان کرده است: Error-Or یک discriminated union ساده و روان از یک خطا یا یک نتیجه است. کلاس Result در این کتابخانه <ErrorOr<T نامیده میشود.
در اینجا نحوه استفاده از آن برای کنترل جریان آمده است:
public async Task<ErrorOr<ShipmentResponse>> Handle(
CreateShipmentCommand request,
CancellationToken cancellationToken)
{
var shipmentAlreadyExists = await context.Shipments
.Where(s => s.OrderId == request.OrderId)
.AnyAsync(cancellationToken);
if (shipmentAlreadyExists)
{
return Error.Conflict($"Shipment for order '{request.OrderId}' is already created");
}
var shipment = request.MapToShipment(shipmentNumber);
context.Shipments.Add(shipment);
await context.SaveChangesAsync(cancellationToken);
return shipment.MapToResponse();
}
در اینجا نوع بازگشتی <ErrorOr<ShipmentResponse است که نشان میدهد آیا یک خطا برگردانده شده است یا ShipmentResponse.
کلاس Error خطاهای داخلی برای انواع زیر دارد، که یک متد برای هر نوع خطا فراهم میکند: 🏷
public enum ErrorType
{
Failure,
Unexpected,
Validation,
Conflict,
NotFound,
Unauthorized,
Forbidden,
}
این کتابخانه به شما اجازه میدهد در صورت نیاز خطاهای سفارشی ایجاد کنید.
در endpoint API میتوانید خطا را مشابه کاری که قبلاً با آبجکت Result سفارشی خودمان انجام دادیم، مدیریت کنید: 👇
public void MapEndpoint(WebApplication app)
{
app.MapPost("/api/v2/shipments", Handle);
}
private static async Task<IResult> Handle(
[FromBody] CreateShipmentRequest request,
IShipmentService service,
CancellationToken cancellationToken)
{
var command = request.MapToCommand();
var response = await mediator.Send(command, cancellationToken);
if (response.IsError)
{
return Results.Conflict(response.Errors);
}
return Results.Ok(response.Value);
}
هنگام کار با Error-Or، من دوست دارم یک متد استاتیک ToProblem بسازم که خطا را به کد وضعیت (status code) مناسب تبدیل میکند: ✨
public static class EndpointResultsExtensions
{
public static IResult ToProblem(this List<Error> errors)
{
if (errors.Count == 0)
{
return Results.Problem();
}
return CreateProblem(errors);
}
private static IResult CreateProblem(List<Error> errors)
{
var statusCode = errors.First().Type switch
{
ErrorType.Conflict => StatusCodes.Status409Conflict,
ErrorType.Validation => StatusCodes.Status400BadRequest,
ErrorType.NotFound => StatusCodes.Status404NotFound,
ErrorType.Unauthorized => StatusCodes.Status401Unauthorized,
_ => StatusCodes.Status500InternalServerError
};
return Results.ValidationProblem(
errors.ToDictionary(k => k.Code, v => new[] { v.Description }),
statusCode: statusCode);
}
}
به این ترتیب، کد قبلی به این شکل تبدیل خواهد شد:
var response = await mediator.Send(command, cancellationToken);
if (response.IsError)
{
return response.Errors.ToProblem();
}
return Results.Ok(response.Value);