web-dev-qa-db-ja.com

実行時にプログラムで要約コメントを取得する

ASP.netのメソッドのXmlコメントの概要部分をプログラムで取得する方法を探しています。

以前の関連記事を見てきましたが、ウェブ環境でそうする方法を提供していません。

私はサードパーティのアプリを使用することはできません。また、Web環境のため、Visual Studioプラグインもあまり使用していません。

動作中のソリューションに最も近いものはJimBlacklerプロジェクトでしたが、DLLでのみ機能します。

当然、「。CSファイルを提供し、XMLドキュメントを取得する」のようなものが最適です。


現在の状況

Webサービスがあり、そのためのドキュメントを動的に生成しようとしています。

メソッドとプロパティの読み取りは簡単ですが、各メソッドの概要を取得することは少し気が散ります。

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

46
Riaan Walters

XMLサマリーは.NETアセンブリに保存されません-ビルドの一部としてXMLファイルにオプションで書き込まれます(Visual Studioを使用している場合)。

そのため、コンパイルされた.NETアセンブリ(.EXEまたは.DLL)のリフレクションを介して各メソッドのXMLサマリーを「引き出す」方法はありません。データは単純にプルできないためです。データが必要な場合は、ビルドプロセスにXMLファイルを出力し、実行時にこれらのXMLファイルを解析して概要情報を取得するようにビルド環境に指示する必要があります。

28
SecurityMatt

回避策-Program.XML /と共にProgram.DLL/EXEでリフレクションを使用する

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+", " ");
38
Joe Frank

System.ComponentModel.DataAnnotations.DisplayAttribute属性を使用してメソッドを「文書化」できます。

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

次に、リフレクションを使用して実行時に説明を引き出します。

22
Netricity

@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 DocumenationExtensions
{
    /// <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 exception;
        }
    }

    /// <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.CodeBase;

        const string prefix = "file:///";

        if (assemblyFilename.StartsWith(prefix))
        {
            StreamReader streamReader;

            try
            {
                streamReader = new StreamReader(Path.ChangeExtension(assemblyFilename.Substring(prefix.Length), ".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);
        }
    }
}
2
Ben Gripka

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デコンパイラを試して、コードとコメントを確認します)

1
Sasha Bond

コメントを取得しようとしているソースコードにアクセスできる場合は、 Roslynコンパイラプラットフォーム を使用してそれを行うことができます。基本的に、すべての中間コンパイラメタデータにアクセスでき、必要なことは何でもできます。

他の人が提案しているものよりも少し複雑ですが、ニーズに応じて、オプションがあります。

この投稿 のようなコードサンプルがあります。

0
Nikita G.