493 lines
17 KiB
C#
Raw Normal View History

// 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<int> 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<ComplexValueType> 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<CustomModelBinderProvider>(p.ModelBinderProvider);
// Since the ModelBinderAttribute didn't specify the valueproviders, we should pull those from config.
Assert.Equal(1, p.ValueProviderFactories.Count());
Assert.IsType<CustomValueProviderFactory>(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<HttpRequestParameterBinding>(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<ModelBinderParameterBinding>(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<CancellationTokenParameterBinding>(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<T>(HttpActionBinding binding, int paramIdx)
{
HttpParameterBinding p = binding.ParameterBindings[paramIdx];
Assert.NotNull(p);
Assert.IsType<T>(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();
}
}
}
}