背景介紹
上周給大家分享了Nancy in .NET Core學(xué)習(xí)筆記 - 路由之后, 就一直在考慮.NET Core能否實(shí)現(xiàn)和Nancy中一樣的路由約束, 最近查閱了一下MSDN及一些國(guó)外博客, 發(fā)現(xiàn).NET Core中已經(jīng)實(shí)現(xiàn)了相同的功能,所以這里給大家分享一下。
路由約束
路由約束是路由中的一種設(shè)置,可以幫助我們限制Url中的參數(shù)類型,只有當(dāng)參數(shù)符合約束條件的時(shí)候,action才會(huì)被激活并觸發(fā)。
比如我們現(xiàn)在有以下2個(gè)Url
[GET] /api/posts/{id}
[GET] /api/posts/{name}
我們希望當(dāng)Posts后面的參數(shù)是int類型的時(shí)候觸發(fā)第一個(gè)Url所指向action, 參數(shù)是string類型的時(shí)候觸發(fā)第二個(gè)Url所指向的action。
在這種場(chǎng)景下,我們就需要使用路由約束。
如何添加路由約束
在.NET Core中有2種添加路由約束的方法。
- 行內(nèi)約束(Inline Constraint)
- 使用MapRoute方法帶Constraint參數(shù)的重載
當(dāng)路由引擎發(fā)現(xiàn)當(dāng)前的請(qǐng)求Url符合某個(gè)路由設(shè)置之后,就會(huì)去觸發(fā)當(dāng)前路由設(shè)置中的所有路由約束,當(dāng)所有的約束都返回true, 這個(gè)路由對(duì)應(yīng)的action就會(huì)被激活。
行內(nèi)約束(Inline Constraint)
所謂的行內(nèi)約束,即在路由Url模板中直接定義。定義的方式是在參數(shù)后面加冒號(hào),并制定約束類型。
例:
"/api/posts/{id:int}"
所以該方式既可以在MapRoute方法中使用,也可以在路由屬性中使用。
在MapRoute方法中使用
routes.MapRoute("default", "{controller=Home}/{action=Index}/{id:int}");
在路由屬性中使用
[Route("Home/Index/{id:int}")]
public string Index(int id)
{
return "I got " + id.ToString();
}
使用MapRoute方法帶Constraint參數(shù)的重載
除了行內(nèi)約束,我們還可以在Startup.cs的中通過(guò)app.UseMvc()方法來(lái)添加約束。
例:
using Microsoft.AspNetCore.Routing.Constraints;
app.UseMvc(routes =>
{
routes.MapRoute("default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index" },
new { id = new IntRouteConstraint() });
});
.NET Core內(nèi)置的路由約束
.NET Core已經(jīng)提供了很多基礎(chǔ)的路由約束,總體上分為3種類型。
- 檢查數(shù)據(jù)類型的約束
- 檢查數(shù)據(jù)的值/長(zhǎng)度/范圍的約束
- 正則表達(dá)式約束
檢查數(shù)據(jù)類型的約束
| 約束 | 行內(nèi) | Constraint類 | 說(shuō)明 |
|---|---|---|---|
| int | <code>{id:int}</code> | <code>IntRouteConstraint</code> | 只允許int32整數(shù) |
| alpha | <code>{id:alpha}</code> | <code>AlphaRouteConstraint</code> | 只能包含大小寫(xiě)字母 |
| bool | <code>{id:bool}</code> | <code>BoolRouteConstraint</code> | 只允許布爾類型 |
| datetime | <code>{id:datetime}</code> | <code>DateTimeRouteConstraint</code> | 只允許日期格式 |
| decimal | <code>{id:decimal}</code> | <code>DecimalRouteConstraint</code> | 只允許decimal類型 |
| double | <code>{id:double}</code> | <code>DoubleRouteConstraint</code> | 只允許double類型 |
| float | <code>{id:float}</code> | <code>FloatRouteConstraint</code> | 只允許float類型 |
| guid | <code>{id:guid}</code> | <code>GuidRouteConstraint</code> | 只允許guid類型 |
檢查數(shù)據(jù)的值/長(zhǎng)度/范圍的約束
| 約束 | 行內(nèi) | Constraint類 | 說(shuō)明 |
|---|---|---|---|
| length(length) | <code>{id:length(12)}</code> | <code>LengthRouteConstraint</code> | 字符串長(zhǎng)度限制 |
| maxlength(value) | <code>{id:maxlength(8)}</code> | <code>MaxLengthRouteConstraint</code> | 字符串最大長(zhǎng)度限制 |
| minlength(value) | <code>{id:minlength(4)}</code> | <code>MinLengthRouteConstraint</code> | 字符串最小長(zhǎng)度限制 |
| range(min,max) | <code>{id:range(18,120)}</code> | <code>RangeRouteConstraint</code> | 數(shù)值范圍限制 |
| min(value) | <code>{id:min(18)}</code> | <code>MinRouteConstraint</code> | 最小數(shù)值限制 |
| max(value) | <code>{id:max(120)}</code> | <code>MaxRouteConstraint</code> | 最大數(shù)值限制 |
正則表達(dá)式約束
| 約束 | 行內(nèi) | Constraint類 | 說(shuō)明 |
|---|---|---|---|
| regex(expression) | <code>{ssn:regex(^\d{{3}}-\d{{2}}-\d{{4}}$)}/<code> | <code>RegexRouteConstraint<code> | 正則表達(dá)式約束 |
自定義路由約束
和Nancy一樣,.NET Core也支持自定義路由約束,我們可以通過(guò)實(shí)現(xiàn)<code>IRouteConstraint</code>接口的<code>Match</code>方法來(lái)自定義路由約束。
我們舉一個(gè)和之前Nancy in .NET Core學(xué)習(xí)筆記 - 路由中的類似的例子。
當(dāng)前我們有一個(gè)<code>PostController</code>類,代碼如下:
[ApiController]
public class PostController : ControllerBase
{
[HttpGet]
[Route("~/api/posts/{id:int}")]
public IActionResult GetPostById(int id)
{
return Content("Coming from GetPostById");
}
[HttpGet]
[Route("~/api/posts/{name:alpha}")]
public IActionResult GetPostByName(string name)
{
return Content("Coming from GetPostByName");
}
}
這時(shí)候我們添加新的action方法GetPostByEmail, 并追加一個(gè)email約束,方法如下:
[HttpGet]
[Route("~/api/posts/{email:email}")]
public IActionResult GetPostByEmail(string email)
{
return Content("Coming from GetPostByEmail");
}
我們希望當(dāng)posts后面的參數(shù)是email格式的時(shí)候,顯示"Coming from GetPostByEmail"。
這里我們首先添加一個(gè)<code>EmailConstraint</code>類,并實(shí)現(xiàn)<code>IRouteConstraint</code>接口的<code>Match</code>方法
public class EmailConstraint : IRouteConstraint
{
public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection)
{
if (httpContext == null)
throw new ArgumentNullException(nameof(httpContext));
if (route == null)
throw new ArgumentNullException(nameof(route));
if (routeKey == null)
throw new ArgumentNullException(nameof(routeKey));
if (values == null)
throw new ArgumentNullException(nameof(values));
object routeValue;
if (values.TryGetValue(routeKey, out routeValue))
{
var parameterValueString = Convert.ToString(routeValue, CultureInfo.InvariantCulture);
return parameterValueString.Contains("@");
}
return false;
}
}
其中<code>values.TryGetValue(routeKey, out routeValue)</code>是嘗試從路由參數(shù)列表中,取出當(dāng)前參數(shù)的值, 如果當(dāng)前值中包含@, 我們就簡(jiǎn)單的認(rèn)為這個(gè)Email約束通過(guò), 并返回true。
上述代碼完成之后,我們打開(kāi)Startup.cs文件, 在<code>ConfigureServices</code>方法中, 我們將這個(gè)自定義的路由約束添加到約束列表中,并指定當(dāng)前的約束名稱是email。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.Configure<RouteOptions>(routeOptions =>
{
routeOptions.ConstraintMap.Add("email", typeof(EmailConstraint));
});
}
最后我們看一下效果, 頁(yè)面中正確顯示除了"Coming from GetPostByEmail"。
