Comment la documentation Xml pour Web Api peut-elle inclure de la documentation au-delà du projet principal?

102

La documentation permettant d'activer l'intégration de XmlDoc dans vos projets Web Api semble gérer uniquement les situations où tous vos types d'API font partie de votre projet WebApi. En particulier, il explique comment rediriger la documentation XML vers App_Data/XmlDocument.xmlet décommenter une ligne de votre configuration qui consommera ce fichier. Cela autorise implicitement uniquement le fichier de documentation d'un projet.

Cependant, dans ma configuration, j'ai mes types de demande et de réponse définis dans un projet "Modèles" commun. Cela signifie que si j'ai un point de terminaison défini tel que:

[Route("auth/openid/login")]
public async Task<AuthenticationResponse> Login(OpenIdLoginRequest request) { ... }

OpenIdLoginRequestest défini dans un projet C # séparé comme ceci:

public class OpenIdLoginRequest
{
    /// <summary>
    /// Represents the OpenId provider that authenticated the user. (i.e. Facebook, Google, etc.)
    /// </summary>
    [Required]
    public string Provider { get; set; }

    ...
}

Malgré les problèmes XML, les propriétés du requestparamètre ne contiennent aucune documentation lorsque vous affichez la page d'aide spécifique au point final (c'est-à-dire http://localhost/Help/Api/POST-auth-openid-login).

Comment puis-je faire en sorte que les types de sous-projets avec la documentation XML apparaissent dans la documentation XML de l'API Web?

Kirk Woll
la source

Réponses:

165

