在开发接口的时候,写接口文档已是一件不可忽视的事情,有了更新也要同步更新很麻烦。ASP.NET 创建的Web API项目可以自己配置接口文档的XML显示,这样接口更新和注释更新了重新发布就有了,确实方便不少,下来就介绍下怎么配置生成API接口注释文档。另外,如果在接口生成的同时能够一并生成测试页面也是不错的选择,能节省不少开发时间和人力成本。
创建Web API项目
请参考:ASP.NET 使用Swagger开发Web API接口项目这里我们以WebAPI项目为例。
修改默认的API路由配置
为什么要修改呢?因为每一个Controller的默认路由都是指定到"api/{controller}/{id}"的,所以默认生成的控制器类似下面:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; namespace WebAPI.Controllers { public class ValuesController : ApiController { // GET api/<controller> public IEnumerable<string> Get() { return new string[] { "value1", "value2" }; } // GET api/<controller>/5 public string Get(int id) { return "value"; } // POST api/<controller> public void Post([FromBody]string value) { } // PUT api/<controller>/5 public void Put(int id, [FromBody]string value) { } // DELETE api/<controller>/5 public void Delete(int id) { } } }
我们需要一个方法提供多种HTTP访问方式,所以应该将action的配置加入,如:"api/{controller}/{action}/{id}"。
修改WebApiConfig.cs文件:
代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web.Http; namespace WebAPI { public static class WebApiConfig { public static void Register(HttpConfiguration config) { // Web API 配置和服务 // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{action}/{id}", defaults: new { id = RouteParameter.Optional } ); } } }
修改HelpPage配置启动
修改HelpPageConfig.cs文件,代码如下:
// Uncomment the following to provide samples for PageResult<T>. Must also add the Microsoft.AspNet.WebApi.OData // package to your project. ////#define Handle_PageResultOfT using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net.Http.Headers; using System.Reflection; using System.Web; using System.Web.Http; using WebAPI.Utils; namespace WebAPI.Areas.HelpPage { /// <summary> /// Use this class to customize the Help Page. /// For example you can set a custom <see cref="System.Web.Http.Description.IDocumentationProvider"/> to supply the documentation /// or you can provide the samples for the requests/responses. /// </summary> public static class HelpPageConfig { [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "WebAPI.Areas.HelpPage.TextSample.#ctor(System.String)", Justification = "End users may choose to merge this string with existing localized resources.")] [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "bsonspec", Justification = "Part of a URI.")] public static void Register(HttpConfiguration config) { //// Uncomment the following to use the documentation from XML documentation file. //config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/App_Data/XmlDocument.xml"))); #region *************************XML显示接口参数和注释*************************************** // 添加本项目或其他引用项目的XML配置:可加多个 config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/WebAPI.XML"))); #endregion //// Uncomment the following to use "sample string" as the sample for all actions that have string as the body parameter or return type. //// Also, the string arrays will be used for IEnumerable<string>. The sample objects will be serialized into different media type //// formats by the available formatters. //config.SetSampleObjects(new Dictionary<Type, object> //{ // {typeof(string), "sample string"}, // {typeof(IEnumerable<string>), new string[]{"sample 1", "sample 2"}} //}); // Extend the following to provide factories for types not handled automatically (those lacking parameterless // constructors) or for which you prefer to use non-default property values. Line below provides a fallback // since automatic handling will fail and GeneratePageResult handles only a single type. #if Handle_PageResultOfT config.GetHelpPageSampleGenerator().SampleObjectFactories.Add(GeneratePageResult); #endif // Extend the following to use a preset object directly as the sample for all actions that support a media // type, regardless of the body parameter or return type. The lines below avoid display of binary content. // The BsonMediaTypeFormatter (if available) is not used to serialize the TextSample object. #region *************************默认JSON显示接口*************************************** //config.SetSampleForMediaType( // new TextSample("Binary JSON content. See http://bsonspec.org for details."), // new MediaTypeHeaderValue("application/bson")); #endregion //// Uncomment the following to use "[0]=foo&[1]=bar" directly as the sample for all actions that support form URL encoded format //// and have IEnumerable<string> as the body parameter or return type. //config.SetSampleForType("[0]=foo&[1]=bar", new MediaTypeHeaderValue("application/x-www-form-urlencoded"), typeof(IEnumerable<string>)); //// Uncomment the following to use "1234" directly as the request sample for media type "text/plain" on the controller named "Values" //// and action named "Put". //config.SetSampleRequest("1234", new MediaTypeHeaderValue("text/plain"), "Values", "Put"); //// Uncomment the following to use the image on "../images/aspNetHome.png" directly as the response sample for media type "image/png" //// on the controller named "Values" and action named "Get" with parameter "id". //config.SetSampleResponse(new ImageSample("../images/aspNetHome.png"), new MediaTypeHeaderValue("image/png"), "Values", "Get", "id"); //// Uncomment the following to correct the sample request when the action expects an HttpRequestMessage with ObjectContent<string>. //// The sample will be generated as if the controller named "Values" and action named "Get" were having string as the body parameter. //config.SetActualRequestType(typeof(string), "Values", "Get"); //// Uncomment the following to correct the sample response when the action returns an HttpResponseMessage with ObjectContent<string>. //// The sample will be generated as if the controller named "Values" and action named "Post" were returning a string. //config.SetActualResponseType(typeof(string), "Values", "Post"); } #if Handle_PageResultOfT private static object GeneratePageResult(HelpPageSampleGenerator sampleGenerator, Type type) { if (type.IsGenericType) { Type openGenericType = type.GetGenericTypeDefinition(); if (openGenericType == typeof(PageResult<>)) { // Get the T in PageResult<T> Type[] typeParameters = type.GetGenericArguments(); Debug.Assert(typeParameters.Length == 1); // Create an enumeration to pass as the first parameter to the PageResult<T> constuctor Type itemsType = typeof(List<>).MakeGenericType(typeParameters); object items = sampleGenerator.GetSampleObject(itemsType); // Fill in the other information needed to invoke the PageResult<T> constuctor Type[] parameterTypes = new Type[] { itemsType, typeof(Uri), typeof(long?), }; object[] parameters = new object[] { items, null, (long)ObjectGenerator.DefaultCollectionSize, }; // Call PageResult(IEnumerable<T> items, Uri nextPageLink, long? count) constructor ConstructorInfo constructor = type.GetConstructor(parameterTypes); return constructor.Invoke(parameters); } } return null; } #endif } }
这里修改支持了多个XML文档方法,扩展类如下:
using System; using System.Linq; using System.Reflection; using System.Web.Http.Controllers; using System.Web.Http.Description; using WebAPI.Areas.HelpPage; using WebAPI.Areas.HelpPage.ModelDescriptions; namespace WebAPI.Utils { /// <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.GetDocumentation(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)); } } }
注意:开启XML文档注释的时候HelpPageConfig.cs需要注释掉如下代码
#region *************************默认JSON显示接口*************************************** //config.SetSampleForMediaType( // new TextSample("Binary JSON content. See http://bsonspec.org for details."), // new MediaTypeHeaderValue("application/bson")); #endregion
编写示例程序验证API文档更新情况
PersonController.cs示例:
using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using WebAPI.Utils; namespace WebAPI.Controllers { /// <summary> /// 人物 /// </summary> public class Person { /// <summary> /// 唯一标识记录ID /// </summary> public long Id { set; get; } /// <summary> /// 姓名 /// </summary> public string Name { set; get; } /// <summary> /// 年龄 /// </summary> public int Age { set; get; } /// <summary> /// 人物构造函数 /// </summary> /// <param name="Id"></param> /// <param name="Name"></param> /// <param name="Age"></param> public Person(long Id, string Name, int Age) { this.Id = Id; this.Name = Name; this.Age = Age; } } /// <summary> /// 结果对象 /// </summary> public class Result { /// <summary> /// 状态:0失败,1成功 /// </summary> public int Status { set; get; } /// <summary> /// 数据 /// </summary> public string Data { set; get; } /// <summary> /// 消息 /// </summary> public string Msg { set; get; } /// <summary> /// 结果构造函数 /// </summary> /// <param name="Status"></param> /// <param name="Data"></param> /// <param name="Msg"></param> public Result(int Status, string Data, string Msg) { this.Status = Status; this.Data = Data; this.Msg = Msg; } } /// <summary> /// 人物视图控制器 /// </summary> public class PersonController : ApiController { /// <summary> /// 获取所有人物 /// </summary> /// <returns></returns> public List<Person> GetPersons() { List<Person> perons = new List<Person>(); Person person = null; for (var i=1;i<=10;i++) { person = new Person(i,"boonya"+1,new Random(100).Next()); perons.Add(person); } return perons; } /// <summary> /// 获取人物 /// </summary> /// <param name="Id">唯一标识记录ID</param> /// <returns></returns> public Person GetPerson(long Id) { return new Person(Id, "boonya" + Id, new Random(100).Next()); } /// <summary> /// 删除人物 /// </summary> /// <param name="Id">唯一标识记录ID</param> /// <returns></returns> public Result DeletePerson(long Id) { return new Result(1,"","Id="+Id+"删除成功"); } /// <summary> /// 添加人物 /// </summary> /// <param name="Name">姓名</param> /// <param name="Age">年龄</param> /// <returns></returns> public Result AddPerson(string Name,int Age) { Person person = new Person(new Random(100).Next(), Name, Age); return new Result(1, JsonConvert.SerializeObject(person), "添加成功"); } } }启动项目运行,查看API链接:
详细接口说明类似下面:
至此,我们看到接口的参数说明和返回值等,不用手动再去写文档了。
注意:参考博客内容里面有怎么生成项目的XML文件。
配置自动生成接口测试页面
添加引用NuGet安装包:
安装完成后运行就可以了:
点击“Test API”按钮就可以测试了:
使用此安装包不需要修改如下文件: