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,則說明找到對應的路由處理該請求了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM