[.NET] Описание элементов перечислений в Swashbuckle (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Swagger — замечательная вещь! Он позволяет легко посмотреть, каким API обладает ваш сервис, сгенерировать клиента для него на различных языках и даже попробовать поработать с сервисом через UI. В ASP.NET Core для поддержки Swagger существует пакет Swashbuckle.AspNetCore.
Но есть один недостаток, который мне не нравится. Swashbuckle способен строить описания методов, параметров и классов, основываясь на XML-комментариях в коде .NET. Но он не показывает те описания, которые применяются непосредственно к членам перечислений.
Позвольте мне показать, о чём идёт речь.
Создание сервиса
Я создал простой Web-сервис:
/// <summary>
/// Contains endpoints that use different enums.
/// </summary>
[Route("api/data")]
[ApiController]
public class EnumsController : ControllerBase
{
/// <summary>
/// Executes operation of requested type and returns result status.
/// </summary>
/// <param name="id">Operation id.</param>
/// <param name="type">Operation type.</param>
/// <returns>Result status.</returns>
[HttpGet]
public Task<Result> ExecuteOperation(int id, OperationType type)
{
return Task.FromResult(Result.Success);
}
/// <summary>
/// Changes data
/// </summary>
[HttpPost]
public Task<IActionResult> Change(DataChange change)
{
return Task.FromResult<IActionResult>(Ok());
}
}
Этот контроллер использует перечисления во множестве мест: в качестве аргументов методов, в качестве результатов работы методов, в качестве типов свойств более сложных объектов:
/// <summary>
/// Operation types.
/// </summary>
public enum OperationType
{
/// <summary>
/// Do operation.
/// </summary>
Do,
/// <summary>
/// Undo operation.
/// </summary>
Undo
}
/// <summary>
/// Operation results.
/// </summary>
public enum Result
{
/// <summary>
/// Operations was completed successfully.
/// </summary>
Success,
/// <summary>
/// Operation failed.
/// </summary>
Failure
}
/// <summary>
/// Data change information.
/// </summary>
public class DataChange
{
/// <summary>
/// Data id.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Source type.
/// </summary>
public Sources Source { get; set; }
/// <summary>
/// Operation type.
/// </summary>
public OperationType Operation { get; set; }
}
/// <summary>
/// Types of sources.
/// </summary>
public enum Sources
{
/// <summary>
/// In-memory data source.
/// </summary>
Memory,
/// <summary>
/// Database data source.
/// </summary>
Database
}
Для поддержки Swagger я установил в проект NuGet-пакет Swashbuckle.AspNetCore. Теперь его нужно подключить. Это делается в Startup-файле:
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c => {
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseSwagger();
app.UseSwaggerUI();
app.UseRouting();
...
}
}
Теперь мы можем запустить наше приложение, и по адресу http://localhost:5000/swagger/index.html мы найдём описание нашего сервиса:
Но пока наши перечисления представлены просто числами:
Лично мне было бы удобно, если бы перечисления представлялись строковыми значениями. Они хотя бы имеют некоторый смысл в отличии от безликих чисел.
Для этого нам нужно внести небольшие изменения в настройку нашего сервиса. Я установил NuGet-пакет Swashbuckle.AspNetCore.Newtonsoft. После этого, я чуть изменил настройки сервисов. Я заменил
services.AddControllers();
на
services.AddControllers().AddNewtonsoftJson(o =>
{
o.SerializerSettings.Converters.Add(new StringEnumConverter
{
CamelCaseText = true
});
});
Теперь наши перечисления представлены в виде строк:
Но и у этого представления есть, на мой взгляд, один недостаток. XML-комментарии, которые были назначены отдельным членам перечислений, не отображаются в Swagger UI.
Описание типов перечислений
Давайте посмотрим, как нам вернуть их. Большей частью поиск в интернете на эту тему ничего не дал мне. Но, в конце концов, я нашёл интересный код. К сожалению, он относится к старой версии Swashbuckle. Но он послужил мне хорошей отправной точкой.
Swashbuckle предоставляет возможность вмешиваться в процесс генерации документации. В частности, существует интерфейс ISchemaFilter, который позволяет изменять описания схем отдельных классов. Следующий код показывает, как изменить описание классов перечислений:
public class EnumTypesSchemaFilter : ISchemaFilter
{
private readonly XDocument _xmlComments;
public EnumTypesSchemaFilter(string xmlPath)
{
if(File.Exists(xmlPath))
{
_xmlComments = XDocument.Load(xmlPath);
}
}
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (_xmlComments == null) return;
if(schema.Enum != null && schema.Enum.Count > 0 &&
context.Type != null && context.Type.IsEnum)
{
schema.Description += "<p>Members:</p><ul>";
var fullTypeName = context.Type.FullName;
foreach (var enumMemberName in schema.Enum.OfType<OpenApiString>().Select(v => v.Value))
{
var fullEnumMemberName = $"F:{fullTypeName}.{enumMemberName}";
var enumMemberComments = _xmlComments.Descendants("member")
.FirstOrDefault(m => m.Attribute("name").Value.Equals(fullEnumMemberName, StringComparison.OrdinalIgnoreCase));
if (enumMemberComments == null) continue;
var summary = enumMemberComments.Descendants("summary").FirstOrDefault();
if (summary == null) continue;
schema.Description += $"<li><i>{enumMemberName}</i> - {summary.Value.Trim()}</li>";
}
schema.Description += "</ul>";
}
}
}
Конструктор этого класса принимает имя файла с XML-комментариями. Его содержимое для удобства работы читается в экземпляр XDocument. Затем в методе Apply мы проверяем, генерируется ли схема для перечисления или нет. Если это схема перечисления, то мы добавляем к описанию класса HTML-список, содержащий описание каждого используемого члена перечисления.
Теперь наш класс нужно подключить, чтобы Swashbuckle знал о нём:
services.AddSwaggerGen(c => {
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath);
});
Это делается с помощью метода SchemaFilter в настройках Swagger. Этому методу в качестве параметра передаётся имя файла с XML-комментариями. Именно оно будет передано в конструктор нашего класса EnumTypesSchemaFilter.
Теперь в Swagger UI описания классов-перечислений выглядят так:
Описание перечислений в параметрах
Это уже лучше. Но всё же не достаточно хорошо. У нас в контроллере есть метод, который принимает перечисление в качестве параметра:
public Task<Result> ExecuteOperation(int id, OperationType type)
Давайте посмотрим, как выглядит его описание в Swagger UI:
Как видите, здесь не присутствует никакого описания членов перечисления. Причина в том, что здесь мы видим описание параметра, а не его типа. Т.е. вы видите XML-комментарий, соответствующий параметру метода, а не типу этого параметра.
Но и эту проблему можно решить. Для этого воспользуемся другим интерфейсом Swashbuckle — IDocumentFilter. Вот его реализация:
public class EnumTypesDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
foreach (var path in swaggerDoc.Paths.Values)
{
foreach(var operation in path.Operations.Values)
{
foreach(var parameter in operation.Parameters)
{
var schemaReferenceId = parameter.Schema.Reference?.Id;
if (string.IsNullOrEmpty(schemaReferenceId)) continue;
var schema = context.SchemaRepository.Schemas[schemaReferenceId];
if (schema.Enum == null || schema.Enum.Count == 0) continue;
parameter.Description += "<p>Variants:</p>";
int cutStart = schema.Description.IndexOf("<ul>");
int cutEnd = schema.Description.IndexOf("</ul>") + 5;
parameter.Description += schema.Description
.Substring(cutStart, cutEnd - cutStart);
}
}
}
}
}
Здесь в методе Apply мы перебираем все параметры всех методов всех контроллеров. К сожалению, здесь Swashbuckle API не даёт доступа в типу параметра, а только к схеме его типа (ну или я не нашёл такой возможности). Поэтому мне пришлось вырезать описание членов перечисления из строки описания типа параметра.
Наш класс нужно зарегистрировать аналогичным образом, с помощью метода DocumentFilter:
services.AddSwaggerGen(c => {
// Set the comments path for the Swagger JSON and UI.
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath);
c.DocumentFilter<EnumTypesDocumentFilter>();
});
Вот как выглядит теперь описание параметра в Swagger UI:
Заключение
Приведённый здесь код является скорее наброском для решения проблемы, чем окончательным вариантом. Надеюсь, он будет полезен вам и позволит добавить описание членов перечисления в ваш Swagger UI. Спасибо!
P.S. Вы можете найти код проекта на GitHub.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Ivan Iakimov
===========Похожие новости:
- [Программирование, .NET, C#] Cоздание переиспользуемых Linq фильтров (построителей предикатов для Where), которые можно применять для разных типов
- [IT-инфраструктура, Service Desk, Управление проектами, Карьера в IT-индустрии] Как мы развернули круглосуточную техническую поддержку с нуля всего за 1 год
- [Информационная безопасность, .NET, PowerShell, Visual Basic for Applications] Созданные с помощью библиотеки .NET документы Excel обходят проверки безопасности (перевод)
- [.NET, C#, Профессиональная литература] Книга «C# 8 и .NET Core. Разработка и оптимизация»
- [.NET, C++, C#] Трансляция кода с C# на C++: работа портера
- [.NET, Разработка игр, C#] Многопоточность в Photon
- [.NET, C#, Xamarin] Улучшаем биндинги в CSharpForMarkup
- [Разработка веб-сайтов, .NET, ASP, C#, Микросервисы] Учим ASP.NET Core новым трюкам на примере Json Rpc 2.0
- [Системное администрирование, .NET, DevOps] Обновление процесса CI/CD: год спустя
- [Java, .NET] «Microsoft Coffee»: первоапрельский ответ на Java
Теги для поиска: #_.net, #_swagger, #_swaggerui, #_.net
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:03
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Swagger — замечательная вещь! Он позволяет легко посмотреть, каким API обладает ваш сервис, сгенерировать клиента для него на различных языках и даже попробовать поработать с сервисом через UI. В ASP.NET Core для поддержки Swagger существует пакет Swashbuckle.AspNetCore. Но есть один недостаток, который мне не нравится. Swashbuckle способен строить описания методов, параметров и классов, основываясь на XML-комментариях в коде .NET. Но он не показывает те описания, которые применяются непосредственно к членам перечислений. Позвольте мне показать, о чём идёт речь. Создание сервиса Я создал простой Web-сервис: /// <summary>
/// Contains endpoints that use different enums. /// </summary> [Route("api/data")] [ApiController] public class EnumsController : ControllerBase { /// <summary> /// Executes operation of requested type and returns result status. /// </summary> /// <param name="id">Operation id.</param> /// <param name="type">Operation type.</param> /// <returns>Result status.</returns> [HttpGet] public Task<Result> ExecuteOperation(int id, OperationType type) { return Task.FromResult(Result.Success); } /// <summary> /// Changes data /// </summary> [HttpPost] public Task<IActionResult> Change(DataChange change) { return Task.FromResult<IActionResult>(Ok()); } } Этот контроллер использует перечисления во множестве мест: в качестве аргументов методов, в качестве результатов работы методов, в качестве типов свойств более сложных объектов: /// <summary>
/// Operation types. /// </summary> public enum OperationType { /// <summary> /// Do operation. /// </summary> Do, /// <summary> /// Undo operation. /// </summary> Undo } /// <summary> /// Operation results. /// </summary> public enum Result { /// <summary> /// Operations was completed successfully. /// </summary> Success, /// <summary> /// Operation failed. /// </summary> Failure } /// <summary> /// Data change information. /// </summary> public class DataChange { /// <summary> /// Data id. /// </summary> public int Id { get; set; } /// <summary> /// Source type. /// </summary> public Sources Source { get; set; } /// <summary> /// Operation type. /// </summary> public OperationType Operation { get; set; } } /// <summary> /// Types of sources. /// </summary> public enum Sources { /// <summary> /// In-memory data source. /// </summary> Memory, /// <summary> /// Database data source. /// </summary> Database } Для поддержки Swagger я установил в проект NuGet-пакет Swashbuckle.AspNetCore. Теперь его нужно подключить. Это делается в Startup-файле: public class Startup
{ // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c => { // Set the comments path for the Swagger JSON and UI. var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseSwagger(); app.UseSwaggerUI(); app.UseRouting(); ... } } Теперь мы можем запустить наше приложение, и по адресу http://localhost:5000/swagger/index.html мы найдём описание нашего сервиса: Но пока наши перечисления представлены просто числами: Лично мне было бы удобно, если бы перечисления представлялись строковыми значениями. Они хотя бы имеют некоторый смысл в отличии от безликих чисел. Для этого нам нужно внести небольшие изменения в настройку нашего сервиса. Я установил NuGet-пакет Swashbuckle.AspNetCore.Newtonsoft. После этого, я чуть изменил настройки сервисов. Я заменил services.AddControllers();
на services.AddControllers().AddNewtonsoftJson(o =>
{ o.SerializerSettings.Converters.Add(new StringEnumConverter { CamelCaseText = true }); }); Теперь наши перечисления представлены в виде строк: Но и у этого представления есть, на мой взгляд, один недостаток. XML-комментарии, которые были назначены отдельным членам перечислений, не отображаются в Swagger UI. Описание типов перечислений Давайте посмотрим, как нам вернуть их. Большей частью поиск в интернете на эту тему ничего не дал мне. Но, в конце концов, я нашёл интересный код. К сожалению, он относится к старой версии Swashbuckle. Но он послужил мне хорошей отправной точкой. Swashbuckle предоставляет возможность вмешиваться в процесс генерации документации. В частности, существует интерфейс ISchemaFilter, который позволяет изменять описания схем отдельных классов. Следующий код показывает, как изменить описание классов перечислений: public class EnumTypesSchemaFilter : ISchemaFilter
{ private readonly XDocument _xmlComments; public EnumTypesSchemaFilter(string xmlPath) { if(File.Exists(xmlPath)) { _xmlComments = XDocument.Load(xmlPath); } } public void Apply(OpenApiSchema schema, SchemaFilterContext context) { if (_xmlComments == null) return; if(schema.Enum != null && schema.Enum.Count > 0 && context.Type != null && context.Type.IsEnum) { schema.Description += "<p>Members:</p><ul>"; var fullTypeName = context.Type.FullName; foreach (var enumMemberName in schema.Enum.OfType<OpenApiString>().Select(v => v.Value)) { var fullEnumMemberName = $"F:{fullTypeName}.{enumMemberName}"; var enumMemberComments = _xmlComments.Descendants("member") .FirstOrDefault(m => m.Attribute("name").Value.Equals(fullEnumMemberName, StringComparison.OrdinalIgnoreCase)); if (enumMemberComments == null) continue; var summary = enumMemberComments.Descendants("summary").FirstOrDefault(); if (summary == null) continue; schema.Description += $"<li><i>{enumMemberName}</i> - {summary.Value.Trim()}</li>"; } schema.Description += "</ul>"; } } } Конструктор этого класса принимает имя файла с XML-комментариями. Его содержимое для удобства работы читается в экземпляр XDocument. Затем в методе Apply мы проверяем, генерируется ли схема для перечисления или нет. Если это схема перечисления, то мы добавляем к описанию класса HTML-список, содержащий описание каждого используемого члена перечисления. Теперь наш класс нужно подключить, чтобы Swashbuckle знал о нём: services.AddSwaggerGen(c => {
// Set the comments path for the Swagger JSON and UI. var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath); }); Это делается с помощью метода SchemaFilter в настройках Swagger. Этому методу в качестве параметра передаётся имя файла с XML-комментариями. Именно оно будет передано в конструктор нашего класса EnumTypesSchemaFilter. Теперь в Swagger UI описания классов-перечислений выглядят так: Описание перечислений в параметрах Это уже лучше. Но всё же не достаточно хорошо. У нас в контроллере есть метод, который принимает перечисление в качестве параметра: public Task<Result> ExecuteOperation(int id, OperationType type)
Давайте посмотрим, как выглядит его описание в Swagger UI: Как видите, здесь не присутствует никакого описания членов перечисления. Причина в том, что здесь мы видим описание параметра, а не его типа. Т.е. вы видите XML-комментарий, соответствующий параметру метода, а не типу этого параметра. Но и эту проблему можно решить. Для этого воспользуемся другим интерфейсом Swashbuckle — IDocumentFilter. Вот его реализация: public class EnumTypesDocumentFilter : IDocumentFilter
{ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { foreach (var path in swaggerDoc.Paths.Values) { foreach(var operation in path.Operations.Values) { foreach(var parameter in operation.Parameters) { var schemaReferenceId = parameter.Schema.Reference?.Id; if (string.IsNullOrEmpty(schemaReferenceId)) continue; var schema = context.SchemaRepository.Schemas[schemaReferenceId]; if (schema.Enum == null || schema.Enum.Count == 0) continue; parameter.Description += "<p>Variants:</p>"; int cutStart = schema.Description.IndexOf("<ul>"); int cutEnd = schema.Description.IndexOf("</ul>") + 5; parameter.Description += schema.Description .Substring(cutStart, cutEnd - cutStart); } } } } } Здесь в методе Apply мы перебираем все параметры всех методов всех контроллеров. К сожалению, здесь Swashbuckle API не даёт доступа в типу параметра, а только к схеме его типа (ну или я не нашёл такой возможности). Поэтому мне пришлось вырезать описание членов перечисления из строки описания типа параметра. Наш класс нужно зарегистрировать аналогичным образом, с помощью метода DocumentFilter: services.AddSwaggerGen(c => {
// Set the comments path for the Swagger JSON and UI. var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile); c.IncludeXmlComments(xmlPath); c.SchemaFilter<EnumTypesSchemaFilter>(xmlPath); c.DocumentFilter<EnumTypesDocumentFilter>(); }); Вот как выглядит теперь описание параметра в Swagger UI: Заключение Приведённый здесь код является скорее наброском для решения проблемы, чем окончательным вариантом. Надеюсь, он будет полезен вам и позволит добавить описание членов перечисления в ваш Swagger UI. Спасибо! P.S. Вы можете найти код проекта на GitHub. =========== Источник: habr.com =========== =========== Автор оригинала: Ivan Iakimov ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 21:03
Часовой пояс: UTC + 5