zoukankan      html  css  js  c++  java
  • asp.net core mvc 3.1 源码分析(一)

    我们先看下IApplicationBuilder接口的扩展方法UseMvc

    public static IApplicationBuilder UseMvc(
                this IApplicationBuilder app,
                Action<IRouteBuilder> configureRoutes)
            {
                if (app == null)
                {
                    throw new ArgumentNullException(nameof(app));
                }
    
                if (configureRoutes == null)
                {
                    throw new ArgumentNullException(nameof(configureRoutes));
                }
    
                VerifyMvcIsRegistered(app);
    
                var options = app.ApplicationServices.GetRequiredService<IOptions<MvcOptions>>();
    
                if (options.Value.EnableEndpointRouting)
                {
                    var message =
                        "Endpoint Routing does not support 'IApplicationBuilder.UseMvc(...)'. To use " +
                        "'IApplicationBuilder.UseMvc' set 'MvcOptions.EnableEndpointRouting = false' inside " +
                        "'ConfigureServices(...).";
                    throw new InvalidOperationException(message);
                }
    
                var routes = new RouteBuilder(app)
                {
                    DefaultHandler = app.ApplicationServices.GetRequiredService<MvcRouteHandler>(),
                };
    
                configureRoutes(routes);
    
                routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices));
    
                return app.UseRouter(routes.Build());
            }

    先判断是否注册了Mvc的相关服务

    再判断MvcOptions的属性EnableEndpointRouting,如果要用UseMvc,则需设置该属性为False

    接着创建RouteBuilder对象,其DefaultHandler属性为MvcRouteHandler,MvcRouteHandler会进入到mvc的处理流程中

    在RouteBuilder的Routers属性插入AttributeRoute,用来处理特性路由,其对应的Handler为MvcAttributeRouteHandler

    当我们注册路由后,默认会由MvcRouteHandler和MvcAttributeRouteHandler这两个Hander来出路mvc逻辑

    我们看下平时是怎么注册路由的

    public static IRouteBuilder MapRoute(
                this IRouteBuilder routeBuilder,
                string name,
                string template,
                object defaults,
                object constraints,
                object dataTokens)
            {
                if (routeBuilder.DefaultHandler == null)
                {
                    throw new RouteCreationException(Resources.FormatDefaultHandler_MustBeSet(nameof(IRouteBuilder)));
                }
    
                routeBuilder.Routes.Add(new Route(
                    routeBuilder.DefaultHandler,
                    name,
                    template,
                    new RouteValueDictionary(defaults),
                    new RouteValueDictionary(constraints),
                    new RouteValueDictionary(dataTokens),
                    CreateInlineConstraintResolver(routeBuilder.ServiceProvider)));
    
                return routeBuilder;
            }

    我们注册的route默认由IRouteBuilder的DefaultHandler来处理

    MvcRouteHandler

    internal class MvcRouteHandler : IRouter
        {
            private readonly IActionInvokerFactory _actionInvokerFactory;
            private readonly IActionSelector _actionSelector;
            private readonly ILogger _logger;
            private readonly DiagnosticListener _diagnosticListener;
    
            public MvcRouteHandler(
                IActionInvokerFactory actionInvokerFactory,
                IActionSelector actionSelector,
                DiagnosticListener diagnosticListener,
                ILoggerFactory loggerFactory)
            {
                _actionInvokerFactory = actionInvokerFactory;
                _actionSelector = actionSelector;
                _diagnosticListener = diagnosticListener;
                _logger = loggerFactory.CreateLogger<MvcRouteHandler>();
            }
    
            public VirtualPathData GetVirtualPath(VirtualPathContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                // We return null here because we're not responsible for generating the url, the route is.
                return null;
            }
    
            public Task RouteAsync(RouteContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                var candidates = _actionSelector.SelectCandidates(context);
                if (candidates == null || candidates.Count == 0)
                {
                    _logger.NoActionsMatched(context.RouteData.Values);
                    return Task.CompletedTask;
                }
    
                var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates);
                if (actionDescriptor == null)
                {
                    _logger.NoActionsMatched(context.RouteData.Values);
                    return Task.CompletedTask;
                }
    
                context.Handler = (c) =>
                {
                    var routeData = c.GetRouteData();
    
                    var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
                    var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
                    if (invoker == null)
                    {
                        throw new InvalidOperationException(
                            Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
                                actionDescriptor.DisplayName));
                    }
    
                    return invoker.InvokeAsync();
                };
    
                return Task.CompletedTask;
            }
        }

    MvcRouteHandler通过IActionSelector找到合适ActionDescriptor,即找到对应Controller的Action方法

    然后调用IActionInvokerFactory的CreateInvoker构建IActionInvoker对象,

    最后调用IActionInvoker对象的InvokeAsync执行Action

    MvcAttributeRouteHandler

    internal class MvcAttributeRouteHandler : IRouter
        {
            private readonly IActionInvokerFactory _actionInvokerFactory;
            private readonly IActionSelector _actionSelector;
            private readonly ILogger _logger;
            private readonly DiagnosticListener _diagnosticListener;
    
            public MvcAttributeRouteHandler(
                IActionInvokerFactory actionInvokerFactory,
                IActionSelector actionSelector,
                DiagnosticListener diagnosticListener,
                ILoggerFactory loggerFactory)
            {
                _actionInvokerFactory = actionInvokerFactory;
                _actionSelector = actionSelector;
                _diagnosticListener = diagnosticListener;
                _logger = loggerFactory.CreateLogger<MvcAttributeRouteHandler>();
            }
    
            public ActionDescriptor[] Actions { get; set; }
    
            public VirtualPathData GetVirtualPath(VirtualPathContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                // We return null here because we're not responsible for generating the url, the route is.
                return null;
            }
    
            public Task RouteAsync(RouteContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                if (Actions == null)
                {
                    var message = Resources.FormatPropertyOfTypeCannotBeNull(
                        nameof(Actions),
                        nameof(MvcAttributeRouteHandler));
                    throw new InvalidOperationException(message);
                }
    
                var actionDescriptor = _actionSelector.SelectBestCandidate(context, Actions);
                if (actionDescriptor == null)
                {
                    _logger.NoActionsMatched(context.RouteData.Values);
                    return Task.CompletedTask;
                }
    
                foreach (var kvp in actionDescriptor.RouteValues)
                {
                    if (!string.IsNullOrEmpty(kvp.Value))
                    {
                        context.RouteData.Values[kvp.Key] = kvp.Value;
                    }
                }
    
                context.Handler = (c) =>
                {
                    var routeData = c.GetRouteData();
    
                    var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor);
                    var invoker = _actionInvokerFactory.CreateInvoker(actionContext);
                    if (invoker == null)
                    {
                        throw new InvalidOperationException(
                            Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(
                                actionDescriptor.DisplayName));
                    }
    
                    return invoker.InvokeAsync();
                };
    
                return Task.CompletedTask;
            }
        }

    MvcAttributeRouteHandler和MvcRouteHandler处理逻辑差不多一样

    主要看下AttributeRoute类如何处理特性路由

    internal class AttributeRoute : IRouter
        {
            private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider;
            private readonly IServiceProvider _services;
            private readonly Func<ActionDescriptor[], IRouter> _handlerFactory;
    
            private TreeRouter _router;
    
            public AttributeRoute(
                IActionDescriptorCollectionProvider actionDescriptorCollectionProvider,
                IServiceProvider services,
                Func<ActionDescriptor[], IRouter> handlerFactory)
            {
                if (actionDescriptorCollectionProvider == null)
                {
                    throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider));
                }
    
                if (services == null)
                {
                    throw new ArgumentNullException(nameof(services));
                }
    
                if (handlerFactory == null)
                {
                    throw new ArgumentNullException(nameof(handlerFactory));
                }
    
                _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider;
                _services = services;
                _handlerFactory = handlerFactory;
            }
    
            /// <inheritdoc />
            public VirtualPathData GetVirtualPath(VirtualPathContext context)
            {
                var router = GetTreeRouter();
                return router.GetVirtualPath(context);
            }
    
            /// <inheritdoc />
            public Task RouteAsync(RouteContext context)
            {
                var router = GetTreeRouter();
                return router.RouteAsync(context);
            }
    
            private TreeRouter GetTreeRouter()
            {
                var actions = _actionDescriptorCollectionProvider.ActionDescriptors;
    
                // This is a safe-race. We'll never set router back to null after initializing
                // it on startup.
                if (_router == null || _router.Version != actions.Version)
                {
                    var builder = _services.GetRequiredService<TreeRouteBuilder>();
                    AddEntries(builder, actions);
                    _router = builder.Build(actions.Version);
                }
    
                return _router;
            }
    
            // internal for testing
            internal void AddEntries(TreeRouteBuilder builder, ActionDescriptorCollection actions)
            {
                var routeInfos = GetRouteInfos(actions.Items);
    
                // We're creating one TreeRouteLinkGenerationEntry per action. This allows us to match the intended
                // action by expected route values, and then use the TemplateBinder to generate the link.
                foreach (var routeInfo in routeInfos)
                {
                    if (routeInfo.SuppressLinkGeneration)
                    {
                        continue;
                    }
    
                    var defaults = new RouteValueDictionary();
                    foreach (var kvp in routeInfo.ActionDescriptor.RouteValues)
                    {
                        defaults.Add(kvp.Key, kvp.Value);
                    }
    
                    try
                    {
                        // We use the `NullRouter` as the route handler because we don't need to do anything for link
                        // generations. The TreeRouter does it all for us.
                        builder.MapOutbound(
                            NullRouter.Instance,
                            routeInfo.RouteTemplate,
                            defaults,
                            routeInfo.RouteName,
                            routeInfo.Order);
                    }
                    catch (RouteCreationException routeCreationException)
                    {
                        throw new RouteCreationException(
                            "An error occurred while adding a route to the route builder. " +
                            $"Route name '{routeInfo.RouteName}' and template '{routeInfo.RouteTemplate.TemplateText}'.",
                            routeCreationException);
                    }
                }
    
                // We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of
                // groups. It's guaranteed that all members of the group have the same template and precedence,
                // so we only need to hang on to a single instance of the RouteInfo for each group.
                var groups = GetInboundRouteGroups(routeInfos);
                foreach (var group in groups)
                {
                    var handler = _handlerFactory(group.ToArray());
    
                    // Note that because we only support 'inline' defaults, each routeInfo group also has the same
                    // set of defaults.
                    //
                    // We then inject the route group as a default for the matcher so it gets passed back to MVC
                    // for use in action selection.
                    builder.MapInbound(
                        handler,
                        group.Key.RouteTemplate,
                        group.Key.RouteName,
                        group.Key.Order);
                }
            }
    
            private static IEnumerable<IGrouping<RouteInfo, ActionDescriptor>> GetInboundRouteGroups(List<RouteInfo> routeInfos)
            {
                return routeInfos
                    .Where(routeInfo => !routeInfo.SuppressPathMatching)
                    .GroupBy(r => r, r => r.ActionDescriptor, RouteInfoEqualityComparer.Instance);
            }
    
            private static List<RouteInfo> GetRouteInfos(IReadOnlyList<ActionDescriptor> actions)
            {
                var routeInfos = new List<RouteInfo>();
                var errors = new List<RouteInfo>();
    
                // This keeps a cache of 'Template' objects. It's a fairly common case that multiple actions
                // will use the same route template string; thus, the `Template` object can be shared.
                //
                // For a relatively simple route template, the `Template` object will hold about 500 bytes
                // of memory, so sharing is worthwhile.
                var templateCache = new Dictionary<string, RouteTemplate>(StringComparer.OrdinalIgnoreCase);
    
                var attributeRoutedActions = actions.Where(a => a.AttributeRouteInfo?.Template != null);
                foreach (var action in attributeRoutedActions)
                {
                    var routeInfo = GetRouteInfo(templateCache, action);
                    if (routeInfo.ErrorMessage == null)
                    {
                        routeInfos.Add(routeInfo);
                    }
                    else
                    {
                        errors.Add(routeInfo);
                    }
                }
    
                if (errors.Count > 0)
                {
                    var allErrors = string.Join(
                        Environment.NewLine + Environment.NewLine,
                        errors.Select(
                            e => Resources.FormatAttributeRoute_IndividualErrorMessage(
                                e.ActionDescriptor.DisplayName,
                                Environment.NewLine,
                                e.ErrorMessage)));
    
                    var message = Resources.FormatAttributeRoute_AggregateErrorMessage(Environment.NewLine, allErrors);
                    throw new RouteCreationException(message);
                }
    
                return routeInfos;
            }
    
            private static RouteInfo GetRouteInfo(
                Dictionary<string, RouteTemplate> templateCache,
                ActionDescriptor action)
            {
                var routeInfo = new RouteInfo()
                {
                    ActionDescriptor = action,
                };
    
                try
                {
                    if (!templateCache.TryGetValue(action.AttributeRouteInfo.Template, out var parsedTemplate))
                    {
                        // Parsing with throw if the template is invalid.
                        parsedTemplate = TemplateParser.Parse(action.AttributeRouteInfo.Template);
                        templateCache.Add(action.AttributeRouteInfo.Template, parsedTemplate);
                    }
    
                    routeInfo.RouteTemplate = parsedTemplate;
                    routeInfo.SuppressPathMatching = action.AttributeRouteInfo.SuppressPathMatching;
                    routeInfo.SuppressLinkGeneration = action.AttributeRouteInfo.SuppressLinkGeneration;
                }
                catch (Exception ex)
                {
                    routeInfo.ErrorMessage = ex.Message;
                    return routeInfo;
                }
    
                foreach (var kvp in action.RouteValues)
                {
                    foreach (var parameter in routeInfo.RouteTemplate.Parameters)
                    {
                        if (string.Equals(kvp.Key, parameter.Name, StringComparison.OrdinalIgnoreCase))
                        {
                            routeInfo.ErrorMessage = Resources.FormatAttributeRoute_CannotContainParameter(
                                routeInfo.RouteTemplate.TemplateText,
                                kvp.Key,
                                kvp.Value);
    
                            return routeInfo;
                        }
                    }
                }
    
                routeInfo.Order = action.AttributeRouteInfo.Order;
                routeInfo.RouteName = action.AttributeRouteInfo.Name;
    
                return routeInfo;
            }
    
            private class RouteInfo
            {
                public ActionDescriptor ActionDescriptor { get; set; }
    
                public string ErrorMessage { get; set; }
    
                public int Order { get; set; }
    
                public string RouteName { get; set; }
    
                public RouteTemplate RouteTemplate { get; set; }
    
                public bool SuppressPathMatching { get; set; }
    
                public bool SuppressLinkGeneration { get; set; }
            }
    
            private class RouteInfoEqualityComparer : IEqualityComparer<RouteInfo>
            {
                public static readonly RouteInfoEqualityComparer Instance = new RouteInfoEqualityComparer();
    
                public bool Equals(RouteInfo x, RouteInfo y)
                {
                    if (x == null && y == null)
                    {
                        return true;
                    }
                    else if (x == null ^ y == null)
                    {
                        return false;
                    }
                    else if (x.Order != y.Order)
                    {
                        return false;
                    }
                    else
                    {
                        return string.Equals(
                            x.RouteTemplate.TemplateText,
                            y.RouteTemplate.TemplateText,
                            StringComparison.OrdinalIgnoreCase);
                    }
                }
    
                public int GetHashCode(RouteInfo obj)
                {
                    if (obj == null)
                    {
                        return 0;
                    }
    
                    var hash = new HashCodeCombiner();
                    hash.Add(obj.Order);
                    hash.Add(obj.RouteTemplate.TemplateText, StringComparer.OrdinalIgnoreCase);
                    return hash;
                }
            }
    
            // Used only to hook up link generation, and it doesn't need to do anything.
            private class NullRouter : IRouter
            {
                public static readonly NullRouter Instance = new NullRouter();
    
                public VirtualPathData GetVirtualPath(VirtualPathContext context)
                {
                    return null;
                }
    
                public Task RouteAsync(RouteContext context)
                {
                    throw new NotImplementedException();
                }
            }
        }

    首先创建TreeRouteBuilder对象,再调用其Build方法创建TreeRouter

    public class TreeRouteBuilder
        {
            private readonly ILogger _logger;
            private readonly ILogger _constraintLogger;
            private readonly UrlEncoder _urlEncoder;
            private readonly ObjectPool<UriBuildingContext> _objectPool;
            private readonly IInlineConstraintResolver _constraintResolver;
    
            /// <summary>
            /// Initializes a new instance of <see cref="TreeRouteBuilder"/>.
            /// </summary>
            /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
            /// <param name="objectPool">The <see cref="ObjectPool{UrlBuildingContext}"/>.</param>
            /// <param name="constraintResolver">The <see cref="IInlineConstraintResolver"/>.</param>
            internal TreeRouteBuilder(
                ILoggerFactory loggerFactory,
                ObjectPool<UriBuildingContext> objectPool,
                IInlineConstraintResolver constraintResolver)
            {
                if (loggerFactory == null)
                {
                    throw new ArgumentNullException(nameof(loggerFactory));
                }
    
                if (objectPool == null)
                {
                    throw new ArgumentNullException(nameof(objectPool));
                }
    
                if (constraintResolver == null)
                {
                    throw new ArgumentNullException(nameof(constraintResolver));
                }
    
                _urlEncoder = UrlEncoder.Default;
                _objectPool = objectPool;
                _constraintResolver = constraintResolver;
    
                _logger = loggerFactory.CreateLogger<TreeRouter>();
                _constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
            }
    
            /// <summary>
            /// Adds a new inbound route to the <see cref="TreeRouter"/>.
            /// </summary>
            /// <param name="handler">The <see cref="IRouter"/> for handling the route.</param>
            /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
            /// <param name="routeName">The route name.</param>
            /// <param name="order">The route order.</param>
            /// <returns>The <see cref="InboundRouteEntry"/>.</returns>
            public InboundRouteEntry MapInbound(
                IRouter handler,
                RouteTemplate routeTemplate,
                string routeName,
                int order)
            {
                if (handler == null)
                {
                    throw new ArgumentNullException(nameof(handler));
                }
    
                if (routeTemplate == null)
                {
                    throw new ArgumentNullException(nameof(routeTemplate));
                }
    
                var entry = new InboundRouteEntry()
                {
                    Handler = handler,
                    Order = order,
                    Precedence = RoutePrecedence.ComputeInbound(routeTemplate),
                    RouteName = routeName,
                    RouteTemplate = routeTemplate,
                };
    
                var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
                foreach (var parameter in routeTemplate.Parameters)
                {
                    if (parameter.InlineConstraints != null)
                    {
                        if (parameter.IsOptional)
                        {
                            constraintBuilder.SetOptional(parameter.Name);
                        }
    
                        foreach (var constraint in parameter.InlineConstraints)
                        {
                            constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
                        }
                    }
                }
    
                entry.Constraints = constraintBuilder.Build();
    
                entry.Defaults = new RouteValueDictionary();
                foreach (var parameter in entry.RouteTemplate.Parameters)
                {
                    if (parameter.DefaultValue != null)
                    {
                        entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
                    }
                }
    
                InboundEntries.Add(entry);
                return entry;
            }
    
            /// <summary>
            /// Adds a new outbound route to the <see cref="TreeRouter"/>.
            /// </summary>
            /// <param name="handler">The <see cref="IRouter"/> for handling the link generation.</param>
            /// <param name="routeTemplate">The <see cref="RouteTemplate"/> of the route.</param>
            /// <param name="requiredLinkValues">The <see cref="RouteValueDictionary"/> containing the route values.</param>
            /// <param name="routeName">The route name.</param>
            /// <param name="order">The route order.</param>
            /// <returns>The <see cref="OutboundRouteEntry"/>.</returns>
            public OutboundRouteEntry MapOutbound(
                IRouter handler,
                RouteTemplate routeTemplate,
                RouteValueDictionary requiredLinkValues,
                string routeName,
                int order)
            {
                if (handler == null)
                {
                    throw new ArgumentNullException(nameof(handler));
                }
    
                if (routeTemplate == null)
                {
                    throw new ArgumentNullException(nameof(routeTemplate));
                }
    
                if (requiredLinkValues == null)
                {
                    throw new ArgumentNullException(nameof(requiredLinkValues));
                }
    
                var entry = new OutboundRouteEntry()
                {
                    Handler = handler,
                    Order = order,
                    Precedence = RoutePrecedence.ComputeOutbound(routeTemplate),
                    RequiredLinkValues = requiredLinkValues,
                    RouteName = routeName,
                    RouteTemplate = routeTemplate,
                };
    
                var constraintBuilder = new RouteConstraintBuilder(_constraintResolver, routeTemplate.TemplateText);
                foreach (var parameter in routeTemplate.Parameters)
                {
                    if (parameter.InlineConstraints != null)
                    {
                        if (parameter.IsOptional)
                        {
                            constraintBuilder.SetOptional(parameter.Name);
                        }
    
                        foreach (var constraint in parameter.InlineConstraints)
                        {
                            constraintBuilder.AddResolvedConstraint(parameter.Name, constraint.Constraint);
                        }
                    }
                }
    
                entry.Constraints = constraintBuilder.Build();
    
                entry.Defaults = new RouteValueDictionary();
                foreach (var parameter in entry.RouteTemplate.Parameters)
                {
                    if (parameter.DefaultValue != null)
                    {
                        entry.Defaults.Add(parameter.Name, parameter.DefaultValue);
                    }
                }
    
                OutboundEntries.Add(entry);
                return entry;
            }
    
            /// <summary>
            /// Gets the list of <see cref="InboundRouteEntry"/>.
            /// </summary>
            public IList<InboundRouteEntry> InboundEntries { get; } = new List<InboundRouteEntry>();
    
            /// <summary>
            /// Gets the list of <see cref="OutboundRouteEntry"/>.
            /// </summary>
            public IList<OutboundRouteEntry> OutboundEntries { get; } = new List<OutboundRouteEntry>();
    
            /// <summary>
            /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
            /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
            /// </summary>
            /// <returns>The <see cref="TreeRouter"/>.</returns>
            public TreeRouter Build()
            {
                return Build(version: 0);
            }
    
            /// <summary>
            /// Builds a <see cref="TreeRouter"/> with the <see cref="InboundEntries"/>
            /// and <see cref="OutboundEntries"/> defined in this <see cref="TreeRouteBuilder"/>.
            /// </summary>
            /// <param name="version">The version of the <see cref="TreeRouter"/>.</param>
            /// <returns>The <see cref="TreeRouter"/>.</returns>
            public TreeRouter Build(int version)
            {
                // Tree route builder builds a tree for each of the different route orders defined by
                // the user. When a route needs to be matched, the matching algorithm in tree router
                // just iterates over the trees in ascending order when it tries to match the route.
                var trees = new Dictionary<int, UrlMatchingTree>();
    
                foreach (var entry in InboundEntries)
                {
                    if (!trees.TryGetValue(entry.Order, out var tree))
                    {
                        tree = new UrlMatchingTree(entry.Order);
                        trees.Add(entry.Order, tree);
                    }
    
                    tree.AddEntry(entry);
                }
    
                return new TreeRouter(
                    trees.Values.OrderBy(tree => tree.Order).ToArray(),
                    OutboundEntries,
                    _urlEncoder,
                    _objectPool,
                    _logger,
                    _constraintLogger,
                    version);
            }
    
            /// <summary>
            /// Removes all <see cref="InboundEntries"/> and <see cref="OutboundEntries"/> from this
            /// <see cref="TreeRouteBuilder"/>.
            /// </summary>
            public void Clear()
            {
                InboundEntries.Clear();
                OutboundEntries.Clear();
            }
        }

    再创建TreeRouter

    /// <summary>
        /// An <see cref="IRouter"/> implementation for attribute routing.
        /// </summary>
        public class TreeRouter : IRouter
        {
            // Key used by routing and action selection to match an attribute route entry to a
            // group of action descriptors.
            public static readonly string RouteGroupKey = "!__route_group";
    
            private readonly LinkGenerationDecisionTree _linkGenerationTree;
            private readonly UrlMatchingTree[] _trees;
            private readonly IDictionary<string, OutboundMatch> _namedEntries;
    
            private readonly ILogger _logger;
            private readonly ILogger _constraintLogger;
    
            /// <summary>
            /// Creates a new instance of <see cref="TreeRouter"/>.
            /// </summary>
            /// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
            /// <param name="linkGenerationEntries">The set of <see cref="OutboundRouteEntry"/>.</param>
            /// <param name="urlEncoder">The <see cref="UrlEncoder"/>.</param>
            /// <param name="objectPool">The <see cref="ObjectPool{T}"/>.</param>
            /// <param name="routeLogger">The <see cref="ILogger"/> instance.</param>
            /// <param name="constraintLogger">The <see cref="ILogger"/> instance used
            /// in <see cref="RouteConstraintMatcher"/>.</param>
            /// <param name="version">The version of this route.</param>
            internal TreeRouter(
                UrlMatchingTree[] trees,
                IEnumerable<OutboundRouteEntry> linkGenerationEntries,
                UrlEncoder urlEncoder,
                ObjectPool<UriBuildingContext> objectPool,
                ILogger routeLogger,
                ILogger constraintLogger,
                int version)
            {
                if (trees == null)
                {
                    throw new ArgumentNullException(nameof(trees));
                }
    
                if (linkGenerationEntries == null)
                {
                    throw new ArgumentNullException(nameof(linkGenerationEntries));
                }
    
                if (urlEncoder == null)
                {
                    throw new ArgumentNullException(nameof(urlEncoder));
                }
    
                if (objectPool == null)
                {
                    throw new ArgumentNullException(nameof(objectPool));
                }
    
                if (routeLogger == null)
                {
                    throw new ArgumentNullException(nameof(routeLogger));
                }
    
                if (constraintLogger == null)
                {
                    throw new ArgumentNullException(nameof(constraintLogger));
                }
    
                _trees = trees;
                _logger = routeLogger;
                _constraintLogger = constraintLogger;
    
                _namedEntries = new Dictionary<string, OutboundMatch>(StringComparer.OrdinalIgnoreCase);
    
                var outboundMatches = new List<OutboundMatch>();
    
                foreach (var entry in linkGenerationEntries)
                {
    
                    var binder = new TemplateBinder(urlEncoder, objectPool, entry.RouteTemplate, entry.Defaults);
                    var outboundMatch = new OutboundMatch() { Entry = entry, TemplateBinder = binder };
                    outboundMatches.Add(outboundMatch);
    
                    // Skip unnamed entries
                    if (entry.RouteName == null)
                    {
                        continue;
                    }
    
                    // We only need to keep one OutboundMatch per route template
                    // so in case two entries have the same name and the same template we only keep
                    // the first entry.
                    if (_namedEntries.TryGetValue(entry.RouteName, out var namedMatch) &&
                        !string.Equals(
                            namedMatch.Entry.RouteTemplate.TemplateText,
                            entry.RouteTemplate.TemplateText,
                            StringComparison.OrdinalIgnoreCase))
                    {
                        throw new ArgumentException(
                            Resources.FormatAttributeRoute_DifferentLinkGenerationEntries_SameName(entry.RouteName),
                            nameof(linkGenerationEntries));
                    }
                    else if (namedMatch == null)
                    {
                        _namedEntries.Add(entry.RouteName, outboundMatch);
                    }
                }
    
                // The decision tree will take care of ordering for these entries.
                _linkGenerationTree = new LinkGenerationDecisionTree(outboundMatches.ToArray());
    
                Version = version;
            }
    
            /// <summary>
            /// Gets the version of this route.
            /// </summary>
            public int Version { get; }
    
            internal IEnumerable<UrlMatchingTree> MatchingTrees => _trees;
    
            /// <inheritdoc />
            public VirtualPathData GetVirtualPath(VirtualPathContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException(nameof(context));
                }
    
                // If it's a named route we will try to generate a link directly and
                // if we can't, we will not try to generate it using an unnamed route.
                if (context.RouteName != null)
                {
                    return GetVirtualPathForNamedRoute(context);
                }
    
                // The decision tree will give us back all entries that match the provided route data in the correct
                // order. We just need to iterate them and use the first one that can generate a link.
                var matches = _linkGenerationTree.GetMatches(context.Values, context.AmbientValues);
    
                if (matches == null)
                {
                    return null;
                }
    
                for (var i = 0; i < matches.Count; i++)
                {
                    var path = GenerateVirtualPath(context, matches[i].Match.Entry, matches[i].Match.TemplateBinder);
                    if (path != null)
                    {
                        return path;
                    }
                }
    
                return null;
            }
    
            /// <inheritdoc />
            public async Task RouteAsync(RouteContext context)
            {
                foreach (var tree in _trees)
                {
                    var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
                    var root = tree.Root;
    
                    var treeEnumerator = new TreeEnumerator(root, tokenizer);
    
                    // Create a snapshot before processing the route. We'll restore this snapshot before running each
                    // to restore the state. This is likely an "empty" snapshot, which doesn't allocate.
                    var snapshot = context.RouteData.PushState(router: null, values: null, dataTokens: null);
    
                    while (treeEnumerator.MoveNext())
                    {
                        var node = treeEnumerator.Current;
                        foreach (var item in node.Matches)
                        {
                            var entry = item.Entry;
                            var matcher = item.TemplateMatcher;
    
                            try
                            {
                                if (!matcher.TryMatch(context.HttpContext.Request.Path, context.RouteData.Values))
                                {
                                    continue;
                                }
    
                                if (!RouteConstraintMatcher.Match(
                                    entry.Constraints,
                                    context.RouteData.Values,
                                    context.HttpContext,
                                    this,
                                    RouteDirection.IncomingRequest,
                                    _constraintLogger))
                                {
                                    continue;
                                }
    
                                _logger.RequestMatchedRoute(entry.RouteName, entry.RouteTemplate.TemplateText);
                                context.RouteData.Routers.Add(entry.Handler);
    
                                await entry.Handler.RouteAsync(context);
                                if (context.Handler != null)
                                {
                                    return;
                                }
                            }
                            finally
                            {
                                if (context.Handler == null)
                                {
                                    // Restore the original values to prevent polluting the route data.
                                    snapshot.Restore();
                                }
                            }
                        }
                    }
                }
            }
    
            private VirtualPathData GetVirtualPathForNamedRoute(VirtualPathContext context)
            {
                if (_namedEntries.TryGetValue(context.RouteName, out var match))
                {
                    var path = GenerateVirtualPath(context, match.Entry, match.TemplateBinder);
                    if (path != null)
                    {
                        return path;
                    }
                }
                return null;
            }
    
            private VirtualPathData GenerateVirtualPath(
                VirtualPathContext context,
                OutboundRouteEntry entry,
                TemplateBinder binder)
            {
                // In attribute the context includes the values that are used to select this entry - typically
                // these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't
                // want to pass these to the link generation code, or else they will end up as query parameters.
                //
                // So, we need to exclude from here any values that are 'required link values', but aren't
                // parameters in the template.
                //
                // Ex:
                //      template: api/Products/{action}
                //      required values: { id = "5", action = "Buy", Controller = "CoolProducts" }
                //
                //      result: { id = "5", action = "Buy" }
                var inputValues = new RouteValueDictionary();
                foreach (var kvp in context.Values)
                {
                    if (entry.RequiredLinkValues.ContainsKey(kvp.Key))
                    {
                        var parameter = entry.RouteTemplate.GetParameter(kvp.Key);
    
                        if (parameter == null)
                        {
                            continue;
                        }
                    }
    
                    inputValues.Add(kvp.Key, kvp.Value);
                }
    
                var bindingResult = binder.GetValues(context.AmbientValues, inputValues);
                if (bindingResult == null)
                {
                    // A required parameter in the template didn't get a value.
                    return null;
                }
    
                var matched = RouteConstraintMatcher.Match(
                    entry.Constraints,
                    bindingResult.CombinedValues,
                    context.HttpContext,
                    this,
                    RouteDirection.UrlGeneration,
                    _constraintLogger);
    
                if (!matched)
                {
                    // A constraint rejected this link.
                    return null;
                }
    
                var pathData = entry.Handler.GetVirtualPath(context);
                if (pathData != null)
                {
                    // If path is non-null then the target router short-circuited, we don't expect this
                    // in typical MVC scenarios.
                    return pathData;
                }
    
                var path = binder.BindValues(bindingResult.AcceptedValues);
                if (path == null)
                {
                    return null;
                }
    
                return new VirtualPathData(this, path);
            }
        }
  • 相关阅读:
    [课程设计]Scrum 1.6 多鱼点餐系统开发进度
    [课程设计]Scrum 1.7 多鱼点餐系统开发进度
    [课程设计]Scrum 1.5 多鱼点餐系统开发进度
    [课程设计]Scrum 1.4 多鱼点餐系统开发进度
    [课程设计]Scrum 1.3 多鱼点餐系统开发进度
    [课程设计]Scrum 1.2 Spring 计划&系统流程&DayOne燃尽图
    [课程设计]Scrum 1.1 NABCD模型&产品Backlog
    [课程设计]Scrum团队分工及明确任务1.0 ----多鱼点餐
    学习进度条
    [课程设计]多鱼点餐系统个人总结
  • 原文地址:https://www.cnblogs.com/lanpingwang/p/12642196.html
Copyright © 2011-2022 走看看