private static void AddActionToNamedGroup( IDictionary <string, IList <ActionDescriptor> > actionsByRouteName, string routeName, ReflectedActionDescriptor actionDescriptor) { IList <ActionDescriptor> namedActionGroup; if (actionsByRouteName.TryGetValue(routeName, out namedActionGroup)) { namedActionGroup.Add(actionDescriptor); } else { namedActionGroup = new List <ActionDescriptor>(); namedActionGroup.Add(actionDescriptor); actionsByRouteName.Add(routeName, namedActionGroup); } }
public ReflectedActionInvoker([NotNull] ActionContext actionContext, [NotNull] IActionBindingContextProvider bindingContextProvider, [NotNull] INestedProviderManager <FilterProviderContext> filterProvider, [NotNull] IControllerFactory controllerFactory, [NotNull] ReflectedActionDescriptor descriptor, [NotNull] IInputFormattersProvider inputFormattersProvider) : base(actionContext, bindingContextProvider, filterProvider) { _descriptor = descriptor; _controllerFactory = controllerFactory; _inputFormattersProvider = inputFormattersProvider; if (descriptor.MethodInfo == null) { throw new ArgumentException( Resources.FormatPropertyOfTypeCannotBeNull("MethodInfo", typeof(ReflectedActionDescriptor)), "descriptor"); } }
private static void ReplaceAttributeRouteTokens( ReflectedActionDescriptor actionDescriptor, IList <string> routeTemplateErrors) { try { actionDescriptor.AttributeRouteInfo.Template = ReflectedAttributeRouteModel.ReplaceTokens( actionDescriptor.AttributeRouteInfo.Template, actionDescriptor.RouteValueDefaults); } catch (InvalidOperationException ex) { var message = Resources.FormatAttributeRoute_IndividualErrorMessage( actionDescriptor.DisplayName, Environment.NewLine, ex.Message); routeTemplateErrors.Add(message); } }
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 async Task Invoke_UsesDefaultValuesIfNotBound() { // Arrange var actionDescriptor = new ReflectedActionDescriptor { MethodInfo = typeof(TestController).GetTypeInfo() .DeclaredMethods .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)), Parameters = new List<ParameterDescriptor> { new ParameterDescriptor { Name = "value", ParameterBindingInfo = new ParameterBindingInfo("value", typeof(int)) } }, FilterDescriptors = new List<FilterDescriptor>() }; var binder = new Mock<IModelBinder>(); var metadataProvider = new EmptyModelMetadataProvider(); binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>())) .Returns(Task.FromResult(result: false)); var context = new Mock<HttpContext>(); context.SetupGet(c => c.Items) .Returns(new Dictionary<object, object>()); var routeContext = new RouteContext(context.Object); var actionContext = new ActionContext(routeContext, actionDescriptor); var bindingContext = new ActionBindingContext(actionContext, Mock.Of<IModelMetadataProvider>(), binder.Object, Mock.Of<IValueProvider>(), Mock.Of<IInputFormatterProvider>(), Enumerable.Empty<IModelValidatorProvider>()); var actionBindingContextProvider = new Mock<IActionBindingContextProvider>(); actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>())) .Returns(Task.FromResult(bindingContext)); var controllerFactory = new Mock<IControllerFactory>(); controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>())) .Returns(new TestController()); var invoker = new ReflectedActionInvoker(actionContext, actionDescriptor, controllerFactory.Object, actionBindingContextProvider.Object, Mock.Of<INestedProviderManager<FilterProviderContext>>()); // Act await invoker.InvokeActionAsync(); // Assert Assert.Equal(5, context.Object.Items["Result"]); }
public async Task GetActionArguments_AddsActionArgumentsToModelStateDictionary_IfBinderReturnsTrue() { // Arrange Func<object, int> method = x => 1; var actionDescriptor = new ReflectedActionDescriptor { MethodInfo = method.Method, Parameters = new List<ParameterDescriptor> { new ParameterDescriptor { Name = "foo", ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object)) } } }; var value = "Hello world"; var binder = new Mock<IModelBinder>(); var metadataProvider = new EmptyModelMetadataProvider(); binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>())) .Callback((ModelBindingContext context) => { context.ModelMetadata = metadataProvider.GetMetadataForType(modelAccessor: null, modelType: typeof(string)); context.Model = value; }) .Returns(Task.FromResult(result: true)); var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()), actionDescriptor); var bindingContext = new ActionBindingContext(actionContext, Mock.Of<IModelMetadataProvider>(), binder.Object, Mock.Of<IValueProvider>(), Mock.Of<IInputFormatterProvider>(), Enumerable.Empty<IModelValidatorProvider>()); var actionBindingContextProvider = new Mock<IActionBindingContextProvider>(); actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>())) .Returns(Task.FromResult(bindingContext)); var invoker = new ReflectedActionInvoker(actionContext, actionDescriptor, Mock.Of<IControllerFactory>(), actionBindingContextProvider.Object, Mock.Of<INestedProviderManager<FilterProviderContext>>()); var modelStateDictionary = new ModelStateDictionary(); // Act var result = await invoker.GetActionArguments(modelStateDictionary); // Assert Assert.Equal(1, result.Count); Assert.Equal(value, result["foo"]); }
public async Task GetActionArguments_DoesNotAddActionArgumentsToModelStateDictionary_IfBinderReturnsFalse() { // Arrange Func<object, int> method = x => 1; var actionDescriptor = new ReflectedActionDescriptor { MethodInfo = method.Method, Parameters = new List<ParameterDescriptor> { new ParameterDescriptor { Name = "foo", ParameterBindingInfo = new ParameterBindingInfo("foo", typeof(object)) } } }; var binder = new Mock<IModelBinder>(); binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>())) .Returns(Task.FromResult(result: false)); var actionContext = new ActionContext(new RouteContext(Mock.Of<HttpContext>()), actionDescriptor); var bindingContext = new ActionBindingContext(actionContext, Mock.Of<IModelMetadataProvider>(), binder.Object, Mock.Of<IValueProvider>(), Mock.Of<IInputFormatterProvider>(), Enumerable.Empty<IModelValidatorProvider>()); var actionBindingContextProvider = new Mock<IActionBindingContextProvider>(); actionBindingContextProvider.Setup(p => p.GetActionBindingContextAsync(It.IsAny<ActionContext>())) .Returns(Task.FromResult(bindingContext)); var invoker = new ReflectedActionInvoker(actionContext, actionDescriptor, Mock.Of<IControllerFactory>(), actionBindingContextProvider.Object, Mock.Of<INestedProviderManager<FilterProviderContext>>()); var modelStateDictionary = new ModelStateDictionary(); // Act var result = await invoker.GetActionArguments(modelStateDictionary); // Assert Assert.Empty(result); }
private ReflectedActionInvoker CreateInvoker(IFilter[] filters, bool actionThrows = false) { var actionDescriptor = new ReflectedActionDescriptor() { FilterDescriptors = new List<FilterDescriptor>(), Parameters = new List<ParameterDescriptor>(), }; if (actionThrows) { actionDescriptor.MethodInfo = typeof(ReflectedActionInvokerTest).GetMethod("ThrowingActionMethod"); } else { actionDescriptor.MethodInfo = typeof(ReflectedActionInvokerTest).GetMethod("ActionMethod"); } var httpContext = new Mock<HttpContext>(MockBehavior.Loose); var httpResponse = new Mock<HttpResponse>(MockBehavior.Loose); httpContext.SetupGet(c => c.Response).Returns(httpResponse.Object); httpResponse.SetupGet(r => r.Body).Returns(new MemoryStream()); var actionContext = new ActionContext( httpContext: httpContext.Object, routeData: new RouteData(), actionDescriptor: actionDescriptor); var controllerFactory = new Mock<IControllerFactory>(); controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>())).Returns(this); var actionBindingContextProvider = new Mock<IActionBindingContextProvider>(MockBehavior.Strict); actionBindingContextProvider .Setup(abcp => abcp.GetActionBindingContextAsync(It.IsAny<ActionContext>())) .Returns(Task.FromResult(new ActionBindingContext(null, null, null, null, null, null))); var filterProvider = new Mock<INestedProviderManager<FilterProviderContext>>(MockBehavior.Strict); filterProvider .Setup(fp => fp.Invoke(It.IsAny<FilterProviderContext>())) .Callback<FilterProviderContext>( context => context.Results.AddRange(filters.Select(f => new FilterItem(null, f)))); var invoker = new ReflectedActionInvoker( actionContext, actionDescriptor, controllerFactory.Object, actionBindingContextProvider.Object, filterProvider.Object); return invoker; }
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); }
private void ValidateActionGroupConfiguration( IDictionary <MethodInfo, IDictionary <ReflectedActionModel, IList <ReflectedActionDescriptor> > > methodMap, ReflectedActionDescriptor actionDescriptor, IDictionary <MethodInfo, string> routingConfigurationErrors) { string combinedErrorMessage = null; var hasAttributeRoutedActions = false; var hasConventionallyRoutedActions = false; var invalidHttpMethodActions = new Dictionary <ReflectedActionModel, IEnumerable <string> >(); var actionsForMethod = methodMap[actionDescriptor.MethodInfo]; foreach (var reflectedAction in actionsForMethod) { foreach (var action in reflectedAction.Value) { if (IsAttributeRoutedAction(action)) { hasAttributeRoutedActions = true; } else { hasConventionallyRoutedActions = true; } } // Keep a list of actions with possible invalid IHttpActionMethodProvider attributes // to generate an error in case the method generates attribute routed actions. ValidateActionHttpMethodProviders(reflectedAction.Key, invalidHttpMethodActions); } // Validate that no method result in attribute and non attribute actions at the same time. // By design, mixing attribute and conventionally actions in the same method is not allowed. // This is for example the case when someone uses[HttpGet("Products")] and[HttpPost] // on the same method. if (hasAttributeRoutedActions && hasConventionallyRoutedActions) { combinedErrorMessage = CreateMixedRoutedActionDescriptorsErrorMessage( actionDescriptor, actionsForMethod); } // Validate that no method that creates attribute routed actions and // also uses attributes that only constrain the set of HTTP methods. For example, // if an attribute that implements IActionHttpMethodProvider but does not implement // IRouteTemplateProvider is used with an attribute that implements IRouteTemplateProvider on // the same action, the HTTP methods provided by the attribute that only implements // IActionHttpMethodProvider would be silently ignored, so we choose to throw to // inform the user of the invalid configuration. if (hasAttributeRoutedActions && invalidHttpMethodActions.Any()) { var errorMessage = CreateInvalidActionHttpMethodProviderErrorMessage( actionDescriptor, invalidHttpMethodActions, actionsForMethod); combinedErrorMessage = CombineErrorMessage(combinedErrorMessage, errorMessage); } if (combinedErrorMessage != null) { routingConfigurationErrors.Add(actionDescriptor.MethodInfo, combinedErrorMessage); } }
private static bool IsAttributeRoutedAction(ReflectedActionDescriptor actionDescriptor) { return(actionDescriptor.AttributeRouteInfo != null && actionDescriptor.AttributeRouteInfo.Template != null); }