public ReflectedApplicationModel BuildModel() { var applicationModel = new ReflectedApplicationModel(); applicationModel.Filters.AddRange(_globalFilters); var assemblies = _controllerAssemblyProvider.CandidateAssemblies; var types = assemblies.SelectMany(a => a.DefinedTypes); var controllerTypes = types.Where(_conventions.IsController); foreach (var controllerType in controllerTypes) { var controllerModel = new ReflectedControllerModel(controllerType); applicationModel.Controllers.Add(controllerModel); foreach (var methodInfo in controllerType.AsType().GetMethods()) { var actionInfos = _conventions.GetActions(methodInfo, controllerType); if (actionInfos == null) { continue; } foreach (var actionInfo in actionInfos) { var actionModel = new ReflectedActionModel(methodInfo); actionModel.ActionName = actionInfo.ActionName; actionModel.IsActionNameMatchRequired = actionInfo.RequireActionNameMatch; actionModel.HttpMethods.AddRange(actionInfo.HttpMethods ?? Enumerable.Empty <string>()); if (actionInfo.AttributeRoute != null) { actionModel.AttributeRouteModel = new ReflectedAttributeRouteModel( actionInfo.AttributeRoute); } foreach (var parameter in methodInfo.GetParameters()) { actionModel.Parameters.Add(new ReflectedParameterModel(parameter)); } controllerModel.Actions.Add(actionModel); } } } return(applicationModel); }
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); }