diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a5ec97..89f7cc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ These are the changes to each version that has been released on the [nuget](https://www.nuget.org/packages/Unchase.Swashbuckle.AspNetCore.Extensions/). +## v2.5.0 `2020-10-15` + +- [x] Fix some bugs in `IncludeXmlCommentsWithRemarks` option +- [x] Add `IncludeXmlCommentsFromInheritDocs` option to add xml comments from inheritdocs (from summary and remarks) into the swagger documentation + ## v2.4.1 `2020-10-13` - [x] Add `params Type[]` parameters to `IncludeXmlCommentsWithRemarks` option to exclude remarks for concrete types diff --git a/README.md b/README.md index 14567cf..1670ce3 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,10 @@ public void ConfigureServices(IServiceCollection services) // use it if you want to hide Paths and Definitions from OpenApi documentation correctly options.UseAllOfToExtendReferenceSchemas(); + // if you want to add xml comments from inheritdocs (from summary and remarks) into the swagger documentation, add: + // you can exclude remarks for concrete types + options.IncludeXmlCommentsFromInheritDocs(includeRemarks: true, excludedTypes: typeof(string)); + // if you want to add xml comments from summary and remarks into the swagger documentation, first of all add: // you can exclude remarks for concrete types var xmlFilePath = Path.Combine(AppContext.BaseDirectory, "WebApi3.1-Swashbuckle.xml"); @@ -352,6 +356,29 @@ public void ConfigureServices(IServiceCollection services) } ``` +7. **Add xml comments from <inheritdoc/> (from summary and remarks) into the swagger documentation**: + +- Since [v2.5.0](https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/releases/tag/v2.5.0) in the _ConfigureServices_ method of _Startup.cs_, inside your `AddSwaggerGen` call, add `IncludeXmlCommentsFromInheritDocs` option: + +```csharp +// This method gets called by the runtime. Use this method to add services to the container. +public void ConfigureServices(IServiceCollection services) +{ + ... + + services.AddSwaggerGen(options => + { + ... + + // add xml comments from inheritdocs (from summary and remarks) into the swagger documentation, add: + // with excluding concrete types + options.IncludeXmlCommentsFromInheritDocs(includeRemarks: true, excludedTypes: typeof(string)); + + ... + }); +} +``` + ## Builds status |Status|Value| @@ -600,6 +627,83 @@ public enum SecondInnerEnum } ``` +### Add xml comments from <inheritdoc/> (from summary and remarks) into the swagger documentation + +![Add xml comments from ingeritdoc](assets/addXmlCommentsFromInheritdoc.png) + +For code: + +```csharp +/// +public class InheritDocClass : IInheritDocClass +{ + /// + public string Name { get; set; } + + /// + public string Common { get; set; } + + /// + public InheritEnum InheritEnum { get; set; } +} + +/// +/// InheritDocClass - inheritdoc +/// +/// +/// InheritDocClass remarks - inheritdoc +/// +public interface IInheritDocClass : IInheritDocCommon +{ + /// + /// Name - inheritdoc + /// + /// + /// Name remarks - inheritdoc + /// + public string Name { get; set; } +} + +/// +/// IInheritDocCommon interface +/// +/// +/// IInheritDocCommon interface remarks +/// +public interface IInheritDocCommon +{ + /// + /// Common - inheritdoc (inner) + /// + /// + /// Common remarks - inheritdoc (inner) + /// + public string Common { get; set; } + + /// + /// InheritEnum - inheritdoc (inner) + /// + public InheritEnum InheritEnum { get; set; } +} + +/// +/// Inherit enum - enum +/// +/// +/// Inherit enum remarks - enum +/// +public enum InheritEnum +{ + /// + /// Inherit enum Value + /// + /// + /// Inherit enum Value remarks + /// + Value = 0 +} +``` + ## HowTos - [ ] Add HowTos in a future diff --git a/Unchase.Swashbuckle.AspNetCore.Extensions.sln b/Unchase.Swashbuckle.AspNetCore.Extensions.sln index 151f138..0112527 100644 --- a/Unchase.Swashbuckle.AspNetCore.Extensions.sln +++ b/Unchase.Swashbuckle.AspNetCore.Extensions.sln @@ -10,6 +10,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "assets", "assets", "{145A03BF-E60E-41F6-BD89-457F940F6C9B}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore + assets\addXmlCommentsFromInheritdoc.png = assets\addXmlCommentsFromInheritdoc.png assets\addXmlCommentsFromSummaryAndRemarks.png = assets\addXmlCommentsFromSummaryAndRemarks.png assets\appendActionCountIntoSwaggerTag.png = assets\appendActionCountIntoSwaggerTag.png appveyor.yml = appveyor.yml diff --git a/appveyor.yml b/appveyor.yml index 53ac2d4..c784d07 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 2.4.{build} +version: 2.5.{build} pull_requests: do_not_increment_build_number: true skip_tags: true diff --git a/assets/addXmlCommentsFromInheritdoc.png b/assets/addXmlCommentsFromInheritdoc.png new file mode 100644 index 0000000..a723df3 Binary files /dev/null and b/assets/addXmlCommentsFromInheritdoc.png differ diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs index d1ca0dc..d414c7c 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Extensions/SwaggerGenOptionsExtensions.cs @@ -105,7 +105,7 @@ public static SwaggerGenOptions IncludeXmlCommentsWithRemarks( { swaggerGenOptions.IncludeXmlComments(xmlDocFactory, includeControllerXmlComments); - var distinctExcludedTypes = excludedTypes.Distinct().ToList(); + var distinctExcludedTypes = excludedTypes?.Distinct().ToArray(); var xmlDoc = xmlDocFactory(); swaggerGenOptions.ParameterFilter(xmlDoc, distinctExcludedTypes); @@ -137,6 +137,24 @@ public static SwaggerGenOptions IncludeXmlCommentsWithRemarks( return swaggerGenOptions.IncludeXmlCommentsWithRemarks(() => new XPathDocument(filePath), includeControllerXmlComments, excludedTypes); } + /// + /// Inject human-friendly descriptions for Schemas and it's Parameters based on <inheritdoc/> XML Comments (from summary and remarks). + /// + /// . + /// + /// Flag to indicate to include remarks from XML comments. + /// + /// Types for which remarks will be excluded. + public static SwaggerGenOptions IncludeXmlCommentsFromInheritDocs( + this SwaggerGenOptions swaggerGenOptions, + bool includeRemarks = false, + params Type[] excludedTypes) + { + var distinctExcludedTypes = excludedTypes?.Distinct().ToArray(); + swaggerGenOptions.SchemaFilter(swaggerGenOptions, includeRemarks, distinctExcludedTypes); + return swaggerGenOptions; + } + #endregion } } \ No newline at end of file diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs new file mode 100644 index 0000000..92267a4 --- /dev/null +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/InheritDocSchemaFilter.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Xml.XPath; +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters +{ + /// + /// Adds documentation that is provided by the <inhertidoc /> tag. + /// + /// + internal class InheritDocSchemaFilter : ISchemaFilter + { + #region Fields + + private const string SummaryTag = "summary"; + private const string RemarksTag = "remarks"; + private const string ExampleTag = "example"; + private readonly bool _includeRemarks; + private readonly List _documents; + private readonly Dictionary _inheritedDocs; + private readonly Type[] _excludedTypes; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// + /// . + /// Include remarks from inheritdoc XML comments. + /// Excluded types. + public InheritDocSchemaFilter(SwaggerGenOptions options, bool includeRemarks = false, params Type[] excludedTypes) + { + _includeRemarks = includeRemarks; + _excludedTypes = excludedTypes; + _documents = options.SchemaFilterDescriptors.Where(x => x.Type == typeof(XmlCommentsSchemaFilter)) + .Select(x => x.Arguments.Single()) + .Cast() + .ToList(); + + _inheritedDocs = _documents.SelectMany( + doc => + { + var inheritedElements = new List<(string, string)>(); + foreach (XPathNavigator member in doc.CreateNavigator().Select("doc/members/member/inheritdoc")) + { + var cref = member.GetAttribute("cref", ""); + member.MoveToParent(); + var parentCref = member.GetAttribute("cref", ""); + if (!string.IsNullOrWhiteSpace(parentCref)) + cref = parentCref; + inheritedElements.Add((member.GetAttribute("name", ""), cref)); + } + + return inheritedElements; + }) + .ToDictionary(x => x.Item1, x => x.Item2); + } + + #endregion + + #region Methods + + /// + /// Apply filter. + /// + /// . + /// . + public void Apply(OpenApiSchema schema, SchemaFilterContext context) + { + if (_excludedTypes.Any() && _excludedTypes.ToList().Contains(context.Type)) + { + return; + } + + // Try to apply a description for inherited types. + var memberName = XmlCommentsNodeNameHelper.GetMemberNameForType(context.Type); + if (string.IsNullOrEmpty(schema.Description) && _inheritedDocs.ContainsKey(memberName)) + { + var cref = _inheritedDocs[memberName]; + var target = GetTargetRecursive(context.Type, cref); + + var targetXmlNode = GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForType(target)); + var summaryNode = targetXmlNode?.SelectSingleNode(SummaryTag); + + if (summaryNode != null) + { + schema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); + + if (_includeRemarks) + { + var remarksNode = targetXmlNode.SelectSingleNode(RemarksTag); + if (remarksNode != null && !string.IsNullOrWhiteSpace(remarksNode.InnerXml)) + { + schema.Description += $" ({XmlCommentsTextHelper.Humanize(remarksNode.InnerXml)})"; + } + } + } + } + + if (schema.Properties == null) + return; + + // Add the summary and examples for the properties. + foreach (var entry in schema.Properties) + { + var memberInfo = ((TypeInfo) context.Type).DeclaredMembers?.FirstOrDefault(p => p.Name.Equals(entry.Key, StringComparison.OrdinalIgnoreCase)); + if (memberInfo != null) + { + ApplyPropertyComments(entry.Value, memberInfo); + } + } + } + + private static MemberInfo GetTarget(MemberInfo memberInfo, string cref) + { + var type = memberInfo.DeclaringType ?? memberInfo.ReflectedType; + + if (type == null) + return null; + + // Find all matching members in all interfaces and the base class. + var targets = type.GetInterfaces() + .Append(type.BaseType) + .SelectMany( + x => x.FindMembers( + memberInfo.MemberType, + BindingFlags.Instance | BindingFlags.Public, + (info, criteria) => info.Name == memberInfo.Name, + null)) + .ToList(); + + // Try to find the target, if one is declared. + if (!string.IsNullOrEmpty(cref)) + { + var crefTarget = targets.SingleOrDefault(t => XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(t) == cref); + + if (crefTarget != null) + return crefTarget; + } + + // We use the last since that will be our base class or the "nearest" implemented interface. + return targets.LastOrDefault(); + } + + private static Type GetTarget(Type type, string cref) + { + var targets = type.GetInterfaces(); + if (type.BaseType != typeof(object)) + targets = targets.Append(type.BaseType).ToArray(); + + // Try to find the target, if one is declared. + if (!string.IsNullOrEmpty(cref)) + { + var crefTarget = targets.SingleOrDefault(t => XmlCommentsNodeNameHelper.GetMemberNameForType(t) == cref); + + if (crefTarget != null) + return crefTarget; + } + + // We use the last since that will be our base class or the "nearest" implemented interface. + return targets.LastOrDefault(); + } + + private void ApplyPropertyComments(OpenApiSchema propertySchema, MemberInfo memberInfo) + { + var memberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(memberInfo); + + if (!_inheritedDocs.ContainsKey(memberName)) + return; + + if (_excludedTypes.Any() && _excludedTypes.ToList() + .Contains(((PropertyInfo)memberInfo).PropertyType)) + { + return; + } + + var cref = _inheritedDocs[memberName]; + var target = GetTargetRecursive(memberInfo, cref); + + var targetXmlNode = GetMemberXmlNode(XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target)); + + if (targetXmlNode == null) + return; + + var summaryNode = targetXmlNode.SelectSingleNode(SummaryTag); + if (summaryNode != null) + { + propertySchema.Description = XmlCommentsTextHelper.Humanize(summaryNode.InnerXml); + + if (_includeRemarks) + { + var remarksNode = targetXmlNode.SelectSingleNode(RemarksTag); + if (remarksNode != null && !string.IsNullOrWhiteSpace(remarksNode.InnerXml)) + { + propertySchema.Description += $" ({XmlCommentsTextHelper.Humanize(remarksNode.InnerXml)})"; + } + } + } + + var exampleNode = targetXmlNode.SelectSingleNode(ExampleTag); + if (exampleNode != null) + propertySchema.Example = new OpenApiString(XmlCommentsTextHelper.Humanize(exampleNode.InnerXml)); + } + + private XPathNavigator GetMemberXmlNode(string memberName) + { + var path = $"/doc/members/member[@name='{memberName}']"; + + foreach (var document in _documents) + { + var node = document.CreateNavigator().SelectSingleNode(path); + + if (node != null) + return node; + } + + return null; + } + + private MemberInfo GetTargetRecursive(MemberInfo memberInfo, string cref) + { + var target = GetTarget(memberInfo, cref); + + if (target == null) + return null; + + var targetMemberName = XmlCommentsNodeNameHelper.GetMemberNameForFieldOrProperty(target); + + if (_inheritedDocs.ContainsKey(targetMemberName)) + return GetTarget(target, _inheritedDocs[targetMemberName]); + + return target; + } + + private Type GetTargetRecursive(Type type, string cref) + { + var target = GetTarget(type, cref); + + if (target == null) + return null; + + var targetMemberName = XmlCommentsNodeNameHelper.GetMemberNameForType(target); + + if (_inheritedDocs.ContainsKey(targetMemberName)) + return GetTarget(target, _inheritedDocs[targetMemberName]); + + return target; + } + + #endregion + } +} diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksDocumentFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksDocumentFilter.cs index 0b24586..4a410ab 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksDocumentFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksDocumentFilter.cs @@ -11,7 +11,7 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters /// /// Inject human-friendly remarks to descriptions for Document's Tags based on XML Comment files. /// - public class XmlCommentsWithRemarksDocumentFilter : IDocumentFilter + internal class XmlCommentsWithRemarksDocumentFilter : IDocumentFilter { #region Fields @@ -56,8 +56,7 @@ public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) foreach (var nameAndType in controllerNamesAndTypes) { - if (_excludedTypes.ToList().Select(t => t.FullName) - .Contains(nameAndType.Value.FullName)) + if (_excludedTypes.Any() && _excludedTypes.ToList().Contains(nameAndType.Value)) { continue; } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksParameterFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksParameterFilter.cs index ce2d133..991fa5c 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksParameterFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksParameterFilter.cs @@ -10,7 +10,7 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters /// /// Inject human-friendly remarks to descriptions for Parameters based on XML Comment files. /// - public class XmlCommentsWithRemarksParameterFilter : IParameterFilter + internal class XmlCommentsWithRemarksParameterFilter : IParameterFilter { #region Fields @@ -53,8 +53,7 @@ public void Apply(OpenApiParameter parameter, ParameterFilterContext context) private void ApplyPropertyTags(OpenApiParameter parameter, PropertyInfo propertyInfo) { - if (propertyInfo.DeclaringType != null && _excludedTypes.ToList().Select(t => t.FullName) - .Contains(propertyInfo.DeclaringType?.FullName)) + if (propertyInfo.DeclaringType != null && _excludedTypes.Any() && _excludedTypes.ToList().Contains(propertyInfo.DeclaringType)) { return; } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksRequestBodyFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksRequestBodyFilter.cs index 3f4266d..d88a4e3 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksRequestBodyFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksRequestBodyFilter.cs @@ -10,7 +10,7 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters /// /// Inject human-friendly remarks to descriptions for RequestBodies based on XML Comment files. /// - public class XmlCommentsWithRemarksRequestBodyFilter : IRequestBodyFilter + internal class XmlCommentsWithRemarksRequestBodyFilter : IRequestBodyFilter { #region Fields @@ -59,8 +59,7 @@ public void Apply(OpenApiRequestBody requestBody, RequestBodyFilterContext conte private void ApplyPropertyTags(OpenApiRequestBody requestBody, PropertyInfo propertyInfo) { - if (propertyInfo.DeclaringType != null && _excludedTypes.ToList().Select(t => t.FullName) - .Contains(propertyInfo.DeclaringType?.FullName)) + if (propertyInfo.DeclaringType != null && _excludedTypes.Any() && _excludedTypes.ToList().Contains(propertyInfo.DeclaringType)) { return; } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksSchemaFilter.cs b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksSchemaFilter.cs index c66f442..7a5e12a 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksSchemaFilter.cs +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Filters/XmlCommentsWithRemarksSchemaFilter.cs @@ -10,7 +10,7 @@ namespace Unchase.Swashbuckle.AspNetCore.Extensions.Filters /// /// Inject human-friendly remarks to descriptions for Schemas based on XML Comment files. /// - public class XmlCommentsWithRemarksSchemaFilter : ISchemaFilter + internal class XmlCommentsWithRemarksSchemaFilter : ISchemaFilter { #region Fields @@ -59,8 +59,7 @@ public void Apply(OpenApiSchema schema, SchemaFilterContext context) private void ApplyTypeTags(OpenApiSchema schema, Type type) { - if (_excludedTypes.ToList().Select(t => t.FullName) - .Contains(type.FullName)) + if (_excludedTypes.Any() && _excludedTypes.ToList().Contains(type)) { return; } @@ -80,8 +79,7 @@ private void ApplyTypeTags(OpenApiSchema schema, Type type) private void ApplyFieldOrPropertyTags(OpenApiSchema schema, MemberInfo fieldOrPropertyInfo) { - if (fieldOrPropertyInfo.DeclaringType != null && _excludedTypes.ToList().Select(t => t.FullName) - .Contains(fieldOrPropertyInfo.DeclaringType?.FullName)) + if (fieldOrPropertyInfo.DeclaringType != null && _excludedTypes.Any() && _excludedTypes.ToList().Contains(fieldOrPropertyInfo.DeclaringType)) { return; } diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj index f5ccaeb..24b95df 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.csproj @@ -14,9 +14,9 @@ 7.3 https://github.com/unchase/Unchase.Swashbuckle.AspNetCore.Extensions/blob/master/assets/icon.png?raw=true - 2.4.1 - 2.4.1.0 - 2.4.1.0 + 2.5.0 + 2.5.0.0 + 2.5.0.0 false Unchase.Swashbuckle.AspNetCore.Extensions.xml diff --git a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml index 69c9947..c42c625 100644 --- a/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml +++ b/src/Unchase.Swashbuckle.AspNetCore.Extensions/Unchase.Swashbuckle.AspNetCore.Extensions.xml @@ -127,6 +127,16 @@ Types for which remarks will be excluded. + + + Inject human-friendly descriptions for Schemas and it's Parameters based on <inheritdoc/> XML Comments (from summary and remarks). + + . + + Flag to indicate to include remarks from XML comments. + + Types for which remarks will be excluded. + factory. @@ -315,6 +325,27 @@ . . + + + Adds documentation that is provided by the <inhertidoc /> tag. + + + + + + Initializes a new instance of the class. + + . + Include remarks from inheritdoc XML comments. + Excluded types. + + + + Apply filter. + + . + . + Document filter for ordering tags by name in OpenApi document. diff --git a/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs b/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs index e9af970..2cff1f2 100644 --- a/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs +++ b/test/WebApi3.1-Swashbuckle/Controllers/TodoController.cs @@ -19,7 +19,7 @@ namespace WebApi3._1_Swashbuckle.Controllers [SwaggerTag("Controller for todo")] public class TodoController : ControllerBase { - private static readonly ItodoContext _context = new TodoContext(); + private static readonly ItodoContext Context = new TodoContext(); /// /// Hided action @@ -61,6 +61,27 @@ public ActionResult ComplicatedAction() }); } + /// + /// InheritDoc action + /// + /// + /// InheritDoc action remarks + /// + /// A InheritDoc class. + /// Returns a InheritDoc class. + [HttpGet("inheritdoc")] + [AllowAnonymous] + [ProducesResponseType(StatusCodes.Status200OK)] + public ActionResult InheritDocAction() + { + return Ok(new InheritDocClass + { + Name = "Name", + Common = "Common", + InheritEnum = InheritEnum.Value + }); + } + /// /// Deletes a specific TodoItem /// @@ -72,14 +93,14 @@ public ActionResult ComplicatedAction() [Authorize(Roles = "AcceptedRole")] public IActionResult Delete(long id) { - var todo = _context.Find(id); + var todo = Context.Find(id); if (todo == null) { return NotFound(); } - _context.Remove(todo); + Context.Remove(todo); return NoContent(); } @@ -108,7 +129,7 @@ public IActionResult Delete(long id) [ProducesResponseType(StatusCodes.Status400BadRequest)] public ActionResult Create(TodoItem item) { - _context.Add(item); + Context.Add(item); return new CreatedResult(string.Empty, item); } diff --git a/test/WebApi3.1-Swashbuckle/Models/IInheritDocClass.cs b/test/WebApi3.1-Swashbuckle/Models/IInheritDocClass.cs new file mode 100644 index 0000000..1f97427 --- /dev/null +++ b/test/WebApi3.1-Swashbuckle/Models/IInheritDocClass.cs @@ -0,0 +1,19 @@ +namespace WebApi3._1_Swashbuckle.Models +{ + /// + /// InheritDocClass - inheritdoc + /// + /// + /// InheritDocClass remarks - inheritdoc + /// + public interface IInheritDocClass : IInheritDocCommon + { + /// + /// Name - inheritdoc + /// + /// + /// Name remarks - inheritdoc + /// + public string Name { get; set; } + } +} diff --git a/test/WebApi3.1-Swashbuckle/Models/IInheritDocCommon.cs b/test/WebApi3.1-Swashbuckle/Models/IInheritDocCommon.cs new file mode 100644 index 0000000..e349d5b --- /dev/null +++ b/test/WebApi3.1-Swashbuckle/Models/IInheritDocCommon.cs @@ -0,0 +1,24 @@ +namespace WebApi3._1_Swashbuckle.Models +{ + /// + /// IInheritDocCommon interface + /// + /// + /// IInheritDocCommon interface remarks + /// + public interface IInheritDocCommon + { + /// + /// Common - inheritdoc (inner) + /// + /// + /// Common remarks - inheritdoc (inner) + /// + public string Common { get; set; } + + /// + /// InheritEnum - inheritdoc (inner) + /// + public InheritEnum InheritEnum { get; set; } + } +} diff --git a/test/WebApi3.1-Swashbuckle/Models/InheritDocClass.cs b/test/WebApi3.1-Swashbuckle/Models/InheritDocClass.cs new file mode 100644 index 0000000..6ad59a1 --- /dev/null +++ b/test/WebApi3.1-Swashbuckle/Models/InheritDocClass.cs @@ -0,0 +1,15 @@ +namespace WebApi3._1_Swashbuckle.Models +{ + /// + public class InheritDocClass : IInheritDocClass + { + /// + public string Name { get; set; } + + /// + public string Common { get; set; } + + /// + public InheritEnum InheritEnum { get; set; } + } +} diff --git a/test/WebApi3.1-Swashbuckle/Models/InheritEnum.cs b/test/WebApi3.1-Swashbuckle/Models/InheritEnum.cs new file mode 100644 index 0000000..39633df --- /dev/null +++ b/test/WebApi3.1-Swashbuckle/Models/InheritEnum.cs @@ -0,0 +1,19 @@ +namespace WebApi3._1_Swashbuckle.Models +{ + /// + /// Inherit enum - enum + /// + /// + /// Inherit enum remarks - enum + /// + public enum InheritEnum + { + /// + /// Inherit enum Value + /// + /// + /// Inherit enum Value remarks + /// + Value = 0 + } +} diff --git a/test/WebApi3.1-Swashbuckle/Startup.cs b/test/WebApi3.1-Swashbuckle/Startup.cs index 5940d2e..6b3671c 100644 --- a/test/WebApi3.1-Swashbuckle/Startup.cs +++ b/test/WebApi3.1-Swashbuckle/Startup.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.IO; using System.Net; +using System.Runtime.InteropServices; using System.Text.Json.Serialization; using Unchase.Swashbuckle.AspNetCore.Extensions.Extensions; using Unchase.Swashbuckle.AspNetCore.Extensions.Filters; @@ -34,18 +35,22 @@ public void ConfigureServices(IServiceCollection services) // use it if you want to hide Paths and Definitions from OpenApi documentation correctly options.UseAllOfToExtendReferenceSchemas(); - #region AddEnumsWithValuesFixFilters + // if you want to add xml comments from inheritdocs (from summary and remarks) into the swagger documentation, add: + // you can exclude remarks for concrete types + options.IncludeXmlCommentsFromInheritDocs(includeRemarks: true, excludedTypes: typeof(string)); // if you want to add xml comments from summary and remarks into the swagger documentation, first of all add: // you can exclude remarks for concrete types var xmlFilePath = Path.Combine(AppContext.BaseDirectory, "WebApi3.1-Swashbuckle.xml"); - options.IncludeXmlCommentsWithRemarks(xmlFilePath, false, + options.IncludeXmlCommentsWithRemarks(filePath: xmlFilePath, includeControllerXmlComments: false, typeof(ComplicatedClass), typeof(InnerEnum)); // or add without remarks //options.IncludeXmlComments(xmlFilePath); + #region AddEnumsWithValuesFixFilters + // Add filters to fix enums // use by default: options.AddEnumsWithValuesFixFilters(); diff --git a/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml b/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml index 806df06..cd5726e 100644 --- a/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml +++ b/test/WebApi3.1-Swashbuckle/WebApi3.1-Swashbuckle.xml @@ -84,6 +84,16 @@ A complicated class. Returns a complicated class. + + + InheritDoc action + + + InheritDoc action remarks + + A InheritDoc class. + Returns a InheritDoc class. + Deletes a specific TodoItem @@ -210,6 +220,71 @@ Second inner enum value remarks + + + InheritDocClass - inheritdoc + + + InheritDocClass remarks - inheritdoc + + + + + Name - inheritdoc + + + Name remarks - inheritdoc + + + + + IInheritDocCommon interface + + + IInheritDocCommon interface remarks + + + + + Common - inheritdoc (inner) + + + Common remarks - inheritdoc (inner) + + + + + InheritEnum - inheritdoc (inner) + + + + + + + + + + + + + + + + + Inherit enum - enum + + + Inherit enum remarks - enum + + + + + Inherit enum Value + + + Inherit enum Value remarks + + Tag for TodoItem