|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.IntegrationTests;
// Integration tests targeting the behavior of the GenericModelBinder and related classes
// with other model binders.
public class GenericModelBinderIntegrationTest
{
// This isn't an especially useful scenario - but it exercises what happens when you
// try to use a Collection of something that is bound greedily by model-type.
//
// In this example we choose IFormCollection because IFormCollection has a dedicated
// model binder.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_WithPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(List<IFormCollection>)
};
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?parameter.index=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<IFormCollection>>(modelBindingResult.Model);
var formCollection = Assert.Single(model);
Assert.NotNull(formCollection);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
}
// This isn't an especially useful scenario - but it exercises what happens when you
// try to use a Collection of something that is bound greedily by model-type.
//
// In this example we choose IFormCollection - because IFormCollection has a dedicated
// model binder.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_EmptyPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(List<IFormCollection>)
};
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?index=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<IFormCollection>>(modelBindingResult.Model);
var formCollection = Assert.Single(model);
Assert.NotNull(formCollection);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
Assert.Empty(modelState);
}
// This isn't an especially useful scenario - but it exercises what happens when you
// try to use a Collection of something that is bound greedily by model-type.
//
// In this example we choose IFormCollection - because IFormCollection has a dedicated
// model binder.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeFromGreedyModelBinder_NoData()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(List<IFormCollection>)
};
// Without a key here so the GenericModelBinder will not recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<IFormCollection>>(modelBindingResult.Model);
Assert.Empty(model);
}
[BindAddress]
private class Address
{
}
private class BindAddressAttribute : Attribute, IBindingSourceMetadata
{
public static readonly BindingSource Source = new BindingSource(
"Address",
displayName: "Address",
isGreedy: true,
isFromRequest: true);
public BindingSource BindingSource
{
get
{
return Source;
}
}
}
private class AddressBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
var allowedBindingSource = context.BindingInfo.BindingSource;
if (allowedBindingSource?.CanAcceptDataFrom(BindAddressAttribute.Source) == true)
{
// Binding Sources are opt-in. This model either didn't specify one or specified something
// incompatible so let other binders run.
return new AddressBinder();
}
return null;
}
}
private class AddressBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
ArgumentNullException.ThrowIfNull(bindingContext);
Debug.Assert(bindingContext.Result == ModelBindingResult.Failed());
var allowedBindingSource = bindingContext.BindingSource;
if (allowedBindingSource == null ||
!allowedBindingSource.CanAcceptDataFrom(BindAddressAttribute.Source))
{
// Binding Sources are opt-in. This model either didn't specify one or specified something
// incompatible so let other binders run.
return Task.CompletedTask;
}
bindingContext.Result = ModelBindingResult.Success(new Address());
return Task.CompletedTask;
}
}
// This isn't an especially useful scenario - but it exercises what happens when you
// try to use a Collection of something that is bound greedily by binding source.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeUsesGreedyModelBinder_WithPrefix_Success()
{
// Arrange
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(
request => request.QueryString = new QueryString("?parameter.index=0"));
var modelState = testContext.ModelState;
var parameterBinder = ModelBindingTestHelper.GetParameterBinder(
testContext.MvcOptions,
new AddressBinderProvider());
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Address[])
};
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Address[]>(modelBindingResult.Model);
Assert.Single(model);
Assert.NotNull(model[0]);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
// Similar to the GenericModelBinder_BindsCollection_ElementTypeUsesGreedyModelBinder_WithPrefix_Success
// scenario but mis-configured. Model using a BindingSource for which no ModelBinder is enabled.
[Fact]
public async Task GenericModelBinder_BindsCollection_ElementTypeUsesGreedyBindingSource_WithPrefix_NullElement()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Address[])
};
// Need to have a key here so that the GenericModelBinder will recurse to bind elements.
var testContext = ModelBindingTestHelper.GetTestContext(
request => request.QueryString = new QueryString("?parameter.index=0"));
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Address[]>(modelBindingResult.Model);
Assert.Single(model);
Assert.Null(model[0]);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsArrayOfDictionary_WithPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, int>[])
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?parameter[0][0].Key=key0¶meter[0][0].Value=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, int>[]>(modelBindingResult.Model);
var dictionary = Assert.Single(model);
var kvp = Assert.Single(dictionary);
Assert.Equal("key0", kvp.Key);
Assert.Equal(10, kvp.Value);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter[0][0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "parameter[0][0].Value").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsArrayOfDictionary_EmptyPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, int>[])
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?[0][0].Key=key0&[0][0].Value=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, int>[]>(modelBindingResult.Model);
var dictionary = Assert.Single(model);
var kvp = Assert.Single(dictionary);
Assert.Equal("key0", kvp.Key);
Assert.Equal(10, kvp.Value);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "[0][0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "[0][0].Value").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsArrayOfDictionary_NoData()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, int>[])
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, int>[]>(modelBindingResult.Model);
Assert.NotNull(model);
Assert.Empty(model);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_WithPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(ICollection<KeyValuePair<string, int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<KeyValuePair<string, int>>>(modelBindingResult.Model);
var kvp = Assert.Single(model);
Assert.Equal("key0", kvp.Key);
Assert.Equal(10, kvp.Value);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_EmptyPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(ICollection<KeyValuePair<string, int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?[0].Key=key0&[0].Value=10");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<KeyValuePair<string, int>>>(modelBindingResult.Model);
var kvp = Assert.Single(model);
Assert.Equal("key0", kvp.Key);
Assert.Equal(10, kvp.Value);
Assert.Equal(2, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "[0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "[0].Value").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsCollectionOfKeyValuePair_NoData()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(ICollection<KeyValuePair<string, int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<List<KeyValuePair<string, int>>>(modelBindingResult.Model);
Assert.NotNull(model);
Assert.Empty(model);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsDictionaryOfList_WithPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, List<int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString(
"?parameter[0].Key=key0¶meter[0].Value[0]=10¶meter[0].Value[1]=11");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, List<int>>>(modelBindingResult.Model);
var kvp = Assert.Single(model);
Assert.Equal("key0", kvp.Key);
Assert.Equal(new List<int>() { 10, 11 }, kvp.Value);
Assert.Equal(3, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value[0]").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value[1]").Value;
Assert.Equal("11", entry.AttemptedValue);
Assert.Equal("11", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsDictionaryOfList_EmptyPrefix_Success()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, List<int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?[0].Key=key0&[0].Value[0]=10&[0].Value[1]=11");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, List<int>>>(modelBindingResult.Model);
var kvp = Assert.Single(model);
Assert.Equal("key0", kvp.Key);
Assert.Equal(new List<int>() { 10, 11 }, kvp.Value);
Assert.Equal(3, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "[0].Key").Value;
Assert.Equal("key0", entry.AttemptedValue);
Assert.Equal("key0", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "[0].Value[0]").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
entry = Assert.Single(modelState, e => e.Key == "[0].Value[1]").Value;
Assert.Equal("11", entry.AttemptedValue);
Assert.Equal("11", entry.RawValue);
}
// This is part of a random sampling of scenarios where a GenericModelBinder is used
// recursively.
[Fact]
public async Task GenericModelBinder_BindsDictionaryOfList_NoData()
{
// Arrange
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Dictionary<string, List<int>>)
};
var testContext = ModelBindingTestHelper.GetTestContext(request =>
{
request.QueryString = new QueryString("?");
});
var modelState = testContext.ModelState;
// Act
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Dictionary<string, List<int>>>(modelBindingResult.Model);
Assert.NotNull(model);
Assert.Empty(model);
Assert.Empty(modelState);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
}
|