ASP Web API 帮助页面 - Link 到 class 来自 XML <see> 标签

ASP Web API Help pages - Link to class from XML <see> tag

我正在开发 Web-API 项目,Microsoft 的 HelpPages 自动生成的文档给我留下了深刻的印象。

我使用官方网站启用了自定义文档 creating Help Pages

文档已成功生成,但是 none <See cref=""> 标签对 类 的引用似乎已添加到描述中,HelpPages 只是忽略了它们(这是为了原因)。

我真的很想在我的项目中使用这个功能,我搜索了很多(有时接近)但 none 给出了令人信服的答案。

这就是为什么我决定 post 我对此调整的解决方案,并希望能使其他程序员受益并节省他们一些时间和精力。 (我的回答在下面的回复中)

我的解决方案如下:

  1. 您的自定义文档(来自生成的 xml 文件)正在运行
  2. 在文档中启用 HTML 和 XML 标签,它们通常会被过滤掉,谢谢 Post 您可以保留它们。
    只需转到:ProjectName > Areas > HelpPage > XmlDocumentationProvider.cs
    在第 123 行的方法中:GetTagValue(XPathNavigator parentNode, string tagName)
    将代码 return node.Value.Trim(); 更改为 return node.InnerXml;
  3. 创建以下局部视图:
    ProjectName\Areas\HelpPage\Views\Help**_XML_SeeTagsRenderer.cshtml**
    这是我的代码:
@using System.Web.Http;
@using MyProject.Areas.HelpPage.Controllers;
@using MyProject.Areas.HelpPage;
@using MyProject.Areas.HelpPage.ModelDescriptions
@using System.Text.RegularExpressions
@model string
@{
    int @index = 0;
    string @xml = Model;
    if (@xml == null)
        @xml = "";
    Regex @seeRegex = new Regex("<( *)see( +)cref=\"([^\"]):([^\"]+)\"( *)/>");//Regex("<see cref=\"T:([^\"]+)\" />");
    Match @xmlSee = @seeRegex.Match(@xml);
    string @typeAsText = "";
    Type @tp;

    ModelDescriptionGenerator modelDescriptionGenerator = (new HelpController()).Configuration.GetModelDescriptionGenerator();

}

@if (xml !="" && xmlSee != null && xmlSee.Length > 0)
{

    while (xmlSee != null && xmlSee.Length > 0)
    {

            @MvcHtmlString.Create(@xml.Substring(@index, @xmlSee.Index - @index))

        int startingIndex = xmlSee.Value.IndexOf(':')+1;
        int endIndex = xmlSee.Value.IndexOf('"', startingIndex);
        typeAsText = xmlSee.Value.Substring(startingIndex, endIndex - startingIndex);  //.Replace("<see cref=\"T:", "").Replace("\" />", "");
        System.Reflection.Assembly ThisAssembly = typeof(ThisProject.Controllers.HomeController).Assembly;
        tp = ThisAssembly.GetType(@typeAsText);

        if (tp == null)//try another referenced project
        {
            System.Reflection.Assembly externalAssembly = typeof(MyExternalReferncedProject.AnyClassInIt).Assembly;
            tp = externalAssembly.GetType(@typeAsText);
        }  

        if (tp == null)//also another referenced project- as needed
        {
            System.Reflection.Assembly anotherExtAssembly = typeof(MyExternalReferncedProject2.AnyClassInIt).Assembly;
            tp = anotherExtAssembly .GetType(@typeAsText);
        }


        if(tp == null)//case of nested class
        {
            System.Reflection.Assembly thisAssembly = typeof(ThisProject.Controllers.HomeController).Assembly;
            //the below code is done to support detecting nested classes.
            var processedTypeString = typeAsText;
            var lastIndexofPoint = typeAsText.LastIndexOf('.');
            while (lastIndexofPoint > 0 && tp == null)
            {
                processedTypeString = processedTypeString.Insert(lastIndexofPoint, "+").Remove(lastIndexofPoint + 1, 1);
                tp = SPLocatorBLLAssembly.GetType(processedTypeString);//nested class are recognized as: namespace.outerClass+nestedClass
                lastIndexofPoint = processedTypeString.LastIndexOf('.');
            }
        }

        if (@tp != null)
        {
            ModelDescription md = modelDescriptionGenerator.GetOrCreateModelDescription(tp);
                @Html.DisplayFor(m => md.ModelType, "ModelDescriptionLink", new { modelDescription = md })            
        }
        else
        {            
                @MvcHtmlString.Create(@typeAsText)            
        }
        index = xmlSee.Index + xmlSee.Length;
        xmlSee = xmlSee.NextMatch();
    }    
            @MvcHtmlString.Create(@xml.Substring(@index, @xml.Length - @index))    
}
else
{    
        @MvcHtmlString.Create(@xml);    
}
  1. 最后转到:ProjectName\Areas\HelpPage\Views\Help\DisplayTemplates**Parameters.cshtml**
    在“20”行,我们有与文档中的描述相对应的代码。
    替换这个:

                <td class="parameter-documentation">
                    <p>
                        @parameter.Documentation
                    </p>
                </td>
    

