跟我一起學.NetCore之路由的最佳實現

Code綜藝圈發表於2020-09-22

前言

路由,這詞絕對不陌生,不管在前端還是後端都經常提到,而這節不說其他,就聊.NetCore的路由;在之前的Asp.Net MVC 中,路由算是面試時必問的考點,可見其重要性,它的主要作用是對映URL,而不需要關注伺服器的物理檔案結構,提高安全性,同時規範了URL請求,有利於搜尋引擎優化;所以在Asp.NetCore中當然也不能缺少,以下說說其應用。

正文

在Asp.NetCore中,註冊路由方式有兩種:

  • 模板路由註冊:適合應用於MVC頁面專案,相對於來說,使用模板的形式更加方便,約定大於配置,統一URL;

  • 特性路由註冊(RouteAttribute):適合應用於API專案,針對於不同業務路由會進行定製,特性標註顯得更加便捷;

少說話,多擼碼,這裡就建立一個WebAPI專案來進行演示:

img

預設情況下,建立的WebAPI專案是推薦使用特性路由方式進行註冊(MVC預設是使用路由模板方式),當然也可以在Api專案中進行使用模板路由方式註冊,如下:

img

當註釋掉特性路由,在註冊終結點時增加路由模板,最終執行時會出現異常,因為用ApiController標識的Controller必須是特性路由,那把ApiController特性也註釋掉,結果就正常執行了,如下:

img

這裡其實有一個重要的知識點:路由匹配規則,考慮到小夥伴們之前使用Asp.Net MVC時就用到,先預設小夥伴們瞭解這塊知識(抽時間單獨整理一篇出來);以下內容著重說說特性路由應用和路由約束這塊,因為有關注到接觸的專案中不管是之前的Asp.Net, 還是現在的Asp.NetCore專案,幾乎沒有看到路由約束的應用,而在很多場景,路由約束很有必要,用於消除路由的歧義。

特性路由應用

直接在Action上加Route 特性,如下:

img

執行結果如下:

img

通常為了避免在每個Action中的重複的指定路徑字首,可以將公共部分提取到Controller類上進行Route標識,如下:

img

統一字首之後,執行結果:

img

以上的方式都是將路徑寫成固定的字串,有時候會要求像模板路由中那樣動態替換指定標記([area],[controller],[action] ),通常專案中會如下使用:

img

以上只是簡單說說特性路由的用法,小夥伴們可以根據實際情況進行標註,當然如果有特殊需求,也可以進行自定義路由特性,只要繼承IRouteTemplateProvider介面即可,上面標註的Route和HttpGet特性也是繼承於這個介面,把RouteAtrribute的原始碼扒出來參考參考:

namespace Microsoft.AspNetCore.Mvc
{
   // 標識此特性只能在類和方法上使用
    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public class RouteAttribute : Attribute, IRouteTemplateProvider
    {
        private int? _order;
        // 建構函式,傳入一個Url模板引數
        public RouteAttribute(string template)
        {
            Template = template ?? throw new ArgumentNullException(nameof(template));
        }

        public string Template { get; }
        // 路由執行順序,預設為0
        public int Order
        {
            get { return _order ?? 0; }
            set { _order = value; }
        }

        /// <inheritdoc />
        int? IRouteTemplateProvider.Order => _order;

        // 路由名稱,後續可以根據路由名稱生成Url,而不是硬編碼編寫
        public string Name { get; set; }
    }
}

來,自己也搞一個特性路由練練:

img

有沒有很簡單,其實這裡是忍住沒去扒程式碼的,後續單獨去扒;用法就先暫時說這麼多,接下來說說路由傳參和約束。

路由傳參和約束

一般情況,會針對路由中動態引數進行約束;從而路由約束可能會被作為輸入校驗,但是官方不推薦,因為當規則不匹配的時候,返回狀態404(找不到),而事實對應地址可以到達請求,只是引數輸入不合法而已,所以官方推薦作為消除路由歧義 ,同時也能避免不規則的路由進行業務處理;直接來案例演示(廢話多了怕掉坑裡):

img

新增的Controller完整程式碼:

namespace RouteDemo.Controllers
{
    [ApiController]
    [Route("/api/[controller]")]
    public class TestConstraintController
    {
        /// <summary>
        /// 驗證必須輸入,沒有輸入就返回404
        /// </summary>
        [HttpGet("TestRequiredConstraint/{name:required}")]
        public string TestRequiredConstraint(string name)
        {
            return "TestRequiredConstraint";
        }

