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 我对此调整的解决方案,并希望能使其他程序员受益并节省他们一些时间和精力。
(我的回答在下面的回复中)
我的解决方案如下:
- 您的自定义文档(来自生成的 xml 文件)正在运行
- 在文档中启用 HTML 和 XML 标签,它们通常会被过滤掉,谢谢 Post 您可以保留它们。
只需转到:ProjectName > Areas > HelpPage > XmlDocumentationProvider.cs
在第 123 行的方法中:GetTagValue(XPathNavigator parentNode, string tagName)
将代码 return node.Value.Trim();
更改为 return node.InnerXml;
- 创建以下局部视图:
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);
}
最后转到: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>
我正在开发 Web-API 项目,Microsoft 的 HelpPages 自动生成的文档给我留下了深刻的印象。
我使用官方网站启用了自定义文档 creating Help Pages
文档已成功生成,但是 none <See cref="">
标签对 类 的引用似乎已添加到描述中,HelpPages 只是忽略了它们(这是为了原因)。
我真的很想在我的项目中使用这个功能,我搜索了很多(有时接近)但 none 给出了令人信服的答案。
这就是为什么我决定 post 我对此调整的解决方案,并希望能使其他程序员受益并节省他们一些时间和精力。 (我的回答在下面的回复中)
我的解决方案如下:
- 您的自定义文档(来自生成的 xml 文件)正在运行
- 在文档中启用 HTML 和 XML 标签,它们通常会被过滤掉,谢谢 Post 您可以保留它们。
只需转到:ProjectName > Areas > HelpPage > XmlDocumentationProvider.cs
在第 123 行的方法中:GetTagValue(XPathNavigator parentNode, string tagName)
将代码return node.Value.Trim();
更改为return node.InnerXml;
- 创建以下局部视图:
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);
}
最后转到: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>