简体   繁体   English

在运行时以编程方式获取摘要注释

[英]Programmatically get Summary comments at runtime

I'm looking for a way to programmatically get the summary portion of Xml-comments of a method in ASP.net.我正在寻找一种方法来以编程方式获取 ASP.net 中方法的 Xml-comments 的摘要部分。

I have looked at the previous related posts and they do not supply a way of doing so in a web environment.我查看了之前的相关帖子,但它们没有提供在 Web 环境中执行此操作的方法。

I can not use any 3rd party apps and due to a web environment, Visual studio plugin's aren't much use either.我无法使用任何 3rd 方应用程序,并且由于网络环境,Visual Studio 插件也没有多大用处。

The closest thing I have found to a working solution was the JimBlackler project, but it only works on DLL's.我发现的最接近工作解决方案的是 JimBlackler 项目,但它只适用于 DLL。

Naturally, something like 'supply .CS file, get XML documentation' would be optimal.自然,诸如“提供 .CS 文件,获取 XML 文档”之类的内容将是最佳选择。


Current situation现在的情况

I have a web-service and trying to dynamically generate documentation for it.我有一个网络服务并试图为其动态生成文档。

Reading the Methods, and properties is easy, but getting the Summary for each method is throwing me off a bit.阅读方法和属性很容易,但是获取每个方法的摘要让我有点失望。

/// <summary>
/// This Is what I'm trying to read
/// </summary>
public class SomeClass()
{
    /// <summary>
    /// This Is what I'm trying to read
    /// </summary>
    public void SomeMethod()
    {
    }
}

A Workaround - Using reflection on Program.DLL/EXE together with Program.XML file解决方法- 将 Program.DLL/EXE 上的反射与 Program.XML 文件一起使用

If you take a look at the sibling .XML file generated by Visual Studio you will see that there is a fairly flat hierarchy of /members/member.如果您查看由 Visual Studio 生成的同级 .XML 文件,您将看到 /members/member 的层次结构相当平坦。 All you have to do is get hold on each method from your DLL via MethodInfo object.您所要做的就是通过 MethodInfo 对象从您的 DLL 中获取每个方法。 Once you have this object you turn to the XML and use XPATH to get the member containing the XML documentation for this method.一旦你有了这个对象,你就转向 XML 并使用 XPATH 来获取包含此方法的 XML 文档的成员。

Members are preceded by a letter.成员前面有一封信。 XML doc for methods are preceded by "M:" for class by "T:" etc.方法的 XML 文档以“M:”开头,类以“T:”等开头。

Load your sibling XML加载您的兄弟 XML

string docuPath = dllPath.Substring(0, dllPath.LastIndexOf(".")) + ".XML";

if (File.Exists(docuPath))
{
  _docuDoc = new XmlDocument();
  _docuDoc.Load(docuPath);
}

Use this xpath to get the member representing the method XML docu使用此 xpath 获取表示方法 XML 文档的成员

string path = "M:" + mi.DeclaringType.FullName + "." + mi.Name;

XmlNode xmlDocuOfMethod = _docuDoc.SelectSingleNode(
    "//member[starts-with(@name, '" + path + "')]");

Now scan childnodes for all the rows of "///" Sometimes the /// Summary contains extra blanks, if this bothers use this to remove现在扫描“///”的所有行的子节点有时///摘要包含额外的空白,如果这很麻烦,请使用它来删除

var cleanStr = Regex.Replace(row.InnerXml, @"\s+", " ");

