【问题标题】:Programmatically get Summary comments at runtime在运行时以编程方式获取摘要注释
【发布时间】:2013-03-24 18:57:07
【问题描述】:

我正在寻找一种以编程方式获取 ASP.net 中方法的 Xml-cmets 的摘要部分的方法。

我查看了以前的相关帖子,但它们没有提供在 Web 环境中执行此操作的方法。

我不能使用任何 3rd 方应用程序,并且由于网络环境的原因,Visual Studio 插件也没有多大用处。

我发现最接近有效解决方案的是 JimBlackler 项目,但它只适用于 DLL。

当然,类似“提供 .CS 文件,获取 XML 文档”之类的内容是最佳选择。


现状

我有一个网络服务并试图为其动态生成文档。

阅读方法和属性很容易,但获取每个方法的摘要让我有点不知所措。

/// <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()
    {
    }
}

【问题讨论】:

  • 您到底想要完成什么?什么是“方法摘要”?为什么你不能在 web 环境中使用第三方代码,我用的一团糟?哪个吉姆布莱克勒项目?为什么它只适用于 DLL 是个问题?这就是 .NET Web 应用程序的部署方式。看起来你假设我们可以读懂你的想法,你必须真正澄清你在说什么以及你已经研究和尝试过什么。
  • 我会尝试澄清一下,将在几秒钟内更新帖子。
  • millimoose,您为此使用了哪些 3rd 方工具,我找不到任何有效的工具?
  • 如果您有权访问您尝试读取的.cs 文件,您可以使用 CodeDOM 对其进行编译并提取 XML 文档 cmets。 MSDN 甚至有一个演示一些 API 的方法:msdn.microsoft.com/en-us/library/ms404261.aspx(尽管反过来,通过在运行时生成代码,但应该可以以某种方式将 CS 文件提供给 CodeDOM。)
  • 我已更改标题 - 如果您不同意,请随时恢复。

标签: c# asp.net reflection documentation


【解决方案1】:

解决方法 - 在 Program.DLL/EXE 上使用反射和 Program.XML 文件

如果您查看由 Visual Studio 生成的同级 .XML 文件,您会发现 /members/member 的层次结构相当扁平。 您所要做的就是通过 MethodInfo 对象从您的 DLL 中获取每个方法。一旦你有了这个对象,你就转向 XML 并使用 XPATH 来获取包含这个方法的 XML 文档的成员。

成员前面有一个字母。方法的 XML 文档前面有“M:”,类的前面有“T:”等。

加载同级 XML

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

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

使用此 xpath 获取表示方法 XML 文档的成员

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

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

现在扫描子节点以查找“///”的所有行 有时 /// 总结包含额外的空白,如果这麻烦使用它来删除

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

【讨论】:

  • 这很好,尽管您可以使用Path.ChangeExtension(dllPath, ".XML") 大大简化您的第一行代码
【解决方案2】:

XML 摘要不存储在 .NET 程序集中 - 作为构建的一部分,它可以选择性地写入 XML 文件(假设您使用的是 Visual Studio)。

因此,无法通过对已编译的 .NET 程序集(.EXE 或 .DLL)的反射来“提取”每个方法的 XML 摘要 - 因为您根本无法提取数据。如果您需要数据,则必须指示构建环境将 XML 文件作为构建过程的一部分输出,并在运行时解析这些 XML 文件以获取摘要信息。

【讨论】:

  • 如何“指示构建环境在构建过程中输出 XML 文件”?
【解决方案3】:

您可以使用System.ComponentModel.DataAnnotations.DisplayAttribute 属性“记录”您的方法,例如

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

然后在运行时使用反射拉取描述。

【讨论】:

  • +1。这种方法看起来很简单,使用系统反射并在运行时获得我们需要的东西。如果需要,我们甚至可以为文档自定义属性,并在 Web api 服务中为消费者阅读我们的文档。非常非常好的方法!
  • 我同意@revobtz,为此创建一个自定义属性可能会更好,因为 System.ComponentModel.DataAnnotations 命名空间的文档指出“...提供用于定义元数据的属性类用于 ASP.NET MVC 和 ASP.NET 数据控件”。因此,它旨在用于 UI 而非文档。
  • 如何使用反射拉取Description?我不知道
【解决方案4】:

@OleksandrIeremenko 在此线程上发布的已删除帖子链接到本文https://jimblackler.net/blog/?p=49,这是我解决方案的基础。

以下是对 Jim Blackler 的代码的修改,该代码对 MemberInfo 和 Type 对象进行了扩展方法,并添加了返回摘要文本或空字符串(如果不可用)的代码。

用法

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

扩展类

/// <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);
        }
    }
}

【讨论】:

  • 在 VisualStudio 中,默认禁用生成文档 xml 文件。但是,我们可以启用它:右键单击要启用 xml 文档生成的项目 > 属性 > 构建选项卡 > 选中“Xml 文档文件”复选框。或者只需在YourProject.csproj 文件中设置 'AddressAndNameOfYourProject.xml` .
【解决方案5】:

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

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

【讨论】:

    【解决方案6】:

    您可以查看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(); 
    

    (对你的 dll 尝试 ILSPY 反编译器,你检查代码和 cmets)

    【讨论】:

    • 谢谢你 - 像魅力一样为我工作。就我而言,我已经适应了this reflection
    【解决方案7】:

    如果您有权访问您尝试获取 cmets 的源代码,那么您可以使用Roslyn compiler platform 来执行此操作。它基本上使您可以访问所有中间编译器元数据,并且您可以使用它做任何您想做的事情。

    这比其他人的建议要复杂一些,但根据您的需求,可能是一种选择。

    看起来this post 有类似的代码示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-11
      • 2010-10-10
      • 2013-07-23
      • 1970-01-01
      • 1970-01-01
      • 2019-07-28
      • 1970-01-01
      相关资源
      最近更新 更多