ASP.Net Core Web API единый формат ответов

281
03 мая 2022, 22:20

Собственно вопрос в тайтле.
Что я хочу: единый формат ответов от API как у VK.
Для успешных результатов примерно такой формат ответов:

{
    "response": {
        "count": 2,
        "items": [
            1,
            2,
        ]
    }
}

А для ошибок примерно такой формат:

{
    "error": {
        "error_code": 5,
        "error_msg": "User authorization failed: invalid access_token (4)."
    }
}

Хотелось бы, чтобы ответы формировались сами (чтобы я из контроллера, например, возвращал IEnumerable, а ответ был как выше в примере), а не через контроллер и возвращаемый тип у методов (public MyResponse<> Foo()).

Answer 1

Для обработки ошибок поможет middleware, который будет перехватывать все исключения и формировать ответ в едином виде:

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger _logger;
    public ErrorHandlingMiddleware(RequestDelegate next, ILoggerFactory loggerFactory)
    {
        _next = next;
        _logger = loggerFactory.CreateLogger(nameof(ErrorHandlingMiddleware));
    }
    public async Task Invoke(HttpContext ctx)
    {
        try
        {
            await _next.Invoke(ctx);
        }
        catch (Exception ex)
        {
            await HandleExceptionAsync(ctx, ex);
        }
    }
    
    private async Task HandleExceptionAsync(HttpContext context, Exception exception)
    {
        _logger.LogError(exception, exception.Message);
        var errorCode = "0"; //Получаете в зависимости от типа Exception
        var statusCode = 400;
        var message = exception.Message;
        context.Response.StatusCode = statusCode;
        context.Response.ContentType = "application/json; charset=utf-8";
        await context.Response.WriteAsync( ErrorResponse.Build(errorCode, message));
    }
}

public class ErrorResponse
{
    public static string Build(string code, string message)
    {
        var builder = new StringBuilder("{");
        builder.AppendLine();
        builder.AppendLine("    \"error\": {");
        builder.AppendLine();
        builder.AppendFormat("        \"code\": \"{0}\",", code);
        builder.AppendLine();
        builder.AppendFormat("        \"message\": \"{0}\",", message);
        builder.AppendLine("    }");
        builder.AppendLine("}");
        
        return builder.ToString();
    }
}

А вот с общим ответом не все так очевидно! Пару вопросов на тему: Будут ли ответы не содержащие коллекции? Будет ли свойство count вычисляться на основе количества элементов, либо же потребуется кастомные значения.

По опыту могу сказать, что использование таких обобщенных ответов не всегда является гибким!

Могу предложить использовать базовый класс для таких ответов:

public abstract class BaseResponse<T>
{
    protected BaseResponse(params T[] items)
    {
        Items = items;
        Count = items?.Length ?? 0;
    }
    public int Count { get; }
    public IEnumerable<T> Items { get; }
}
READ ALSO
Проблема с анимацией в Unity3d

Проблема с анимацией в Unity3d

Прилагаю скриншот проблемы

314
Как изменить значение экземпляра класса или структуры изнутри самого класса или структуры?

Как изменить значение экземпляра класса или структуры изнутри самого класса или структуры?

У меня есть структура, в которой есть поле Mod, высчитываемое при инициализации:

229
Имя &#39;Ajax&#39; не существует в текущем контексте

Имя 'Ajax' не существует в текущем контексте

Следующий код @using (AjaxBeginForm("CheckChanging", new AjaxOptions { UpdateTargetId = "results" })) вызывает ошибку CS0103 Имя "Ajax" не существует в текущем контексте

145