ASP.NET Core 路由手动挡:借助路由从 url 取值

问题场景

基于 ASP.NET Core 路由模板(route template)从 url 字符串取值,路由模板是 "/{blogApp}/{postType}/{idOrSlug}.html",需要取值的博文 url 地址示例 https://www.cnblogs.com/cmt/p/14408628.html

实现代码

参考 ASP.NET Core 源码中的测试代码 RouteTest.cs#L56,通过 Route.RouteAsync() 方法实现了,RouteTest 中好几处用了 Mock,这里实现时没有使用 Mock。

实现代码如下

class Program
{
    static async Task Main(string[] args)
    {
        var requestPath = "/cmt/p/14408628.html";

        var routeTemplate = "/{blogApp}/{postType}/{idOrSlug}.html";
        var routeHandler = new RouteHandler((context) => Task.CompletedTask);

        IServiceCollection services = new ServiceCollection();
        services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
        services.AddOptions<RouteOptions>();
        var sp = services.BuildServiceProvider();
        var routeOptions = sp.GetRequiredService<IOptions<RouteOptions>>();
        var inlineConstraintResolver = new DefaultInlineConstraintResolver(routeOptions, sp);

        var route = new Route(
            routeHandler,
            routeTemplate,
            defaults: null,
            constraints: null
            dataTokens: null,
            inlineConstraintResolver: inlineConstraintResolver);

        var httpContext = new DefaultHttpContext();
        httpContext.Request.Path = new PathString(requestPath);
        httpContext.RequestServices = sp;
        var routeContext = new RouteContext(httpContext);

        await route.RouteAsync(routeContext);

        Console.WriteLine("blogApp: " + routeContext.RouteData.Values["blogApp"]);
        Console.WriteLine("postType: " + routeContext.RouteData.Values["postType"]);
        Console.WriteLine("idOrSlug: " + routeContext.RouteData.Values["idOrSlug"]);
    }
}

输出结果如下

blogApp: cmt
postType: p
idOrSlug: 14408628

2024年1月4日更新:今天在 stackoverflow 上找到了非常简单的解决方法,重构后的实现代码如下

namespace Cnblogs.Web
{
    public static class RouteUtility
    {
        public static KeyValuePair<string, RouteValueDictionary> GetRouteValuesFromUrl(string url, params string[] routeTemplates)
        {
            var path = new Uri(url).AbsolutePath;
            foreach (string template in routeTemplates)
            {
                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);
                }
            }

            return default;
        }
    }
}

相关博问 https://q.cnblogs.com/q/146271/

2024年1月6日更新:这两种方法都存在路由模板中类型约束(type contraint)不起作用的问题,详见博问 https://q.cnblogs.com/q/146281/

2024年1月8日更新:采用下面的实现代码解决了问题

namespace Cnblogs.Web;

public static class RouteUtility
{
    public static KeyValuePair<string, RouteValueDictionary> GetRouteValues(string url, params string[] routeTemplates)
    {
        var path = new Uri(url).AbsolutePath;

        IServiceCollection services = new ServiceCollection();
        services.AddOptions<RouteOptions>();
        using var sp = services.BuildServiceProvider();
        var routeOptions = sp.GetRequiredService<IOptions<RouteOptions>>();
        var constraintResolver = new DefaultInlineConstraintResolver(routeOptions, sp);

        foreach (string routeTemplate in routeTemplates)
        {
            bool isSkipped = false;
            var parsedTemplate = TemplateParser.Parse(routeTemplate);
            var values = new RouteValueDictionary();
            var matcher = new TemplateMatcher(parsedTemplate, values);
            if (matcher.TryMatch(path, values))
            {
                foreach (var parameter in parsedTemplate.Parameters)
                {
                    foreach (var inlineConstraint in parameter.InlineConstraints)
                    {
                        var routeConstraint = constraintResolver.ResolveConstraint(inlineConstraint.Constraint);
                        var routeDirection = RouteDirection.IncomingRequest;
                        if (routeConstraint != null && !string.IsNullOrEmpty(parameter.Name))
                        {
                            bool isMatch = routeConstraint.Match(httpContext: null, route: null, parameter.Name, values, routeDirection);
                            if (!isMatch)
                            {
                                isSkipped = true;
                                continue;
                            }
                        }
                    }
                    if (isSkipped) continue;
                }

                if (isSkipped) continue;
                return new KeyValuePair<string, RouteValueDictionary>(routeTemplate, values);
            }
        }

        return default;
    }
}
posted @ 2021-03-01 16:10  dudu  阅读(448)  评论(1编辑  收藏  举报