Web API 想法 3/3 - 扩展 Web API 文档






4.95/5 (114投票s)
与 ASP.NET Web API 相关的项目。通过关于数据流、使用 HTTPS 和扩展 Web API 文档的文章,涉及该技术的大部分方面。
这是关于该主题的延续部分。如需回顾,请参阅 WebAPI Thoughts - Introduction 文章。
PM > Install-Package WebAPI-DocumentX |
扩展 Web API 文档
Web API 最酷的另一个特性是它能够记录 Web API 服务方法。该功能通过列出、描述服务方法并为其对应的请求/响应值生成示例来帮助记录服务方法。尽管现有功能提供了良好的 API 文档,但它无法记录某些请求/响应数据类型,也没有告知用户文档的确切情况。
问题
假设您定义了一个名为 AddInternalPhysician
的服务方法,它接受 Physician
类类型。但是这个 Physician
类是一个 abstract
类。然后,您尝试通过 API 文档帮助部分查看该方法的文档,但即使该方法已在您的 ApiContoller
中定义,您也看不到它。您甚至不知道该方法 API 文档发生了什么。对于返回 HttpResponseMessage
对象的方法以及接受 HttpRequestMessage
对象的方法也是如此。
Visual Studio 2012 更新 1 (SP1) 试图通过添加一个 ResponseType
属性(位于 System.Web.Http.Description
命名空间)来解决此问题,该属性将用于定义自定义响应类型。尽管如此,由于我之前已解释过的各种原因,问题仍然存在。
解决方案
一种可能的解决方案是扩展 API 文档提供程序,以便 Web API 方法将使用实际的请求/响应类进行标记。这个概念与 VS 团队尝试过的类似,但更通用,并且可以解决上述问题。
该库将记录具有以下特征的 Web API 方法:
- 多个请求参数。
- 请求/响应类型的抽象/接口。
- 请求/响应类型的
HttpRequestMessage
和HttpResponseMessage
。 - 根据服务方法逻辑,返回
SuccessResponseType
和ErrorResponseType
的响应。 - 提供关于方法请求/响应类型情况的解释。
参与的项目
WebAPICommonLibrary
WebAPIDocumentationDemo
WebAPIDocumentationExtenderLibrary
POCOLibrary
如何使用
- 将
WebAPIDocumentationExtenderLibrary
添加/引用到您的 Web API 项目 - 在 Global.asax.cs 或 WebApiConfig.cs 下引用该库
using WebAPIDocumentationExtenderLibrary;
- 对于 Global.asax.cs
protected void Application_Start() { // ......................................... // Full code is available in the source code // ......................................... GlobalConfiguration.Configuration.RegisterRequestResponseHelp(); }
对于 WebApiConfig.cs
public static class WebApiConfig { public static void Register(HttpConfiguration config) { // ......................................... // Full code is available in the source code // ......................................... config.RegisterRequestResponseHelp(); } }
- 一旦将库引用(步骤 2)到所需的 خدمة控制器类,就向操作添加
RequestTypeAttribute
和ResponseTypeAttribute
,并指定相应的请求/响应类型。例如[RequestType(typeof(InternalPhysicianBase), "physicianRequest")] [ResponseType(typeof(bool), typeof(Message))] // for success return true/false, for error/exception return message type with appropriate content. [HttpPost] public HttpResponseMessage AddInternalPhysician(HttpRequestMessage physicianRequest) { InternalPhysician internalPhysician = physicianRequest.Content.ReadAsAsync<InternalPhysician>().Result; return base.AddPhysician(internalPhysician); }
以下是 AddInternalPhysician
方法文档的输出示例
Web API 文档扩展库的工作原理
该解决方案背后的基本思想是通过反射来检测 Web API 项目程序集,然后为 Web API 项目中定义的每个方法构建示例,最后将自定义消息和示例注册到 Web API 文档部分。该库由许多类和接口组成,以促进解决方案的实现。它还通过 .NET 反射使用现有的 Web API 文档类来生成所需的文档示例,并将 Web API 方法注册到适当的文档位置。这些类/接口显示在以下类图
从图中可以看出,有三个核心接口促进了文档示例的生成。这些接口定义如下
/// <summary>
/// API Documentation Builder interface
/// </summary>
internal interface IFluentBuilder
{
/// <summary>
/// Get Sample
/// </summary>
string Sample { get; }
/// <summary>
/// Build sample API Documentation
/// </summary>
/// <param name="input">Input value</param>
/// <returns>IFluentBuilder object</returns>
IFluentBuilder BuildSample(string input);
}
/// <summary>
/// Request sample builder interface
/// </summary>
internal interface IFluentRequestBuilder : IFluentBuilder
{
/// <summary>
/// Build request sample API Documentation
/// </summary>
/// <param name="type">Type value</param>
/// <param name="parameterName">ParameterName value</param>
/// <returns>IFluentRequestBuilder object</returns>
IFluentRequestBuilder BuildSample(Type type, string parameterName);
}
/// <summary>
/// Response sample builder interface
/// </summary>
internal interface IFluentResponseBuilder : IFluentBuilder
{
/// <summary>
/// Build response sample API Documentation
/// </summary>
/// <param name="type">Type value</param>
/// <returns>IFluentResponseBuilder object</returns>
IFluentResponseBuilder BuildSample(Type type);
/// <summary>
/// Build response sample API Documentation
/// </summary>
/// <param name="successResponseType">Success response type</param>
/// <param name="errorResponseType">Error response type</param>
/// <returns>IFluentResponseBuilder object</returns>
IFluentResponseBuilder BuildSample(Type successResponseType, Type errorResponseType);
}
名为 APISampleBuilder
的基本 abstract
类实现了 IFluentRequestBuilder
和 IFluentResponseBuilder
这两个接口,用于为方法的请求和响应构建所需的示例输出。它还包含用于促进示例输出的 private
方法。定义了两个派生类 JSONSampleBuilder
和 XMLSampleBuilder
,分别用于促进 JSON 和 XML 示例输出。这些类中的每一个都实现了一个名为 BuildSample
的 abstract
方法,该方法接受一个对象实例作为输入。
/// <summary>
/// API Documentation builder abstract class
/// </summary>
internal abstract class APISampleBuilder : IFluentRequestBuilder, IFluentResponseBuilder
{
// --------------------------------------------
// Full Code is available in the source control
// --------------------------------------------
/// <summary>
/// Build sample API Documentation
/// </summary>
/// <param name="instance">Instance value</param>
/// <returns>IFluentBuilder object</returns>
public abstract IFluentBuilder BuildSample(object instance);
/// <summary>
/// Build sample API Documentation
/// </summary>
/// <param name="input">Input value</param>
/// <returns>IFluentBuilder object</returns>
public IFluentBuilder BuildSample(string input)
{
if (!string.IsNullOrWhiteSpace(input))
sampleStringBuilder.AppendLine(input);
return this;
}
/// <summary>
/// Build request sample
/// </summary>
/// <param name="type">Type value</param>
/// <param name="parameterName">ParameterName value</param>
/// <returns>IFluentRequestBuilder object</returns>
public IFluentRequestBuilder BuildSample(Type type, string parameterName)
{
string header = "Request";
string messageHeader = string.Empty;
if (!string.IsNullOrWhiteSpace(parameterName))
{
messageHeader = string.Format("{0} sample for {1} ", header, parameterName);
BuildSample(messageHeader)
.BuildSample(messageLiner);
}
else
BuildSample(messageLiner);
return BuilderSample(type, header) as IFluentRequestBuilder;
}
/// <summary>
/// Build response sample
/// </summary>
/// <param name="type">Type value</param>
/// <returns>IFluentResponseBuilder object</returns>
public IFluentResponseBuilder BuildSample(Type type)
{
string header = "Response";
BuildSample(messageLiner);
return BuilderSample(type, header) as IFluentResponseBuilder;
}
/// <summary>
/// Build response sample API Documentation
/// </summary>
/// <param name="successResponseType">Success response type</param>
/// <param name="errorResponseType">Error response type</param>
/// <returns>IFluentResponseBuilder object</returns>
public IFluentResponseBuilder BuildSample(Type successResponseType, Type errorResponseType)
{
return ((BuildSample("Success response message sample") as IFluentResponseBuilder)
.BuildSample(successResponseType)
.BuildSample("Error response message sample") as IFluentResponseBuilder)
.BuildSample(errorResponseType);
}
}
/// <summary>
/// JSON sample builder class
/// </summary>
sealed class JSONSampleBuilder : APISampleBuilder
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
/// <summary>
/// BuildSample API Documentation sample
/// </summary>
/// <param name="instance">Instance value</param>
/// <returns>IFluentBuilder object</returns>
public override IFluentBuilder BuildSample(object instance)
{
string json = string.Empty;
try
{
// Helps to serialzied the exact type of the object. i.e Base vs Derived classes
JsonSerializerSettings jss = new JsonSerializerSettings();
if (_jsonFormatter != null && _jsonFormatter.SerializerSettings != null &&
_jsonFormatter.SerializerSettings.TypeNameHandling != TypeNameHandling.None)
{
jss = _jsonFormatter.SerializerSettings;
}
else
{
jss.TypeNameHandling = TypeNameHandling.Auto;
};
json = JsonConvert.SerializeObject(instance, Formatting.Indented, jss);
}
catch (Exception)
{
}
return base.BuildSample(json);
}
}
/// <summary>
/// XML Sample builder class
/// </summary>
sealed class XMLSampleBuilder : APISampleBuilder
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
/// <summary>
/// BuildSample API Documentation sample
/// </summary>
/// <param name="instance">Instance value</param>
/// <returns>IFluentBuilder object</returns>
public override IFluentBuilder BuildSample(object instance)
{
string xml = string.Empty;
try
{
using (Stream streamWriter = new MemoryStream())
using (StreamReader streamReader = new StreamReader(streamWriter))
{
DataContractSerializer xmlSerializer = new DataContractSerializer(instance.GetType());
xmlSerializer.WriteObject(streamWriter, instance);
streamWriter.Position = 0;
xml = streamReader.ReadToEnd();
xml = XElement.Parse(xml).ToString(); // Helps for proper indentation
}
}
catch (Exception)
{
}
return base.BuildSample(xml);
}
}
RequestTypeAttribute
和 ResponseTypeAttibute
将帮助装饰 Web API 控制器方法的所需请求/响应类型。RequestTypeAttribute
通过其构造函数接受两个参数。一个用于要记录的实际类型,另一个用于显示类型是定义在操作参数中的哪个参数。
/// <summary>
/// RequestType attribute class used to decorate Web API request objects
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public class RequestTypeAttribute : Attribute
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
/// <summary>
/// RequestType attribute class used to decorate Web API request objects
/// </summary>
/// <param name="type">Type value that represents the any request value</param>
/// <param name="parameterName">ParameterName value that represents the request value</param>
public RequestTypeAttribute(Type type, string parameterName)
{
if (type == null)
throw new ArgumentNullException("Request type value is null !");
Type = type;
_parameterName = parameterName;
}
}
ResponseTypeAttribute
有两个重载的构造函数定义,用于
- 通用响应对象
- 与第一个类似,除了用户需要传递两个类型,即
SuccessResponse
和ErrorResponse
。分别代表成功响应和错误响应。
/// <summary>
/// Response attribute class used to decorate Web API response objects
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ResponseTypeAttribute : Attribute
{
// --------------------------------------------
// Full Code is available in the source control
// --------------------------------------------
/// <summary>
/// ResponseType attribute class used to decorate Web API response objects
/// </summary>
/// <param name="type">Type value that represents the any response object</param>
public ResponseTypeAttribute(Type type)
{
if (type == null)
throw new ArgumentNullException("Response type value is null !");
Type = type;
}
/// <summary>
/// ResponseType attribute class used to decorate Web API response objects
/// </summary>
/// <param name="successResponseType">
/// Type value that represents success response</param>
/// <param name="errorResponseType"> Type value that represents error response </param>
public ResponseTypeAttribute(Type successResponseType, Type errorResponseType)
{
if (successResponseType == null)
throw new ArgumentNullException("Success Response type value is null !");
if (errorResponseType == null)
throw new ArgumentNullException("Error Response type value is null !");
SuccessResponseType = successResponseType;
ErrorResponseType = errorResponseType;
}
}
为清晰起见,请参阅 如何使用 部分。
一旦定义了所有这些类,示例文档将按以下方式生成
- 加载 Web API 项目程序集
- 验证程序集
- 它应该至少包含一个
ApiController
类型。 - 它必须包含一个
HelpPageConfigurationExtensions
类。 - 它必须包含一个
ObjectGenerator
类。 HelpPageConfigurationExtensions
类应该定义带有正确输入参数的SetSampleRequest
和SetSampleResponse
方法。ObjectGenerator
类应该定义带有正确输入参数的GenerateObject
方法。
- 它应该至少包含一个
- 收集适用于请求和响应 API 文档的方法。
- 按
ApiController
过滤掉任何重复的请求和响应方法。 - 最后,将这些方法注册以生成 API 文档的示例。
因此,为了执行上述过程,三个类参与其中,即 RegisterRequestTypes
、RegisterResponseTypes
和 RegisterAPIHelp
。顾名思义,前两个负责请求和响应示例文档,最后一个负责验证和注册请求/响应示例 API 文档。执行其职责的操作显示在以下代码中
/// <summary>
/// Register Request Type class
/// </summary>
internal sealed class RegisterRequestTypes
{
/// <summary>
/// Register Request types
/// </summary>
/// <param name="httpConfiguration">HttpConfiguration value</param>
/// <param name="setSampleRequest">SampleRequest MethodInfo value</param>
/// <param name="controllerName">ControllerName value</param>
/// <param name="requestActions">RequestActions value</param>
internal static void Register(HttpConfiguration httpConfiguration, MethodInfo setSampleRequest,
string controllerName, IEnumerable<methodinfo> requestActions, MethodInfo generateObject)
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
}
}
/// <summary>
/// Register Response Type class
/// </summary>
internal sealed class RegisterResponseTypes
{
/// <summary>
/// Register Response types
/// </summary>
/// <param name="httpConfiguration">HttpConfiguration value</param>
/// <param name="setSampleResponse">SampleReponse MethodInfo value</param>
/// <param name="controllerName">ControllerName value</param>
/// <param name="responseActions">ResponseActions value</param>
internal static void Register(HttpConfiguration httpConfiguration, MethodInfo setSampleResponse,
string controllerName, IEnumerable<methodinfo> responseActions,
MethodInfo generateObject)
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
}
}
/// <summary>
/// Register API Help class
/// </summary>
public static class RegisterAPIHelp
{
/// <summary>
/// Register Response/Request sample API Documentation
/// </summary>
/// <param name="httpConfiguration">HttpConfiguration object</param>
/// <param name="assembly"/>Assembly object</param>
public static void RegisterRequestResponseHelp
(this HttpConfiguration httpConfiguration, Assembly assembly = null)
{
// --------------------------------------------
// Full code is available in the source control
// --------------------------------------------
}
}
关注点
尽管本文的主要目的是展示如何扩展 Web API 文档,但在此过程中还有很多东西可以从代码中学习,例如
- 通过反射调用方法
- 属性用法
- 序列化
- 不同的设计原则和模式。例如流畅接口
历史和 GitHub 版本
- WebAPI Thoughts @ Github
- 第一个版本 1.0,2014 年 11 月 4 日
- 第一个版本 1.0,2014 年 12 月 1 日