如何在Swagger中处理<see />XML文档标签?

问题描述 投票:0回答:3

我有一个 ASP.Net Web API,其中包含整个 XML 文档,包括许多

<see />
标签。

我最近在解决方案中添加了 Swagger(Swashbuckle UI 东西),并注意到它不处理像

<see />
这样的 XML 标签。在网上浏览后,我发现了这个 - https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/57,所以开发人员似乎不想解决这个问题。

有人有什么建议吗?我尝试运行一个小 .exe 将生成的 XML 文件中的所有标签替换为其对象名称(例如

<see cref="MyObject"/>
变为
MyObject
),但这一直搞砸,即使我手动执行此操作时,Swagger 由于某种原因也没有获取更改(XML 是否已加载到内存中的某个位置?)

asp.net asp.net-core swagger swagger-ui swashbuckle
3个回答
1
投票

XML 文档注释生成到一个文件中,其名称与同一文件夹中的程序集相同(通常)。您可以使用 XDocument 和相关类解析该文件,然后使用 schema 过滤器 (

ISchemaFilter
) 在 Swashbuckle 公开的 Open API schema 上设置适当的属性。

我不确定什么架构元素适合“另请参阅”或“相关资源”功能。您需要对此进行调查(可能是 ASP.NET Core 的开放 API 实现中的自定义属性,或者通过将文本附加到描述属性)。

至于

XML 是否已加载到内存中的某个位置?

是的。 Swashbuckle 本身会解析此 XML 以执行其当前执行的所有操作。粗略地说,它将其构建到一个文档对象模型 (DOM) 中,该文档对象模型构建在上述 Microsoft 的 Open API 实现之上。使用模式过滤器将使您能够访问该 DOM。


1
投票

根据 Kit 的建议,我找到了一种检索和更改文档的方法,但需要一种解析 XML 标签的方法。我发现 this 虽然它指的是 HTML,但也删除了 XML 标签。

因此,按照需要的顺序:

  1. 我有一个通用的实用工具类,我在其中放置了上面的 HtmlAgilityPack 代码(尽管我调用了方法RemoveXMLTags)
  2. 添加了一个新的 XmlDocumentFilter 类,如下所示:
public class XmlDocumentFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        foreach (var path in swaggerDoc.Paths)
        {
            foreach (var operation in path.Value.Operations)
            {
                operation.Value.Description = Utilities.RemoveXMLTags(operation.Value.Description);

                operation.Value.Summary = Utilities.RemoveXMLTags(operation.Value.Summary);

                if (operation.Value.RequestBody != null)
                {
                    operation.Value.RequestBody.Description = Utilities.RemoveXMLTags(operation.Value.RequestBody.Description);
                }

                foreach (var parameter in operation.Value.Parameters)
                {
                    parameter.Description = Utilities.RemoveXMLTags(parameter.Description);
                }
            }
        }
    }
}
  1. 在我的 Startup 类中添加了以下内容:
services.AddSwaggerGen(setupAction =>
    {
        setupAction.SwaggerDoc(
            "MyAppAPIDocumentation",
            new OpenApiInfo() { Title = "MyApp API", Version = "1" });

        var xmlDocumentationFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        var xmlDocumentationFullPath = Path.Combine(AppContext.BaseDirectory, xmlDocumentationFile);

        setupAction.IncludeXmlComments(xmlDocumentationFullPath);
        setupAction.DocumentFilter<XmlDocumentFilter>();
    });
  1. 利润

0
投票

我创建了自己的 this 版本,它不依赖于外部依赖项(如 HtmlAgilityPack)。这也删除了不受支持的

<see cref="..." />
但保留了其中的值。但我不知道哪个在性能方面更快(和/或更好)。

