Хорошо, потратив некоторое время на ASP.NET Core, я могу решить 4 основных вопроса.Сама тема довольно сложна и обширна, и, честно говоря, я не думаю, что есть серебряная пуля или лучшая практика для этого.
Для пользовательского типа контента (допустим, вы хотите реализовать application/hal+json
) Официальный и, вероятно, самый элегантный способ - создать пользовательский форматировщик вывода .Таким образом, ваши действия не будут знать ничего о выходном формате, но вы все равно сможете контролировать поведение форматирования в своих контроллерах благодаря механизму внедрения зависимостей и времени жизни.
Это наиболее популярный способ, используемый официальными библиотеками OData C # и json: api framework для ASP.Net Core .Вероятно, это лучший способ для реализации гипермедиа форматов.
Чтобы управлять вашим пользовательским форматером вывода из контроллера, вы должны либо создать свой собственный «контекст» для передачи данных между вашими контроллерами и пользовательским форматером и добавить его в контейнер DI с помощьювремя действия в области:
services.AddScoped<ApiContext>();
Таким образом, будет только один экземпляр ApiContext
на запрос.Вы можете внедрить его как в свои контроллеры, так и в выходные форматтеры и передавать между ними данные.
Вы также можете использовать ActionContextAccessor
и HttpContextAccessor
и получать доступ к своему контроллеру и действиям внутри своего пользовательского форматера вывода.Чтобы получить доступ к контроллеру, вы должны привести ActionContextAccessor.ActionContext.ActionDescriptor
к ControllerActionDescriptor
.Затем вы можете сгенерировать ссылки внутри выходных форматеров, используя IUrlHelper
и имена действий, так что контроллер будет свободен от этой логики.
IActionContextAccessor
не является обязательным и не добавляется в контейнер по умолчанию, чтобы использовать его вваш проект, вы должны добавить его в контейнер IoC.
services.AddSingleton<IActionContextAccessor, ActionContextAccessor>()
Использование служб внутри пользовательского выходного форматера:
Выне может сделать внедрение зависимости конструктора в класс форматера.Например, вы не можете получить регистратор, добавив параметр регистратора в конструктор.Чтобы получить доступ к сервисам, вы должны использовать объект контекста, который передается вашим методам.
https://docs.microsoft.com/en-us/aspnet/core/web-api/advanced/custom-formatters?view=aspnetcore-2.0#read-write
Поддержка Swashbuckle :
Swashbuckle, очевидно, не будет генерировать правильный пример ответа с этим подходом и подходом с фильтрами.Вам, вероятно, придется создать свой пользовательский фильтр документов .
Пример: Как добавить нумерацию страниц :
Обычно подкачка, фильтрация решается с помощью шаблон спецификации у вас обычно будет какая-то общая модель для спецификации в ваших [Get]
действиях.Затем вы можете определить в своем редакторе форматирования, возвращает ли текущее выполненное действие список элементов по типу параметра или по другому:
var specificationParameter = actionContextAccessor.ActionContext.ActionDescriptor.Parameters.SingleOrDefault(p => p.ParameterType == typeof(ISpecification<>));
if (specificationParameter != null)
{
// add pagination links or whatever
var urlHelper = new UrlHelper(actionContextAccessor.ActionContext);
var link = urlHelper.Action(new UrlActionContext()
{
Protocol = httpContext.Request.Scheme,
Host = httpContext.Request.Host.ToUriComponent(),
Values = yourspecification
})
}
Преимущества (или нет) :
Ваши действия не определяют формат, они ничего не знают о формате или о том, как генерировать ссылки и где их размещать.Они знают только тип результата, а не метаданные, описывающие результат.
Возможность многократного использования, вы можете легко добавлять формат в другие проекты, не беспокоясь о том, как обработать его в своемдействия.Все, что связано с линковкой, форматированием обрабатывается под капотом.Вам не нужно никакой логики в ваших действиях.
Реализация сериализации зависит от вас, вам не нужно использовать Newtonsoft.JSON, вы можете использовать Jil дляпример.
Недостатки :
Один недостаток этого подхода заключается в том, что он будет работать только с определенным типом контента.Поэтому для поддержки XML нам нужно создать еще один пользовательский форматировщик вывода с Content-Type, например vnd.myapi+xml
вместо vnd.myapi+json
.
Мы не работаем напрямую с результатом действия
- Может быть более сложным для реализации
Фильтры результатов позволяют нам определить какое-то поведение, которое будет выполняться до возврата нашего действия.Я думаю об этом как о некоторой форме пост-хука.Я не думаю, что это правильное место для упаковки нашего ответа.
Они могут применяться для каждого действия или глобально ко всем действиям.
Лично я бы не использовал его дляэто что-то вроде , но используйте его как дополнение к 3-му варианту.
Пример фильтра результатов, обертывающий вывод:
public class ResultFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is ObjectResult objectResult)
{
objectResult.Value = new ApiResult { Data = objectResult.Value };
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
Вы можете поместить ту же логику в IActionFilter
и это также должно работать:
public class ActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Result is ObjectResult objectResult)
{
objectResult.Value = new ApiResult { Data = objectResult.Value };
}
}
}
Это самый простой способ обернуть ваши ответы, особенно если у вас уже есть существующий проект с контроллерами.Поэтому, если вам небезразлично время, выберите это.
3.Явное форматирование / упаковка результатов в ваших действиях
(как я это делаю в своем вопросе)
Это также используется здесь: https://github.com/nbarbettini/BeautifulRestApi/tree/master/src для реализации https://github.com/ionwg/ion-doc/blob/master/index.adoc лично я думаю, что это было бы лучше подходит для пользовательского выходного форматера.
Это, вероятно, самый простой способ, но он также "запечатывает" ваш API к этому конкретному формату.У этого подхода есть свои преимущества, но могут быть и некоторые недостатки.Например, если вы хотите изменить формат вашего API, вы не сможете сделать это легко, потому что ваши действия связаны с этой конкретной моделью ответа, и если у вас есть некоторая логика в этой модели, например, вы 'добавляю ссылки на страницы для следующей и предыдущей.Вы практически должны переписать все свои действия и логику форматирования для поддержки этого нового формата.С помощью специального форматера вывода вы можете даже поддерживать оба формата в зависимости от заголовка Content-Type.
Преимущества:
- Работает со всеми типами контента, форматомявляется неотъемлемой частью вашего API.
- Swashbuckle работает "из коробки", при использовании
ActionResult<T>
(2.1+) вы также можете добавить атрибут [ProducesResponseType]
к своим действиям.
Недостатки:
- Вы не можете управлять форматом с заголовком
Content-Type
.Он всегда остается одинаковым для application/json
и application/xml
.(может быть, это преимущество?) - Ваши действия несут ответственность за возвращение правильно отформатированного ответа.Что-то вроде:
return new ApiResponse(obj);
или вы можете создать метод расширения и назвать его как obj.ToResponse()
, но вы всегда должны думать о правильном формате ответа. - Теоретически настраиваемый тип содержимого типа
vnd.myapi+json
не даетлюбая выгода и реализация пользовательского выходного форматера только для имени не имеет смысла, так как форматирование по-прежнему является ответственностью за действия контроллера.
Я думаю, что это больше похоже на ярлык для правильной обработки выходного формата.Я думаю, что, следуя принципу единственной ответственности , это должно быть заданием для форматера вывода, поскольку название предполагает, что он форматирует вывод.
Последнее, что вы можете сделать, - это настраиваемое промежуточное программное обеспечение, оттуда можно разрешить IActionResultExecutor
и вернуть IActionResult
, как если бы вы работали в контроллерах MVC.
https://github.com/aspnet/Mvc/issues/7238#issuecomment-357391426
Вы также можете разрешить IActionContextAccessor
, чтобы получить доступ к контексту действия MVC, и привести ActionDescriptor
к ControllerActionDescriptor
, если вам нужен доступ к информации контроллера.
Документы говорят:
Фильтры ресурсов работают как промежуточное ПО в том смысле, что они окружают выполнение всего, что происходит позже в конвейере.Но фильтры отличаются от промежуточного программного обеспечения тем, что они являются частью MVC, что означает, что они имеют доступ к контексту и конструкциям MVC.
Но это не совсем так, потому что вы можете получить доступ к контексту действия и можетевернуть результаты действий, которые являются частью MVC, из вашего промежуточного программного обеспечения.
Если вам есть что добавить, поделитесь собственным опытом и преимуществами или недостатками, не стесняйтесь комментировать.