1. 程式人生 > >ASP.NET Web API編程——路由

ASP.NET Web API編程——路由

完整 sele tail detail col 2-0 text -- 項目

路由過程大致分為三個階段:

1)請求URI匹配已存在路由模板

2)選擇控制器

3)選擇操作

1匹配已存在的路由模板

路由模板

WebApiConfig.Register方法中定義路由,例如模板默認生成的路由為:

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

上面使用了public static IHttpRoute MapHttpRoute(this HttpRouteCollection routes, string name, string routeTemplate, object defaults)方法來配置路由。相關參數為:

name:路由名稱。

routeTemplate:路由模板,與URI相似。

例如:

api/{controller}/{id}、

api/{controller}/{action}/{id}、api/{controller}/public/{category}/{id}

defaults:路由值對象。可為占位符設置默認值。

例如

api/{controller}/public/{category}/{id}

設置defaults: new { category = "all" }

路由詞典

如果Web API匹配到一個已存在的路由模板,會創建一個路由詞典,詞典的鍵是模板中占位符的名稱,值是占位符對應的值。如果路由值對象被指定為RouteParameter.Optional,那麽這個值不會被放入詞典中。路由詞典會被存儲到IHttpRouteData實例中。

匹配示例

對於api/{controller}/{id}

首先匹配字符串api,然後匹配控制器(controller),第三匹配以HTTP方法開頭的操作(Action),占位符id匹配Action接收的參數。

對於api/{controller}/{action}/{id}

首先匹配字符串api,然後匹配控制器(controller),最後匹配操作(Action),占位符id匹配Action接收的參數。

對於api/root/{id}

務必對defaults設置控制器(controller)的默認值,,不然無法執行路由過程。可以不設置操作(Action)。首先匹配api和root,然後匹配默認的控制器(controller),最後占位符id匹配操作(Action)接收的參數。若不設置操作(Action)那麽匹配以HTTP方法開頭的操作(Action)。

2控制器的選擇

控制器(controller)的選擇是由IHttpControllerSelector.SelectController完成的,IHttpControllerSelector接口默認實現是DefaultHttpControllerSelectorIHttpControllerSelector.SelectController方法獲取HttpRequestMessage實例並返回HttpControllerDescriptor。

DefaultHttpControllerSelector查找控制器(controller)的算法為:

在路由詞典中查找鍵為“controller”的值,找到鍵“controller”對應的值後,將字符串Controller拼接到這個值的後邊,便可獲得控制器(Controller)名。根據獲得的控制器(Controller)名查找Web API中的控制器(controller)。如果沒有查找到控制器(controller)名或者匹配到了多個,那麽返回錯誤。DefaultHttpControllerSelector使用IHttpControllerTypeResolver來獲得Web API控制器(controller)類型列表。IHttpControllerTypeResolver的默認實現返回具有如下特征的公有類:

1)實現了IHttpController接口。

2)不被abstract修飾。

3)命名以“Controller”結尾。

3匹配控制器操作

IHttpActionSelector.SelectAction方法獲取HttpControllerContext並返回HttpActionDescriptor,IHttpActionSelector接口的默認實現是ApiControllerActionSelector。ApiControllerActionSelector會查找請求的HTTP方法、路由模板中的{action}占位符、控制器操作的參數列表。

Web API框架認為控制器(controller)的操作(Action)具有如下特征:

1)公有類型的實例方法。

2)繼承自ApiController的方法

3)非構造器,事件,操作符重載等特殊方法。

Web API框架僅選擇那些匹配請求的HTTP方法的操作,原則為:

1)指定了相應特性的操作,例如使用HttpGet特性的操作,只能匹配Get請求。

2)如果控制器(controller)操作以"Get", "Post", "Put", "Delete", "Head", "Options", or "Patch"開頭,按照慣例控制器(controller)操作支持對應的HTTP請求。

3)如果不滿足以上兩條,默認支持POST請求。

ApiControllerActionSelector選擇控制器(controller)操作的算法如下:

1)創建一個鏈表,鏈表元素為所有與HTTP請求相匹配的操作(Action)。

2)如果路由詞典中包含關於操作(Action)的鍵值對,移除鏈表中名稱和值不匹配的操作(Action)。

3)匹配操作(Action)參數與URI。

l 對於每一個操作(Action),獲得簡單類型的參數列表,參數綁定從URI獲得操作(Action)參數,不包括可選的參數。

l 在參數列表中,從路由表中或請求URI查詢字符串中,為每一個參數名找到一個匹配,匹配是不區分大小寫的,並且不依賴於參數順序。

l 選擇一個操作(Action),其參數列表中的每一個參數在請求URI中都對應一個值。

