Como a documentação Xml para Web Api pode incluir documentação além do projeto principal?

102

A documentação para habilitar a integração de XmlDoc em seus projetos de API da Web parece lidar apenas com situações em que todos os seus tipos de API fazem parte de seu projeto de WebApi. Em particular, ele discute como redirecionar a documentação XML App_Data/XmlDocument.xmle descomentar uma linha em sua configuração que consumirá esse arquivo. Isso implicitamente permite apenas o arquivo de documentação de um projeto.

No entanto, em minha configuração, tenho meus tipos de solicitação e resposta definidos em um projeto comum de "Modelos". Isso significa que se eu tiver um endpoint definido como:

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

Onde OpenIdLoginRequesté definido em um projeto C # separado como:

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

    ...
}

Apesar dos comentários do documento XML, as propriedades do requestparâmetro não contêm documentação quando você visualiza a página de ajuda específica do terminal (isto é http://localhost/Help/Api/POST-auth-openid-login).

Como posso fazer com que os tipos de subprojetos com documentação XML apareçam na documentação XML da API da Web?

Kirk Woll
fonte

Respostas:

165

Não existe uma forma incorporada de o conseguir. No entanto, requer apenas algumas etapas:

  1. Habilite a documentação XML para seu subprojeto (das propriedades / construção do projeto) como você fez para seu projeto de API da Web. Exceto desta vez, roteie-o diretamente para XmlDocument.xmlque seja gerado na pasta raiz do seu projeto.

  2. Modifique o evento postbuild do seu projeto de API da Web para copiar este arquivo XML em sua App_Datapasta:

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

    Onde Subproject.xmldeve ser renomeado para qualquer que seja o nome do seu projeto .xml.

  3. Em seguida, abra Areas\HelpPage\App_Start\HelpPageConfige localize a seguinte linha:

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

    Esta é a linha que você descomentou inicialmente para habilitar a documentação de ajuda XML em primeiro lugar. Substitua essa linha por:

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

    Esta etapa garante que XmlDocumentationProviderseja passado o diretório que contém seus arquivos XML, em vez do arquivo XML específico para seu projeto.

  4. Finalmente, modifique Areas\HelpPage\XmlDocumentationProviderdas seguintes maneiras:

    uma. Substitua o _documentNavigatorcampo por:

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

    b. Substitua o construtor por:

    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. Adicione o seguinte método abaixo do construtor:

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

    d. E, por último, corrija todos os erros do compilador (deve haver três) resultando em referências _documentNavigator.SelectSingleNodee remova a _documentNavigator.parte para que agora chame o novo SelectSingleNodemétodo definido acima.

Esta última etapa é o que modifica o provedor de documentos para oferecer suporte à procura de texto de ajuda em vários documentos XML, em vez de apenas no projeto principal.

Agora, quando você examinar a documentação da Ajuda, ela incluirá a documentação XML de tipos em seu projeto relacionado.

Kirk Woll
fonte
7
Excelente resposta. Na verdade, acho que é um pouco mais fácil para o construtor aceitar uma matriz de strings: public XmlDocumentationProvider (string appDataPath) e enumerar essa lista no provedor de documentação.
Capitão John
14
Fantástico, isso era exatamente o que eu procurava !! Sugira substituir a var files...linha por var files = Directory.GetFiles(documentPath, "*.xml");se você (como eu) nem sempre souber os nomes / número de arquivos de documentação xml que estarão lá. Também pode fazer mais filtragem conforme necessário.
sǝɯɐſ
2
@Leandro, obrigado por melhorar a resposta! :) Que bom que você achou útil.
Kirk Woll
5
Se eu pudesse, teria +10 para você por esta resposta detalhada e correta
Mark van Straten,
2
Eu gostaria de adicionar minhas modificações em cima de algumas das outras aqui. Usei a notação ... \ para ter o arquivo xml criado na pasta App_Data \ documentation do projeto raiz. Em seguida, usei o método @ sǝɯɐſ de puxar todos os arquivos xml desse diretório. Isso funciona muito bem e estou surpreso que não seja apenas assim que funciona fora da caixa. Muito Obrigado.
Darroll de
32

Também encontrei isso, mas não queria editar ou duplicar nenhum código gerado para evitar problemas mais tarde.

Com base nas outras respostas, aqui está um provedor de documentação independente para várias fontes XML. Basta colocar isso em seu projeto:

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

... e habilite-o HelpPageConfigcom os caminhos para os documentos XML que você deseja:

config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Api.xml"), HttpContext.Current.Server.MapPath("~/App_Data/Api.Models.xml")));
Pathoschild
fonte
Esta é uma otima soluçao. Eu prefiro isso em vez de soluções que requerem modificação das classes HelpPage padrão, pois elas serão substituídas nas atualizações.
AronVanAmmers
3
Isso funciona perfeitamente, obrigado por postar. Para economizar um pouco de tempo para alguém usar isso, você ainda precisa fazer os dois primeiros estágios da resposta aceita de kirk acima, ou seja, 1) Habilitar a documentação XML para seu subprojeto e 2) Modificar o evento pós-construção de seu projeto de API da Web para copiar este arquivo XML para sua pasta App_Data.
tomRedox 01 de
1
e então esta linha se torna: config.SetDocumentationProvider (new MultiXmlDocumentationProvider (HttpContext.Current.Server.MapPath ("~ / App_Data / [nome de arquivo xml do projeto web api original, o padrão é XmlDocument] .xml"), HttpContext.Current.Server.MapPath ("~ / App_Data / [Qualquer que seja o nome do arquivo xml do subprojeto] .xml")));
tomRedox 01 de
Seguiu as etapas, mas não funcionou :(. Não há erro, então não tenho certeza do que deu errado. Ele ainda mostra apenas o documento da API, mas não o documento adicional do projeto. Eu também tentei as etapas na resposta aceita e é a mesma coisa .
Por algum motivo, ainda vejo a api / me GET padrão que vem com o modelo de projeto de introdução no VS.
John Zabroski
0

A maneira mais fácil de corrigir esse problema é criando a pasta App_Code no servidor que você implantou. Em seguida, copie o XmlDocument.xml que você tem em sua pasta bin localmente para a pasta App_Code

Ziregbe Otee
fonte
Obrigado pela sugestão !! Não há mais -1 para essa resposta útil. Sim, se você implantá-lo no Serviço de Aplicativo em Nuvem do Azure, muitos problemas ocorrem com a implantação de vários * .xml, portanto, torná-los disponíveis para swagger, por exemplo, pode ser realmente complicado. Mas eu prefiro escolher outra pasta do lado do servidor ASP.Net padrão, ou seja, App_GlobalResources, uma vez que os arquivos xmldoc são muito semelhantes aos recursos. É especialmente verdade porque eu ainda não tinha a pasta App_Code em meu projeto e não importava qual pasta padrão criar.
moudrick de
A seguinte pasta padrão funcionou para mim: App_Code - não é visível do cliente nas configurações padrão App_GlobalResources - não é visível do cliente nas configurações padrão App_LocalResources - não é visível no cliente nas configurações padrão
moudrick
Deixe-me também listar os problemas com cada uma das pastas padrão que não funcionaram para mim. bin - apenas * .xml para montagem principal é copiado para App_Data - a configuração mais prática é pular tudo nesta pasta na implantação na nuvem
moudrick
Alguém interessado poderia editar esta resposta para refletir todas as considerações acima, provavelmente com extensas especulações?
moudrick de