如何在Web Api的Xml文档中包含来自主项目以外的文档?

107

似乎只有处理您的Web Api项目中所有API类型都是一部分的情况下,才能启用XmlDoc集成的文档。特别是它讨论了如何将XML文档重定向到App_Data/XmlDocument.xml并取消注释配置中将使用该文件的行。这仅隐含地允许一个项目的文档文件。

然而,在我的设置中,我在一个公共“Models”项目中定义了我的请求和响应类型。这意味着如果我定义了一个端点,例如:

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

在一个单独的C#项目中定义了OpenIdLoginRequest,代码如下:

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

    ...
}

尽管存在XML文档注释,但在查看特定端点帮助页面(即http://localhost/Help/Api/POST-auth-openid-login)时,request参数的属性不包含任何文档。

如何让具有XML文档的子项目中的类型显示在Web API XML文档中?

5个回答

170

没有内置的方法来实现这一点。但是,只需要几个步骤:

  1. 为你的子项目启用XML文档(从项目属性/构建中),就像你为Web API项目所做的那样。除了这次将其直接路由到XmlDocument.xml,以便在项目的根文件夹中生成。

  2. 修改你的Web API项目的后构建事件,将此XML文件复制到你的App_Data文件夹中:

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

    Subproject.xml重命名为您的项目名称加上.xml

  3. 接下来打开Areas\HelpPage\App_Start\HelpPageConfig,并找到以下行:

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

    这是您最初取消注释以启用XML帮助文档的行。请将该行替换为:

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

    这个步骤确保将包含XML文件的目录传递给XmlDocumentationProvider,而不是传递项目特定的XML文件。

  5. 最后,按照以下方式修改Areas\HelpPage\XmlDocumentationProvider

    a. 用以下内容替换_documentNavigator字段:

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

    b. 用以下代码替换构造函数:

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

    在构造函数下面添加以下方法:

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

    d. 最后,修复所有编译器错误(应该有三个),涉及到_documentNavigator.SelectSingleNode的引用并移除_documentNavigator.部分,以便现在调用我们上面定义的新SelectSingleNode方法。

这最后一步修改了文档提供程序,以支持在多个XML文档中查找帮助文本,而不仅仅是主项目的。

现在当您检查您的帮助文档时,它将包括来自相关项目类型的XML文档。


8
很棒的回答。我认为让构造函数接受一个字符串数组会更容易一些:public XmlDocumentationProvider(string appDataPath),然后在文档提供程序中枚举这个列表。 - Captain John
15
太棒了,这正是我在寻找的!如果您(像我一样)不知道将会有多少个XML文档文件或它们的名称,请建议用var files = Directory.GetFiles(documentPath, "*.xml")来替换var files...这一行。如有必要,还可以进行进一步的筛选。 - sǝɯɐſ
2
@Leandro,感谢您改进了答案! :) 很高兴您发现它有用。 - Kirk Woll
5
如果我能的话,我会给你加10分,因为你的答案详细而正确。 - Mark van Straten
2
我想在其他人的基础上添加我的修改。 我使用了...\符号来创建xml文件,将其放置在根项目App_Data\documentation文件夹中。 然后我使用@sǝɯɐſ的方法从该目录中提取所有的xml文件。 这个方法非常有效,我很惊讶它不是开箱即用的。非常感谢。 - Darroll
显示剩余11条评论

32

我也遇到了这个问题,但我不想编辑或复制生成的任何代码,以避免以后出现问题。

借鉴其他回答的方法,这里提供了一个自包含的多XML源文档提供者。只需将其放入您的项目中:

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

...并在您的HelpPageConfig中启用它,并使用您想要的XML文档的路径:

config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/Api.xml"), HttpContext.Current.Server.MapPath("~/App_Data/Api.Models.xml")));

这是一个很棒的解决方案。我更喜欢它而不是需要修改默认HelpPage类的解决方案,因为它们会在更新时被覆盖。 - AronVanAmmers
3
这个方法非常有效,感谢您的发布。为了节省其他人使用它的时间,您仍然需要完成Kirk所列的前两个步骤,即: 1)为子项目启用XML文档 2)修改您的Web API项目的后建事件以将此XML文件复制到App_Data文件夹中。 - tomRedox
1
然后这一行变成:config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("/App_Data/[原始Web API项目的XML文件名,默认为XmlDocument].xml"), HttpContext.Current.Server.MapPath("/App_Data/[您称呼的子项目XML文件名].xml"))); - tomRedox
按照步骤操作,但是没有成功:(。没有错误提示,所以不确定哪里出了问题。它仍然只显示API文档,而不是附加的项目文档。我也尝试了被接受的答案中的步骤,但结果相同。有什么特别需要检查的吗? - dragonfly02
由于某些原因,我仍然在 VS 的快速入门项目模板中看到默认的 GET api/me。 - John Zabroski
显示剩余2条评论

5

0
最简单的解决方法是在您部署的服务器上创建App_Code文件夹。然后将您在bin文件夹中拥有的XmlDocument.xml本地复制到App_Code文件夹中。

谢谢建议!!不会再因为这么有帮助的答案而得到-1分。 如果您将其部署到Azure Cloud App Service,多个*.xml文件的部署可能会出现许多问题,因此使它们可用于Swagger等可能非常棘手。 但我宁愿选择另一个标准的ASP.Net服务器端文件夹,即App_GlobalResources,因为xmldoc文件与资源非常相似。 特别是因为我的项目中仍然没有App_Code文件夹,并且创建哪个标准文件夹并不重要。 - moudrick
以下标准文件夹适用于我:App_Code - 在默认设置下,客户端无法看到 App_GlobalResources - 在默认设置下,客户端无法看到 App_LocalResources - 在默认设置下,客户端无法看到 - moudrick
让我列出每个标准文件夹的问题,这些文件夹对我都没有起作用。bin - 只有主程序集的 *.xml 被部署到了 App_Data - 在部署到云上时,最实际的设置是跳过此文件夹中的所有内容 - moudrick
有兴趣的人可以编辑这个答案,以反映上述所有考虑因素,可能还包括扩展的推测。 - moudrick

0

我找到了一个更好的解决方案

  1. 进入你的解决方案属性,在“生成”、“输出”、“文档XML文件”中,只需填写你的应用数据文件夹。

  2. 添加一行代码,像这样插入你想要插入到文档中的文件。

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

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接