/// <summary> /// Creates instances of <see cref="ControllerActionDescriptor"/> from <see cref="ApplicationModel"/>. /// </summary> /// <param name="application">The <see cref="ApplicationModel"/>.</param> /// <returns>The list of <see cref="ControllerActionDescriptor"/>.</returns> public static IList <ControllerActionDescriptor> Build(ApplicationModel application) { var actions = new List <ControllerActionDescriptor>(); var hasAttributeRoutes = false; var removalConstraints = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var methodInfoMap = new MethodToActionMap(); var routeTemplateErrors = new List <string>(); var attributeRoutingConfigurationErrors = new Dictionary <MethodInfo, string>(); foreach (var controller in application.Controllers) { // Only add properties which are explictly marked to bind. // The attribute check is required for ModelBinder attribute. var controllerPropertyDescriptors = controller.ControllerProperties .Where(p => p.BindingInfo != null) .Select(CreateParameterDescriptor) .ToList(); foreach (var action in controller.Actions) { // Controllers with multiple [Route] attributes (or user defined implementation of // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider // instance. // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations // have already been identified as different actions during action discovery. var actionDescriptors = CreateActionDescriptors(application, controller, action); foreach (var actionDescriptor in actionDescriptors) { actionDescriptor.ControllerName = controller.ControllerName; actionDescriptor.ControllerTypeInfo = controller.ControllerType; AddApiExplorerInfo(actionDescriptor, application, controller, action); AddRouteConstraints(removalConstraints, actionDescriptor, controller, action); AddProperties(actionDescriptor, action, controller, application); actionDescriptor.BoundProperties = controllerPropertyDescriptors; if (IsAttributeRoutedAction(actionDescriptor)) { hasAttributeRoutes = true; // An attribute routed action will ignore conventional routed constraints. We still // want to provide these values as ambient values for link generation. AddConstraintsAsDefaultRouteValues(actionDescriptor); // Replaces tokens like [controller]/[action] in the route template with the actual values // for this action. ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors); // Attribute routed actions will ignore conventional routed constraints. Instead they have // a single route constraint "RouteGroup" associated with it. ReplaceRouteConstraints(actionDescriptor); } } methodInfoMap.AddToMethodInfo(action, actionDescriptors); actions.AddRange(actionDescriptors); } } var actionsByRouteName = new Dictionary <string, IList <ActionDescriptor> >( StringComparer.OrdinalIgnoreCase); // Keeps track of all the methods that we've validated to avoid visiting each action group // more than once. var validatedMethods = new HashSet <MethodInfo>(); foreach (var actionDescriptor in actions) { if (!validatedMethods.Contains(actionDescriptor.MethodInfo)) { ValidateActionGroupConfiguration( methodInfoMap, actionDescriptor, attributeRoutingConfigurationErrors); validatedMethods.Add(actionDescriptor.MethodInfo); } if (!IsAttributeRoutedAction(actionDescriptor)) { // Any attribute routes are in use, then non-attribute-routed action descriptors can't be // selected when a route group returned by the route. if (hasAttributeRoutes) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( AttributeRouting.RouteGroupKey, string.Empty)); } // Add a route constraint with DenyKey for each constraint in the set to all the // actions that don't have that constraint. For example, if a controller defines // an area constraint, all actions that don't belong to an area must have a route // constraint that prevents them from matching an incomming request. AddRemovalConstraints(actionDescriptor, removalConstraints); } else { var attributeRouteInfo = actionDescriptor.AttributeRouteInfo; if (attributeRouteInfo.Name != null) { // Build a map of attribute route name to action descriptors to ensure that all // attribute routes with a given name have the same template. AddActionToNamedGroup(actionsByRouteName, attributeRouteInfo.Name, actionDescriptor); } // We still want to add a 'null' for any constraint with DenyKey so that link generation // works properly. // // Consider an action like { area = "", controller = "Home", action = "Index" }. Even if // it's attribute routed, it needs to know that area must be null to generate a link. foreach (var key in removalConstraints) { if (!actionDescriptor.RouteValueDefaults.ContainsKey(key)) { actionDescriptor.RouteValueDefaults.Add(key, value: null); } } } } 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(actions); }
/// <summary> /// Creates instances of <see cref="ControllerActionDescriptor"/> from <see cref="ApplicationModel"/>. /// </summary> /// <param name="application">The <see cref="ApplicationModel"/>.</param> /// <returns>The list of <see cref="ControllerActionDescriptor"/>.</returns> public static IList <ControllerActionDescriptor> Build(ApplicationModel application) { var actions = new List <ControllerActionDescriptor>(); var methodInfoMap = new MethodToActionMap(); var routeTemplateErrors = new List <string>(); var attributeRoutingConfigurationErrors = new Dictionary <MethodInfo, string>(); foreach (var controller in application.Controllers) { // Only add properties which are explicitly marked to bind. // The attribute check is required for ModelBinder attribute. var controllerPropertyDescriptors = controller.ControllerProperties .Where(p => p.BindingInfo != null) .Select(CreateParameterDescriptor) .ToList(); foreach (var action in controller.Actions) { // Controllers with multiple [Route] attributes (or user defined implementation of // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider // instance. // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations // have already been identified as different actions during action discovery. var actionDescriptors = CreateActionDescriptors(application, controller, action); foreach (var actionDescriptor in actionDescriptors) { actionDescriptor.ControllerName = controller.ControllerName; actionDescriptor.ControllerTypeInfo = controller.ControllerType; AddApiExplorerInfo(actionDescriptor, application, controller, action); AddRouteValues(actionDescriptor, controller, action); AddProperties(actionDescriptor, action, controller, application); actionDescriptor.BoundProperties = controllerPropertyDescriptors; if (IsAttributeRoutedAction(actionDescriptor)) { // Replaces tokens like [controller]/[action] in the route template with the actual values // for this action. ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors); } } methodInfoMap.AddToMethodInfo(action, actionDescriptors); actions.AddRange(actionDescriptors); } } var actionsByRouteName = new Dictionary <string, IList <ActionDescriptor> >( StringComparer.OrdinalIgnoreCase); // Keeps track of all the methods that we've validated to avoid visiting each action group // more than once. var validatedMethods = new HashSet <MethodInfo>(); foreach (var actionDescriptor in actions) { if (!validatedMethods.Contains(actionDescriptor.MethodInfo)) { ValidateActionGroupConfiguration( methodInfoMap, actionDescriptor, attributeRoutingConfigurationErrors); validatedMethods.Add(actionDescriptor.MethodInfo); } var attributeRouteInfo = actionDescriptor.AttributeRouteInfo; if (attributeRouteInfo?.Name != null) { // Build a map of attribute route name to action descriptors to ensure that all // attribute routes with a given name have the same template. AddActionToNamedGroup(actionsByRouteName, attributeRouteInfo.Name, actionDescriptor); } } 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(actions); }