public static class XmlCleaner
{
    public static string RemoveUnsupportedCref(string xml)
    {
        //<see cref="P:abc" />
        //<see cref="T:abc" />
        //<see cref="F:abc" />
        //etc.
    
        //Filter only on valid xml input
        if (string.IsNullOrEmpty(xml) || xml.Contains('<') == false) { return xml; }
    
        //Explanation: creates three groups.
        //group 1: all text in front of '<see cref="<<randomAlphabeticCharacterGoesHere>>:'
        //group 2: all text after the match of '<see cref="<<randomAlphabeticCharacterGoesHere>>:' UNTIL there is a match with '" />'
        //group 3: all text after '" />'
        //Then, merges group1, group2 and group3 together. This effectively removes '<see cref="X: " />' but keeps the value in between the " and ".
        xml = Regex.Replace(xml, "(.*)<see cref=\"[A-Za-z]:(.*)\" \\/>(.*)", "$1$2$3");
    
        return xml;
    }
}

基本上就是这样。但是,我想要从 Swagger 获得更多东西(例如作为字符串的枚举、带有我想要包含的依赖项的注释的外部 .xml 文件等),因此还有一些额外的代码。也许你们中的一些人可能会发现这很有帮助。

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc(
        "MyAppAPIDocumentation",
        new OpenApiInfo() { Title = "MyApp API", Version = "1" });

    //Prevent schemas with the same type name (duplicates) from crashing Swagger
    options.CustomSchemaIds(type => type.ToString());

    //Will sort the schemas and their parameters alphabetically
    options.DocumentFilter<SwaggerHelper.DocumentSorter>();

    //Will show enums as strings
    options.SchemaFilter<SwaggerHelper.ShowEnumsAsStrings>();

    var dir = new DirectoryInfo(AppContext.BaseDirectory);
    foreach (var fi in dir.EnumerateFiles("*.xml"))
    {
        var doc = XDocument.Load(fi.FullName);
        
        //Removes unsupported <see cref=""/> statements
        var xml = SwaggerHelper.XmlCleaner.RemoveUnsupportedCref(doc.ToString());
        doc = XDocument.Parse(xml);
        
        options.IncludeXmlComments(() => new XPathDocument(doc.CreateReader()), true);
        //Adds associated Xml information to each enum
        options.SchemaFilter<SwaggerHelper.XmlCleaner.RemoveUnsupportedCref>(doc);
    }
}

//Adds support for converting strings to enums
builder.Services.AddControllers().AddJsonOptions(options =>
{
    options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
});

SwaggerHelper类如下:

public static class SwaggerHelper
{
    /*
     * FROM: https://stackoverflow.com/questions/61507662/sorting-the-schemas-portion-of-a-swagger-page-using-swashbuckle/62639027#62639027
     */
    /// <summary>
    /// Sorts the schemas and associated Xml documentation files.
    /// </summary>
    public class SortSchemas : IDocumentFilter
    {
        // Implements IDocumentFilter.Apply().
        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            if (swaggerDoc == null) { return; }

            //Re-order the schemas alphabetically
            swaggerDoc.Components.Schemas = swaggerDoc.Components.Schemas
                .OrderBy(kvp => kvp.Key, StringComparer.InvariantCulture)
                .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