l 如果有多個操作(Action)滿足以上規則,選擇有最多參數匹配的一個操作(Action)。

4)忽略被標記為[NonAction]的方法。

補充說明:

對於步驟3)一個參數可以從URI,請求消息體,或者自定義綁定中獲得它的值。對於來自於URI的參數,要確保URI確實包含對應參數的值,這個值可能在路由詞典中或查詢字符串中。

對於可選的參數,如果綁定不能從URI中獲得參數的值,對於操作(Action)的選擇也沒有影響。

對於復雜類型,只能通過自定義綁定來匹配URI中的參數值。操作(Action)選擇算法的目的是在完成模型綁定之前選出操作(Action),因此操作(Action)選擇算法對復雜類型無效。

一旦操作(Action)被選出,模型綁定器才會被調用。

4路由過程的擴展

接口

描述

IHttpControllerSelector

選擇控制器

IHttpControllerTypeResolver

獲得控制器(controller)類型列表,DefaultHttpControllerSelector會從這個列表中選擇控制器(controller)類型。

IAssembliesResolver

獲得項目程序集列表,IHttpControllerTypeResolver 會從這個列表中找到控制器(controller)類型

IHttpControllerActivator

創建新的控制器(controller)實例

IHttpActionSelector

選擇操作(Action)

IHttpActionInvoker

調用操作(Action)

要想使用自定義的上述接口實現,那麽要註冊服務。

public static class WebApiConfig
{
        public static void Register(HttpConfiguration config)
        {
            //其他配置
            config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector());
        }
}

例:擴展IHttpControllerSelector

實現GetControllerMapping和SelectController方法,GetControllerMapping為發現系統所有可能的控制器(controller),SelectController會使用這些所有可能的控制器(controller),因此需要CustomHttpControllerSelector的屬性存儲所有可能的控制器(controller)。具體示例見“ASP.NET Web API編程——版本控制”

public class CustomHttpControllerSelector : IHttpControllerSelector
{

        public IDictionary<string, System.Web.Http.Controllers.HttpControllerDescriptor> GetControllerMapping()
        {
            throw new NotImplementedException();
        }

        public System.Web.Http.Controllers.HttpControllerDescriptor SelectController(HttpRequestMessage request)
        {
            throw new NotImplementedException();
        }
}

此外,有時擴展Web API框架的DefaultHttpControllerSelector或許是更加合理的方式。

例:擴展IAssembliesResolver動態加載控制器(controller)

可以將控制器(controller)類單獨編制為一個dll,放在指定的文件夾內,這樣無需編譯整個框架,就能修改控制器(controller)

    public class ServiceAssembliesResolver : IAssembliesResolver
    {
        private string path;
        public ServiceAssembliesResolver(string path)
        {
            this.path = path;
        }
        public ICollection<System.Reflection.Assembly> GetAssemblies()
        {
            List<Assembly> assemblies = new List<Assembly>();
            try
            {
                //初始化
                assemblies = new List<Assembly>();
                //加載每一個服務插件
                foreach (string file in Directory.GetFiles(path, "*.dll"))
                {
                    var controllersAssembly = Assembly.LoadFrom(file);
                    assemblies.Add(controllersAssembly);
                }
            }
            catch (Exception ex)
            {
                //處理異常
            }
            return assemblies;
        }
    }

此外繼承Web API框架默認的DefaultAssembliesResolver也是一個好辦法。

    public class ServiceAssembliesResolver : DefaultAssembliesResolver
    {
        //服務插件路徑
        private string path;
        public ServiceAssembliesResolver(string path):base()
        {
            this.path = path;
        }
        public override ICollection<Assembly> GetAssemblies()
        {
            List<Assembly> assemblies = new List<Assembly>();
            try
            {
                //獲得已有的服務
                ICollection<Assembly> baseAssemblies = base.GetAssemblies();
                //初始化
                assemblies = new List<Assembly>(baseAssemblies);
                //加載每一個服務插件
                foreach (string file in Directory.GetFiles(path, "*.dll"))
                {
                    var controllersAssembly = Assembly.LoadFrom(file);
                    assemblies.Add(controllersAssembly);
                }
            }
            catch (Exception ex)
            {
               //處理異常
            }
            return assemblies;
        }
    }

5使用特性設置路由

為了更好地支持URI參數,所以使用路由特性。

5.1使用特性

RouteAttribute

路由特性定義為:

public sealed class RouteAttribute : Attribute, IDirectRouteFactory, IHttpRouteInfoProvider
{
        public RouteAttribute();
        //template:描述要匹配的 URI 模式的路由模板
        public RouteAttribute(string template);
        //路由名稱
        public string Name { get; set; }
        //路由順序
        public int Order { get; set; }
        //描述要匹配的 URI 模式的路由模板
        public string Template { get; }
}