Il n'y a pas de moyen intégré pour y parvenir. Cependant, cela ne nécessite que quelques étapes:

  1. Activez la documentation XML pour votre sous-projet (à partir des propriétés / build du projet) comme vous l'avez fait pour votre projet API Web. Sauf cette fois, acheminez-le directement vers XmlDocument.xmlafin qu'il soit généré dans le dossier racine de votre projet.

  2. Modifiez l'événement de post-construction de votre projet API Web pour copier ce fichier XML dans votre App_Datadossier:

    copy "$(SolutionDir)SubProject\XmlDocument.xml" "$(ProjectDir)\App_Data\Subproject.xml"

    Subproject.xmldevrait être renommé quel que soit le nom de votre projet plus .xml.

  3. Ensuite, ouvrez Areas\HelpPage\App_Start\HelpPageConfiget localisez la ligne suivante:

    config.SetDocumentationProvider(new XmlDocumentationProvider(
        HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml")));

    Il s'agit de la ligne que vous avez initialement décommentée afin d'activer la documentation d'aide XML en premier lieu. Remplacez cette ligne par:

    config.SetDocumentationProvider(new XmlDocumentationProvider(
        HttpContext.Current.Server.MapPath("~/App_Data")));

    Cette étape garantit que XmlDocumentationProviderle répertoire contenant vos fichiers XML est transmis plutôt que le fichier XML spécifique à votre projet.

  4. Enfin, modifiez Areas\HelpPage\XmlDocumentationProviderde la manière suivante:

    une. Remplacez le _documentNavigatorchamp par:

    private List<XPathNavigator> _documentNavigators = new List<XPathNavigator>();

    b. Remplacez le constructeur par:

    public XmlDocumentationProvider(string appDataPath)
    {
        if (appDataPath == null)
        {
            throw new ArgumentNullException("appDataPath");
        }
    
        var files = new[] { "XmlDocument.xml", "Subproject.xml" };
        foreach (var file in files)
        {
            XPathDocument xpath = new XPathDocument(Path.Combine(appDataPath, file));
            _documentNavigators.Add(xpath.CreateNavigator());
        }
    }

    c. Ajoutez la méthode suivante sous le constructeur:

    private XPathNavigator SelectSingleNode(string selectExpression)
    {
        foreach (var navigator in _documentNavigators)
        {
            var propertyNode = navigator.SelectSingleNode(selectExpression);
            if (propertyNode != null)
                return propertyNode;
        }
        return null;
    }

    ré. Enfin, corrigez toutes les erreurs du compilateur (il devrait y en avoir trois) entraînant des références _documentNavigator.SelectSingleNodeet supprimez la _documentNavigator.partie afin qu'elle appelle maintenant la nouvelle SelectSingleNodeméthode que nous avons définie ci-dessus.

Cette dernière étape est ce qui modifie le fournisseur de documents pour prendre en charge la recherche dans plusieurs documents XML du texte d'aide plutôt que du seul projet principal.

Maintenant, lorsque vous examinez votre documentation d'aide, elle inclura la documentation XML des types de votre projet associé.

Kirk Woll
la source
7
Excellente réponse. Je pense en fait qu'il est un peu plus facile pour le constructeur d'accepter un tableau de chaînes: public XmlDocumentationProvider (string appDataPath) et d'énumérer cette liste dans le fournisseur de documentation.
Captain John
14
Fantastique, c'était exactement ce que je cherchais !! Suggérez de remplacer la var files...ligne par var files = Directory.GetFiles(documentPath, "*.xml");si vous (comme moi) ne connaissez pas toujours les noms / nombre de fichiers de documentation xml qui seront là. Pourrait également effectuer un filtrage supplémentaire si nécessaire.
sǝɯɐſ
2
@Leandro, merci d'avoir amélioré la réponse! :) Heureux que vous l'ayez trouvé utile.
Kirk Woll
5
Si je pouvais, je voudrais +10 vous pour cette réponse détaillée et correcte
Mark van Straten
2
Je voudrais ajouter mes modifications par-dessus certaines des autres ici. J'ai utilisé la notation ... \ pour créer le fichier xml dans le dossier racine App_Data \ documentation du projet. J'ai ensuite utilisé la méthode @ sǝɯɐſ pour extraire tous les fichiers xml de ce répertoire. Cela fonctionne à merveille et je suis surpris que ce ne soit pas seulement la façon dont cela fonctionne hors de la boîte. Merci beaucoup.
Darroll
32

J'ai rencontré cela aussi, mais je ne voulais pas modifier ou dupliquer le code généré pour éviter des problèmes plus tard.

En s'appuyant sur les autres réponses, voici un fournisseur de documentation autonome pour plusieurs sources XML. Déposez simplement ceci dans votre projet:

/// <summary>A custom <see cref="IDocumentationProvider"/> that reads the API documentation from a collection of XML documentation files.</summary>
public class MultiXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider
{
    /*********
    ** Properties
    *********/
    /// <summary>The internal documentation providers for specific files.</summary>
    private readonly XmlDocumentationProvider[] Providers;


    /*********
    ** Public methods
    *********/
    /// <summary>Construct an instance.</summary>
    /// <param name="paths">The physical paths to the XML documents.</param>
    public MultiXmlDocumentationProvider(params string[] paths)
    {
        this.Providers = paths.Select(p => new XmlDocumentationProvider(p)).ToArray();
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetDocumentation(MemberInfo subject)
    {
        return this.GetFirstMatch(p => p.GetDocumentation(subject));
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetDocumentation(Type subject)
    {
        return this.GetFirstMatch(p => p.GetDocumentation(subject));
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetDocumentation(HttpControllerDescriptor subject)
    {
        return this.GetFirstMatch(p => p.GetDocumentation(subject));
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetDocumentation(HttpActionDescriptor subject)
    {
        return this.GetFirstMatch(p => p.GetDocumentation(subject));
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetDocumentation(HttpParameterDescriptor subject)
    {
        return this.GetFirstMatch(p => p.GetDocumentation(subject));
    }

    /// <summary>Gets the documentation for a subject.</summary>
    /// <param name="subject">The subject to document.</param>
    public string GetResponseDocumentation(HttpActionDescriptor subject)
    {
        return this.GetFirstMatch(p => p.GetResponseDocumentation(subject));
    }


    /*********
    ** Private methods
    *********/
    /// <summary>Get the first valid result from the collection of XML documentation providers.</summary>
    /// <param name="expr">The method to invoke.</param>
    private string GetFirstMatch(Func<XmlDocumentationProvider, string> expr)
    {
        return this.Providers
            .Select(expr)
            .FirstOrDefault(p => !String.IsNullOrWhiteSpace(p));
    }
}

... et activez-le dans votre HelpPageConfigavec les chemins d'accès aux documents XML souhaités:

config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Api.xml"), HttpContext.Current.Server.MapPath("~/App_Data/Api.Models.xml")));
Pathoschild
la source
C'est une excellente solution. Je préfère cela aux solutions qui nécessitent la modification des classes HelpPage par défaut car elles seront écrasées lors des mises à jour.
AronVanAmmers
3
Cela fonctionne à merveille, merci d'avoir posté. Pour économiser un peu de temps à quiconque l'utilise, vous devez toujours effectuer les deux premières étapes de la réponse acceptée de kirk ci-dessus, c'est-à-dire 1) Activer la documentation XML pour votre sous-projet et 2) Modifier l'événement de post-construction de votre projet API Web pour copier ce fichier XML dans votre dossier App_Data.
tomRedox
1
puis cette ligne devient: config.SetDocumentationProvider (new MultiXmlDocumentationProvider (HttpContext.Current.Server.MapPath ("~ / App_Data / [nom de fichier xml du projet Web api d'origine, par défaut XmlDocument] .xml"), HttpContext.Current.Server.MapPath ("~ / App_Data / [Peu importe ce que vous avez appelé le nom de fichier xml de votre sous-projet] .xml")));
tomRedox
J'ai suivi les étapes, mais cela n'a pas fonctionné: (. Il n'y a pas d'erreur, donc je ne sais pas ce qui ne va pas. Il n'affiche toujours que le document API, mais pas le document de projet supplémentaire. J'ai également essayé les étapes de la réponse acceptée et c'est la même chose .
Y a
Pour une raison quelconque, je vois toujours l'api / me GET par défaut fourni avec le modèle de projet de démarrage dans VS.
John Zabroski
0

Le moyen le plus simple de résoudre ce problème consiste à créer le dossier App_Code sur le serveur que vous avez déployé. Copiez ensuite le XmlDocument.xml que vous avez dans votre dossier bin localement dans le dossier App_Code

Ziregbe Otee
la source
Merci pour la suggestion !! Pas plus de -1 pour une réponse aussi utile. Oui, si vous le déployez sur Azure Cloud App Service, de nombreux problèmes se produisent avec le déploiement de plusieurs fichiers * .xml, donc les rendre disponibles pour swagger, par exemple, peut être très délicat. Mais je préférerais choisir un autre dossier côté serveur ASP.Net standard, à savoir App_GlobalResources, car les fichiers xmldoc sont assez similaires aux ressources. C'est particulièrement vrai parce que je n'avais toujours pas de dossier App_Code dans mon projet et que le dossier standard à créer n'avait pas d'importance.
moudrick
Le dossier standard suivant a fonctionné pour moi: App_Code - n'est pas visible depuis le client sur les paramètres par défaut App_GlobalResources - n'est pas visible depuis le client sur les paramètres par défaut App_LocalResources - n'est pas visible depuis le client sur les paramètres par défaut
moudrick
Permettez-moi également d'énumérer les problèmes avec chacun des dossiers standard qui ne fonctionnaient pas pour moi. bin - seul * .xml pour l'assembly principal est déplopé vers App_Data - le paramètre le plus pratique consiste à ignorer tout ce qui se trouve dans ce dossier lors du déploiement sur le cloud
moudrick
Quelqu'un pourrait-il modifier cette réponse pour refléter toutes ces considérations ci-dessus, probablement avec des spéculations étendues?
moudrick