The XML summary isn't stored in the .NET assembly - it's optionally written out to an XML file as part of your build (assuming you're using Visual Studio). XML 摘要不存储在 .NET 程序集中 - 作为构建的一部分(假设您使用的是 Visual Studio),它可以选择写入 XML 文件。

Consequently there is no way to "pull out" the XML summaries of each method via reflection on a compiled .NET assembly (either .EXE or .DLL) - because the data simply isn't there for you to pull out.因此,无法通过对已编译的 .NET 程序集(.EXE 或 .DLL)的反射来“提取”每个方法的 XML 摘要——因为数据根本不存在供您提取。 If you want the data, you'll have to instruct your build environment to output the XML files as part of your build process and parse those XML files at runtime to get at the summary information.如果需要数据,则必须指示构建环境将 XML 文件作为构建过程的一部分输出,并在运行时解析这些 XML 文件以获取摘要信息。

You could 'document' your method using the System.ComponentModel.DataAnnotations.DisplayAttribute attribute, eg您可以使用System.ComponentModel.DataAnnotations.DisplayAttribute属性“记录”您的方法,例如

[Display(Name = "Foo", Description = "Blah")]
void Foo()
{
}

then use reflection to pull the description at runtime.然后使用反射在运行时提取描述。

A deleted post, made by @OleksandrIeremenko, on this thread links to this article https://jimblackler.net/blog/?p=49 which was the basis for my solution.由@OleksandrIeremenko 在此线程上发布的已删除帖子链接到本文https://jimblackler.net/blog/?p=49 ,这是我的解决方案的基础。

Below is a modification of Jim Blackler's code making extension methods off the MemberInfo and Type objects and adding code that returns the summary text or an empty string if not available.下面是对 Jim Blackler 代码的修改,使扩展方法脱离 MemberInfo 和 Type 对象,并添加返回摘要文本或空字符串(如果不可用)的代码。

Usage用法

var typeSummary = typeof([Type Name]).GetSummary();
var methodSummary = typeof([Type Name]).GetMethod("[Method Name]").GetSummary();

Extension Class扩展类

/// <summary>
/// Utility class to provide documentation for various types where available with the assembly
/// </summary>
public static class DocumentationExtensions
{
    /// <summary>
    /// Provides the documentation comments for a specific method
    /// </summary>
    /// <param name="methodInfo">The MethodInfo (reflection data ) of the member to find documentation for</param>
    /// <returns>The XML fragment describing the method</returns>
    public static XmlElement GetDocumentation(this MethodInfo methodInfo)
    {
        // Calculate the parameter string as this is in the member name in the XML
        var parametersString = "";
        foreach (var parameterInfo in methodInfo.GetParameters())
        {
            if (parametersString.Length > 0)
            {
                parametersString += ",";
            }

            parametersString += parameterInfo.ParameterType.FullName;
        }

        //AL: 15.04.2008 ==> BUG-FIX remove “()” if parametersString is empty
        if (parametersString.Length > 0)
            return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name + "(" + parametersString + ")");
        else
            return XmlFromName(methodInfo.DeclaringType, 'M', methodInfo.Name);
    }

    /// <summary>
    /// Provides the documentation comments for a specific member
    /// </summary>
    /// <param name="memberInfo">The MemberInfo (reflection data) or the member to find documentation for</param>
    /// <returns>The XML fragment describing the member</returns>
    public static XmlElement GetDocumentation(this MemberInfo memberInfo)
    {
        // First character [0] of member type is prefix character in the name in the XML
        return XmlFromName(memberInfo.DeclaringType, memberInfo.MemberType.ToString()[0], memberInfo.Name);
    }
    /// <summary>
    /// Returns the Xml documenation summary comment for this member
    /// </summary>
    /// <param name="memberInfo"></param>
    /// <returns></returns>
    public static string GetSummary(this MemberInfo memberInfo)
    {
        var element = memberInfo.GetDocumentation();
        var summaryElm = element?.SelectSingleNode("summary");
        if (summaryElm == null) return "";
        return summaryElm.InnerText.Trim();
    }

    /// <summary>
    /// Provides the documentation comments for a specific type
    /// </summary>
    /// <param name="type">Type to find the documentation for</param>
    /// <returns>The XML fragment that describes the type</returns>
    public static XmlElement GetDocumentation(this Type type)
    {
        // Prefix in type names is T
        return XmlFromName(type, 'T', "");
    }

    /// <summary>
    /// Gets the summary portion of a type's documenation or returns an empty string if not available
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static string GetSummary(this Type type)
    {
        var element = type.GetDocumentation();
        var summaryElm = element?.SelectSingleNode("summary");
        if (summaryElm == null) return "";
        return summaryElm.InnerText.Trim();
    }

    /// <summary>
    /// Obtains the XML Element that describes a reflection element by searching the 
    /// members for a member that has a name that describes the element.
    /// </summary>
    /// <param name="type">The type or parent type, used to fetch the assembly</param>
    /// <param name="prefix">The prefix as seen in the name attribute in the documentation XML</param>
    /// <param name="name">Where relevant, the full name qualifier for the element</param>
    /// <returns>The member that has a name that describes the specified reflection element</returns>
    private static XmlElement XmlFromName(this Type type, char prefix, string name)
    {
        string fullName;

        if (string.IsNullOrEmpty(name))
            fullName = prefix + ":" + type.FullName;
        else
            fullName = prefix + ":" + type.FullName + "." + name;

        var xmlDocument = XmlFromAssembly(type.Assembly);

        var matchedElement = xmlDocument["doc"]["members"].SelectSingleNode("member[@name='" + fullName + "']") as XmlElement;

        return matchedElement;
    }

    /// <summary>
    /// A cache used to remember Xml documentation for assemblies
    /// </summary>
    private static readonly Dictionary<Assembly, XmlDocument> Cache = new Dictionary<Assembly, XmlDocument>();

    /// <summary>
    /// A cache used to store failure exceptions for assembly lookups
    /// </summary>
    private static readonly Dictionary<Assembly, Exception> FailCache = new Dictionary<Assembly, Exception>();

    /// <summary>
    /// Obtains the documentation file for the specified assembly
    /// </summary>
    /// <param name="assembly">The assembly to find the XML document for</param>
    /// <returns>The XML document</returns>
    /// <remarks>This version uses a cache to preserve the assemblies, so that 
    /// the XML file is not loaded and parsed on every single lookup</remarks>
    public static XmlDocument XmlFromAssembly(this Assembly assembly)
    {
        if (FailCache.ContainsKey(assembly))
        {
            throw FailCache[assembly];
        }

        try
        {

            if (!Cache.ContainsKey(assembly))
            {
                // load the docuemnt into the cache
                Cache[assembly] = XmlFromAssemblyNonCached(assembly);
            }

            return Cache[assembly];
        }
        catch (Exception exception)
        {
            FailCache[assembly] = exception;
            throw;
        }
    }

    /// <summary>
    /// Loads and parses the documentation file for the specified assembly
    /// </summary>
    /// <param name="assembly">The assembly to find the XML document for</param>
    /// <returns>The XML document</returns>
    private static XmlDocument XmlFromAssemblyNonCached(Assembly assembly)
    {
        var assemblyFilename = assembly.Location;
   
        if (!string.IsNullOrEmpty(assemblyFilename))
        {
            StreamReader streamReader;

            try
            {
                streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename, ".xml"));
            }
            catch (FileNotFoundException exception)
            {
                throw new Exception("XML documentation not present (make sure it is turned on in project properties when building)", exception);
            }

            var xmlDocument = new XmlDocument();
            xmlDocument.Load(streamReader);
            return xmlDocument;
        }
        else
        {
            throw new Exception("Could not ascertain assembly filename", null);
        }
    }
}