RoutePrefix

使用RoutePrefix特性為整個控制器(controller)設置路由前綴,路由前綴特性定義為:

public class RoutePrefixAttribute : Attribute, IRoutePrefix
{
        protected RoutePrefixAttribute();
        //prefix: 控制器的路由前綴。
        public RoutePrefixAttribute(string prefix);
        //獲取路由前綴。
        public virtual string Prefix { get; }
}

例子:

    [RoutePrefix("api/values")]
    public class ValuesController : ApiController
    {
         //GET api/values/getvalues
        [Route("getvalues")]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }

使用“~”可重寫路由前綴,例如:

[RoutePrefix("api/values")]
public class ValuesController : ApiController
{
        [Route("~/api/allvalues")]
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }
}

路由前綴可以包含參數:

[RoutePrefix("api/values/{id}")]
public class ValuesController : ApiController
{
    //GET api/values/1/getvalues
        [Route("getvalues")]
        public IEnumerable<string> Get(string id)
        {
            //
        }
}

路由約束

限制參數的類型,語法為:{parameter:constraint},可以指定多個約束,每個約束用:分隔。Route和RoutePrefix特性均支持這種用法。

[RoutePrefix("api/values/{id:int:min(1)}")]
public class ValuesController : ApiController
{
        [Route("GetValues")]
        public IEnumerable<string> Get(int id)
        {
            //具體實現
        }
}

約束規則如下:

約束

描述

例子

alpha

匹配大寫或小寫拉丁字母(A-Z,a-z)

{x:alpha}

bool

匹配Boolean 類型

{x:bool}

datetime

匹配DateTime 類型

{x:datetime}

decimal

匹配decimal類型

{x:decimal}

double

匹配double類型

{x:double}

float

匹配float類型

{x:float}

guid

匹配GUID值

{x:guid}

int

匹配int類型

{x:int}

length

匹配指定長度或指定長度範圍內的字符串

{x:length(6)} {x:length(1,20)}

long

匹配long類型

{x:long}

max

匹配整型,其值不能大於設置的值

{x:max(10)}

maxlength

匹配字符串,它的長度不能超過設定的值

{x:maxlength(10)}

min

匹配整型,其值不能小於設定的值

{x:min(10)}

minlength

匹配字符串,它的長度不能小於設置的值

{x:minlength(10)}

range

指定整型的範圍

{x:range(10,50)}

regex

匹配正則表達式

{x:regex(^\d{3}-\d{3}-\d{4}$)}

可選URI參數與默認值

使用?來標識路由值為可選的,同時必須為操作參數設置默認值。

例:

        [Route("api/v1/user/{id:int?}")]
        [HttpGet]
        public IHttpActionResult User(int id=1)
        {
            return Json("id:"+id);
        }

設置路由名稱

設置路由名稱後,可以在使用控制器(controller)的屬性ApiController.Url或ApiController.Route拼接URL。

例:在GetPublicationNew中獲得路由到操作GetPublicationURL

        [Route("api/v1/publication",Name="V1Publication")]
        public IHttpActionResult GetPublication()
        {
            return Json("api/v1/publication");
        }

        [HttpGet]
        [Route("api/v2/publication")]
        public IHttpActionResult GetPublicationNew()
        {
            string url = Url.Link("V1Publication", null);
            return Json(url);
        }

路由順序

RouteOrder值較小的路由先被使用,默認的RouteOrder值為0。

比較順序的規則為:

1)先比較RouteOrder的值

2)查看路由模板的URI參數,對於每一個參數,由參數決定的順序為:

  • 字面值順序排第一。
  • 含有路由約束的順序排第二。
  • 沒有路由約束的順序排第三。
  • 含有通配符和路由約束的順序排第四。
  • 含有通配符和無路由約束的順序排第五。

3)在上述規則無法區分的情況下,即上述規則判定順序相同的兩個路由,決定順序的依據是:不區分大小寫地,比較字符串的序號。

例:這裏引用官網文檔的例子

https://docs.microsoft.com/en-us/aspnet/web-api/overview/web-api-routing-and-actions/attribute-routing-in-web-api-2)

[RoutePrefix("orders")]
public class OrdersController : ApiController
{
    [Route("{id:int}")] // 設置路由約束
    public HttpResponseMessage Get(int id) { ... }

    [Route("details")]  // 字面值
    public HttpResponseMessage GetDetails() { ... }

    [Route("pending", RouteOrder = 1)]//指定路由順序
    public HttpResponseMessage GetPending() { ... }

