private static ReflectedActionDescriptor CreateActionDescriptor( ReflectedActionModel action, ReflectedAttributeRouteModel controllerAttributeRoute, ControllerDescriptor controllerDescriptor) { var parameterDescriptors = new List <ParameterDescriptor>(); foreach (var parameter in action.Parameters) { var isFromBody = parameter.Attributes.OfType <FromBodyAttribute>().Any(); var paramDescriptor = new ParameterDescriptor() { Name = parameter.ParameterName, IsOptional = parameter.IsOptional }; if (isFromBody) { paramDescriptor.BodyParameterInfo = new BodyParameterInfo( parameter.ParameterInfo.ParameterType); } else { paramDescriptor.ParameterBindingInfo = new ParameterBindingInfo( parameter.ParameterName, parameter.ParameterInfo.ParameterType); } parameterDescriptors.Add(paramDescriptor); } var attributeRouteInfo = CreateAttributeRouteInfo( action.AttributeRouteModel, controllerAttributeRoute); var actionDescriptor = new ReflectedActionDescriptor() { Name = action.ActionName, ControllerDescriptor = controllerDescriptor, MethodInfo = action.ActionMethod, Parameters = parameterDescriptors, RouteConstraints = new List <RouteDataActionConstraint>(), AttributeRouteInfo = attributeRouteInfo }; actionDescriptor.DisplayName = string.Format( "{0}.{1}", action.ActionMethod.DeclaringType.FullName, action.ActionMethod.Name); return(actionDescriptor); }
private static IList <ReflectedActionDescriptor> CreateActionDescriptors( ReflectedActionModel action, ReflectedControllerModel controller, ControllerDescriptor controllerDescriptor) { var actionDescriptors = new List <ReflectedActionDescriptor>(); // 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 // attribute identical attribute routes. if (controller.AttributeRoutes != null && controller.AttributeRoutes.Count > 0 && (action.AttributeRouteModel == null || !action.AttributeRouteModel.IsAbsoluteTemplate)) { foreach (var controllerAttributeRoute in controller.AttributeRoutes) { var actionDescriptor = CreateActionDescriptor( action, controllerAttributeRoute, controllerDescriptor); actionDescriptors.Add(actionDescriptor); } } else { actionDescriptors.Add(CreateActionDescriptor( action, controllerAttributeRoute: null, controllerDescriptor: controllerDescriptor)); } return(actionDescriptors); }
public List<ReflectedActionDescriptor> Build(ReflectedApplicationModel model) { var actions = new List<ReflectedActionDescriptor>(); var routeGroupsByTemplate = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var removalConstraints = new HashSet<string>(StringComparer.OrdinalIgnoreCase); var routeTemplateErrors = new List<string>(); foreach (var controller in model.Controllers) { var controllerDescriptor = new ControllerDescriptor(controller.ControllerType); foreach (var action in controller.Actions) { var parameterDescriptors = new List<ParameterDescriptor>(); foreach (var parameter in action.Parameters) { var isFromBody = parameter.Attributes.OfType<FromBodyAttribute>().Any(); parameterDescriptors.Add(new ParameterDescriptor() { Name = parameter.ParameterName, IsOptional = parameter.IsOptional, ParameterBindingInfo = isFromBody ? null : new ParameterBindingInfo( parameter.ParameterName, parameter.ParameterInfo.ParameterType), BodyParameterInfo = isFromBody ? new BodyParameterInfo(parameter.ParameterInfo.ParameterType) : null }); } var actionDescriptor = new ReflectedActionDescriptor() { Name = action.ActionName, ControllerDescriptor = controllerDescriptor, MethodInfo = action.ActionMethod, Parameters = parameterDescriptors, RouteConstraints = new List<RouteDataActionConstraint>(), }; actionDescriptor.DisplayName = string.Format( "{0}.{1}", action.ActionMethod.DeclaringType.FullName, action.ActionMethod.Name); var httpMethods = action.HttpMethods; if (httpMethods != null && httpMethods.Count > 0) { actionDescriptor.MethodConstraints = new List<HttpMethodConstraint>() { new HttpMethodConstraint(httpMethods) }; } actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "controller", controller.ControllerName)); if (action.IsActionNameMatchRequired) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "action", action.ActionName)); } else { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "action", RouteKeyHandling.DenyKey)); } foreach (var constraintAttribute in controller.RouteConstraints) { if (constraintAttribute.BlockNonAttributedActions) { removalConstraints.Add(constraintAttribute.RouteKey); } // Skip duplicates if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( constraintAttribute.RouteKey, constraintAttribute.RouteValue)); } } var templateText = AttributeRouteTemplate.Combine( controller.RouteTemplate, action.RouteTemplate); if (templateText != null) { // An attribute routed action will ignore conventional routed constraints. We still // want to provide these values as ambient values. foreach (var constraint in actionDescriptor.RouteConstraints) { actionDescriptor.RouteValueDefaults.Add(constraint.RouteKey, constraint.RouteValue); } // Replaces tokens like [controller]/[action] in the route template with the actual values // for this action. try { templateText = AttributeRouteTemplate.ReplaceTokens( templateText, actionDescriptor.RouteValueDefaults); } catch (InvalidOperationException ex) { var message = Resources.FormatAttributeRoute_IndividualErrorMessage( actionDescriptor.DisplayName, Environment.NewLine, ex.Message); routeTemplateErrors.Add(message); } actionDescriptor.AttributeRouteTemplate = templateText; // An attribute routed action is matched by its 'route group' which identifies all equivalent // actions. string routeGroup; if (!routeGroupsByTemplate.TryGetValue(templateText, out routeGroup)) { routeGroup = GetRouteGroup(templateText); routeGroupsByTemplate.Add(templateText, routeGroup); } var routeConstraints = new List<RouteDataActionConstraint>(); routeConstraints.Add(new RouteDataActionConstraint( AttributeRouting.RouteGroupKey, routeGroup)); actionDescriptor.RouteConstraints = routeConstraints; } actionDescriptor.FilterDescriptors = action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action)) .Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller))) .Concat(model.Filters.Select(f => new FilterDescriptor(f, FilterScope.Global))) .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) .ToList(); actions.Add(actionDescriptor); } } foreach (var actionDescriptor in actions) { foreach (var key in removalConstraints) { if (actionDescriptor.AttributeRouteTemplate == null) { // Any any attribute routes are in use, then non-attribute-routed ADs can't be selected // when a route group returned by the route. if (routeGroupsByTemplate.Any()) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( AttributeRouting.RouteGroupKey, RouteKeyHandling.DenyKey)); } if (!HasConstraint(actionDescriptor.RouteConstraints, key)) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( key, RouteKeyHandling.DenyKey)); } } else { // 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. if (!actionDescriptor.RouteValueDefaults.ContainsKey(key)) { actionDescriptor.RouteValueDefaults.Add(key, null); } } } } if (routeTemplateErrors.Any()) { var message = Resources.FormatAttributeRoute_AggregateErrorMessage( Environment.NewLine, string.Join(Environment.NewLine + Environment.NewLine, routeTemplateErrors)); throw new InvalidOperationException(message); } return actions; }
public List <ReflectedActionDescriptor> Build(ReflectedApplicationModel model) { var actions = new List <ReflectedActionDescriptor>(); var routeGroupsByTemplate = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); var removalConstraints = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var routeTemplateErrors = new List <string>(); foreach (var controller in model.Controllers) { var controllerDescriptor = new ControllerDescriptor(controller.ControllerType); foreach (var action in controller.Actions) { var parameterDescriptors = new List <ParameterDescriptor>(); foreach (var parameter in action.Parameters) { var isFromBody = parameter.Attributes.OfType <FromBodyAttribute>().Any(); parameterDescriptors.Add(new ParameterDescriptor() { Name = parameter.ParameterName, IsOptional = parameter.IsOptional, ParameterBindingInfo = isFromBody ? null : new ParameterBindingInfo( parameter.ParameterName, parameter.ParameterInfo.ParameterType), BodyParameterInfo = isFromBody ? new BodyParameterInfo(parameter.ParameterInfo.ParameterType) : null }); } var actionDescriptor = new ReflectedActionDescriptor() { Name = action.ActionName, ControllerDescriptor = controllerDescriptor, MethodInfo = action.ActionMethod, Parameters = parameterDescriptors, RouteConstraints = new List <RouteDataActionConstraint>(), }; actionDescriptor.DisplayName = string.Format( "{0}.{1}", action.ActionMethod.DeclaringType.FullName, action.ActionMethod.Name); var httpMethods = action.HttpMethods; if (httpMethods != null && httpMethods.Count > 0) { actionDescriptor.MethodConstraints = new List <HttpMethodConstraint>() { new HttpMethodConstraint(httpMethods) }; } actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "controller", controller.ControllerName)); if (action.IsActionNameMatchRequired) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "action", action.ActionName)); } else { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "action", RouteKeyHandling.DenyKey)); } foreach (var constraintAttribute in controller.RouteConstraints) { if (constraintAttribute.BlockNonAttributedActions) { removalConstraints.Add(constraintAttribute.RouteKey); } // Skip duplicates if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( constraintAttribute.RouteKey, constraintAttribute.RouteValue)); } } var templateText = AttributeRouteTemplate.Combine( controller.RouteTemplate, action.RouteTemplate); if (templateText != null) { // An attribute routed action will ignore conventional routed constraints. We still // want to provide these values as ambient values. foreach (var constraint in actionDescriptor.RouteConstraints) { actionDescriptor.RouteValueDefaults.Add(constraint.RouteKey, constraint.RouteValue); } // Replaces tokens like [controller]/[action] in the route template with the actual values // for this action. try { templateText = AttributeRouteTemplate.ReplaceTokens( templateText, actionDescriptor.RouteValueDefaults); } catch (InvalidOperationException ex) { var message = Resources.FormatAttributeRoute_IndividualErrorMessage( actionDescriptor.DisplayName, Environment.NewLine, ex.Message); routeTemplateErrors.Add(message); } actionDescriptor.AttributeRouteTemplate = templateText; // An attribute routed action is matched by its 'route group' which identifies all equivalent // actions. string routeGroup; if (!routeGroupsByTemplate.TryGetValue(templateText, out routeGroup)) { routeGroup = GetRouteGroup(templateText); routeGroupsByTemplate.Add(templateText, routeGroup); } var routeConstraints = new List <RouteDataActionConstraint>(); routeConstraints.Add(new RouteDataActionConstraint( AttributeRouting.RouteGroupKey, routeGroup)); actionDescriptor.RouteConstraints = routeConstraints; } actionDescriptor.FilterDescriptors = action.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action)) .Concat(controller.Filters.Select(f => new FilterDescriptor(f, FilterScope.Controller))) .Concat(model.Filters.Select(f => new FilterDescriptor(f, FilterScope.Global))) .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) .ToList(); actions.Add(actionDescriptor); } } foreach (var actionDescriptor in actions) { foreach (var key in removalConstraints) { if (actionDescriptor.AttributeRouteTemplate == null) { // Any any attribute routes are in use, then non-attribute-routed ADs can't be selected // when a route group returned by the route. if (routeGroupsByTemplate.Any()) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( AttributeRouting.RouteGroupKey, RouteKeyHandling.DenyKey)); } if (!HasConstraint(actionDescriptor.RouteConstraints, key)) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( key, RouteKeyHandling.DenyKey)); } } else { // 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. if (!actionDescriptor.RouteValueDefaults.ContainsKey(key)) { actionDescriptor.RouteValueDefaults.Add(key, null); } } } } if (routeTemplateErrors.Any()) { var message = Resources.FormatAttributeRoute_AggregateErrorMessage( Environment.NewLine, string.Join(Environment.NewLine + Environment.NewLine, routeTemplateErrors)); throw new InvalidOperationException(message); } return(actions); }
public List <ReflectedActionDescriptor> Build(ReflectedApplicationModel model) { var actions = new List <ReflectedActionDescriptor>(); 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 model.Controllers) { var controllerDescriptor = new ControllerDescriptor(controller.ControllerType); 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(action, controller, controllerDescriptor); foreach (var actionDescriptor in actionDescriptors) { AddActionFilters(actionDescriptor, action.Filters, controller.Filters, model.Filters); AddActionConstraints(actionDescriptor, action, controller); AddControllerRouteConstraints( actionDescriptor, controller.RouteConstraints, removalConstraints); 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, RouteKeyHandling.DenyKey)); } // 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, 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); }