Прикручиваем к Проекту
Swashbuckle — это NuGet пакет, встраивающий в WebAPI автогенерацию информации об узлах в соответствии со спецификацией OpenAPI. Эта спецификация является, дефакто, стандартом, как некогда WSDL. Для установки, потребуется четыре простых шага.
- Устанавливаем из NuGet командой
Install-Package Swashbuckle - Включаем XML документацию в настройках проекта
- В файле
SwaggerConfig.cs, который создаётся с установкой пакета, раскомментируем строкуc.IncludeXmlComments(GetXmlCommentsPath()); - В реализации метода
GetXmlCommentsPath()пишемreturn string.Format(@"{0}\bin\BookStoreApiService.XML", AppDomain.CurrentDomain.BaseDirectory);
Всё. Дальше необходимо описать методы API, response codes и кастомизировать далее.
Нюансы при Деплое WebAPI
При деплое WebAPI в продакшн может возникнуть проблема с тем, что XML файл отсутствует. Релиз сборка не включает их по умолчанию, но можно это обойти, подредактировав csproj файл. Надо в PropertyGroup проекта добавить
false и файл останется в bin/.Другая проблема подстерегает тех, кто прячет свой API за прокси. Решение не является универсальным, но в моем случае работает. Прокси добавляет хедеры к реквесту, по которым мы узнаём, какой должен быть URL ендпонитов для клиента.
Пример распознования URL за прокси
// в файле SwaggerConfig.cs c.RootUrl(req => ComputeClientHost(req)); // ниже пишем реализацию метода public static string ComputeClientHost(HttpRequestMessage req) { var authority = req.RequestUri.Authority; var scheme = req.RequestUri.Scheme; // получаем хост, который видит клиент if (req.Headers.Contains("X-Forwarded-Host")) { // в случае с цепочкой прокси необходимо взять самый первый var xForwardedHost = req.Headers.GetValues("X-Forwarded-Host").First(); var firstForwardedHost = xForwardedHost.Split(',')[0]; authority = firstForwardedHost; } // получаем протокл, который используется клиентом if (req.Headers.Contains("X-Forwarded-Proto")) { var xForwardedProto = req.Headers.GetValues("X-Forwarded-Proto").First(); xForwardedProto = xForwardedProto.Split(',')[0]; scheme = xForwardedProto; } return scheme + "://" + authority; } Добавляем Response Codes
Возвращаемые HTTP Status Codes можно добавить двумя способами: с помощью XML комментариев и с помощью атрибутов.
Примеры добавления статус кодов
/// Not Found [SwaggerResponse(HttpStatusCode.NotFound, Type = typeof(Model), Description = "Not Found: no such endpoint")] При этом необхродимо помнить, что XML комментарии имеют приоритет перед атрибутами. Последние будут проигнорированы, если два способа одновременно будут использованы для одного и того же метода. Так же, если используются XML комментарии, то указывать необходимо все кода, включая 200 (OK), а возвращаемую модель указать невозможно. Поэтому использование SwaggerResponse предпочтительнее, т.к. он лишен этих недостатков. Когда эндпоинт возвращает другой код, например 201 (Created), вместо дефолтного 200, первый необходимо удалить атрибутом
[SwaggerResponseRemoveDefaults].Для ленивых есть возможность добавить общие кода (например 400 (BadRequest) или 401 (Unauthorized)) сразу ко всем методам. Для этого надо реализовать интерфейс IOperationFilter и зарегистрировать такой класс с помощью c.OperationFilter();.
Пример метода Apply для добавления некоторого списка кодов
HttpStatusCode[] _codes; // коды для добавления public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { // не всегда эта пропертя инициализирована if (operation.responses == null) operation.responses = new Dictionary(); foreach (var code in _codes) { var codeNum = ((int)code).ToString(); var codeName = code.ToString(); // добавляем описание if (!operation.responses.ContainsKey(codeNum)) operation.responses.Add(codeNum, new Response { description = codeName }); } } Авторизация WebAPI и Swashbuckle
В тексте ниже рассматривается несколько вариантов реализации Basic авторизации. Но пакет поддерживает и другие.
Если используется AuthorizeAttribute то Swashbuckle построит UI, но запросы не пройдут. Есть несколько путей предоставления этой информации:
- через встроеную в браузер авторизацию
- через встроеную форму авторизации в пакете
- через параметры операций
- через javascript
Встроеная в Браузер
Встроеная в браузер авторизация будет доступна «из коробки», если используется атрибут и фильтр:
// Basic Authorization attributes config.Filters.Add(new AuthorizeAttribute()); config.Filters.Add(new BasicAuthenticationFilter()); // реализация IAuthenticationFilter Добавив их в конфигурации WebAPI, браузер предложит ввести данные для аутентификации в момент выполнения запроса. Сложность тут в том, что сбросить эти данные не так удобно и быстро, как ввести.
Встроеная Форма Авторизации в Swashbuckle
Другой способ удобнее в этом плане, т.к. предоставляет специальную форму. Чтобы включить встроеную форму аутентификации в пакет необходимо сделать следующее:
- как и выше включить атрибут и фильтр для аутентификации
- в настройках Swagger разкомментировать строку
c.BasicAuth("basic").Description("Basic HTTP Authentication"); - добавить специальный IOperationFilter, добавляющий информацию об этом в узлы
c.OperationFilter();
Реализация метода Apply этого фильтра
public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { var filterPipeline = apiDescription.ActionDescriptor.GetFilterPipeline(); // check if authorization is required var isAuthorized = filterPipeline .Select(filterInfo => filterInfo.Instance) .Any(filter => filter is IAuthorizationFilter); // check if anonymous access is allowed var allowAnonymous = apiDescription.ActionDescriptor.GetCustomAttributes().Any(); if (isAuthorized && !allowAnonymous) { if (operation.security == null) operation.security = new List>>(); var auth = new Dictionary> { {"basic", Enumerable.Empty()} }; operation.security.Add(auth); } } После этого можно будет использовать такую форму авторизации, а введенные данные будут использоваться для всех запросов.

Авторизация Параметром и JS Кодом
Следующие два способа следует рассматривать, как примеры работы с IOperationFilter и инжектированием своего JavaScript.
Параметры могут отправлять данные не только в body и query, но и в header. В этом случае надо будет вводить хеш.
Добавление такого параметра
operation.parameters.Add(new Parameter { name = "Authorization", @in = "header", // обозначим, что значение отправится в хедере description = "Basic U3dhZ2dlcjpUZXN0", // Basic Swagger:Test required = true, // обязательность параметра type = "string" }); С помощью инжектирования своего JavaScript тоже можно отправлять данные в хедере запросов. Для этого необходимо сделать следующее:
- добавить JS файл, как embedded ресурс
- в конфигурации Swagger разскомментировать строку и указать свой файл как имя ресурса:
c.InjectJavaScript(thisAssembly, "assembly.namesapce.swagger-basic-auth.js"); - в файле написать так:
swaggerUi.api.clientAuthorizations.add("basic", new SwaggerClient.ApiKeyAuthorization("Authorization", "Basic U3dhZ2dlcjpUZXN0", "header"));
Теперь эти данные будут добавляться в виде хедера к каждому запросу. Вообще, с помощью этого JS кода можно отправить любые хедеры, как я понял. Параметр key, который равен «basic» в примере, должен быть уникальным, чтобы не выскочила JS ошибка в момент отправки запроса.
Например JS отправляющего хедеры в Swagger
Работаем с Обязательными Хедерами
В некоторых случаях неавторизационные хедеры могут быть обязательными. Например, хедеры с информацией о клиенте. Обычно, в pipeline WebAPI встраивается message handler, а именно реализуется DelegatingHandler и регистрируется в конфигурации WebAPI
config.MessageHandlers.Add(new MandatoryHeadersHandler());. В таком случае Swagger перестанет показывать что-либо, т.к. запросы к нему не пройдут, т.к. хендлер их запретит. Из коробки это никак не решается, поэтому необходимо предусмотреть данный случай в своем хендлере. Т.е. в случае запроса к URL swagger пропускать его. А далее поможет добавление хедеров с помощью JS, как описывалось выше.Эндпоинты с Перегруженными Методами
WebAPI позволяет создавать несколько экшн-методов для одного эндпоинта, вызов которых зависит от параметров запроса.
[ResponseType(typeof (IList))] public IHttpActionResult Get() {...} [ResponseType(typeof (IList))] public IHttpActionResult Get(int count, bool descending) {...} Такие методы не поддерживаются Swagger по умолчанию и UI выдаст ошибку 500: Not supported by Swagger 2.0: Multiple operations with path 'api/' and method ''. See the config setting — \«ResolveConflictingActions\» for a potential workaround.
Как и советуеся в сообщении, следует самостоятельно решить ситуацию и есть несколько вариантов:
- выбрать только один метод
- сделать один метод со всеми параметрами
- изменить генерацию документа
первый и второй способы реализуются с помощью настройки
c.ResolveConflictingActions(Func, ApiDescription> conflictingActionsResolver). Суть метода сводится к тому, чтобы взять несколько конфликтующих методов и вернуть один.Пример того, как объеденить все параметры
return apiDescriptions => { var descriptions = apiDescriptions as ApiDescription[] ?? apiDescriptions.ToArray(); var first = descriptions.First(); // строим относительно первого метода var parameters = descriptions.SelectMany(d => d.ParameterDescriptions).ToList(); first.ParameterDescriptions.Clear(); // добавляем все параметры и делаем их опциональными foreach (var parameter in parameters) if (first.ParameterDescriptions.All(x => x.Name != parameter.Name)) { first.ParameterDescriptions.Add(new ApiParameterDescription { Documentation = parameter.Documentation, Name = parameter.Name, ParameterDescriptor = new OptionalHttpParameterDescriptor((ReflectedHttpParameterDescriptor) parameter.ParameterDescriptor), Source = parameter.Source }); } return first; }; // это наследование необходимо, т.к. IsOptional имеет только getter public class OptionalHttpParameterDescriptor : ReflectedHttpParameterDescriptor { public OptionalHttpParameterDescriptor(ReflectedHttpParameterDescriptor parameterDescriptor) : base(parameterDescriptor.ActionDescriptor, parameterDescriptor.ParameterInfo) { } public override bool IsOptional => true; } Крадинальный Способ
Третий способ более кардинальный и является отхождением от OpenAPI спецификации. Можно вывести все эндпоинты с параметрами:

Для этого необходимо изменить способ генерации документа Swagger с помощью IDocumentFilter и сгенерировать описание самостоятельно.
В жизни такой способ редко когда понадобится, поэтому копнем еще глубже. Еще один способ, который я рекомендовал бы только тем, кому интересны внутренности Swashbuckle — это заменить SwaggerGenerator. Это делается в строчке
c.CustomProvider(defaultProvider => new NewSwaggerProvider(defaultProvider));. Что бы это сделать, можно поступить так:- создать свой class MySwaggerGenerator: ISwaggerProvider
- в репозитории Swashbuckle на GitHub найти SwaggerGenerator.cs (он тут)
- скопировать метод GetSwagger и другие связанные с ним методы в свой
- продублировать внутренние переменные и инициализировать их в конструкторе своего класса
- зарегистрировать в конфигурации Swagger
Инициализация внутренних переменных
private readonly IApiExplorer _apiExplorer; private readonly IDictionary _apiVersions; private readonly JsonSerializerSettings _jsonSerializerSettings; private readonly SwaggerGeneratorOptions _options; public MultiOperationSwaggerGenerator(ISwaggerProvider sp) { var sg = (SwaggerGenerator) sp; var privateFields = sg.GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic); _apiExplorer = privateFields.First(pf => pf.Name == "_apiExplorer").GetValue(sg) as IApiExplorer; _jsonSerializerSettings = privateFields.First(pf => pf.Name == "_jsonSerializerSettings").GetValue(sg) as JsonSerializerSettings; _apiVersions = privateFields.First(pf => pf.Name == "_apiVersions").GetValue(sg) as IDictionary; _options = privateFields.First(pf => pf.Name == "_options").GetValue(sg) as SwaggerGeneratorOptions; } После этого надо найти место
var paths = GetApiDescriptionsFor(apiVersion)..... Это то место, где создаются пути. Например, чтобы получить то, что в примере, необходимо GroupBy() заменить на .GroupBy(apiDesc => apiDesc.RelativePath).Литература
- Swagger example
- RESTful Web API specification formats
- Customize Swashbuckle-generated API definitions
- Swagger object schema
- Authentication Filters in ASP.NET Web API 2
- A WebAPI Basic Authentication Authorization Filter
- Customize Authentication Header in SwaggerUI using Swashbuckle
- HTTP Message Handlers in ASP.NET Web API
- Managing Action Conflicts in ASP.Net 5 with Swashbuckle
- Tutorial Swagger project at GitHub