    [Route("{customerName}")]  //無路由約束
    public HttpResponseMessage GetByCustomer(string customerName) { ... }

    [Route("{*date:datetime}")]  // 含有通配符
    public HttpResponseMessage Get(DateTime date) { ... }
}

路由順序依次為:

第一.orders/details

第二.orders/{id}

第三.orders/{customerName}

第四.orders/{*date}

第五.orders/pending

使路由特性起作用

要想使路由特性起作用,必須在WebApiConfig.Register方法中加入代碼:config.MapHttpAttributeRoutes();

如下完整代碼:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //啟用路由特性
            config.MapHttpAttributeRoutes();

            // 其他配置
        }
    }

可以同時使用路由特性與基於協定路由:

public static void Register(HttpConfiguration config)
{
        //啟用路由特性
        config.MapHttpAttributeRoutes();

        // 基於協定的路由
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
}

自定義路由約束

實現一個繼承自IHttpRouteConstraint接口的類,然後註冊此類。

例:

自定義CustomHttpRouteConstraint

public class CustomHttpRouteConstraint : IHttpRouteConstraint
{

        public bool Match(HttpRequestMessage request, IHttpRoute route, string parameterName, IDictionary<string, object> values, HttpRouteDirection routeDirection)
        {
            //實現驗證過程   
        }
}

註冊CustomHttpRouteConstraint,為這個約束提供一個簡稱。

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //其他配置

            var constraintResolver = new DefaultInlineConstraintResolver();
            constraintResolver.ConstraintMap.Add("customcons", typeof(CustomHttpRouteConstraint));

            config.MapHttpAttributeRoutes(constraintResolver);
        }
    }

使用自定義約束

        [Route("{name:customcons}")]
        public IHttpActionResult SUser(string name)
        {
            return Json("name:" + name);
        }

5.2應用場景:

支持多版本API:

假設隨著業務的擴展,對API接口進行升級改造,老的接口還要使用一段時間而不會立即停用,這時需要版本控制機制。如下面的例子,使用路由特性後,

雖然URI片段中的指定的操作(Action)名稱一樣,但是調用的操作(Action)卻不一樣。

例:

        [Route("api/v1/publication")]
        public IHttpActionResult GetPublication()
        {
            return Json("api/v1/publication");
        }

        [Route("api/v2/publication")]
        public IHttpActionResult GetPublicationNew()
        {
            return Json("api/v2/publication");
        }

當在瀏覽器中輸入:http://localhost:45778/api/v1/publication時,顯示"api/v1/publication"

當在瀏覽器中輸入:http://localhost:45778/api/v2/publication時,顯示"api/v2/publication"

由於上述操作定義在同一個控制器(Controller)類中,所以方法名不能相同。

註意:由於上述操作名稱中含有Get字符串,所以支持Get請求。

重載

為了支持重載的方法,使用路由特性

例:

        [Route("api/v1/user/{id}")]
        public IHttpActionResult GetUser(int id)
        {
            return Json("id:"+id);
        }

        [Route("api/v2/user/{name}")]
        public IHttpActionResult GetUser(string name)
        {
            return Json("name:" + name);
        }

當在瀏覽器中輸入http://localhost:45778/api/v1/user/1時,頁面顯示“id:1”

當在瀏覽器中輸入http://localhost:45778/api/v2/user/coding時,頁面顯示“name:coding”

支持URI時間參數

例:

請求Url:http://localhost:45778/api/user/1982-02-01

        [HttpGet]
        [Route("api/user/{time:datetime}")]
        public IHttpActionResult User(DateTime time)
        {
            return Json("time:" + time);
        }

輸出為:"time:1982/2/1 0:00:00"

請求Url:http://localhost:45778/api/user/1982/02/01

        [HttpGet]
        [Route("api/user/{*time:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
        public IHttpActionResult User(DateTime time)
        {
            return Json("time:" + time);
        }

輸出為:"time:1982/2/1 0:00:00"

也可以將兩種約束一起使用,這樣可以同時支持兩種格式了

        [HttpGet]     
        [Route("api/user/{*time:datetime:regex(\\d{4}/\\d{2}/\\d{2})}")]
        [Route("api/user/{time:datetime:regex(\\d{4}-\\d{2}-\\d{2})}")]
        public IHttpActionResult User(DateTime time)
        {
            return Json("time:" + time);
        }

參考

https://docs.microsoft.com/en-us/aspnet/web-api/

---------------------------------------------------------------------

轉載與引用請註明出處。

時間倉促,水平有限,如有不當之處,歡迎指正。

ASP.NET Web API編程——路由