asp.net core mvc 3.1 源码分析(五)

创建完ApplicationModel后,调用ControllerActionDescriptorBuilder类的Build方法创建对应的ControllerActionDescriptor

internal static class ControllerActionDescriptorBuilder
    {
        public static IList<ControllerActionDescriptor> Build(ApplicationModel application)
        {
            return ApplicationModelFactory.Flatten(application, CreateActionDescriptor);
        }

        private static ControllerActionDescriptor CreateActionDescriptor(
            ApplicationModel application,
            ControllerModel controller,
            ActionModel action,
            SelectorModel selector)
        {
            var actionDescriptor = new ControllerActionDescriptor
            {
                ActionName = action.ActionName,
                MethodInfo = action.ActionMethod,
            };

            actionDescriptor.ControllerName = controller.ControllerName;
            actionDescriptor.ControllerTypeInfo = controller.ControllerType;
            AddControllerPropertyDescriptors(actionDescriptor, controller);

            AddActionConstraints(actionDescriptor, selector);
            AddEndpointMetadata(actionDescriptor, selector);
            AddAttributeRoute(actionDescriptor, selector);
            AddParameterDescriptors(actionDescriptor, action);
            AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters);
            AddApiExplorerInfo(actionDescriptor, application, controller, action);
            AddRouteValues(actionDescriptor, controller, action);
            AddProperties(actionDescriptor, action, controller, application);

            return actionDescriptor;
        }

        private static void AddControllerPropertyDescriptors(ActionDescriptor actionDescriptor, ControllerModel controller)
        {
            actionDescriptor.BoundProperties = controller.ControllerProperties
                .Where(p => p.BindingInfo != null)
                .Select(CreateParameterDescriptor)
                .ToList();
        }

        private static void AddParameterDescriptors(ActionDescriptor actionDescriptor, ActionModel action)
        {
            var parameterDescriptors = new List<ParameterDescriptor>();
            foreach (var parameter in action.Parameters)
            {
                var parameterDescriptor = CreateParameterDescriptor(parameter);
                parameterDescriptors.Add(parameterDescriptor);
            }

            actionDescriptor.Parameters = parameterDescriptors;
        }
        
        private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameterModel)
        {
            var parameterDescriptor = new ControllerParameterDescriptor()
            {
                Name = parameterModel.ParameterName,
                ParameterType = parameterModel.ParameterInfo.ParameterType,
                BindingInfo = parameterModel.BindingInfo,
                ParameterInfo = parameterModel.ParameterInfo,
            };

            return parameterDescriptor;
        }

        private static ParameterDescriptor CreateParameterDescriptor(PropertyModel propertyModel)
        {
            var parameterDescriptor = new ControllerBoundPropertyDescriptor()
            {
                BindingInfo = propertyModel.BindingInfo,
                Name = propertyModel.PropertyName,
                ParameterType = propertyModel.PropertyInfo.PropertyType,
                PropertyInfo = propertyModel.PropertyInfo,
            };

            return parameterDescriptor;
        }

        private static void AddApiExplorerInfo(
            ControllerActionDescriptor actionDescriptor,
            ApplicationModel application,
            ControllerModel controller,
            ActionModel action)
        {
            var isVisible =
                action.ApiExplorer?.IsVisible ??
                controller.ApiExplorer?.IsVisible ??
                application.ApiExplorer?.IsVisible ??
                false;

            var isVisibleSetOnActionOrController =
                action.ApiExplorer?.IsVisible ??
                controller.ApiExplorer?.IsVisible ??
                false;

            // ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure
            // it at the application level when you have a mix of controller types. We'll just skip over enabling
            // ApiExplorer for conventional-routed controllers when this happens.
            var isVisibleSetOnApplication = application.ApiExplorer?.IsVisible ?? false;

            if (isVisibleSetOnActionOrController && !IsAttributeRouted(actionDescriptor))
            {
                // ApiExplorer is only supported on attribute routed actions.
                throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
                    actionDescriptor.DisplayName));
            }
            else if (isVisibleSetOnApplication && !IsAttributeRouted(actionDescriptor))
            {
                // This is the case where we're going to be lenient, just ignore it.
            }
            else if (isVisible)
            {
                Debug.Assert(IsAttributeRouted(actionDescriptor));

                var apiExplorerActionData = new ApiDescriptionActionData()
                {
                    GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
                };

                actionDescriptor.SetProperty(apiExplorerActionData);
            }
        }

        private static void AddProperties(
            ControllerActionDescriptor actionDescriptor,
            ActionModel action,
            ControllerModel controller,
            ApplicationModel application)
        {
            foreach (var item in application.Properties)
            {
                actionDescriptor.Properties[item.Key] = item.Value;
            }

            foreach (var item in controller.Properties)
            {
                actionDescriptor.Properties[item.Key] = item.Value;
            }

            foreach (var item in action.Properties)
            {
                actionDescriptor.Properties[item.Key] = item.Value;
            }
        }

        private static void AddActionFilters(
            ControllerActionDescriptor actionDescriptor,
            IEnumerable<IFilterMetadata> actionFilters,
            IEnumerable<IFilterMetadata> controllerFilters,
            IEnumerable<IFilterMetadata> globalFilters)
        {
            actionDescriptor.FilterDescriptors =
                actionFilters.Select(f => new FilterDescriptor(f, FilterScope.Action))
                .Concat(controllerFilters.Select(f => new FilterDescriptor(f, FilterScope.Controller)))
                .Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)))
                .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer)
                .ToList();
        }

        private static void AddActionConstraints(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
        {
            if (selectorModel.ActionConstraints?.Count > 0)
            {
                actionDescriptor.ActionConstraints = new List<IActionConstraintMetadata>(selectorModel.ActionConstraints);
            }
        }

        private static void AddEndpointMetadata(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
        {
            if (selectorModel.EndpointMetadata?.Count > 0)
            {
                actionDescriptor.EndpointMetadata = new List<object>(selectorModel.EndpointMetadata);
            }
        }

        private static void AddAttributeRoute(ControllerActionDescriptor actionDescriptor, SelectorModel selectorModel)
        {
            if (selectorModel.AttributeRouteModel != null)
            {
                actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo
                {
                    Template = selectorModel.AttributeRouteModel.Template,
                    Order = selectorModel.AttributeRouteModel.Order ?? 0,
                    Name = selectorModel.AttributeRouteModel.Name,
                    SuppressLinkGeneration = selectorModel.AttributeRouteModel.SuppressLinkGeneration,
                    SuppressPathMatching = selectorModel.AttributeRouteModel.SuppressPathMatching,
                };
            }
        }

        public static void AddRouteValues(
            ControllerActionDescriptor actionDescriptor,
            ControllerModel controller,
            ActionModel action)
        {
            // Apply all the constraints defined on the action, then controller (for example, [Area])
            // to the actions. Also keep track of all the constraints that require preventing actions
            // without the constraint to match. For example, actions without an [Area] attribute on their
            // controller should not match when a value has been given for area when matching a url or
            // generating a link.
            foreach (var kvp in action.RouteValues)
            {
                // Skip duplicates
                if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
                {
                    actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
                }
            }

            foreach (var kvp in controller.RouteValues)
            {
                // Skip duplicates - this also means that a value on the action will take precedence
                if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key))
                {
                    actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value);
                }
            }

            // Lastly add the 'default' values
            if (!actionDescriptor.RouteValues.ContainsKey("action"))
            {
                actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty);
            }

            if (!actionDescriptor.RouteValues.ContainsKey("controller"))
            {
                actionDescriptor.RouteValues.Add("controller", controller.ControllerName);
            }
        }

        private static bool IsAttributeRouted(ActionDescriptor actionDescriptor)
        {
            return actionDescriptor.AttributeRouteInfo != null;
        }
    }
public static List<TResult> Flatten<TResult>(
            ApplicationModel application,
            Func<ApplicationModel, ControllerModel, ActionModel, SelectorModel, TResult> flattener)
        {
            var results = new List<TResult>();
            var errors = new Dictionary<MethodInfo, IList<string>>();

            var actionsByMethod = new Dictionary<MethodInfo, List<(ActionModel, SelectorModel)>>();
            var actionsByRouteName = new Dictionary<string, List<(ActionModel, SelectorModel)>>(StringComparer.OrdinalIgnoreCase);

            var routeTemplateErrors = new List<string>();

            foreach (var controller in application.Controllers)
            {
                foreach (var action in controller.Actions)
                {
                    foreach (var selector in ActionAttributeRouteModel.FlattenSelectors(action))
                    {
                        // PostProcess attribute routes so we can observe any errors.
                        ReplaceAttributeRouteTokens(controller, action, selector, routeTemplateErrors);

                        // Add to the data structures we use to find errors.
                        AddActionToMethodInfoMap(actionsByMethod, action, selector);
                        AddActionToRouteNameMap(actionsByRouteName, action, selector);

                        var result = flattener(application, controller, action, selector);
                        Debug.Assert(result != null);

                        results.Add(result);
                    }
                }
            }

            var attributeRoutingConfigurationErrors = new Dictionary<MethodInfo, string>();
            foreach (var (method, actions) in actionsByMethod)
            {
                ValidateActionGroupConfiguration(
                    method,
                    actions,
                    attributeRoutingConfigurationErrors);
            }

            if (attributeRoutingConfigurationErrors.Any())
            {
                var message = CreateAttributeRoutingAggregateErrorMessage(attributeRoutingConfigurationErrors.Values);

                throw new InvalidOperationException(message);
            }

            var namedRoutedErrors = ValidateNamedAttributeRoutedActions(actionsByRouteName);
            if (namedRoutedErrors.Any())
            {
                var message = CreateAttributeRoutingAggregateErrorMessage(namedRoutedErrors);
                throw new InvalidOperationException(message);
            }

            if (routeTemplateErrors.Any())
            {
                var message = CreateAttributeRoutingAggregateErrorMessage(routeTemplateErrors);
                throw new InvalidOperationException(message);
            }


            return results;
        }