有了这个:

                <td class="parameter-documentation">
                    <p>
                        @Html.Partial("_XML_SeeTagsRenderer", (@parameter.Documentation == null? "" : @parameter.Documentation.ToString()))
                    </p>
                </td>

& 瞧,你现在必须让它工作了。
备注:

  • 我尝试将 HTML 列表放入文档中,但效果很好
  • 我尝试了多个 class 引用(多个 <see cref="MyClass"> 我工作正常
  • 您不能引用在 class
  • 中声明的 class
  • 当您引用当前项目之外的 class 时,请在该项目中添加 class 的程序集 .getType(检查我上面的代码)
  • <see cref> 中找到的任何未找到的 class 将在描述中打印其全名(例如,如果您引用 属性 或名称空间,代码将不会' t 将其识别为 type/class 但它会被打印)

我已经实现了 class 处理 xml 文档块并将文档标签更改为 html 标签。

/// <summary>
/// Reprensets xml help block converter interface.
/// </summary>
public class HelpBlockRenderer : IHelpBlockRenderer
{
    /// <summary>
    /// Stores regex to parse <c>See</c> tag.
    /// </summary>
    private static readonly Regex regexSeeTag = new Regex("<( *)see( +)cref=\"(?<prefix>[^\"]):(?<member>[^\"]+)\"( *)/>",
        RegexOptions.IgnoreCase);

    /// <summary>
    /// Stores the pair tag coversion dictionary.
    /// </summary>
    private static readonly Dictionary<string, string> pairedTagConvertsion = new Dictionary<string, string>()
    {
        { "para", "p" },
        { "c", "b" }
    };

    /// <summary>
    /// Stores configuration.
    /// </summary>
    private HttpConfiguration config;

    /// <summary>
    /// Initializes a new instance of the <see cref="HelpBlockRenderer"/> class. 
    /// </summary>
    /// <param name="config">The configuration.</param>
    public HelpBlockRenderer(HttpConfiguration config)
    {
        this.config = config;
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="HelpBlockRenderer"/> class. 
    /// </summary>
    public HelpBlockRenderer()
        : this(GlobalConfiguration.Configuration)
    {
    }

    /// <summary>
    /// Renders specified xml help block to valid html content.
    /// </summary>
    /// <param name="helpBlock">The help block.</param>
    /// <param name="urlHelper">The url helper for link building.</param>
    /// <returns>The html content.</returns>
    public HtmlString RenderHelpBlock(string helpBlock, UrlHelper urlHelper)
    {
        if (string.IsNullOrEmpty(helpBlock))
        {
            return new HtmlString(string.Empty);
        }

        string result = helpBlock;

        result = this.RenderSeeTag(result, urlHelper);

        result = this.RenderPairedTags(result);

        return new HtmlString(result);
    }

    /// <summary>
    /// Process <c>See</c> tag.
    /// </summary>
    /// <param name="helpBlock">Hte original help block string.</param>
    /// <param name="urlHelper">The url helper for link building.</param>
    /// <returns>The html content.</returns>
    private string RenderSeeTag(string helpBlock, UrlHelper urlHelper)
    {
        string result = helpBlock;

        Match match = null;
        while ((match = HelpBlockRenderer.regexSeeTag.Match(result)).Success)
        {
            var originalValues = match.Value;

            var prefix = match.Groups["prefix"].Value;

            var anchorText = string.Empty;
            var link = string.Empty;

            switch (prefix)
            {
                case "T":
                    {
                        // if See tag has reference to type, then get value from member regex group.
                        var modelType = match.Groups["member"].Value;

                        anchorText = modelType.Substring(modelType.LastIndexOf(".") + 1);
                        link = urlHelper.Action("ResourceModel", "Help",
                            new
                            {
                                modelName = anchorText,
                                area = "ApiHelpPage"
                            });
                        break;
                    }
                case "M":
                    {
                        // Check that specified type member is API member.
                        var apiDescriptor = this.GetApiDescriptor(match.Groups["member"].Value);
                        if (apiDescriptor != null)
                        {
                            anchorText = apiDescriptor.ActionDescriptor.ActionName;
                            link = urlHelper.Action("Api", "Help",
                                new
                                {
                                    apiId = ApiDescriptionExtensions.GetFriendlyId(apiDescriptor),
                                    area = "ApiHelpPage"
                                });
                        }
                        else
                        {
                            // Web API Help can generate help only for whole API model,
                            // So, in case if See tag contains link to model member, replace link with link to model class.
                            var modelType = match.Groups["member"].Value.Substring(0, match.Groups["member"].Value.LastIndexOf("."));

                            anchorText = modelType.Substring(modelType.LastIndexOf(".") + 1);
                            link = urlHelper.Action("ResourceModel", "Help",
                                new
                                {
                                    modelName = anchorText,
                                    area = "ApiHelpPage"
                                });
                        }
                        break;
                    }
                default:
                    {
                        anchorText = match.Groups["member"].Value;

                        // By default link will be rendered with empty anrchor.
                        link = "#";
                        break;
                    }
            }

            // Build the anchor.
            var anchor = string.Format("<a href=\"{0}\">{1}</a>", link, anchorText);

            result = result.Replace(originalValues, anchor);
        }

        return result;
    }

    /// <summary>
    /// Converts original help paired tags to html tags.
    /// </summary>
    /// <param name="helpBlock">The help block.</param>
    /// <returns>The html content.</returns>
    private string RenderPairedTags(string helpBlock)
    {
        var result = helpBlock;
        foreach (var key in HelpBlockRenderer.pairedTagConvertsion.Keys)
        {
            Regex beginTagRegex = new Regex(string.Format("<{0}>", key), RegexOptions.IgnoreCase);
            Regex endTagRegex = new Regex(string.Format("</{0}>", key), RegexOptions.IgnoreCase);

            result = beginTagRegex.Replace(result, string.Format("<{0}>", HelpBlockRenderer.pairedTagConvertsion[key]));
            result = endTagRegex.Replace(result, string.Format("</{0}>", HelpBlockRenderer.pairedTagConvertsion[key]));
        }

        return result;
    }

    /// <summary>
    /// Gets the api descriptor by specified member name.
    /// </summary>
    /// <param name="member">The member fullname.</param>
    /// <returns>The api descriptor.</returns>
    private ApiDescription GetApiDescriptor(string member)
    {
        Regex controllerActionRegex = new Regex("[a-zA-Z0-9\.]+\.(?<controller>[a-zA-Z0-9]+)Controller\.(?<action>[a-zA-Z0-9]+)\(.*\)");

        var match = controllerActionRegex.Match(member);

        if (match.Success)
        {
            var controller = match.Groups["controller"].Value;
            var action = match.Groups["action"].Value;

            var descriptions = this.config.Services.GetApiExplorer().ApiDescriptions;

            return descriptions.FirstOrDefault(x => x.ActionDescriptor.ActionName.Equals(action) &&
                x.ActionDescriptor.ControllerDescriptor.ControllerName == controller);
        }

        return null;
    }
}

要使用它,您需要更改 XmlDocumentationProvider class:

private static string GetTagValue(XPathNavigator parentNode, string tagName)
    {
        if (parentNode != null)
        {
            XPathNavigator node = parentNode.SelectSingleNode(tagName);
            if (node != null)
            {
                return node.InnerXml;
            }
        }

        return null;
    }

然后我编写了扩展程序 class 以直接从视图中使用此 class:

/// <summary>
/// Represents html help content extension class.
/// Contains methods to convert Xml help blocks to html string.
/// </summary>
public static class HtmlHelpContentExtensions
{
    /// <summary>
    /// Converts help block in xml format to html string with proper tags, links and etc.
    /// </summary>
    /// <param name="helpBlock">The help block content.</param>
    /// <param name="urlHelper">The url helper for link building.</param>
    /// <returns>The resulting html string.</returns>
    public static HtmlString ToHelpContent(this string helpBlock, UrlHelper urlHelper)
    {
        // Initialize your rendrer here or take it from IoC

        return renderer.RenderHelpBlock(helpBlock, urlHelper);
    }
}

最后,例如 Parameters.cshtml:

<td class="parameter-documentation">
    <p>@parameter.Documentation.ToHelpContent(Url)</p>
    @if(!string.IsNullOrEmpty(parameter.Remarks))
    { 
         <p>@parameter.Remarks.ToHelpContent(Url)</p>
    }
</td>