路由約束
ASP.NET Core中,通過定義路由模板,可以在Url上傳遞變量,同時可以針對變量提供默認值、可選和約束。
約束的使用方法是在屬性路由上添加指定的約束名,用法如下:
// 單個使用
[Route("users/{id:int}")]
public User GetUserById(int id) { }
// 組合使用
[Route("users/{id:int:min(1)}")]
public User GetUserById(int id) { }
框架內部已經提供了一些約束,如下所示:
約束 | 示例 | 匹配項示例 | 說明 |
---|---|---|---|
int | 123456789, -123456789 | 匹配任何整數 | |
bool | true, FALSE | 匹配 true或 false(區分大小寫) | |
datetime | 2016-12-31, 2016-12-31 7:32pm | 匹配有效的 DateTime 值(位於固定區域性中 - 查看警告) | |
decimal | 49.99, -1,000.01 | 匹配有效的 decimal 值(位於固定區域性中 - 查看警告) | |
double | 1.234, -1,001.01e8 | 匹配有效的 double 值(位於固定區域性中 - 查看警告) | |
float | 1.234, -1,001.01e8 | 匹配有效的 float 值(位於固定區域性中 - 查看警告) | |
guid | CD2C1638-1638-72D5-1638-DEADBEEF1638, | 匹配有效的 Guid 值 | |
long | 123456789, -123456789 | 匹配有效的 long 值 | |
minlength(value) | {username:minlength(4)} | Rick | 字符串必須至少為 4 個字符 |
maxlength(value) | {filename:maxlength(8)} | Richard | 字符串不得超過 8 個字符 |
length(length) | {filename:length(12)} | somefile.txt | 字符串必須正好為 12 個字符 |
length(min,max) | {filename:length(8,16)} | somefile.txt | 字符串必須至少為 8 個字符,且不得超過 16 個字符 |
min(value) | {age:min(18)} | 19 | 整數值必須至少為 18 |
max(value) | {age:max(120)} | 91 | 整數值不得超過 120 |
range(min,max) | {age:range(18,120)} | 91 | 整數值必須至少為 18,且不得超過 120 |
alpha | Rick | 字符串必須由一個或多個字母字符(a-z,區分大小寫)組成 | |
regex(expression) | {ssn:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)} | 123-45-6789 | 字符串必須匹配正則表達式(參見有關定義正則表達式的提示) |
required | Rick | 用於強制在 URL 生成過程中存在非參數值 |
內置的約束能夠適用於大部分常見的應用場景,但是有時候我們還是需要去自定義我們想要的效果。
自定義路由約束
自定義約束是要實現IRouteConstraint
接口,然后重載Match
方法,該方法有四個參數。
第一個參數httpContext
是當前請求的上下文
第二個參數route
是當前約束所屬的路由
第三個參數routeKey
是當前檢查的變量名,例如文章開頭示例中的id
第四個參數values
是當前Url匹配的字典值,例如文章開頭的示例的路由,如果Url是users/1
,那么就有一個字典,其key = id
,value = 1
。當然還有其他的變量的值,比如controller
,action
等。
第五個參數routeDirection
是一個枚舉值,代表是web請求的還是用Url.Action
等方法生成Url。
舉一個實例,我們想要定義一個約束,指定路由傳過來的參數必須是指定的枚舉值。
我們先定義一個枚舉:
public enum BoolEnum
{
True,
False
}
然后定義約束:
public class EnumConstraint : IRouteConstraint
{
private Type _enumType;
public EnumConstraint(string enumTypeName)
{
_enumType = Type.GetType(enumTypeName);
}
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
var value = values[routeKey];
if (value == null)
{
return false;
}
if (Enum.TryParse(_enumType, value.ToString(), out object result))
{
if (Enum.IsDefined(_enumType, result))
{
return true;
}
}
return false;
}
}
在Startup.cs
的ConfigureServices
方法添加自定義約束:
services.Configure<RouteOptions>(options =>
{
options.ConstraintMap.Add("enum", typeof(EnumConstraint));
});
在路由上使用約束:
(WebApplicationTest
是當前的namespace
)
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
// GET: api/Test
[HttpGet("{bool:enum(" + nameof(WebApplicationTest) + "." + nameof(BoolEnum) + ")}")]
public string Get(BoolEnum @bool)
{
return "bool: " + @bool;
}
[HttpGet("{id:int:min(2)}", Name = "Get")]
public string Get(int id)
{
return "id: " + id;
}
[HttpGet("{name}")]
public string Get(string name)
{
return "name: " + name;
}
}
{id:int:min(2)}
路由必須使用min(2)
,否則對於id = 0
或id = 1
會有沖突。
運行程序,當路由是api/Test/0
、api/Test/1
、api/Test/True
和api/Test/False
的時候,匹配我們的自定義約束。
當路由是api/Test/{大於2的整數}
的時候,匹配第二個路由。
其他情況匹配第三個路由。
結論
路由約束在某些場景下是非常有用的功能,可以減少controller
中校驗參數,將部分參數校驗的功能使用聲明式的attruibute
來實現,某些重復的校驗可以通過抽取成約束公共使用。
constraint
的構造函數可以使用注入,所以可以擴展性十分強,可以通過查詢數據庫做一些參數校驗。
官網上對於路由約束只是簡單的提了一下,本文對路由約束的使用提供了具體的示例。