簡體   English   中英

在運行時以編程方式獲取摘要注釋

[英]Programmatically get Summary comments at runtime

我正在尋找一種方法來以編程方式獲取 ASP.net 中方法的 Xml-comments 的摘要部分。

我查看了之前的相關帖子,但它們沒有提供在 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()
    {
    }
}

解決方法- 將 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+", " ");

XML 摘要不存儲在 .NET 程序集中 - 作為構建的一部分(假設您使用的是 Visual Studio),它可以選擇寫入 XML 文件。

因此,無法通過對已編譯的 .NET 程序集(.EXE 或 .DLL)的反射來“提取”每個方法的 XML 摘要——因為數據根本不存在供您提取。 如果需要數據,則必須指示構建環境將 XML 文件作為構建過程的一部分輸出,並在運行時解析這些 XML 文件以獲取摘要信息。

您可以使用System.ComponentModel.DataAnnotations.DisplayAttribute屬性“記錄”您的方法,例如

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

然后使用反射在運行時提取描述。

由@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);
        }
    }
}

您可以使用Namotion.Reflection NuGet 包來獲取這些信息:

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

您可以查看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 反編譯器,您檢查代碼和注釋)

如果您有權訪問要獲取評論的源代碼,則可以使用Roslyn 編譯器平台來執行此操作。 它基本上讓您可以訪問所有中間編譯器元數據,並且您可以使用它做任何您想做的事情。

這比其他人建議的要復雜一些,但根據您的需求,可能是一種選擇。

看起來這篇文章有一個類似的代碼示例。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM