private ControllerActionDescriptor CreateActionDescriptor( string httpMethod, string routeTemplate, string actionFixtureName) { var descriptor = new ControllerActionDescriptor(); descriptor.SetProperty(new ApiDescriptionActionData()); descriptor.DisplayName = actionFixtureName; descriptor.ActionConstraints = new List<IActionConstraintMetadata> { new HttpMethodConstraint(new[] { httpMethod }) }; descriptor.AttributeRouteInfo = new AttributeRouteInfo { Template = routeTemplate }; descriptor.MethodInfo = typeof(ActionFixtures).GetMethod(actionFixtureName); if (descriptor.MethodInfo == null) throw new InvalidOperationException( string.Format("{0} is not declared in ActionFixtures", actionFixtureName)); descriptor.Parameters = descriptor.MethodInfo.GetParameters() .Select(paramInfo => new ParameterDescriptor { Name = paramInfo.Name, ParameterType = paramInfo.ParameterType, BindingInfo = BindingInfo.GetBindingInfo(paramInfo.GetCustomAttributes(false)) }) .ToList(); // Set some additional properties - typically done via IApplicationModelConvention var attributes = descriptor.MethodInfo.GetCustomAttributes(true); descriptor.Properties.Add("ActionAttributes", attributes); descriptor.Properties.Add("IsObsolete", attributes.OfType<ObsoleteAttribute>().Any()); return descriptor; }
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 ReplaceAttributeRouteTokens( ControllerActionDescriptor actionDescriptor, IList <string> routeTemplateErrors) { try { actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens( actionDescriptor.AttributeRouteInfo.Template, actionDescriptor.RouteValueDefaults); } catch (InvalidOperationException ex) { // Routing will throw an InvalidOperationException here if we can't parse/replace tokens // in the template. var message = Resources.FormatAttributeRoute_IndividualErrorMessage( actionDescriptor.DisplayName, Environment.NewLine, ex.Message); routeTemplateErrors.Add(message); } }
public static void AddRouteConstraints( ControllerActionDescriptor actionDescriptor, ControllerModel controller, ActionModel action) { 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", string.Empty)); } }
private static void AddApiExplorerInfo( ControllerActionDescriptor actionDescriptor, ActionModel action, ControllerModel controller) { var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false; if (apiExplorerIsVisible) { if (!IsAttributeRoutedAction(actionDescriptor)) { // ApiExplorer is only supported on attribute routed actions. throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction( actionDescriptor.DisplayName)); } var apiExplorerActionData = new ApiDescriptionActionData() { GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName, }; actionDescriptor.SetProperty(apiExplorerActionData); } }
private static bool IsAttributeRoutedAction(ControllerActionDescriptor actionDescriptor) { return(actionDescriptor.AttributeRouteInfo?.Template != null); }
public static void AddRouteConstraints( ISet <string> removalConstraints, 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 constraintAttribute in action.RouteConstraints) { if (constraintAttribute.BlockNonAttributedActions) { removalConstraints.Add(constraintAttribute.RouteKey); } // Skip duplicates if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll) { actionDescriptor.RouteConstraints.Add( RouteDataActionConstraint.CreateCatchAll( constraintAttribute.RouteKey)); } else { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( constraintAttribute.RouteKey, constraintAttribute.RouteValue)); } } } foreach (var constraintAttribute in controller.RouteConstraints) { if (constraintAttribute.BlockNonAttributedActions) { removalConstraints.Add(constraintAttribute.RouteKey); } // Skip duplicates - this also means that a value on the action will take precedence if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey)) { if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll) { actionDescriptor.RouteConstraints.Add( RouteDataActionConstraint.CreateCatchAll( constraintAttribute.RouteKey)); } else { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( constraintAttribute.RouteKey, constraintAttribute.RouteValue)); } } } // Lastly add the 'default' values if (!HasConstraint(actionDescriptor.RouteConstraints, "action")) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "action", action.ActionName ?? string.Empty)); } if (!HasConstraint(actionDescriptor.RouteConstraints, "controller")) { actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( "controller", controller.ControllerName)); } }
public TestControllerActionInvoker( ActionContext actionContext, IFilterProvider[] filterProvider, MockControllerFactory controllerFactory, ControllerActionDescriptor descriptor, IReadOnlyList<IInputFormatter> inputFormatters, IReadOnlyList<IOutputFormatter> outputFormatters, IControllerActionArgumentBinder controllerActionArgumentBinder, IReadOnlyList<IModelBinder> modelBinders, IReadOnlyList<IModelValidatorProvider> modelValidatorProviders, IReadOnlyList<IValueProviderFactory> valueProviderFactories, IScopedInstance<ActionBindingContext> actionBindingContext, ITempDataDictionary tempData, ILoggerFactory loggerFactory, int maxAllowedErrorsInModelState) : base( actionContext, filterProvider, controllerFactory, descriptor, inputFormatters, outputFormatters, controllerActionArgumentBinder, modelBinders, modelValidatorProviders, valueProviderFactories, actionBindingContext, tempData, loggerFactory, maxAllowedErrorsInModelState) { ControllerFactory = controllerFactory; }
public async Task Invoke_UsesDefaultValuesIfNotBound() { // Arrange var actionDescriptor = new ControllerActionDescriptor { ControllerTypeInfo = typeof(TestController).GetTypeInfo(), BoundProperties = new List<ParameterDescriptor>(), MethodInfo = typeof(TestController).GetTypeInfo() .DeclaredMethods .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)), Parameters = new List<ParameterDescriptor> { new ParameterDescriptor { Name = "value", ParameterType = typeof(int), BindingInfo = new BindingInfo(), } }, FilterDescriptors = new List<FilterDescriptor>() }; var binder = new Mock<IModelBinder>(); binder.Setup(b => b.BindModelAsync(It.IsAny<ModelBindingContext>())) .Returns(Task.FromResult<ModelBindingResult>(result: null)); var context = new Mock<HttpContext>(); context.SetupGet(c => c.Items) .Returns(new Dictionary<object, object>()); context.Setup(c => c.RequestServices.GetService(typeof(ITempDataDictionary))) .Returns(new Mock<ITempDataDictionary>().Object); context.Setup(c => c.RequestServices.GetService(typeof(ILoggerFactory))) .Returns(new NullLoggerFactory()); var actionContext = new ActionContext(context.Object, new RouteData(), actionDescriptor); var controllerFactory = new Mock<IControllerFactory>(); controllerFactory.Setup(c => c.CreateController(It.IsAny<ActionContext>())) .Returns(new TestController()); var metadataProvider = new EmptyModelMetadataProvider(); var invoker = new ControllerActionInvoker( actionContext, new List<IFilterProvider>(), controllerFactory.Object, actionDescriptor, new IInputFormatter[0], new IOutputFormatter[0], new DefaultControllerActionArgumentBinder( metadataProvider, new DefaultObjectValidator(new IExcludeTypeValidationFilter[0], metadataProvider)), new IModelBinder[] { binder.Object }, new IModelValidatorProvider[0], new IValueProviderFactory[0], new MockScopedInstance<ActionBindingContext>(), Mock.Of<ITempDataDictionary>(), new NullLoggerFactory(), 200); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(5, context.Object.Items["Result"]); }
private TestControllerActionInvoker CreateInvoker( IFilter[] filters, bool actionThrows = false, ITempDataDictionary tempData = null, int maxAllowedErrorsInModelState = 200) { var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List<FilterDescriptor>(), Parameters = new List<ParameterDescriptor>(), }; if (actionThrows) { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ThrowingActionMethod"); } else { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod"); } tempData = tempData ?? new Mock<ITempDataDictionary>().Object; var httpContext = new Mock<HttpContext>(MockBehavior.Loose); var httpRequest = new DefaultHttpContext().Request; var httpResponse = new DefaultHttpContext().Response; httpContext.SetupGet(c => c.Request).Returns(httpRequest); httpContext.SetupGet(c => c.Response).Returns(httpResponse); httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary))) .Returns(tempData); httpContext.Setup(o => o.RequestServices.GetService(typeof(ILogger<ObjectResult>))) .Returns(new Mock<ILogger<ObjectResult>>().Object); httpResponse.Body = new MemoryStream(); var formatter = new Mock<IOutputFormatter>(); formatter .Setup(f => f.CanWriteResult(It.IsAny<OutputFormatterContext>(), It.IsAny<MediaTypeHeaderValue>())) .Returns(true); formatter .Setup(f => f.WriteAsync(It.IsAny<OutputFormatterContext>())) .Returns<OutputFormatterContext>(async c => { await c.HttpContext.Response.WriteAsync(c.Object.ToString()); }); var options = new MvcOptions(); options.OutputFormatters.Add(formatter.Object); var optionsAccessor = new Mock<IOptions<MvcOptions>>(); optionsAccessor .SetupGet(o => o.Options) .Returns(options); httpContext .Setup(o => o.RequestServices.GetService(typeof(IOptions<MvcOptions>))) .Returns(optionsAccessor.Object); var actionContext = new ActionContext( httpContext: httpContext.Object, routeData: new RouteData(), actionDescriptor: actionDescriptor); var filterProvider = new Mock<IFilterProvider>(MockBehavior.Strict); filterProvider .Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>())) .Callback<FilterProviderContext>(context => { foreach (var filter in filters.Select(f => new FilterItem(null, f))) { context.Results.Add(filter); } }); filterProvider.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>())) .Verifiable(); filterProvider.SetupGet(fp => fp.Order) .Returns(DefaultOrder.DefaultFrameworkSortOrder); var invoker = new TestControllerActionInvoker( actionContext, new[] { filterProvider.Object }, new MockControllerFactory(this), actionDescriptor, new IInputFormatter[0], new IOutputFormatter[0], Mock.Of<IControllerActionArgumentBinder>(), new IModelBinder[0], new IModelValidatorProvider[0], new IValueProviderFactory[0], new MockScopedInstance<ActionBindingContext>(), tempData, new NullLoggerFactory(), maxAllowedErrorsInModelState); return invoker; }
public TestControllerActionInvoker( ActionContext actionContext, IFilterProvider[] filterProvider, MockControllerFactory controllerFactory, ControllerActionDescriptor descriptor, IInputFormattersProvider inputFormattersProvider, IControllerActionArgumentBinder controllerActionArgumentBinder, IModelBinderProvider modelBinderProvider, IModelValidatorProviderProvider modelValidatorProviderProvider, IValueProviderFactoryProvider valueProviderFactoryProvider, IScopedInstance<ActionBindingContext> actionBindingContext, ITempDataDictionary tempData) : base( actionContext, filterProvider, controllerFactory, descriptor, inputFormattersProvider, controllerActionArgumentBinder, modelBinderProvider, modelValidatorProviderProvider, valueProviderFactoryProvider, actionBindingContext, tempData) { ControllerFactory = controllerFactory; }
private TestControllerActionInvoker CreateInvoker( IFilter[] filters, bool actionThrows = false, ITempDataDictionary tempData = null) { var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List<FilterDescriptor>(), Parameters = new List<ParameterDescriptor>(), }; if (actionThrows) { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ThrowingActionMethod"); } else { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod("ActionMethod"); } tempData = tempData ?? new Mock<ITempDataDictionary>().Object; var httpContext = new Mock<HttpContext>(MockBehavior.Loose); var httpRequest = new DefaultHttpContext().Request; var httpResponse = new DefaultHttpContext().Response; var mockFormattersProvider = new Mock<IOutputFormattersProvider>(); mockFormattersProvider.SetupGet(o => o.OutputFormatters) .Returns( new List<IOutputFormatter>() { new JsonOutputFormatter() }); httpContext.SetupGet(c => c.Request).Returns(httpRequest); httpContext.SetupGet(c => c.Response).Returns(httpResponse); httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider))) .Returns(mockFormattersProvider.Object); httpContext.Setup(o => o.RequestServices.GetService(typeof(ITempDataDictionary))) .Returns(tempData); httpResponse.Body = new MemoryStream(); var options = new Mock<IOptions<MvcOptions>>(); options.SetupGet(o => o.Options) .Returns(new MvcOptions()); httpContext.Setup(o => o.RequestServices.GetService(typeof(IOptions<MvcOptions>))) .Returns(options.Object); var actionContext = new ActionContext( httpContext: httpContext.Object, routeData: new RouteData(), actionDescriptor: actionDescriptor); var filterProvider = new Mock<IFilterProvider>(MockBehavior.Strict); filterProvider .Setup(fp => fp.OnProvidersExecuting(It.IsAny<FilterProviderContext>())) .Callback<FilterProviderContext>(context => { foreach (var filter in filters.Select(f => new FilterItem(null, f))) { context.Results.Add(filter); } }); filterProvider.Setup(fp => fp.OnProvidersExecuted(It.IsAny<FilterProviderContext>())) .Verifiable(); filterProvider.SetupGet(fp => fp.Order) .Returns(DefaultOrder.DefaultFrameworkSortOrder); var inputFormattersProvider = new Mock<IInputFormattersProvider>(); inputFormattersProvider.SetupGet(o => o.InputFormatters) .Returns(new List<IInputFormatter>()); var excludeFilterProvider = new Mock<IValidationExcludeFiltersProvider>(); excludeFilterProvider.SetupGet(o => o.ExcludeFilters) .Returns(new List<IExcludeTypeValidationFilter>()); var invoker = new TestControllerActionInvoker( actionContext, new[] { filterProvider.Object }, new MockControllerFactory(this), actionDescriptor, inputFormattersProvider.Object, Mock.Of<IControllerActionArgumentBinder>(), new MockModelBinderProvider(), new MockModelValidatorProviderProvider(), new MockValueProviderFactoryProvider(), new MockScopedInstance<ActionBindingContext>(), tempData); return invoker; }
public TestControllerActionInvoker( ActionContext actionContext, IFilterProvider[] filterProvider, MockControllerFactory controllerFactory, ControllerActionDescriptor descriptor, IReadOnlyList<IInputFormatter> inputFormatters, IReadOnlyList<IOutputFormatter> outputFormatters, IControllerActionArgumentBinder controllerActionArgumentBinder, IReadOnlyList<IModelBinder> modelBinders, IReadOnlyList<IModelValidatorProvider> modelValidatorProviders, IReadOnlyList<IValueProviderFactory> valueProviderFactories, IActionBindingContextAccessor actionBindingContext, ILogger logger, INotifier notifier, int maxAllowedErrorsInModelState) : base( actionContext, filterProvider, controllerFactory, descriptor, inputFormatters, outputFormatters, controllerActionArgumentBinder, modelBinders, modelValidatorProviders, valueProviderFactories, actionBindingContext, logger, notifier, maxAllowedErrorsInModelState) { ControllerFactory = controllerFactory; }
private void ValidateActionGroupConfiguration( IDictionary <MethodInfo, IDictionary <ActionModel, IList <ControllerActionDescriptor> > > methodMap, ControllerActionDescriptor actionDescriptor, IDictionary <MethodInfo, string> routingConfigurationErrors) { string combinedErrorMessage = null; var hasAttributeRoutedActions = false; var hasConventionallyRoutedActions = false; var invalidHttpMethodActions = new Dictionary <ActionModel, 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); } }