ASP Web API帮助页面 - 从XML <see>标记链接到类

时间:2015-12-04 09:49:36

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

我正在开发一个Web-API项目,我对微软帮助页面自动生成的文档印象深刻。

我使用官方网站creating Help Pages

启用了自定义文档

文档生成成功但是<See cref="">标记中的类的引用似乎都没有添加到描述中,HelpPages只是忽略它们(这是有原因的)。

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

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

2 个答案:

答案 0 :(得分:2)

我的解决方案如下:

  1. 您已获得自定义文档(来自生成的xml文件)
  2. 在文档中启用HTML和XML标记,它们通常会被过滤掉,感谢Post您可以保留它们。
    只需转到:ProjectName&gt;区域&gt; HelpPage&gt; XmlDocumentationProvider.cs
    在方法的第123行:GetTagValue(XPathNavigator parentNode,string tagName)
    将代码return node.Value.Trim();更改为return node.InnerXml;
  3. 创建以下局部视图:
    项目名\区\ HelpPage \意见\帮助** _ ** XML_SeeTagsRenderer.cshtml
    这是我的代码:
  4. @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 **
      在线&#39; 20&#39;我们有与文档中的描述相对应的代码 替换这个:

                  <td class="parameter-documentation">
                      <p>
                          @parameter.Documentation
                      </p>
                  </td>
      
    2. 有了这个:

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

      &安培;你现在必须让它工作 注意:

      • 我尝试将HTML列表放在文档中并且它呈现得很好
      • 我尝试了多个类引用(多个<see cref="MyClass">,我工作正常
      • 您无法引用在类
      • 中声明的类
      • 当您引用当前项目之外的类时,请在该项目中添加类的程序集.getType(查看上面的代码)
      • <see cref>中找到的任何未找到的类都会在说明中打印出它的全名(例如,如果您引用属性或命名空间,则代码不会将其标识为一个类型/类,但它将被打印)

答案 1 :(得分:0)

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