首页 新闻 会员 周边 捐助

ASP.NET Core: TemplateMatcher 匹配时会忽略类型约束(type contraint)

0
悬赏园豆:30 [已解决问题] 解决于 2024-01-08 08:16

在使用 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?

dudu的主页 dudu | 高人七级 | 园豆:30939
提问于:2024-01-06 11:46

ASP.NET Core 源码中对应的类型约束配置在 RouteOptions.cs#L100

dudu 10个月前

类型约束是通过 DefaultInlineConstraintResolver 实现的

dudu 10个月前

从 TemplateMatcher 的测试代码 TemplateMatcherTests.cs 看,通过 TemplateMatcher 本身实现不了

dudu 10个月前
< >
分享
最佳答案
0

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);
}
dudu | 高人七级 |园豆:30939 | 2024-01-07 22:16

可以通过下面的代码将 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
dudu | 园豆:30939 (高人七级) | 2024-01-08 07:01

创建 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
dudu | 园豆:30939 (高人七级) | 2024-01-08 07:34

终于实现了!

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
dudu | 园豆:30939 (高人七级) | 2024-01-08 08:07
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册