1. 程式人生 > >ASP.NET Web API專案自動生成介面文件和測試頁面

ASP.NET Web API專案自動生成介面文件和測試頁面

在開發介面的時候,寫介面文件已是一件不可忽視的事情,有了更新也要同步更新很麻煩。ASP.NET 建立的Web API專案可以自己配置介面文件的XML顯示,這樣介面更新和註釋更新了重新發布就有了,確實方便不少,下來就介紹下怎麼配置生成API介面註釋文件。另外,如果在介面生成的同時能夠一併生成測試頁面也是不錯的選擇,能節省不少開發時間和人力成本。

建立Web API專案

修改預設的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”按鈕就可以測試了:

使用此安裝包不需要修改如下檔案: