web-dev-qa-db-fra.com

Obtenir par programme des commentaires récapitulatifs lors de l'exécution

Je cherche un moyen d'obtenir par programme la partie récapitulative des commentaires Xml d'une méthode dans ASP.net.

J'ai regardé les articles connexes précédents et ils ne fournissent aucun moyen de le faire dans un environnement Web.

Je ne peux pas utiliser d'applications tierces et en raison d'un environnement Web, les plugins Visual studio ne sont pas non plus très utiles.

La chose la plus proche que j'ai trouvée pour une solution de travail était le projet JimBlackler, mais il ne fonctionne que sur les DLL.

Naturellement, quelque chose comme "fournir un fichier .CS, obtenir une documentation XML" serait optimal.


Situation actuelle

J'ai un service Web et j'essaie de générer dynamiquement de la documentation pour celui-ci.

La lecture des méthodes et des propriétés est facile, mais obtenir le résumé de chaque méthode me décourage un peu.

/// <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

Le résumé XML n'est pas stocké dans l'assembly .NET - il est éventuellement écrit dans un fichier XML dans le cadre de votre build (en supposant que vous utilisez Visual Studio).

Par conséquent, il n'y a aucun moyen de "retirer" les résumés XML de chaque méthode via une réflexion sur un assemblage .NET compilé (.EXE ou .DLL) - car les données ne sont tout simplement pas là pour que vous les retiriez. Si vous voulez les données, vous devrez demander à votre environnement de génération de générer les fichiers XML dans le cadre de votre processus de génération et d'analyser ces fichiers XML au moment de l'exécution pour obtenir les informations récapitulatives.

28
SecurityMatt

Solution - Utilisation de la réflexion sur Program.DLL/EXE avec le fichier Program.XML

Si vous regardez le fichier frère .XML généré par Visual Studio, vous verrez qu'il existe une hiérarchie assez plate de/members/member. Tout ce que vous avez à faire est de saisir chaque méthode de votre DLL via l'objet MethodInfo. Une fois que vous avez cet objet, vous vous tournez vers le XML et utilisez XPATH pour obtenir le membre contenant la documentation XML pour cela méthode.

Les membres sont précédés d'une lettre. Les documents XML pour les méthodes sont précédés de "M:" pour les classes par "T:" etc.

Chargez votre frère XML

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

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

Utilisez ce xpath pour obtenir le membre représentant la méthode XML docu

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

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

Maintenant, scannez les nœuds enfants pour toutes les lignes de "///" Parfois, le résumé /// contient des blancs supplémentaires, si cela dérange, utilisez-le pour supprimer

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

Vous pouvez "documenter" votre méthode en utilisant le System.ComponentModel.DataAnnotations.DisplayAttribute attribut, par exemple.

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

puis utilisez la réflexion pour extraire la description lors de l'exécution.

22
Netricity

Un article supprimé, rédigé par @OleksandrIeremenko, sur ce fil renvoie à cet article https://jimblackler.net/blog/?p=49 qui était la base de ma solution.

Vous trouverez ci-dessous une modification du code de Jim Blackler créant des méthodes d'extension à partir des objets MemberInfo et Type et ajoutant du code qui renvoie le texte récapitulatif ou une chaîne vide si elle n'est pas disponible.

Usage

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

Classe d'extension

/// <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

Vous pouvez consulter https://github.com/NSwag/NSwag - source pour nuget NSwag.CodeGeneration - il obtient également un résumé, l'utilisation

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

(essayez le décompilateur ILSPY contre votre dll, vous vérifiez le code et les commentaires)

1
Sasha Bond

Si vous avez accès au code source pour lequel vous essayez d'obtenir des commentaires, vous pouvez utiliser plateforme de compilation Roslyn pour ce faire. Il vous donne essentiellement accès à toutes les métadonnées du compilateur intermédiaire et vous pouvez faire tout ce que vous voulez avec.

C'est un peu plus compliqué que ce que les autres suggèrent, mais selon vos besoins, cela pourrait être une option.

Il ressemble à cet article a un exemple de code pour quelque chose de similaire.

0
Nikita G.