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