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;
}
}