        /// <summary>
        /// 整型約束,非整型資料就返回404
        /// </summary>
        [HttpGet("TestValueConstraint/{id:int}")]
        public string TestValueConstraint(int id)
        {
            return "TestValueConstraint";
        }

        /// <summary>
        /// 傳入最小值為100,傳入值小於100就返回404
        /// </summary>
        [HttpGet("TestMinConstraint/{id:min(100)}")]
        public string TestMinConstraint(int id)
        {
            return "TestMinConstraint";
        }

        /// <summary>
        /// 範圍約束8到18,不在這個範圍內就返回404
        /// </summary>
        [HttpGet("TestRangeConstraint/{id:range(8,18)}")]
        public string TestRangeConstraint(int id)
        {
            return "TestRangConstraint";
        }

        /// <summary>
        /// 驗證最小長度為5,低於這個長度就返回404
        /// </summary>
        [HttpGet("TestMinLengthConstraint/{name:minLength(5)}")]
        public string TestMinLengthConstraint(string name)
        {
            return "TestMinLengthConstraint";
        } 
        /// <summary>
        /// 正規表示式約束,以三個數字開頭,不滿足就返回404
        /// </summary>
        [HttpGet("TestRegexConstraint/{msg:regex(^\\d{{3}})}")]
        public string TestRegexConstraint(string msg)
        {
            return "TestRegexConstraint";
        }

        /// <summary>
        /// 整型約束,範圍約束8到18,不在這個範圍內就返回404
        /// </summary>
        [HttpGet("TestMultiConstraint/{id:int:range(8,18)}")]
        public string TestMultiConstraint(int id)
        {
            return "TestRangConstraint";
        }
    }
}

以上只是挑了幾個預設內建的約束進行舉例演示,其實還有很多,剩下的小夥伴下來一定要試試,用法都很簡單,如下:

  • 單個約束時

    引數:約束

    案例:[HttpGet("TestRequiredConstraint/{name:required}")]

  • 多個約束時

    引數:約束1:約束2:....

    案例:[HttpGet("TestMultiConstraint/{id:int:range(8,18)}")]

框架本身內建的約束,如下(小夥伴一定要敲敲):

約束 示例 說明
int {id:int} 匹配任何整數
bool {active:bool} 匹配 truefalse。 不區分大小寫
datetime {dob:datetime} 在固定區域性中匹配有效的 DateTime 值。
decimal {price:decimal} 在固定區域性中匹配有效的 decimal 值。
double {weight:double} 在固定區域性中匹配有效的 double 值。
float {weight:float} 在固定區域性中匹配有效的 float 值。
guid {id:guid} 匹配有效的 Guid
long {ticks:long} 匹配有效的 long
minlength(value) {username:minlength(4)} 字串必須至少為 4 個字元
maxlength(value) {filename:maxlength(8)} 字串不得超過 8 個字元
length(length) {filename:length(12)} 字串必須正好為 12 個字元
length(min,max) {filename:length(8,16)} 字串必須至少為 8 個字元,且不得超過 16 個字元
min(value) {age:min(18)} 整數值必須至少為 18
max(value) {age:max(120)} 整數值不得超過 120
range(min,max) {age:range(18,120)} 整數值必須至少為 18,且不得超過 120
alpha {name:alpha} 字串必須由一個或多個字母字元組成,a-z,並區分大小寫。
regex(expression) {ssn:regex(^\\d{{3}}-\\d{{2}}-\\d{{4}}$)} 字串必須與正規表示式匹配。 請參閱有關定義正規表示式的提示。
required {name:required} 用於強制在 URL 生成過程中存在非引數值

不用說,內建約束肯定不可能面面俱到,所以針對個性化約束,還需要自定義,繼承 IRouteConstraint 介面便可實現自定義路由約束。 介面中包含 Match,當滿足約束時,它返回 true,否則返回 false。如下例演示:

img

img

總結

特性路由和約束就說到這吧,根據實際需求進行路由約束,但不能盲目,如果是要進行輸入資料校驗,請考慮使用模型驗證!下一節說說整合Swagger。

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

CSDN:Code綜藝圈

知乎:Code綜藝圈

掘金:Code綜藝圈

部落格園:Code綜藝圈

bilibili:Code綜藝圈

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

一個被程式搞醜的帥小夥,關注"Code綜藝圈",識別關注跟我一起學~~~

擼文不易,莫要白瞟,三連走起~~~~

相關文章