65.9K
CodeProject 正在变化。 阅读更多。
Home

将文档添加到 ASP.NET Web API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.58/5 (6投票s)

2018年3月12日

CPOL

5分钟阅读

viewsIcon

12109

downloadIcon

106

当您提供 Web API 时,会有一个问题:如何告知用户它的所有功能、请求的语法等。通常,您需要创建一个 Web 页面来讨论这些主题。但是,如果 Web API 本身可以提供文档访问,那不是很好吗?

如果您查看 GitHub 上任何严肃项目的页面,您会看到编写精良的 Readme.md 文档。这个 Markdown 文档描述了存储库的目的,并且经常包含指向其他文档的链接。这里很棒的一点是 GitHub 会自动将这些文档转换为 HTML 并显示给您。这使得 Markdown 文件成为存储项目文档的好地方。首先,它们可以提供丰富的格式。此外,它们与代码一起存储在 VCS 中。这使得这些文件成为一流的公民。您可以将它们视为代码的一部分,并在修改代码时修改它们。至少,理论上应该是这样。现在您的所有文档都已在您的存储库中。

如果您的存储库是公开的,那很好。但我为一家为外部客户提供 Web API 的公司工作。这些客户无法访问我们的代码存储库。我应该如何为这些服务提供文档?

我可以创建一个单独的文档站点。但现在,我的产品信息存储在两个地方:Markdown 文件和这个站点上。我可以自动化创建文档站点的过程,从我的 Markdown 文件生成它。或者我可以创建一个单独的文档(例如 PDF),其中包含这些文件的内容。

这种方法没有什么不好。但我认为我们可以再向前迈出一步。为什么要将我们的 API 与文档分开?我们不能将它们放在同一个地方吗?例如,如果我们的 Web API 可在 URL http://www.something.com/api/data 访问,那么关于此 API 的文档可以访问 URL http://www.something.com/api/help.md

使用 ASP.NET Web API 实现这样的文档系统有多难?让我们来看看。

我将从一个简单的 OWIN Web API 开始。这是我的 Startup 文件

[assembly: OwinStartup(typeof(OwinMarkdown.Startup))]

namespace OwinMarkdown
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            HttpConfiguration config = new HttpConfiguration();

            config.Formatters.Clear();
            config.Formatters.Add(
                new JsonMediaTypeFormatter
                {
                    SerializerSettings = GetJsonSerializerSettings()
                });

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new {id = RouteParameter.Optional}
            );

            app.UseWebApi(config);
        }
        
        private static JsonSerializerSettings GetJsonSerializerSettings()
        {
            var settings = new JsonSerializerSettings();

            settings.Converters.Add(new StringEnumConverter { CamelCaseText = false });

            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            return settings;
        }
    }
}

我将在我的项目中添加一些 Markdown 文件

我想谈谈这些文件。首先,可能会有一个包含我文档不同部分的复杂子文件夹结构。其次,这里还有其他文件,例如图像。我的 Markdown 文件可以引用它们。我们的解决方案必须同时支持:文件夹树和附加文件。

让我们从 Web.config 文件开始。我们需要对其进行一些修改。您知道,Internet Information Services (IIS) 可以自己提供静态文件。例如,如果我请求 http://myhost/help/root.md,IIS 会知道磁盘上存在这样一个文件。然后,它会尝试返回它。这意味着 IIS 不会将请求传递给我们的应用程序。但这并不是我们想要的。我们不希望返回原始 Markdown 文件。我们想先将其转换为 HTML。这就是为什么我们需要修改 Web.config。我们必须指示 IIS 将所有请求传递给我们的应用程序。我们通过修改 system.webServer 部分来实现这一点。

<system.webServer>
  <modules runAllManagedModulesForAllRequests="true" />
  <handlers>
    <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
    <remove name="OPTIONSVerbHandler" />
    <remove name="TRACEVerbHandler" />
    <add name="Owin" verb="" path="*" type="Microsoft.Owin.Host.SystemWeb.OwinHttpHandler, 
     Microsoft.Owin.Host.SystemWeb" />
  </handlers>
</system.webServer>

现在 IIS 将不会处理静态文件。但我们仍然需要它(例如,用于文档中的图像)。这就是为什么我们将使用 Microsoft.Owin.StaticFiles NuGet 包。假设我想让我的文档在路径 "/api/doc" 下可用。在这种情况下,我将这样配置此包。

[assembly: OwinStartup(typeof(OwinMarkdown.Startup))]

namespace OwinMarkdown
{
    public class Startup
    {
        private static readonly string HelpUrlPart = "/api/doc";

