¿Cómo puede Xml Documentation for Web Api incluir documentación más allá del proyecto principal?

102

La documentación para habilitar la integración de XmlDoc en sus proyectos Web Api parece solo manejar situaciones en las que todos sus tipos de API son parte de su proyecto WebApi. En particular, analiza cómo redirigir la documentación XML App_Data/XmlDocument.xmly descomentar una línea en su configuración que consumirá ese archivo. Esto implícitamente solo permite el archivo de documentación de un proyecto.

Sin embargo, en mi configuración tengo mis tipos de solicitud y respuesta definidos en un proyecto común de "Modelos". Esto significa que si tengo un punto final definido como:

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

Donde OpenIdLoginRequestse define en un proyecto C # separado así:

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

    ...
}

A pesar de los comentarios de los requestdocumentos XML, las propiedades del parámetro no contienen documentación cuando visualiza la página de ayuda específica del punto final (es decir http://localhost/Help/Api/POST-auth-openid-login).

¿Cómo puedo hacer que los tipos de subproyectos con documentación XML aparezcan en la documentación XML de la API web?

Kirk Woll
fuente

Respuestas:

165

No hay una forma incorporada de lograr esto. Sin embargo, solo requiere unos pocos pasos:

  1. Habilite la documentación XML para su subproyecto (desde las propiedades / compilación del proyecto) como lo hizo para su proyecto de API web. Excepto que esta vez, enrutelo directamente para XmlDocument.xmlque se genere en la carpeta raíz de su proyecto.

  2. Modifique el evento posterior a la compilación de su proyecto de API web para copiar este archivo XML en su App_Datacarpeta:

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

    Dónde Subproject.xmldebería cambiarse el nombre a cualquiera que sea el nombre de su proyecto plus .xml.

  3. A continuación, abra Areas\HelpPage\App_Start\HelpPageConfigy busque la siguiente línea:

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

    Esta es la línea que inicialmente descomentó para habilitar la documentación de ayuda XML en primer lugar. Reemplaza esa línea con:

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

    Este paso asegura que XmlDocumentationProviderse pasa el directorio que contiene sus archivos XML, en lugar del archivo XML específico para su proyecto.

  4. Finalmente, modifique Areas\HelpPage\XmlDocumentationProviderde las siguientes formas:

    a. Reemplazar el _documentNavigatorcampo con:

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

    si. Reemplaza el constructor con:

    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. Agregue el siguiente método debajo del constructor:

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

    re. Y por último, corrija todos los errores del compilador (debería haber tres) que resulten en referencias _documentNavigator.SelectSingleNodey elimine la _documentNavigator.parte para que ahora llame al nuevo SelectSingleNodemétodo que definimos anteriormente.

Este último paso es lo que modifica el proveedor de documentos para admitir la búsqueda en varios documentos XML para el texto de ayuda en lugar de solo el proyecto principal.

Ahora, cuando examine su documentación de ayuda, incluirá documentación XML de los tipos de su proyecto relacionado.

Kirk Woll
fuente
7
Excelente respuesta. De hecho, creo que es un poco más fácil para el constructor aceptar una matriz de cadenas: public XmlDocumentationProvider (cadena appDataPath) y enumerar esta lista en el proveedor de documentación.
Capitán John
14
¡Fantástico, esto era justo lo que estaba buscando! Sugiera reemplazar la var files...línea con var files = Directory.GetFiles(documentPath, "*.xml");si usted (como yo) no siempre sabrá los nombres / número de archivos de documentación xml que estarán allí. También podría realizar un filtrado adicional según sea necesario.
2014
2
@Leandro, ¡gracias por mejorar la respuesta! :) Me alegro de que le haya resultado útil.
Kirk Woll
5
Si pudiera, le haría un +10 por esta respuesta detallada y correcta
Mark van Straten
2
Me gustaría agregar mis modificaciones además de algunas de las otras aquí. Usé la notación ... \ para crear el archivo xml en la carpeta de documentación del proyecto raíz App_Data \. Luego utilicé el método @ sǝɯɐſ para extraer todos los archivos xml de ese directorio. Esto funciona maravillosamente y me sorprende que no sea así como funciona de inmediato. Muchas gracias.
Darroll
32

También me encontré con esto, pero no quería editar ni duplicar el código generado para evitar problemas más adelante.

Sobre la base de las otras respuestas, aquí hay un proveedor de documentación autónomo para múltiples fuentes XML. Simplemente coloque esto en su proyecto:

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

... y habilítelo en su HelpPageConfigcon las rutas a los documentos XML que desee:

config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Api.xml"), HttpContext.Current.Server.MapPath("~/App_Data/Api.Models.xml")));
Pathoschild
fuente
Esta es una gran solucion. Lo prefiero a las soluciones que requieren la modificación de las clases de HelpPage predeterminadas, ya que se sobrescribirán en las actualizaciones.
AronVanAmmers
3
Esto funciona de manera brillante, gracias por publicar. Para ahorrarle a cualquiera que use esto un poco de tiempo, aún debe realizar las dos primeras etapas de la respuesta aceptada de kirk anterior, es decir, 1) Habilitar la documentación XML para su subproyecto y 2) Modificar el evento de postbuild de su proyecto de API web para copiar este archivo XML en su carpeta App_Data.
tomRedox
1
y luego esta línea se convierte en: config.SetDocumentationProvider (new MultiXmlDocumentationProvider (HttpContext.Current.Server.MapPath ("~ / App_Data / [nombre de archivo xml del proyecto web api original, el valor predeterminado es XmlDocument] .xml"), HttpContext.Current.Server.MapPath ("~ / App_Data / [Como sea que hayas llamado el nombre de archivo xml de tu subproyecto] .xml")));
tomRedox
Seguí los pasos pero no funcionó :(. No hay ningún error, así que no estoy seguro de qué salió mal. Todavía solo muestra el documento api pero no el documento adicional del proyecto. También probé los pasos en la respuesta aceptada y es lo mismo . ¿Algo en particular que deba verificar?
stt106
Por alguna razón, todavía veo la api / me GET predeterminada que viene con la plantilla del proyecto de introducción en VS.
John Zabroski
0

La forma más sencilla de solucionar este problema es crear la carpeta App_Code en el servidor que implementó. Luego copie el XmlDocument.xml que tiene en su carpeta bin localmente en la carpeta App_Code

Ziregbe Otee
fuente
¡Gracias por la sugerencia! No más -1 para una respuesta tan útil. Sí, si lo implementa en Azure Cloud App Service, se producirán muchos problemas con la implementación de varios * .xml, por lo que hacerlos disponibles para fanfarronear, por ejemplo, puede ser realmente complicado. Pero preferiría elegir otra carpeta del lado del servidor ASP.Net estándar, a saber, App_GlobalResources, ya que los archivos xmldoc son bastante similares a los recursos. Es especialmente cierto porque todavía no tenía la carpeta App_Code en mi proyecto y no importaba qué carpeta estándar crear.
moudrick
La siguiente carpeta estándar funcionó para mí: App_Code - no es visible desde el cliente en la configuración predeterminada App_GlobalResources - no es visible desde el cliente en la configuración predeterminada App_LocalResources - no es visible desde el cliente en la configuración predeterminada
moudrick
Permítame también enumerar los problemas con cada una de las carpetas estándar que no funcionaron para mí. bin: solo * .xml para el ensamblaje principal se implementa en App_Data; la configuración más práctica es omitir todo en esta carpeta al implementar en la nube
moudrick
¿Podría alguien interesado editar esta respuesta para reflejar todas estas consideraciones anteriores, probablemente con especulaciones extendidas?
moudrick