【问题标题】:ASP Web API Help pages - Link to class from XML <see> tagASP Web API 帮助页面 - 从 XML <see> 标记链接到类
【发布时间】:2015-12-04 09:49:35
【问题描述】:

我正在开发一个 Web-API 项目,我对 Microsoft 的 HelpPages 自动生成的文档印象深刻。

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

文档已成功生成,但似乎没有将来自&lt;See cref=""&gt; 标记的类的引用添加到描述中,HelpPages 只是忽略它们(这是有原因的)。

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

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

【问题讨论】:

标签: c# asp.net documentation asp.net-web-api asp.net-web-api-helppages


【解决方案1】:

我的解决方案如下:

  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 列表放入文档中,结果很好
  • 我尝试了多个类引用(多个&lt;see cref="MyClass"&gt;,我工作得很好
  • 不能引用在类中声明的类
  • 当您引用当前项目之外的类时,请在该项目中添加类的程序集 .getType(检查我上面的代码)
  • &lt;see cref&gt; 中找到的任何未找到的类都将在描述中打印它的全名(例如,如果您引用一个属性或命名空间,代码不会将其识别为类型/类,但它将是打印)

【讨论】:

    【解决方案2】:

    我已经实现了处理 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 类:

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

    然后我写了扩展类来直接从视图中使用这个类:

    /// <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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-18
      • 1970-01-01
      • 2014-12-27
      • 1970-01-01
      • 2016-09-24
      • 2012-05-09
      相关资源
      最近更新 更多