在使用 TemplateMatcher 解决如何通过手动档实现 route template 匹配字符串的问题后发现一个新问题,如果 route template 中包含类型约束(即inline constraint),比如 "/{blogApp}/{postType}/{id:int}"
,匹配时会忽略类型约束,请问如何解决这个问题?
使用 TemplateMatcher 的实现代码如下:
var routeTemplate = TemplateParser.Parse(template);
var values = new RouteValueDictionary();
var matcher = new TemplateMatcher(routeTemplate, values);
if (matcher.TryMatch(path, values))
{
return new KeyValuePair<string, RouteValueDictionary>(template, values);
}
stackoverflow 上也有人遇到这个问题,但到目前没有回答,详见 TemplateMatcher with type constraint?
在 IntRouteConstraintsTests.cs#L22 找到了重要线索 ConstraintsTestHelper.cs#L8
public static bool TestConstraint(IRouteConstraint constraint, object value)
{
var parameterName = "fake";
var values = new RouteValueDictionary() { { parameterName, value } };
var routeDirection = RouteDirection.IncomingRequest;
return constraint.Match(httpContext: null, route: null, parameterName, values, routeDirection);
}
可以通过下面的代码将 TemplateParser.Parse 的结果中的 InlineConstraints 打印出来
var routeTemplate = "/{blogApp}/{postType}/{id:int}/{**slug}";
var parsedTemplate = TemplateParser.Parse(routeTemplate);
var values = new RouteValueDictionary();
foreach (var parameter in parsedTemplate.Parameters)
{
foreach (var inlineContraint in parameter.InlineConstraints)
{
Console.WriteLine(parameter.Name + ":" + inlineContraint.Constraint);
}
}
输出
id:int
创建 IRouteConstraint 实例,紧接楼上的代码
IServiceCollection services = new ServiceCollection();
services.AddOptions<RouteOptions>();
using ServiceProvider sp = services.BuildServiceProvider();
var routeOptions = sp.GetRequiredService<IOptions<RouteOptions>>();
var constraintResolver = new DefaultInlineConstraintResolver(routeOptions, sp);
var routeContraint = constraintResolver.ResolveConstraint(inlineContraint.Constraint);
Console.WriteLine(routeContraint);
输出
Microsoft.AspNetCore.Routing.Constraints.IntRouteConstraint
终于实现了!
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Template;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
var routeTemplates = new[]
{
"/{blogApp}/{postType}/{id:int}/{**slug}",
"/{blogApp}/{postType}/{idOrSlug}.html"
};
var url = "https://www.cnblogs.com/cmt/p/17520031.html";
var urlPath = new Uri(url).AbsolutePath;
IServiceCollection services = new ServiceCollection();
services.AddOptions<RouteOptions>();
using ServiceProvider sp = services.BuildServiceProvider();
var routeOptions = sp.GetRequiredService<IOptions<RouteOptions>>();
var constraintResolver = new DefaultInlineConstraintResolver(routeOptions, sp);
foreach (string routeTemplate in routeTemplates)
{
Console.WriteLine("routeTemplate: " + routeTemplate);
var parsedTemplate = TemplateParser.Parse(routeTemplate);
var values = new RouteValueDictionary();
var matcher = new TemplateMatcher(parsedTemplate, values);
if (matcher.TryMatch(urlPath, values))
{
Console.WriteLine("TemplateMatcher matched: true");
foreach (var item in values)
{
Console.WriteLine("{0}: {1}", item.Key, item.Value);
}
foreach (var parameter in parsedTemplate.Parameters)
{
foreach (var inlineConstraint in parameter.InlineConstraints)
{
Console.WriteLine(parameter.Name + ":" + inlineConstraint.Constraint);
var routeConstraint = constraintResolver.ResolveConstraint(inlineConstraint.Constraint);
var routeDirection = RouteDirection.IncomingRequest;
bool matched = routeConstraint!.Match(httpContext: null, route: null, parameter.Name!, values, routeDirection);
Console.WriteLine($"{routeConstraint.GetType().Name} matched: {matched}");
Console.WriteLine();
}
}
}
}
输出
routeTemplate: /{blogApp}/{postType}/{id:int}/{**slug}
TemplateMatcher matched: true
blogApp: cmt
postType: p
id: 17520031.html
slug:
id:int
IntRouteConstraint matched: False
routeTemplate: /{blogApp}/{postType}/{idOrSlug}.html
TemplateMatcher matched: true
blogApp: cmt
postType: p
idOrSlug: 17520031
ASP.NET Core 源码中对应的类型约束配置在 RouteOptions.cs#L100
– dudu 11个月前类型约束是通过 DefaultInlineConstraintResolver 实现的
– dudu 11个月前参考博文:Matching route templates manually in ASP.NET Core
– dudu 11个月前从 TemplateMatcher 的测试代码 TemplateMatcherTests.cs 看,通过 TemplateMatcher 本身实现不了
– dudu 11个月前