public void Build_WithPropertiesSet_ActionOverwritesApplicationAndControllerModel() { // Arrange var applicationModel = new ApplicationModel(); applicationModel.Properties["test"] = "application"; var controller = new ControllerModel(typeof(TestController).GetTypeInfo(), new List<object>() { }); controller.Application = applicationModel; controller.Properties["test"] = "controller"; applicationModel.Controllers.Add(controller); var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction)); var actionModel = new ActionModel(methodInfo, new List<object>() { }); actionModel.Selectors.Add(new SelectorModel()); actionModel.Controller = controller; actionModel.Properties["test"] = "action"; controller.Actions.Add(actionModel); // Act var descriptors = ControllerActionDescriptorBuilder.Build(applicationModel); // Assert Assert.Equal("action", descriptors.Single().Properties["test"]); }
public void Apply(ControllerModel controller) { if (controller == null) { throw new ArgumentNullException(nameof(controller)); } if (IsConventionApplicable(controller)) { var newActions = new List<ActionModel>(); foreach (var action in controller.Actions) { SetHttpMethodFromConvention(action); // Action Name doesn't really come into play with attribute routed actions. However for a // non-attribute-routed action we need to create a 'named' version and an 'unnamed' version. if (!IsActionAttributeRouted(action)) { var namedAction = action; var unnamedAction = new ActionModel(namedAction); unnamedAction.RouteValues.Add("action", null); newActions.Add(unnamedAction); } } foreach (var action in newActions) { controller.Actions.Add(action); } } }
public void CopyConstructor_DoesDeepCopyOfOtherModels() { // Arrange var action = new ActionModel(typeof(TestController).GetMethod(nameof(TestController.Edit)), new List<object>()); var parameter = new ParameterModel(action.ActionMethod.GetParameters()[0], new List<object>()); parameter.Action = action; action.Parameters.Add(parameter); var route = new AttributeRouteModel(new HttpGetAttribute("api/Products")); action.Selectors.Add(new SelectorModel() { AttributeRouteModel = route }); var apiExplorer = action.ApiExplorer; apiExplorer.IsVisible = false; apiExplorer.GroupName = "group1"; // Act var action2 = new ActionModel(action); // Assert Assert.NotSame(action, action2.Parameters[0]); Assert.NotSame(apiExplorer, action2.ApiExplorer); Assert.NotSame(action.Selectors, action2.Selectors); Assert.NotNull(action2.Selectors); Assert.Single(action2.Selectors); Assert.NotSame(route, action2.Selectors[0].AttributeRouteModel); }
public void Apply(ActionModel action) { foreach (var param in action.Parameters) { if (param.Attributes.Any(p => p.GetType() == typeof(FromHeaderAttribute))) { param.Action.Properties["source"] = "From Header"; } } }
public void Apply(ActionModel action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } if (IsConventionApplicable(action.Controller)) { var optionalParameters = new HashSet<string>(); var uriBindingSource = (new FromUriAttribute()).BindingSource; foreach (var parameter in action.Parameters) { // Some IBindingSourceMetadata attributes like ModelBinder attribute return null // as their binding source. Special case to ensure we do not ignore them. if (parameter.BindingInfo?.BindingSource != null || parameter.Attributes.OfType<IBindingSourceMetadata>().Any()) { // This has a binding behavior configured, just leave it alone. } else if (CanConvertFromString(parameter.ParameterInfo.ParameterType)) { // Simple types are by-default from the URI. parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo(); parameter.BindingInfo.BindingSource = uriBindingSource; } else { // Complex types are by-default from the body. parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo(); parameter.BindingInfo.BindingSource = BindingSource.Body; } // For all non IOptionalBinderMetadata, which are not URL source (like FromQuery etc.) do not // participate in overload selection and hence are added to the hashset so that they can be // ignored in OverloadActionConstraint. var optionalMetadata = parameter.Attributes.OfType<IOptionalBinderMetadata>().SingleOrDefault(); if (parameter.ParameterInfo.HasDefaultValue && parameter.BindingInfo.BindingSource == uriBindingSource || optionalMetadata != null && optionalMetadata.IsOptional || optionalMetadata == null && parameter.BindingInfo.BindingSource != uriBindingSource) { optionalParameters.Add(parameter.ParameterName); } } action.Properties.Add("OptionalParameters", optionalParameters); } }
public void Apply(ActionModel action) { if (!action.Controller.ControllerType.IsSubclassOf(typeof(ServiceEndpoint))) return; foreach (var parameter in action.Parameters) { var paramType = parameter.ParameterInfo.ParameterType; if (typeof(ServiceRequest).IsAssignableFrom(typeof(ServiceRequest))) { if (!action.Filters.Any(x => x is JsonFilter)) action.Filters.Add(new JsonFilter()); break; } } }
public void Build_WithControllerPropertiesSet_AddsPropertiesWithBinderMetadataSet() { // Arrange var applicationModel = new ApplicationModel(); var controller = new ControllerModel( typeof(TestController).GetTypeInfo(), new List<object>() { }); var propertyInfo = controller.ControllerType.AsType().GetProperty("BoundProperty"); controller.ControllerProperties.Add( new PropertyModel( propertyInfo, new List<object>() { }) { BindingInfo = BindingInfo.GetBindingInfo(new object[] { new FromQueryAttribute() }), PropertyName = "BoundProperty" }); controller.ControllerProperties.Add( new PropertyModel( controller.ControllerType.AsType().GetProperty("UnboundProperty"), new List<object>() { })); controller.Application = applicationModel; applicationModel.Controllers.Add(controller); var methodInfo = typeof(TestController).GetMethod(nameof(TestController.SomeAction)); var actionModel = new ActionModel(methodInfo, new List<object>() { }); actionModel.Selectors.Add(new SelectorModel()); actionModel.Controller = controller; controller.Actions.Add(actionModel); // Act var descriptors = ControllerActionDescriptorBuilder.Build(applicationModel); // Assert var controllerDescriptor = Assert.Single(descriptors); var parameter = Assert.Single(controllerDescriptor.BoundProperties); var property = Assert.IsType<ControllerBoundPropertyDescriptor>(parameter); Assert.Equal("BoundProperty", property.Name); Assert.Equal(propertyInfo, property.PropertyInfo); Assert.Equal(typeof(string), property.ParameterType); Assert.Equal(BindingSource.Query, property.BindingInfo.BindingSource); }
private bool IsActionAttributeRouted(ActionModel action) { foreach (var controllerSelectorModel in action.Controller.Selectors) { if (controllerSelectorModel.AttributeRouteModel?.Template != null) { return true; } } foreach (var actionSelectorModel in action.Selectors) { if (actionSelectorModel.AttributeRouteModel?.Template != null) { return true; } } return false; }
public ActionModel(ActionModel other) { if (other == null) { throw new ArgumentNullException(nameof(other)); } ActionMethod = other.ActionMethod; ActionName = other.ActionName; // Not making a deep copy of the controller, this action still belongs to the same controller. Controller = other.Controller; // These are just metadata, safe to create new collections Attributes = new List<object>(other.Attributes); Filters = new List<IFilterMetadata>(other.Filters); Properties = new Dictionary<object, object>(other.Properties); RouteValues = new Dictionary<string, string>(other.RouteValues, StringComparer.OrdinalIgnoreCase); // Make a deep copy of other 'model' types. ApiExplorer = new ApiExplorerModel(other.ApiExplorer); Parameters = new List<ParameterModel>(other.Parameters.Select(p => new ParameterModel(p))); Selectors = new List<SelectorModel>(other.Selectors.Select(s => new SelectorModel(s))); }
public void Apply(ActionModel model) { model.Properties["description"] = _value; }
public void CopyConstructor_CopiesAllProperties() { // Arrange var action = new ActionModel( typeof(TestController).GetMethod("Edit"), new List<object>() { new HttpGetAttribute(), new MyFilterAttribute(), }); var selectorModel = new SelectorModel(); selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" })); action.Selectors.Add(selectorModel); action.ActionName = "Edit"; action.Controller = new ControllerModel(typeof(TestController).GetTypeInfo(), new List<object>()); action.Filters.Add(new MyFilterAttribute()); action.RouteConstraints.Add(new MyRouteConstraintAttribute()); action.Properties.Add(new KeyValuePair<object, object>("test key", "test value")); // Act var action2 = new ActionModel(action); // Assert foreach (var property in typeof(ActionModel).GetProperties()) { // Reflection is used to make sure the test fails when a new property is added. if (property.Name.Equals("ApiExplorer") || property.Name.Equals("Selectors") || property.Name.Equals("Parameters")) { // This test excludes other ApplicationModel objects on purpose because we deep copy them. continue; } var value1 = property.GetValue(action); var value2 = property.GetValue(action2); if (typeof(IEnumerable<object>).IsAssignableFrom(property.PropertyType)) { Assert.Equal<object>((IEnumerable<object>)value1, (IEnumerable<object>)value2); // Ensure non-default value Assert.NotEmpty((IEnumerable<object>)value1); } else if (typeof(IDictionary<object, object>).IsAssignableFrom(property.PropertyType)) { Assert.Equal(value1, value2); // Ensure non-default value Assert.NotEmpty((IDictionary<object, object>)value1); } else if (property.PropertyType.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(property.PropertyType) != null) { Assert.Equal(value1, value2); // Ensure non-default value Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType)); } else { Assert.Same(value1, value2); // Ensure non-default value Assert.NotNull(value1); } } }
public static T GetProperty <T>(this ActionModel action)
public void Apply(ActionModel action) { action.ActionName = "ChangedAction"; }
public void CopyConstructor_CopiesAllProperties() { // Arrange var action = new ActionModel( typeof(TestController).GetMethod("Edit"), new List <object>() { new HttpGetAttribute(), new MyFilterAttribute(), }); var selectorModel = new SelectorModel(); selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new string[] { "GET" })); action.Selectors.Add(selectorModel); action.ActionName = "Edit"; action.Controller = new ControllerModel (typeof(TestController).GetTypeInfo(), new List <object>()); action.Filters.Add(new MyFilterAttribute()); action.RouteValues.Add("key", "value"); action.Properties.Add(new KeyValuePair <object, object>("test key", "test value")); // Act var action2 = new ActionModel(action); // Assert foreach (var property in typeof(ActionModel).GetProperties()) { // Reflection is used to make sure the test fails when a new property is added. if (property.Name.Equals("ApiExplorer") || property.Name.Equals("Selectors") || property.Name.Equals("Parameters")) { // This test excludes other ApplicationModel objects on purpose because we deep copy them. continue; } var value1 = property.GetValue(action); var value2 = property.GetValue(action2); if (typeof(IEnumerable <object>).IsAssignableFrom(property.PropertyType)) { Assert.Equal <object>((IEnumerable <object>)value1, (IEnumerable <object>)value2); // Ensure non-default value Assert.NotEmpty((IEnumerable <object>)value1); } else if (typeof(IDictionary <string, string>).IsAssignableFrom(property.PropertyType)) { Assert.Equal(value1, value2); // Ensure non-default value Assert.NotEmpty((IDictionary <string, string>)value1); } else if (typeof(IDictionary <object, object>).IsAssignableFrom(property.PropertyType)) { Assert.Equal(value1, value2); // Ensure non-default value Assert.NotEmpty((IDictionary <object, object>)value1); } else if (property.PropertyType.GetTypeInfo().IsValueType || Nullable.GetUnderlyingType(property.PropertyType) != null) { Assert.Equal(value1, value2); // Ensure non-default value Assert.NotEqual(value1, Activator.CreateInstance(property.PropertyType)); } else if (property.Name.Equals(nameof(ActionModel.DisplayName))) { // DisplayName is re-calculated, hence reference equality wouldn't work. Assert.Equal(value1, value2); } else { Assert.Same(value1, value2); // Ensure non-default value Assert.NotNull(value1); } } }
/// <summary> /// Determines if this instance of <see cref="IActionModelConvention"/> applies to a specified <paramref name="action"/>. /// </summary> /// <param name="action">The <see cref="ActionModel"/>.</param> /// <returns> /// <see langword="true"/> if the convention applies, otherwise <see langword="false"/>. /// Derived types may override this method to selectively apply this convention. /// </returns> protected virtual bool ShouldApply(ActionModel action) => true;
private static TValue GetProperty <TValue>(ActionModel action) { return(Assert.IsType <TValue>(action.Properties[typeof(TValue)])); }
private static void NormalizeSelectorRoutes(string moduleName, string controllerName, ActionModel action) { foreach (var selector in action.Selectors) { if (selector.AttributeRouteModel == null) { selector.AttributeRouteModel = CreateAbpServiceAttributeRouteModel( moduleName, controllerName, action ); } } }
private void ConfigureSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpServiceControllerSetting configuration) { RemoveEmptySelectors(action.Selectors); if (!action.Selectors.Any()) { AddAbpServiceSelector(moduleName, controllerName, action, configuration); } else { NormalizeSelectorRoutes(moduleName, controllerName, action); } }
private static bool CanUseFormBodyBinding(ActionModel action) { foreach (var selector in action.Selectors) { if (selector.ActionConstraints == null) { continue; } foreach (var actionConstraint in selector.ActionConstraints) { var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint; if (httpMethodActionConstraint == null) { continue; } if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD"))) { return false; } } } return true; }
public void Apply(ActionModel model) { model.ActionName = _actionName; }
private void SetHttpMethodFromConvention(ActionModel action) { foreach (var selector in action.Selectors) { if (selector.ActionConstraints.OfType<HttpMethodActionConstraint>().Count() > 0) { // If the HttpMethods are set from attributes, don't override it with the convention return; } } // The Method name is used to infer verb constraints. Changing the action name has no impact. foreach (var verb in SupportedHttpMethodConventions) { if (action.ActionMethod.Name.StartsWith(verb, StringComparison.OrdinalIgnoreCase)) { foreach (var selector in action.Selectors) { selector.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb })); } return; } } // If no convention matches, then assume POST foreach (var actionSelectorModel in action.Selectors) { actionSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { "POST" })); } }
private void ConfigureApiExplorer(ActionModel action) { if (action.ApiExplorer.IsVisible == null) { var remoteServiceAtt = ReflectionHelper.GetSingleAttributeOrDefault<RemoteServiceAttribute>(action.ActionMethod); action.ApiExplorer.IsVisible = remoteServiceAtt?.IsEnabledFor(action.ActionMethod); } }
private bool CanUseFormBodyBinding(ActionModel action, ParameterModel parameter) { if (_configuration.Value.FormBodyBindingIgnoredTypes.Any(t => t.IsAssignableFrom(parameter.ParameterInfo.ParameterType))) { return false; } foreach (var selector in action.Selectors) { if (selector.ActionConstraints == null) { continue; } foreach (var actionConstraint in selector.ActionConstraints) { var httpMethodActionConstraint = actionConstraint as HttpMethodActionConstraint; if (httpMethodActionConstraint == null) { continue; } if (httpMethodActionConstraint.HttpMethods.All(hm => hm.IsIn("GET", "DELETE", "TRACE", "HEAD"))) { return false; } } } return true; }
private void AddAbpServiceSelector(string moduleName, string controllerName, ActionModel action, [CanBeNull] AbpServiceControllerSetting configuration) { var abpServiceSelectorModel = new SelectorModel { AttributeRouteModel = CreateAbpServiceAttributeRouteModel(moduleName, controllerName, action) }; var verb = configuration?.UseConventionalHttpVerbs == true ? ProxyScriptingHelper.GetConventionalVerbForMethodName(action.ActionName) : ProxyScriptingHelper.DefaultHttpVerb; abpServiceSelectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(new[] { verb })); action.Selectors.Add(abpServiceSelectorModel); }
public void Apply(ActionModel action) { action.Properties.Add("TestProperty", "TestValue"); }
private static AttributeRouteModel CreateAbpServiceAttributeRouteModel(string moduleName, string controllerName, ActionModel action) { return new AttributeRouteModel( new RouteAttribute( $"api/services/{moduleName}/{controllerName}/{action.ActionName}" ) ); }
/// <summary> /// Creates the <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/>. /// </summary> /// <param name="typeInfo">The controller <see cref="TypeInfo"/>.</param> /// <param name="methodInfo">The action <see cref="MethodInfo"/>.</param> /// <returns> /// An <see cref="ActionModel"/> instance for the given action <see cref="MethodInfo"/> or /// <c>null</c> if the <paramref name="methodInfo"/> does not represent an action. /// </returns> internal ActionModel?CreateActionModel( TypeInfo typeInfo, MethodInfo methodInfo) { if (typeInfo == null) { throw new ArgumentNullException(nameof(typeInfo)); } if (methodInfo == null) { throw new ArgumentNullException(nameof(methodInfo)); } if (!IsAction(typeInfo, methodInfo)) { return(null); } var attributes = methodInfo.GetCustomAttributes(inherit: true); var actionModel = new ActionModel(methodInfo, attributes); AddRange(actionModel.Filters, attributes.OfType <IFilterMetadata>()); var actionName = attributes.OfType <ActionNameAttribute>().FirstOrDefault(); if (actionName?.Name != null) { actionModel.ActionName = actionName.Name; } else { actionModel.ActionName = CanonicalizeActionName(methodInfo.Name); } var apiVisibility = attributes.OfType <IApiDescriptionVisibilityProvider>().FirstOrDefault(); if (apiVisibility != null) { actionModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; } var apiGroupName = attributes.OfType <IApiDescriptionGroupNameProvider>().FirstOrDefault(); if (apiGroupName != null) { actionModel.ApiExplorer.GroupName = apiGroupName.GroupName; } foreach (var routeValueProvider in attributes.OfType <IRouteValueProvider>()) { actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); } // Now we need to determine the action selection info (cross-section of routes and constraints) // // For attribute routes on a action, we want to support 'overriding' routes on a // virtual method, but allow 'overriding'. So we need to walk up the hierarchy looking // for the first definition to define routes. // // Then we want to 'filter' the set of attributes, so that only the effective routes apply. var currentMethodInfo = methodInfo; IRouteTemplateProvider[] routeAttributes; while (true) { routeAttributes = currentMethodInfo .GetCustomAttributes(inherit: false) .OfType <IRouteTemplateProvider>() .ToArray(); if (routeAttributes.Length > 0) { // Found 1 or more route attributes. break; } // GetBaseDefinition returns 'this' when it gets to the bottom of the chain. var nextMethodInfo = currentMethodInfo.GetBaseDefinition(); if (currentMethodInfo == nextMethodInfo) { break; } currentMethodInfo = nextMethodInfo; } // This is fairly complicated so that we maintain referential equality between items in // ActionModel.Attributes and ActionModel.Attributes[*].Attribute. var applicableAttributes = new List <object>(routeAttributes.Length); foreach (var attribute in attributes) { if (attribute is IRouteTemplateProvider) { // This attribute is a route-attribute, leave it out. } else { applicableAttributes.Add(attribute); } } applicableAttributes.AddRange(routeAttributes); AddRange(actionModel.Selectors, CreateSelectors(applicableAttributes)); return(actionModel); }