internal static class ActionAttributeRouteModel
    {
        public static IEnumerable<SelectorModel> FlattenSelectors(ActionModel actionModel)
        {
            // Loop through all attribute routes defined on the controller. 
            // These perform a cross-product with all of the action-level attribute routes.
            var controllerSelectors = actionModel.Controller.Selectors
                .Where(sm => sm.AttributeRouteModel != null)
                .ToList();

            // We also include metadata and action constraints from the controller
            // even when there are no routes, or when an action overrides the route template.
            SelectorModel additionalSelector = null;
            if (actionModel.Controller.Selectors.Count > 0)
            {
                // This logic seems arbitrary but there's a good reason for it.
                // 
                // When we build the controller level selectors, any metadata or action constraints
                // that aren't IRouteTemplateProvider will be included in all selectors. So we 
                // pick any selector and then grab all of the stuff that isn't IRouteTemplateProvider
                // then we've found all of the items that aren't routes.
                //
                // This is fragile wrt application model customizing the data - but no one has
                // run into an issue with this and its pretty esoteric.
                additionalSelector = new SelectorModel(actionModel.Controller.Selectors.First());
                additionalSelector.AttributeRouteModel = null;

                for (var i = additionalSelector.ActionConstraints.Count - 1; i >= 0; i--)
                {
                    if (additionalSelector.ActionConstraints[i] is IRouteTemplateProvider)
                    {
                        additionalSelector.ActionConstraints.RemoveAt(i);
                    }
                }

                for (var i = additionalSelector.EndpointMetadata.Count - 1; i >= 0; i--)
                {
                    if (additionalSelector.EndpointMetadata[i] is IRouteTemplateProvider)
                    {
                        additionalSelector.EndpointMetadata.RemoveAt(i);
                    }
                }
            }

            var actionConstraints = new List<IActionConstraintMetadata>();

            foreach (var actionSelector in actionModel.Selectors)
            {
                var actionRouteModel = actionSelector.AttributeRouteModel;

                // We check the action to see if the template allows combination behavior
                // (It doesn't start with / or ~/) so that in the case where we have multiple
                // [Route] attributes on the controller we don't end up creating multiple
                if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate)
                {
                    // We're overriding the routes from the controller, but any *unbound* constraints
                    // still apply.
                    var selector = new SelectorModel(actionSelector);

                    selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(
                        left: null,
                        right: actionRouteModel);

                    AddActionConstraints(selector, additionalSelector?.ActionConstraints);
                    AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata);

                    yield return selector;
                }
                else if (controllerSelectors.Count > 0)
                {
                    for (var i = 0; i < controllerSelectors.Count; i++)
                    {
                        var controllerSelector = controllerSelectors[i];

                        // We're using the attribute routes from the controller
                        var selector = new SelectorModel(actionSelector);

                        selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(
                            controllerSelector.AttributeRouteModel,
                            actionRouteModel);

                        AddActionConstraints(selector, controllerSelector.ActionConstraints);
                        AddEndpointMetadata(selector, controllerSelector.EndpointMetadata);

                        // No need to include the additional selector here because it would duplicate
                        // data in controllerSelector.

                        yield return selector;
                    }
                }
                else
                {
                    // There are no routes on the controller, but any *unbound* constraints
                    // still apply.
                    var selector = new SelectorModel(actionSelector);

                    selector.AttributeRouteModel = AttributeRouteModel.CombineAttributeRouteModel(
                        left: null,
                        right: actionRouteModel);

                    AddActionConstraints(selector, additionalSelector?.ActionConstraints);
                    AddEndpointMetadata(selector, additionalSelector?.EndpointMetadata);

                    yield return selector;
                }
            }
        }

        private static void AddActionConstraints(SelectorModel selector, IList<IActionConstraintMetadata> actionConstraints)
        {
            if (actionConstraints != null)
            {
                for (var i = 0; i < actionConstraints.Count;i++)
                {
                    selector.ActionConstraints.Add(actionConstraints[i]);
                }
            }
        }

        private static void AddEndpointMetadata(SelectorModel selector, IList<object> controllerMetadata)
        {
            if (controllerMetadata != null)
            {
                // It is criticial to get the order in which metadata appears in endpoint metadata correct. More significant metadata
                // must appear later in the sequence. In this case, the values in `controllerMetadata` should have their order
                // preserved, but appear earlier than the entries in `selector.EndpointMetadata`.
                for (var i = 0; i < controllerMetadata.Count; i++)
                {
                    selector.EndpointMetadata.Insert(i, controllerMetadata[i]);
                }
            }
        }

        public static IEnumerable<(AttributeRouteModel route, SelectorModel actionSelector, SelectorModel controllerSelector)> GetAttributeRoutes(ActionModel actionModel)
        {
            var controllerAttributeRoutes = actionModel.Controller.Selectors
                .Where(sm => sm.AttributeRouteModel != null)
                .Select(sm => sm.AttributeRouteModel)
                .ToList();

            foreach (var actionSelectorModel in actionModel.Selectors)
            {
                var actionRouteModel = actionSelectorModel.AttributeRouteModel;

                // We check the action to see if the template allows combination behavior
                // (It doesn't start with / or ~/) so that in the case where we have multiple
                // [Route] attributes on the controller we don't end up creating multiple
                if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate)
                {
                    var route = AttributeRouteModel.CombineAttributeRouteModel(
                        left: null,
                        right: actionRouteModel);

                    yield return (route, actionSelectorModel, null);
                }
                else if (controllerAttributeRoutes.Count > 0)
                {
                    for (var i = 0; i < actionModel.Controller.Selectors.Count; i++)
                    {
                        // We're using the attribute routes from the controller
                        var controllerSelector = actionModel.Controller.Selectors[i];

                        var route = AttributeRouteModel.CombineAttributeRouteModel(
                            controllerSelector.AttributeRouteModel,
                            actionRouteModel);

                        yield return (route, actionSelectorModel, controllerSelector);
                    }
                }

                else
                {
                    var route = AttributeRouteModel.CombineAttributeRouteModel(
                        left: null,
                        right: actionRouteModel);

                    yield return (route, actionSelectorModel, null);
                }
            }
        }
    }

 

posted @ 2020-04-06 16:54  蓝平凡  阅读(397)  评论(0编辑  收藏  举报