        public void Configuration(IAppBuilder app)
        {
            var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

            app.UseStaticFiles(new StaticFileOptions
            {
                RequestPath = new PathString(HelpUrlPart),
                FileSystem = new PhysicalFileSystem(Path.Combine(basePath, "Help"))
            });

            HttpConfiguration config = new HttpConfiguration();

            config.Formatters.Clear();
            config.Formatters.Add(
                new JsonMediaTypeFormatter
                {
                    SerializerSettings = GetJsonSerializerSettings()
                });

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new {id = RouteParameter.Optional}
            );

            app.UseWebApi(config);
        }
        
        private static JsonSerializerSettings GetJsonSerializerSettings()
        {
            var settings = new JsonSerializerSettings();

            settings.Converters.Add(new StringEnumConverter { CamelCaseText = false });

            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            return settings;
        }
    }
}

现在我们可以通过 "/api/doc" 路径为应用程序的 "Help" 文件夹提供静态文件。但我们仍然需要某种方式将 Markdown 文件转换为 HTML 并提供它们。为此,我们将编写 OWIN 中间件。这个中间件将使用 Markdig NuGet 包。

[assembly: OwinStartup(typeof(OwinMarkdown.Startup))]

namespace OwinMarkdown
{
    public class Startup
    {
        private static readonly string HelpUrlPart = "/api/doc";

        public void Configuration(IAppBuilder app)
        {
            var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();

            app.Use(async (context, next) =>
            {
                var markDownFile = GetMarkdownFile(context.Request.Path.ToString());

                if (markDownFile == null)
                {
                    await next();

                    return;
                }

                using (var reader = markDownFile.OpenText())
                {
                    context.Response.ContentType = @"text/html";

                    var fileContent = reader.ReadToEnd();

                    fileContent = Markdown.ToHtml(fileContent, pipeline);

                    // Send our modified content to the response body.
                    await context.Response.WriteAsync(fileContent);
                }

            });

            var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;

            app.UseStaticFiles(new StaticFileOptions
            {
                RequestPath = new PathString(HelpUrlPart),
                FileSystem = new PhysicalFileSystem(Path.Combine(basePath, "Help"))
            });

            HttpConfiguration config = new HttpConfiguration();

            config.Formatters.Clear();
            config.Formatters.Add(
                new JsonMediaTypeFormatter
                {
                    SerializerSettings = GetJsonSerializerSettings()
                });

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new {id = RouteParameter.Optional}
            );

            app.UseWebApi(config);
        }
        
        private static JsonSerializerSettings GetJsonSerializerSettings()
        {
            var settings = new JsonSerializerSettings();

            settings.Converters.Add(new StringEnumConverter { CamelCaseText = false });

            settings.ContractResolver = new CamelCasePropertyNamesContractResolver();

            return settings;
        }

        private static FileInfo GetMarkdownFile(string path)
        {
            if (Path.GetExtension(path) != ".md")
                return null;

            var basePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
            var helpPath = Path.Combine(basePath, "Help");

            var helpPosition = path.IndexOf(HelpUrlPart + "/", StringComparison.OrdinalIgnoreCase);
            if (helpPosition < 0)
                return null;

            var markDownPathPart = path.Substring(helpPosition + HelpUrlPart.Length + 1);
            var markDownFilePath = Path.Combine(helpPath, markDownPathPart);
            if (!File.Exists(markDownFilePath))
                return null;

            return new FileInfo(markDownFilePath);
        }
    }
}

让我们看看这个中间件是如何工作的。首先,它会检查请求是否是 Markdown 文件。这就是 GetMarkdownFile 函数所做的。它会尝试查找与请求对应的 Markdown 文件,如果找到文件则返回其 FileInfo 对象,否则返回 null。我承认我这个函数的实现不是最好的。它只是为了证明概念。您可以将其替换为您想要的任何其他实现。

如果找不到文件,我们的中间件将通过 await next() 将请求传递给下一个。但如果文件找到了,它将读取其内容,将其转换为 HTML 并返回响应。

现在,您的 API 用户可以在多个地方找到文档。它可以在您的 VCS 存储库中找到。它也可以直接通过您的 Web API 找到。您的文档是您代码的一部分,存储在 VCS 下。

从我的角度来看,这是一项了不起的成就。但有一个缺点我想讨论一下。如果您的产品稳定,这个系统就很好。但在开发的早期阶段,您的 API 应该是什么样子、请求和响应的形式是什么等并不总是清楚的。这意味着在这个阶段,您的文档应该开放评论。必须有一个系统来评论 Markdown 文件的内容。GitHub 有 Issues 系统,您可以在其中编写关于代码的评论。现在文档是代码的一部分,我们可以在开发阶段使用 Issues 来讨论文档内容。但我认为这不是最佳解决方案。像我们在 Confluence 中那样,直接在文档上编写评论会更好。简而言之,我认为在开发早期阶段,我们仍然需要一个工具来讨论 Markdown 文档。

您可以在 我的博客 上阅读我更多的文章。

© . All rights reserved.