asp.net core 3.1 Routing(一)
先看下IApplicationBuilder的扩展方法
public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, IRouter router) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (router == null) { throw new ArgumentNullException(nameof(router)); } if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } return builder.UseMiddleware<RouterMiddleware>(router); } /// <summary> /// Adds a <see cref="RouterMiddleware"/> middleware to the specified <see cref="IApplicationBuilder"/> /// with the <see cref="IRouter"/> built from configured <see cref="IRouteBuilder"/>. /// </summary> /// <param name="builder">The <see cref="IApplicationBuilder"/> to add the middleware to.</param> /// <param name="action">An <see cref="Action{IRouteBuilder}"/> to configure the provided <see cref="IRouteBuilder"/>.</param> /// <returns>A reference to this instance after the operation has completed.</returns> public static IApplicationBuilder UseRouter(this IApplicationBuilder builder, Action<IRouteBuilder> action) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (action == null) { throw new ArgumentNullException(nameof(action)); } if (builder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } var routeBuilder = new RouteBuilder(builder); action(routeBuilder); return builder.UseRouter(routeBuilder.Build()); }
当调用UseRouter的时候,其实就注册引用了RouterMiddleware中间件
public class RouterMiddleware { private readonly ILogger _logger; private readonly RequestDelegate _next; private readonly IRouter _router; public RouterMiddleware( RequestDelegate next, ILoggerFactory loggerFactory, IRouter router) { _next = next; _router = router; _logger = loggerFactory.CreateLogger<RouterMiddleware>(); } public async Task Invoke(HttpContext httpContext) { var context = new RouteContext(httpContext); context.RouteData.Routers.Add(_router); await _router.RouteAsync(context); if (context.Handler == null) { _logger.RequestNotMatched(); await _next.Invoke(httpContext); } else { var routingFeature = new RoutingFeature() { RouteData = context.RouteData }; // Set the RouteValues on the current request, this is to keep the IRouteValuesFeature inline with the IRoutingFeature httpContext.Request.RouteValues = context.RouteData.Values; httpContext.Features.Set<IRoutingFeature>(routingFeature); await context.Handler(context.HttpContext); } } }
RouterMiddleware中间件实现很简单,通过构造函数传进来一个IRouter对象
调用IRouter的RouteAsync方法,判断Handler属性是否为null,如果是则调用下一个中间件,如果不为null,则调用Handler处理请求
public interface IRouter { Task RouteAsync(RouteContext context); VirtualPathData GetVirtualPath(VirtualPathContext context); }
RouteBase是一个抽象类
public RouteBase( string template, string name, IInlineConstraintResolver constraintResolver, RouteValueDictionary defaults, IDictionary<string, object> constraints, RouteValueDictionary dataTokens) { if (constraintResolver == null) { throw new ArgumentNullException(nameof(constraintResolver)); } template = template ?? string.Empty; Name = name; ConstraintResolver = constraintResolver; DataTokens = dataTokens ?? new RouteValueDictionary(); try { // Data we parse from the template will be used to fill in the rest of the constraints or // defaults. The parser will throw for invalid routes. ParsedTemplate = TemplateParser.Parse(template); Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints); Defaults = GetDefaults(ParsedTemplate, defaults); } catch (Exception exception) { throw new RouteCreationException(Resources.FormatTemplateRoute_Exception(name, template), exception); } }
看下其构造函数
先对分析传进来的路由模板,构建RouteTemplate对象,具体如何分析在RoutePatternParser类中实现
再获取路由对应的路由约束
最后设置路由的默认值
public virtual Task RouteAsync(RouteContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } EnsureMatcher(); EnsureLoggers(context.HttpContext); var requestPath = context.HttpContext.Request.Path; if (!_matcher.TryMatch(requestPath, context.RouteData.Values)) { // If we got back a null value set, that means the URI did not match return Task.CompletedTask; } // Perf: Avoid accessing dictionaries if you don't need to write to them, these dictionaries are all // created lazily. if (DataTokens.Count > 0) { MergeValues(context.RouteData.DataTokens, DataTokens); } if (!RouteConstraintMatcher.Match( Constraints, context.RouteData.Values, context.HttpContext, this, RouteDirection.IncomingRequest, _constraintLogger)) { return Task.CompletedTask; } _logger.RequestMatchedRoute(Name, ParsedTemplate.TemplateText); return OnRouteMatched(context); }
RouterBase的RouteAsync方法中,先调用TemplateMatcher类的TryMatch方法判断路由是否匹配
再判断路由约束是否通过
以上条件都通过后,再调用其抽象方法OnRouteMatched
再看下Route类,其构造函数接收IRouter的参数,当调用OnRouteMatched方法时,其实是调用了参数IRouter的RouteAsync方法
再看下RouteBuilder类
public class RouteBuilder : IRouteBuilder { public RouteBuilder(IApplicationBuilder applicationBuilder) : this(applicationBuilder, defaultHandler: null) { } public RouteBuilder(IApplicationBuilder applicationBuilder, IRouter defaultHandler) { if (applicationBuilder == null) { throw new ArgumentNullException(nameof(applicationBuilder)); } if (applicationBuilder.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } ApplicationBuilder = applicationBuilder; DefaultHandler = defaultHandler; ServiceProvider = applicationBuilder.ApplicationServices; Routes = new List<IRouter>(); } public IApplicationBuilder ApplicationBuilder { get; } public IRouter DefaultHandler { get; set; } public IServiceProvider ServiceProvider { get; } public IList<IRouter> Routes { get; } public IRouter Build() { var routeCollection = new RouteCollection(); foreach (var route in Routes) { routeCollection.Add(route); } return routeCollection; } }
RouteBuilder中有Routes集合,我们可以添加自己的路由
Build()方法创建RouteCollection集合,把Routes中的路由添加到RouteCollection中
public class RouteCollection : IRouteCollection { private readonly static char[] UrlQueryDelimiters = new char[] { '?', '#' }; private readonly List<IRouter> _routes = new List<IRouter>(); private readonly List<IRouter> _unnamedRoutes = new List<IRouter>(); private readonly Dictionary<string, INamedRouter> _namedRoutes = new Dictionary<string, INamedRouter>(StringComparer.OrdinalIgnoreCase); private RouteOptions _options; public IRouter this[int index] { get { return _routes[index]; } } public int Count { get { return _routes.Count; } } public void Add(IRouter router) { if (router == null) { throw new ArgumentNullException(nameof(router)); } var namedRouter = router as INamedRouter; if (namedRouter != null) { if (!string.IsNullOrEmpty(namedRouter.Name)) { _namedRoutes.Add(namedRouter.Name, namedRouter); } } else { _unnamedRoutes.Add(router); } _routes.Add(router); } public async virtual Task RouteAsync(RouteContext context) { // Perf: We want to avoid allocating a new RouteData for each route we need to process. // We can do this by snapshotting the state at the beginning and then restoring it // for each router we execute. var snapshot = context.RouteData.PushState(null, values: null, dataTokens: null); for (var i = 0; i < Count; i++) { var route = this[i]; context.RouteData.Routers.Add(route); try { await route.RouteAsync(context); if (context.Handler != null) { break; } } finally { if (context.Handler == null) { snapshot.Restore(); } } } } public virtual VirtualPathData GetVirtualPath(VirtualPathContext context) { EnsureOptions(context.HttpContext); if (!string.IsNullOrEmpty(context.RouteName)) { VirtualPathData namedRoutePathData = null; if (_namedRoutes.TryGetValue(context.RouteName, out var matchedNamedRoute)) { namedRoutePathData = matchedNamedRoute.GetVirtualPath(context); } var pathData = GetVirtualPath(context, _unnamedRoutes); // If the named route and one of the unnamed routes also matches, then we have an ambiguity. if (namedRoutePathData != null && pathData != null) { var message = Resources.FormatNamedRoutes_AmbiguousRoutesFound(context.RouteName); throw new InvalidOperationException(message); } return NormalizeVirtualPath(namedRoutePathData ?? pathData); } else { return NormalizeVirtualPath(GetVirtualPath(context, _routes)); } } private VirtualPathData GetVirtualPath(VirtualPathContext context, List<IRouter> routes) { for (var i = 0; i < routes.Count; i++) { var route = routes[i]; var pathData = route.GetVirtualPath(context); if (pathData != null) { return pathData; } } return null; } private VirtualPathData NormalizeVirtualPath(VirtualPathData pathData) { if (pathData == null) { return pathData; } var url = pathData.VirtualPath; if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash)) { var indexOfSeparator = url.IndexOfAny(UrlQueryDelimiters); var urlWithoutQueryString = url; var queryString = string.Empty; if (indexOfSeparator != -1) { urlWithoutQueryString = url.Substring(0, indexOfSeparator); queryString = url.Substring(indexOfSeparator); } if (_options.LowercaseUrls) { urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant(); } if (_options.LowercaseUrls && _options.LowercaseQueryStrings) { queryString = queryString.ToLowerInvariant(); } if (_options.AppendTrailingSlash && !urlWithoutQueryString.EndsWith("/", StringComparison.Ordinal)) { urlWithoutQueryString += "/"; } // queryString will contain the delimiter ? or # as the first character, so it's safe to append. url = urlWithoutQueryString + queryString; return new VirtualPathData(pathData.Router, url, pathData.DataTokens); } return pathData; } private void EnsureOptions(HttpContext context) { if (_options == null) { _options = context.RequestServices.GetRequiredService<IOptions<RouteOptions>>().Value; } } }
RouteCollection也实现了IRouter接口
在执行RouteAsync方法时,其会遍历内部的各个路由,并实现每个路由的RouteAsync方法,再判断RouteContext的Handler属性,如果Handler属性不为null,则说明找到对应的路由处理该请求了