            //Re-order the properties per schema alphabetically
            foreach (var schema in swaggerDoc.Components.Schemas)
            {
                schema.Value.Properties = schema.Value.Properties
                    .OrderBy(kvp => kvp.Key, StringComparer.InvariantCulture)
                    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
            }
        }
    }

    /*
     * FROM: https://stackoverflow.com/questions/36452468/swagger-ui-web-api-documentation-present-enums-as-strings/61906056#61906056
     */
    /// <summary>
    /// Shows enums as strings in the generated Swagger output.
    /// </summary>
    public class ShowEnumsAsStrings : ISchemaFilter
    {
        public void Apply(OpenApiSchema model, SchemaFilterContext context)
        {
            if (context.Type.IsEnum)
            {
                model.Enum.Clear();
                Enum.GetNames(context.Type)
                    .ToList()
                    .ForEach(n =>
                    {
                        model.Enum.Add(new OpenApiString(n));
                        model.Type = "string";
                        model.Format = null;
                    });
            }
        }
    }

    /*
     * FROM: https://stackoverflow.com/questions/53282170/swaggerui-not-display-enum-summary-description-c-sharp-net-core/69089035#69089035
     */
    /// <summary>
    /// Swagger schema filter to modify description of enum types so they
    /// show the Xml documentation attached to each member of the enum.
    /// </summary>
    public class AddXmlCommentsToEnums : ISchemaFilter
    {
        private readonly XDocument xmlComments;
        private readonly string assemblyName;

        /// <summary>
        /// Initialize schema filter.
        /// </summary>
        /// <param name="xmlComments">Document containing XML docs for enum members.</param>
        public AddXmlCommentsToEnums(XDocument xmlComments)
        {
            this.xmlComments = xmlComments;
            this.assemblyName = DetermineAssembly(xmlComments);
        }

        /// <summary>
        /// Pre-amble to use before the enum items
        /// </summary>
        public static string Prefix { get; set; } = "<p>Possible values:</p>";

        /// <summary>
        /// Format to use, 0 : value, 1: Name, 2: Description
        /// </summary>
        public static string Format { get; set; } = "<b>{0} - {1}</b>: {2}";

        /// <summary>
        /// Apply this schema filter.
        /// </summary>
        /// <param name="schema">Target schema object.</param>
        /// <param name="context">Schema filter context.</param>
        public void Apply(OpenApiSchema schema, SchemaFilterContext context)
        {
            var type = context.Type;

            // Only process enums and...
            if (!type.IsEnum)
            {
                return;
            }

            // ...only the comments defined in their origin assembly
            if (type.Assembly.GetName().Name != assemblyName)
            {
                return;
            }
            var sb = new StringBuilder(schema.Description);

            if (!string.IsNullOrEmpty(Prefix))
            {
                sb.AppendLine(Prefix);
            }

            sb.AppendLine("<ul>");

            // TODO: Handle flags better e.g. Hex formatting
            foreach (var name in Enum.GetValues(type))
            {
                // Allows for large enums
                var value = Convert.ToInt64(name);
                var fullName = $"F:{type.FullName}.{name}";

                var description = xmlComments.XPathEvaluate(
                    $"normalize-space(//member[@name = '{fullName}']/summary/text())"
                ) as string;

                sb.AppendLine(string.Format("<li>" + Format + "</li>", value, name, description));
            }

            sb.AppendLine("</ul>");

            schema.Description = sb.ToString();
        }

        private string DetermineAssembly(XDocument doc)
        {
            var name = ((IEnumerable)doc.XPathEvaluate("/doc/assembly")).Cast<XElement>().ToList().FirstOrDefault();

            return name?.Value;
        }
    }

    /// <summary>
    /// Cleans Xml documentation files.
    /// </summary>
    public static class XmlCleaner
    {
        public static string RemoveUnsupportedCref(string xml)
        {
            //<see cref="P:abc" />
            //<see cref="T:abc" />
            //<see cref="F:abc" />
            //etc.

            //Filter only on valid xml input
            if (string.IsNullOrEmpty(xml) || xml.Contains('<') == false) { return xml; }

            //Explanation: creates three groups.
            //group 1: all text in front of '<see cref="<<randomAlphabeticCharacterGoesHere>>:'
            //group 2: all text after the match of '<see cref="<<randomAlphabeticCharacterGoesHere>>:' UNTIL there is a match with '" />'
            //group 3: all text after '" />'
            //Then, merges group1, group2 and group3 together. This effectively removes '<see cref="X: " />' but keeps the value in between the " and ".
            xml = Regex.Replace(xml, "(.*)<see cref=\"[A-Za-z]:(.*)\" \\/>(.*)", "$1$2$3");

            return xml;
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.