// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.ComponentModel; using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading; using System.Web.Http.Controllers; using System.Web.Http.ValueProviders; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http.ModelBinding { // These tests primarily focus on getting the right binding contract. They don't actually execute the contract. public class DefaultActionValueBinderTest { [Fact] public void BindValuesAsync_Throws_Null_ActionDescriptor() { // Arrange HttpActionDescriptor actionDescriptor = new ReflectedHttpActionDescriptor { MethodInfo = (MethodInfo)MethodInfo.GetCurrentMethod() }; // Act and Assert Assert.ThrowsArgumentNull( () => new DefaultActionValueBinder().GetBinding(null), "actionDescriptor"); } private void Action_Int(int id) { } [Fact] public void Check_Int_Is_ModelBound() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Int")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_Int_FromUri([FromUri] int id) { } [Fact] public void Check_Explicit_Int_Is_ModelBound() { // Even though int is implicitly model bound, still ok to specify it explicitly DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Int_FromUri")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } // All types in this signature are model bound private void Action_SimpleTypes(char ch, Byte b, Int16 i16, UInt16 u16, Int32 i32, UInt32 u32, Int64 i64, UInt64 u64, string s, DateTime d, Decimal dec, Guid g, DateTimeOffset dateTimeOffset, TimeSpan timespan) { } [Fact] public void Check_SimpleTypes_Are_ModelBound() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_SimpleTypes")); for(int i = 0; i < binding.ParameterBindings.Length; i++) { AssertIsModelBound(binding, 0); } } private void Action_ComplexTypeWithStringConverter(ComplexTypeWithStringConverter x) { } [Fact] public void Check_String_TypeConverter_Is_ModelBound() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_ComplexTypeWithStringConverter")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_ComplexTypeWithStringConverter_Body_Override([FromBody] ComplexTypeWithStringConverter x) { } [Fact] public void Check_String_TypeConverter_With_Body_Override() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_ComplexTypeWithStringConverter_Body_Override")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_NullableInt(Nullable id) { } [Fact] public void Check_NullableInt_Is_ModelBound() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_NullableInt")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_Nullable_ValueType(Nullable id) { } [Fact] public void Check_Nullable_ValueType_Is_FromBody() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Nullable_ValueType")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_IntArray(int[] arrayFrombody) { } [Fact] public void Check_IntArray_Is_FromBody() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_IntArray")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_SimpleType_Body([FromBody] int i) { } [Fact] public void Check_SimpleType_Body() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_SimpleType_Body")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_Empty() { } [Fact] public void Check_Empty_Action() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Empty")); Assert.NotNull(binding.ParameterBindings); Assert.Equal(0, binding.ParameterBindings.Length); } private void Action_String_String(string s1, string s2) { } [Fact] public void Check_String_String_IsModelBound() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_String_String")); Assert.Equal(2, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); AssertIsModelBound(binding, 1); } private void Action_Complex_Type(ComplexType complex) { } [Fact] public void Check_Complex_Type_FromBody() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Complex_Type")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_Complex_ValueType(ComplexValueType complex) { } [Fact] public void Check_Complex_ValueType_FromBody() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Complex_ValueType")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsBody(binding, 0); } private void Action_Default_Custom_Model_Binder([ModelBinder] ComplexType complex) { } [Fact] public void Check_Customer_Binder() { // Mere presence of a ModelBinder attribute means the type is model bound. DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Default_Custom_Model_Binder")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_Complex_Type_Uri([FromUri] ComplexType complex) { } [Fact] public void Check_Complex_Type_FromUri() { // [FromUri] is just a specific instance of ModelBinder attribute DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Complex_Type_Uri")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_Two_Complex_Types(ComplexType complexBody1, ComplexType complexBody2) { } [Fact] public void Check_Two_Complex_Types_FromBody() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); // It's illegal to have multiple parameters from the body. // But we should still be able to get a binding for it. We just can't execute it. var binding = binder.GetBinding(GetAction("Action_Two_Complex_Types")); Assert.Equal(2, binding.ParameterBindings.Length); AssertIsError(binding, 0); AssertIsError(binding, 1); } private void Action_Complex_Type_UriAndBody([FromUri] ComplexType complexUri, ComplexType complexBody) { } [Fact] public void Check_Complex_Type_FromBody_And_FromUri() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Complex_Type_UriAndBody")); Assert.Equal(2, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); AssertIsBody(binding, 1); } private void Action_CancellationToken(CancellationToken ct) { } [Fact] public void Check_Cancellation_Token() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_CancellationToken")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsCancellationToken(binding, 0); } private void Action_CustomModelBinder_On_Parameter_WithProvider([ModelBinder(typeof(CustomModelBinderProvider))] ComplexType complex) { } [Fact] public void Check_CustomModelBinder_On_Parameter() { HttpConfiguration config = new HttpConfiguration(); config.Services.ReplaceRange(typeof(ValueProviderFactory), new ValueProviderFactory[] { new CustomValueProviderFactory(), }); DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_CustomModelBinder_On_Parameter_WithProvider", config)); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); ModelBinderParameterBinding p = (ModelBinderParameterBinding) binding.ParameterBindings[0]; Assert.IsType(p.ModelBinderProvider); // Since the ModelBinderAttribute didn't specify the valueproviders, we should pull those from config. Assert.Equal(1, p.ValueProviderFactories.Count()); Assert.IsType(p.ValueProviderFactories.First()); } // Model binder attribute is on the type's declaration. private void Action_ComplexParameter_With_ModelBinder(ComplexTypeWithModelBinder complex) { } [Fact] public void Check_Parameter_With_ModelBinder_Attribute_On_Type() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_ComplexParameter_With_ModelBinder")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsModelBound(binding, 0); } private void Action_Conflicting_Attributes([FromBody][FromUri] int i) { } [Fact] public void Error_Conflicting_Attributes() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Conflicting_Attributes")); // Have 2 attributes that conflict with each other. Still get the contract, but it has an error in it. Assert.Equal(1, binding.ParameterBindings.Length); AssertIsError(binding, 0); } private void Action_HttpContent_Parameter(HttpContent c) { } [Fact] public void Check_HttpContent() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_HttpContent_Parameter")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsError(binding, 0); } private void Action_Derived_HttpContent_Parameter(StreamContent c) { } [Fact] public void Check_Derived_HttpContent() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Derived_HttpContent_Parameter")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsError(binding, 0); } private void Action_Request_Parameter(HttpRequestMessage request) { } [Fact] public void Check_Request_Parameter() { DefaultActionValueBinder binder = new DefaultActionValueBinder(); var binding = binder.GetBinding(GetAction("Action_Request_Parameter")); Assert.Equal(1, binding.ParameterBindings.Length); AssertIsCustomBinder(binding, 0); } // Assert that the binding contract says the given parameter comes from the body private void AssertIsBody(HttpActionBinding binding, int paramIdx) { HttpParameterBinding p = binding.ParameterBindings[paramIdx]; Assert.NotNull(p); Assert.True(p.IsValid); Assert.True(p.WillReadBody); } // Assert that the binding contract says the given parameter is not from the body (will be handled by model binding) private void AssertIsModelBound(HttpActionBinding binding, int paramIdx) { HttpParameterBinding p = binding.ParameterBindings[paramIdx]; Assert.NotNull(p); Assert.IsType(p); Assert.True(p.IsValid); Assert.False(p.WillReadBody); } // Assert that the binding contract says the given parameter will be bound to the cancellation token. private void AssertIsCancellationToken(HttpActionBinding binding, int paramIdx) { AssertIsCustomBinder(binding, paramIdx); } private void AssertIsError(HttpActionBinding binding, int paramIdx) { HttpParameterBinding p = binding.ParameterBindings[paramIdx]; Assert.NotNull(p); Assert.False(p.IsValid); Assert.False(p.WillReadBody); } private void AssertIsCustomBinder(HttpActionBinding binding, int paramIdx) { HttpParameterBinding p = binding.ParameterBindings[paramIdx]; Assert.NotNull(p); Assert.IsType(p); Assert.True(p.IsValid); Assert.False(p.WillReadBody); } // Helper to get an ActionDescriptor for a method name. private HttpActionDescriptor GetAction(string name) { return GetAction(name, new HttpConfiguration()); } private HttpActionDescriptor GetAction(string name, HttpConfiguration config) { MethodInfo method = this.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); Assert.NotNull(method); return new ReflectedHttpActionDescriptor { MethodInfo = method, Configuration = config }; } // Complex type to use with tests class ComplexType { } struct ComplexValueType { } // Complex type to use with tests [ModelBinder] class ComplexTypeWithModelBinder { } // Add Type converter for string, which causes the type to be viewed as a Simple type. [TypeConverter(typeof(MyTypeConverter))] public class ComplexTypeWithStringConverter { public string Data { get; set; } public ComplexTypeWithStringConverter(string data) { Data = data; } } // A string type converter public class MyTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { if (value is string) { return new ComplexTypeWithStringConverter((string)value); } return base.ConvertFrom(context, culture, value); } } class CustomModelBinderProvider : ModelBinderProvider { public override IModelBinder GetBinder(HttpActionContext actionContext, ModelBindingContext bindingContext) { throw new NotImplementedException(); } } class CustomValueProviderFactory : ValueProviderFactory { public override IValueProvider GetValueProvider(HttpActionContext actionContext) { throw new NotImplementedException(); } } } }