您可以使用Namotion.Reflection NuGet 包来获取这些信息:

string summary = typeof(Foo).GetXmlDocsSummary();

You can look at https://github.com/NSwag/NSwag - source for nuget NSwag.CodeGeneration - it gets summary as well, usage您可以查看https://github.com/NSwag/NSwag - nuget NSwag.CodeGeneration 的来源 - 它也有摘要,用法

var generator = new WebApiAssemblyToSwaggerGenerator(settings);<br/>
var swaggerService = generator.GenerateForController("namespace.someController");<br/>
// string with comments <br/>
var swaggerJson = swaggerService.ToJson(); 

(try ILSPY decompiler against your dll, you check code and comments) (针对您的 dll 尝试 ILSPY 反编译器,您检查代码和注释)

If you have access to the source code you're trying to get comments for, then you can use Roslyn compiler platform to do that.如果您有权访问要获取评论的源代码,则可以使用Roslyn 编译器平台来执行此操作。 It basically gives you access to all the intermediary compiler metadata and you can do anything you want with it.它基本上让您可以访问所有中间编译器元数据,并且您可以使用它做任何您想做的事情。

It's a bit more complicated than what other people are suggesting, but depending on what your needs are, might be an option.这比其他人建议的要复杂一些,但根据您的需求,可能是一种选择。

It looks like this post has a code sample for something similar.看起来这篇文章有一个类似的代码示例。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM