|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.ObjectModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Moq;
namespace Microsoft.AspNetCore.Mvc.ModelBinding;
public class ModelBindingHelperTest
{
[Fact]
public async Task TryUpdateModel_ReturnsFalse_IfBinderIsUnsuccessful()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(ModelBindingResult.Failed());
var model = new MyModel();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
string.Empty,
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binder),
Mock.Of<IValueProvider>(),
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object);
// Assert
Assert.False(result);
Assert.Null(model.MyProperty);
}
[Fact]
public async Task TryUpdateModel_ReturnsFalse_IfModelValidationFails()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel();
var values = new Dictionary<string, object>
{
{ "", null }
};
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var actionContext = GetActionContext();
var modelState = actionContext.ModelState;
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
"",
actionContext,
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert
Assert.False(result);
var error = Assert.Single(modelState["MyProperty"].Errors);
Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage("MyProperty"), error.ErrorMessage);
}
[Fact]
public async Task TryUpdateModel_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel { MyProperty = "Old-Value" };
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" }
};
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
}
[Fact]
public async Task TryUpdateModel_UsingPropertyFilterOverload_ReturnsFalse_IfBinderIsUnsuccessful()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(ModelBindingResult.Failed());
var model = new MyModel();
Func<ModelMetadata, bool> propertyFilter = (m) => true;
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
string.Empty,
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binder),
Mock.Of<IValueProvider>(),
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
propertyFilter);
// Assert
Assert.False(result);
Assert.Null(model.MyProperty);
Assert.Null(model.IncludedProperty);
Assert.Null(model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModel_UsingPropertyFilterOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel
{
MyProperty = "Old-Value",
IncludedProperty = "Old-IncludedPropertyValue",
ExcludedProperty = "Old-ExcludedPropertyValue"
};
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" },
{ "IncludedProperty", "IncludedPropertyValue" },
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(m.PropertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
string.Equals(m.PropertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
propertyFilter);
// Assert
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsFalse_IfBinderIsUnsuccessful()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(ModelBindingResult.Failed());
var model = new MyModel();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
string.Empty,
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binder),
Mock.Of<IValueProvider>(),
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
m => m.IncludedProperty);
// Assert
Assert.False(result);
Assert.Null(model.MyProperty);
Assert.Null(model.IncludedProperty);
Assert.Null(model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModel_UsingIncludeExpressionOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel
{
MyProperty = "Old-Value",
IncludedProperty = "Old-IncludedPropertyValue",
ExcludedProperty = "Old-ExcludedPropertyValue"
};
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" },
{ "IncludedProperty", "IncludedPropertyValue" },
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
"",
GetActionContext(),
TestModelMetadataProvider.CreateDefaultProvider(),
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
m => m.IncludedProperty,
m => m.MyProperty);
// Assert
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModel_UsingDefaultIncludeOverload_IncludesAllProperties()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel
{
MyProperty = "Old-Value",
IncludedProperty = "Old-IncludedPropertyValue",
ExcludedProperty = "Old-ExcludedPropertyValue"
};
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" },
{ "IncludedProperty", "IncludedPropertyValue" },
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert
// Includes everything.
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
Assert.Equal("ExcludedPropertyValue", model.ExcludedProperty);
}
[Fact]
public void GetPropertyName_PropertyMemberAccessReturnsPropertyName()
{
// Arrange
Expression<Func<User, object>> expression = m => m.Address;
// Act
var propertyName = ModelBindingHelper.GetPropertyName(expression.Body);
// Assert
Assert.Equal(nameof(User.Address), propertyName);
}
[Fact]
public void GetPropertyName_ChainedExpression_Throws()
{
// Arrange
Expression<Func<User, object>> expression = m => m.Address.Street;
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ModelBindingHelper.GetPropertyName(expression.Body));
Assert.Equal(string.Format(
CultureInfo.CurrentCulture,
"The passed expression of expression node type '{0}' is invalid." +
" Only simple member access expressions for model properties are supported.",
expression.Body.NodeType),
ex.Message);
}
public static IEnumerable<object[]> InvalidExpressionDataSet
{
get
{
Expression<Func<User, object>> expression = m => new Func<User>(() => m);
yield return new object[] { expression }; // lambda expression.
expression = m => m.Save();
yield return new object[] { expression }; // method call expression.
expression = m => m.Friends[0]; // ArrayIndex expression.
yield return new object[] { expression };
expression = m => m.Colleagues[0]; // Indexer expression.
yield return new object[] { expression };
expression = m => m; // Parameter expression.
yield return new object[] { expression };
object someVariable = "something";
expression = m => someVariable; // Variable accessor.
yield return new object[] { expression };
}
}
[Theory]
[MemberData(nameof(InvalidExpressionDataSet))]
public void GetPropertyName_ExpressionsOtherThanMemberAccess_Throws(Expression<Func<User, object>> expression)
{
// Arrange Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ModelBindingHelper.GetPropertyName(expression.Body));
Assert.Equal(
$"The passed expression of expression node type '{expression.Body.NodeType}' is invalid." +
" Only simple member access expressions for model properties are supported.",
ex.Message);
}
[Fact]
public void GetPropertyName_NonParameterBasedExpression_Throws()
{
// Arrange
var someUser = new User();
// PropertyAccessor with a property name invalid as it originates from a variable accessor.
Expression<Func<User, object>> expression = m => someUser.Address;
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ModelBindingHelper.GetPropertyName(expression.Body));
Assert.Equal(
$"The passed expression of expression node type '{expression.Body.NodeType}' is invalid." +
" Only simple member access expressions for model properties are supported.",
ex.Message);
}
[Fact]
public void GetPropertyName_TopLevelCollectionIndexer_Throws()
{
// Arrange
Expression<Func<List<User>, object>> expression = m => m[0];
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ModelBindingHelper.GetPropertyName(expression.Body));
Assert.Equal(
$"The passed expression of expression node type '{expression.Body.NodeType}' is invalid." +
" Only simple member access expressions for model properties are supported.",
ex.Message);
}
[Fact]
public void GetPropertyName_FieldExpression_Throws()
{
// Arrange
Expression<Func<User, object>> expression = m => m._userId;
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() =>
ModelBindingHelper.GetPropertyName(expression.Body));
Assert.Equal(
$"The passed expression of expression node type '{expression.Body.NodeType}' is invalid." +
" Only simple member access expressions for model properties are supported.",
ex.Message);
}
[Fact]
public async Task TryUpdateModelNonGeneric_PropertyFilterOverload_ReturnsFalse_IfBinderIsUnsuccessful()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(ModelBindingResult.Failed());
var model = new MyModel();
Func<ModelMetadata, bool> propertyFilter = (m) => true;
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
model.GetType(),
prefix: "",
actionContext: GetActionContext(),
metadataProvider: metadataProvider,
modelBinderFactory: GetModelBinderFactory(binder),
valueProvider: Mock.Of<IValueProvider>(),
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
propertyFilter: propertyFilter);
// Assert
Assert.False(result);
Assert.Null(model.MyProperty);
Assert.Null(model.IncludedProperty);
Assert.Null(model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModelNonGeneric_PropertyFilterOverload_ReturnsTrue_ModelBindsAndValidatesSuccessfully()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel
{
MyProperty = "Old-Value",
IncludedProperty = "Old-IncludedPropertyValue",
ExcludedProperty = "Old-ExcludedPropertyValue"
};
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" },
{ "IncludedProperty", "IncludedPropertyValue" },
{ "ExcludedProperty", "ExcludedPropertyValue" }
};
Func<ModelMetadata, bool> propertyFilter = (m) =>
string.Equals(m.PropertyName, "IncludedProperty", StringComparison.OrdinalIgnoreCase) ||
string.Equals(m.PropertyName, "MyProperty", StringComparison.OrdinalIgnoreCase);
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
model.GetType(),
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()),
propertyFilter);
// Assert
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
Assert.Equal("IncludedPropertyValue", model.IncludedProperty);
Assert.Equal("Old-ExcludedPropertyValue", model.ExcludedProperty);
}
[Fact]
public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsFalse_IfBinderIsUnsuccessful()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder(ModelBindingResult.Failed());
var model = new MyModel();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
modelType: model.GetType(),
prefix: "",
actionContext: GetActionContext(),
metadataProvider: metadataProvider,
modelBinderFactory: GetModelBinderFactory(binder.Object),
valueProvider: Mock.Of<IValueProvider>(),
objectModelValidator: new Mock<IObjectModelValidator>(MockBehavior.Strict).Object);
// Assert
Assert.False(result);
Assert.Null(model.MyProperty);
}
[Fact]
public async Task TryUpdateModelNonGeneric_ModelTypeOverload_ReturnsTrue_IfModelBindsAndValidatesSuccessfully()
{
// Arrange
var binderProviders = new IModelBinderProvider[]
{
new SimpleTypeModelBinderProvider(),
new ComplexObjectModelBinderProvider(),
};
var validator = new DataAnnotationsModelValidatorProvider(
new ValidationAttributeAdapterProvider(),
Options.Create(new MvcDataAnnotationsLocalizationOptions()),
stringLocalizerFactory: null);
var model = new MyModel { MyProperty = "Old-Value" };
var values = new Dictionary<string, object>
{
{ "", null },
{ "MyProperty", "MyPropertyValue" }
};
var valueProvider = new TestValueProvider(values);
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
// Act
var result = await ModelBindingHelper.TryUpdateModelAsync(
model,
model.GetType(),
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binderProviders),
valueProvider,
new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()));
// Assert
Assert.True(result);
Assert.Equal("MyPropertyValue", model.MyProperty);
}
[Fact]
public async Task TryUpdateModel_ModelTypeDifferentFromModel_Throws()
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var binder = new StubModelBinder();
var model = new MyModel();
Func<ModelMetadata, bool> propertyFilter = (m) => true;
var modelName = model.GetType().FullName;
var userName = typeof(User).FullName;
var expectedMessage = $"The model's runtime type '{modelName}' is not assignable to the type '{userName}'.";
// Act & Assert
var exception = await ExceptionAssert.ThrowsArgumentAsync(
() => ModelBindingHelper.TryUpdateModelAsync(
model,
typeof(User),
"",
GetActionContext(),
metadataProvider,
GetModelBinderFactory(binder.Object),
Mock.Of<IValueProvider>(),
new Mock<IObjectModelValidator>(MockBehavior.Strict).Object,
propertyFilter),
"modelType",
expectedMessage);
}
[Theory]
[InlineData("")]
[InlineData(null)]
public void ClearValidationState_ForComplexTypeModel_EmptyModelKey(string modelKey)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForType(typeof(Product));
var dictionary = new ModelStateDictionary();
dictionary.AddModelError("Name", "MyProperty invalid.");
dictionary.AddModelError("Id", "Id invalid.");
dictionary.AddModelError("Id", "Id is required.");
dictionary.MarkFieldValid("Category");
dictionary.AddModelError("Unrelated", "Unrelated is required.");
// Act
ModelBindingHelper.ClearValidationStateForModel(modelMetadata, dictionary, modelKey);
// Assert
Assert.Empty(dictionary["Name"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Name"].ValidationState);
Assert.Empty(dictionary["Id"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Id"].ValidationState);
Assert.Empty(dictionary["Category"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["Category"].ValidationState);
Assert.Single(dictionary["Unrelated"].Errors);
Assert.Equal(ModelValidationState.Invalid, dictionary["Unrelated"].ValidationState);
}
// Not a wholly realistic scenario, but testing it regardless.
[Theory]
[InlineData("")]
[InlineData(null)]
public void ClearValidationState_ForSimpleTypeModel_EmptyModelKey(string modelKey)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForType(typeof(string));
var dictionary = new ModelStateDictionary();
dictionary.AddModelError(string.Empty, "MyProperty invalid.");
dictionary.AddModelError("Unrelated", "Unrelated is required.");
// Act
ModelBindingHelper.ClearValidationStateForModel(modelMetadata, dictionary, modelKey);
// Assert
Assert.Empty(dictionary[string.Empty].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary[string.Empty].ValidationState);
Assert.Single(dictionary["Unrelated"].Errors);
Assert.Equal(ModelValidationState.Invalid, dictionary["Unrelated"].ValidationState);
}
[Theory]
[InlineData("")]
[InlineData(null)]
public void ClearValidationState_ForCollectionsModel_EmptyModelKey(string modelKey)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForType(typeof(List<Product>));
var dictionary = new ModelStateDictionary();
dictionary.AddModelError("[0].Name", "Name invalid.");
dictionary.AddModelError("[0].Id", "Id invalid.");
dictionary.AddModelError("[0].Id", "Id required.");
dictionary.MarkFieldValid("[0].Category");
dictionary.MarkFieldValid("[1].Name");
dictionary.MarkFieldValid("[1].Id");
dictionary.AddModelError("[1].Category", "Category invalid.");
dictionary.AddModelError("Unrelated", "Unrelated is required.");
// Act
ModelBindingHelper.ClearValidationStateForModel(modelMetadata, dictionary, modelKey);
// Assert
Assert.Empty(dictionary["[0].Name"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Name"].ValidationState);
Assert.Empty(dictionary["[0].Id"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Id"].ValidationState);
Assert.Empty(dictionary["[0].Category"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[0].Category"].ValidationState);
Assert.Empty(dictionary["[1].Name"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Name"].ValidationState);
Assert.Empty(dictionary["[1].Id"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Id"].ValidationState);
Assert.Empty(dictionary["[1].Category"].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary["[1].Category"].ValidationState);
Assert.Single(dictionary["Unrelated"].Errors);
Assert.Equal(ModelValidationState.Invalid, dictionary["Unrelated"].ValidationState);
}
[Theory]
[InlineData("product")]
[InlineData("product.Name")]
[InlineData("product.Order[0].Name")]
[InlineData("product.Order[0].Address.Street")]
[InlineData("product.Category.Name")]
[InlineData("product.Order")]
public void ClearValidationState_ForComplexModel_NonEmptyModelKey(string prefix)
{
// Arrange
var metadataProvider = new EmptyModelMetadataProvider();
var modelMetadata = metadataProvider.GetMetadataForType(typeof(Product));
var dictionary = new ModelStateDictionary();
dictionary.AddModelError("product.Name", "Name invalid.");
dictionary.AddModelError("product.Id", "Id invalid.");
dictionary.AddModelError("product.Id", "Id required.");
dictionary.MarkFieldValid("product.Category");
dictionary.MarkFieldValid("product.Category.Name");
dictionary.AddModelError("product.Order[0].Name", "Order name invalid.");
dictionary.AddModelError("product.Order[0].Address.Street", "Street invalid.");
dictionary.MarkFieldValid("product.Order[1].Name");
dictionary.AddModelError("product.Order[0]", "Order invalid.");
// Act
ModelBindingHelper.ClearValidationStateForModel(modelMetadata, dictionary, prefix);
// Assert
foreach (var entry in dictionary.Keys)
{
if (entry.StartsWith(prefix, StringComparison.Ordinal))
{
Assert.Empty(dictionary[entry].Errors);
Assert.Equal(ModelValidationState.Unvalidated, dictionary[entry].ValidationState);
}
}
}
public static ModelBinderFactory GetModelBinderFactory(IModelBinder binder)
{
var binderProvider = new Mock<IModelBinderProvider>();
binderProvider
.Setup(p => p.GetBinder(It.IsAny<ModelBinderProviderContext>()))
.Returns(binder);
return TestModelBinderFactory.Create(binderProvider.Object);
}
private static ModelBinderFactory GetModelBinderFactory(params IModelBinderProvider[] providers)
{
return TestModelBinderFactory.CreateDefault(providers);
}
public class User
{
public string _userId;
public Address Address { get; set; }
public User[] Friends { get; set; }
public List<User> Colleagues { get; set; }
public bool IsReadOnly
{
get
{
throw new NotImplementedException();
}
}
public User Save()
{
return this;
}
}
public class Address
{
public string Street { get; set; }
}
private class MyModel
{
[Required]
public string MyProperty { get; set; }
public string IncludedProperty { get; set; }
public string ExcludedProperty { get; set; }
}
private class Product
{
public string Name { get; set; }
public int Id { get; set; }
public Category Category { get; set; }
public List<Order> Orders { get; set; }
}
public class Category
{
public string Name { get; set; }
}
public class Order
{
public string Name { get; set; }
public Address Address { get; set; }
}
[Fact]
public void ConvertTo_ReturnsNullForReferenceTypes_WhenValueIsNull()
{
var convertedValue = ModelBindingHelper.ConvertTo(value: null, type: typeof(string), culture: null);
Assert.Null(convertedValue);
}
[Fact]
public void ConvertTo_ReturnsDefaultForValueTypes_WhenValueIsNull()
{
var convertedValue = ModelBindingHelper.ConvertTo(value: null, type: typeof(int), culture: null);
Assert.Equal(0, convertedValue);
}
[Fact]
public void ConvertToCanConvertArraysToSingleElements()
{
// Arrange
var value = new int[] { 1, 20, 42 };
// Act
var converted = ModelBindingHelper.ConvertTo(value, typeof(string), culture: null);
// Assert
Assert.Equal("1", converted);
}
[Fact]
public void ConvertToCanConvertSingleElementsToArrays()
{
// Arrange
var value = 42;
// Act
var converted = ModelBindingHelper.ConvertTo<string[]>(value, culture: null);
// Assert
Assert.NotNull(converted);
var result = Assert.Single(converted);
Assert.Equal("42", result);
}
[Fact]
public void ConvertToCanConvertSingleElementsToSingleElements()
{
// Arrange
// Act
var converted = ModelBindingHelper.ConvertTo<string>(42, culture: null);
// Assert
Assert.NotNull(converted);
Assert.Equal("42", converted);
}
[Fact]
public void ConvertingNullStringToNullableIntReturnsNull()
{
// Arrange
// Act
var returned = ModelBindingHelper.ConvertTo<int?>(value: null, culture: null);
// Assert
Assert.Null(returned);
}
[Fact]
public void ConvertingWhiteSpaceStringToNullableIntReturnsNull()
{
// Arrange
var original = " ";
// Act
var returned = ModelBindingHelper.ConvertTo<int?>(original, culture: null);
// Assert
Assert.Null(returned);
}
[Fact]
public void ConvertToReturnsNullIfArrayElementValueIsNull()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new string[] { null }, typeof(int), culture: null);
// Assert
Assert.Null(outValue);
}
[Fact]
public void ConvertToReturnsNullIfTryingToConvertEmptyArrayToSingleElement()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new int[0], typeof(int), culture: null);
// Assert
Assert.Null(outValue);
}
[Theory]
[InlineData("")]
[InlineData(" \t \r\n ")]
public void ConvertToReturnsNullIfTrimmedValueIsEmptyString(object value)
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(value, typeof(int), culture: null);
// Assert
Assert.Null(outValue);
}
[Fact]
public void ConvertToReturnsNull_IfConvertingNullToArrayType()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(value: null, type: typeof(int[]), culture: null);
// Assert
Assert.Null(outValue);
}
[Fact]
public void ConvertToReturnsValueIfArrayElementIsIntegerAndDestinationTypeIsEnum()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new object[] { 1 }, typeof(IntEnum), culture: null);
// Assert
Assert.Equal(IntEnum.Value1, outValue);
}
[Theory]
[InlineData(1, typeof(IntEnum), IntEnum.Value1)]
[InlineData(1L, typeof(LongEnum), LongEnum.Value1)]
[InlineData(long.MaxValue, typeof(LongEnum), LongEnum.MaxValue)]
[InlineData(1U, typeof(UnsignedIntEnum), UnsignedIntEnum.Value1)]
[InlineData(1UL, typeof(IntEnum), IntEnum.Value1)]
[InlineData((byte)1, typeof(ByteEnum), ByteEnum.Value1)]
[InlineData(byte.MaxValue, typeof(ByteEnum), ByteEnum.MaxValue)]
[InlineData((sbyte)1, typeof(ByteEnum), ByteEnum.Value1)]
[InlineData((short)1, typeof(IntEnum), IntEnum.Value1)]
[InlineData((ushort)1, typeof(IntEnum), IntEnum.Value1)]
[InlineData(int.MaxValue, typeof(IntEnum?), IntEnum.MaxValue)]
[InlineData(null, typeof(IntEnum?), null)]
[InlineData(1L, typeof(LongEnum?), LongEnum.Value1)]
[InlineData(null, typeof(LongEnum?), null)]
[InlineData(uint.MaxValue, typeof(UnsignedIntEnum?), UnsignedIntEnum.MaxValue)]
[InlineData((byte)1, typeof(ByteEnum?), ByteEnum.Value1)]
[InlineData(null, typeof(ByteEnum?), null)]
[InlineData((ushort)1, typeof(LongEnum?), LongEnum.Value1)]
public void ConvertToReturnsValueIfArrayElementIsAnyIntegerTypeAndDestinationTypeIsEnum(
object input,
Type enumType,
object expected)
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new object[] { input }, enumType, culture: null);
// Assert
Assert.Equal(expected, outValue);
}
[Fact]
public void ConvertToReturnsValueIfArrayElementIsStringValueAndDestinationTypeIsEnum()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new object[] { "1" }, typeof(IntEnum), culture: null);
// Assert
Assert.Equal(IntEnum.Value1, outValue);
}
[Fact]
public void ConvertToReturnsValueIfArrayElementIsStringKeyAndDestinationTypeIsEnum()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new object[] { "Value1" }, typeof(IntEnum), culture: null);
// Assert
Assert.Equal(IntEnum.Value1, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableInteger()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo("12", typeof(int?), culture: null);
// Assert
Assert.Equal(12, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsStringAndDestinationIsNullableDouble()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo("12.5", typeof(double?), culture: null);
// Assert
Assert.Equal(12.5, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableInteger()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(12M, typeof(int?), culture: null);
// Assert
Assert.Equal(12, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsDecimalAndDestinationIsNullableDouble()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(12.5M, typeof(double?), culture: null);
// Assert
Assert.Equal(12.5, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableInteger()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(12M, typeof(int?), culture: null);
// Assert
Assert.Equal(12, outValue);
}
[Fact]
public void ConvertToReturnsValueIfElementIsDecimalDoubleAndDestinationIsNullableLong()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(12M, typeof(long?), culture: null);
// Assert
Assert.Equal(12L, outValue);
}
[Fact]
public void ConvertToReturnsValueIfArrayElementInstanceOfDestinationType()
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(new object[] { "some string" }, typeof(string), culture: null);
// Assert
Assert.Equal("some string", outValue);
}
[Theory]
[InlineData(new object[] { new object[] { 1, 0 } })]
[InlineData(new object[] { new[] { "Value1", "Value0" } })]
[InlineData(new object[] { new[] { "Value1", "value0" } })]
public void ConvertTo_ConvertsEnumArrays(object value)
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(value, typeof(IntEnum[]), culture: null);
// Assert
var result = Assert.IsType<IntEnum[]>(outValue);
Assert.Equal(2, result.Length);
Assert.Equal(IntEnum.Value1, result[0]);
Assert.Equal(IntEnum.Value0, result[1]);
}
[Theory]
[InlineData(new object[] { new object[] { 1, 2 }, new[] { FlagsEnum.Value1, FlagsEnum.Value2 } })]
[InlineData(new object[] { new[] { "Value1", "Value2" }, new[] { FlagsEnum.Value1, FlagsEnum.Value2 } })]
[InlineData(new object[] { new object[] { 5, 2 }, new[] { FlagsEnum.Value1 | FlagsEnum.Value4, FlagsEnum.Value2 } })]
public void ConvertTo_ConvertsFlagsEnumArrays(object value, FlagsEnum[] expected)
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo(value, typeof(FlagsEnum[]), culture: null);
// Assert
var result = Assert.IsType<FlagsEnum[]>(outValue);
Assert.Equal(2, result.Length);
Assert.Equal(expected[0], result[0]);
Assert.Equal(expected[1], result[1]);
}
[Fact]
public void ConvertToReturnsValueIfInstanceOfDestinationType()
{
// Arrange
var original = new[] { "some string" };
// Act
var outValue = ModelBindingHelper.ConvertTo(original, typeof(string[]), culture: null);
// Assert
Assert.Same(original, outValue);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(double?))]
[InlineData(typeof(IntEnum?))]
public void ConvertToThrowsIfConverterThrows(Type destinationType)
{
// Arrange
// Act & Assert
var ex = Assert.Throws<FormatException>(
() => ModelBindingHelper.ConvertTo("this-is-not-a-valid-value", destinationType, culture: null));
}
[Fact]
public void ConvertToUsesProvidedCulture()
{
// Arrange
// Act
var cultureResult = ModelBindingHelper.ConvertTo("12,5", typeof(decimal), new CultureInfo("fr-FR"));
// Assert
Assert.Equal(12.5M, cultureResult);
Assert.Throws<FormatException>(
() => ModelBindingHelper.ConvertTo("12,5", typeof(decimal), new CultureInfo("en-GB")));
}
[Theory]
[MemberData(nameof(IntrinsicConversionData))]
public void ConvertToCanConvertIntrinsics<T>(object initialValue, T expectedValue)
{
// Arrange
// Act & Assert
Assert.Equal(expectedValue, ModelBindingHelper.ConvertTo(initialValue, typeof(T), culture: null));
}
public static IEnumerable<object[]> IntrinsicConversionData
{
get
{
yield return new object[] { 42, 42L };
yield return new object[] { 42, (short)42 };
yield return new object[] { 42, (float)42.0 };
yield return new object[] { 42, (double)42.0 };
yield return new object[] { 42M, 42 };
yield return new object[] { 42L, 42 };
yield return new object[] { 42, (byte)42 };
yield return new object[] { (short)42, 42 };
yield return new object[] { (float)42.0, 42 };
yield return new object[] { (double)42.0, 42 };
yield return new object[] { (byte)42, 42 };
yield return new object[] { "2008-01-01", new DateTime(2008, 01, 01) };
yield return new object[] { "00:00:20", TimeSpan.FromSeconds(20) };
yield return new object[]
{
"c6687d3a-51f9-4159-8771-a66d2b7d7038",
Guid.Parse("c6687d3a-51f9-4159-8771-a66d2b7d7038", CultureInfo.InvariantCulture)
};
}
}
// None of the types here have converters from MyClassWithoutConverter.
[Theory]
[InlineData(typeof(TimeSpan))]
[InlineData(typeof(DateTime))]
[InlineData(typeof(DateTimeOffset))]
[InlineData(typeof(Guid))]
[InlineData(typeof(IntEnum))]
public void ConvertTo_Throws_IfValueIsNotConvertible(Type destinationType)
{
// Arrange
var expectedMessage = $"The parameter conversion from type '{typeof(MyClassWithoutConverter)}' to type " +
$"'{destinationType}' failed because no type converter can convert between these types.";
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
() => ModelBindingHelper.ConvertTo(new MyClassWithoutConverter(), destinationType, culture: null));
Assert.Equal(expectedMessage, ex.Message);
}
// String does not have a converter to MyClassWithoutConverter.
[Fact]
public void ConvertTo_Throws_IfDestinationTypeIsNotConvertible()
{
// Arrange
var value = "Hello world";
var destinationType = typeof(MyClassWithoutConverter);
var expectedMessage = $"The parameter conversion from type '{value.GetType()}' to type " +
$"'{typeof(MyClassWithoutConverter)}' failed because no type converter can convert between these types.";
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(
() => ModelBindingHelper.ConvertTo(value, destinationType, culture: null));
Assert.Equal(expectedMessage, ex.Message);
}
// Happens very rarely in practice since conversion is almost-always from strings or string arrays.
[Theory]
[InlineData(typeof(MyClassWithoutConverter))]
[InlineData(typeof(MySubClassWithoutConverter))]
public void ConvertTo_ReturnsValue_IfCompatible(Type destinationType)
{
// Arrange
var value = new MySubClassWithoutConverter();
// Act
var result = ModelBindingHelper.ConvertTo(value, destinationType, culture: null);
// Assert
Assert.Same(value, result);
}
[Theory]
[InlineData(typeof(MyClassWithoutConverter[]))]
[InlineData(typeof(MySubClassWithoutConverter[]))]
public void ConvertTo_ReusesArrayElements_IfCompatible(Type destinationType)
{
// Arrange
var value = new MyClassWithoutConverter[]
{
new MySubClassWithoutConverter(),
new MySubClassWithoutConverter(),
new MySubClassWithoutConverter(),
};
// Act
var result = ModelBindingHelper.ConvertTo(value, destinationType, culture: null);
// Assert
Assert.IsType(destinationType, result);
Assert.Collection(
result as IEnumerable<MyClassWithoutConverter>,
element => { Assert.Same(value[0], element); },
element => { Assert.Same(value[1], element); },
element => { Assert.Same(value[2], element); });
}
[Theory]
[InlineData(new object[] { 2, FlagsEnum.Value2 })]
[InlineData(new object[] { 5, FlagsEnum.Value1 | FlagsEnum.Value4 })]
[InlineData(new object[] { 15, FlagsEnum.Value1 | FlagsEnum.Value2 | FlagsEnum.Value4 | FlagsEnum.Value8 })]
[InlineData(new object[] { 16, (FlagsEnum)16 })]
[InlineData(new object[] { 0, (FlagsEnum)0 })]
[InlineData(new object[] { null, (FlagsEnum)0 })]
[InlineData(new object[] { "Value1,Value2", (FlagsEnum)3 })]
[InlineData(new object[] { "Value1,Value2,value4, value8", (FlagsEnum)15 })]
public void ConvertTo_ConvertsEnumFlags(object value, object expected)
{
// Arrange
// Act
var outValue = ModelBindingHelper.ConvertTo<FlagsEnum>(value, culture: null);
// Assert
Assert.Equal(expected, outValue);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(int[]))]
[InlineData(typeof(IEnumerable<int>))]
[InlineData(typeof(IReadOnlyCollection<int>))]
[InlineData(typeof(IReadOnlyList<int>))]
[InlineData(typeof(ICollection<int>))]
[InlineData(typeof(IList<int>))]
[InlineData(typeof(List<int>))]
[InlineData(typeof(Collection<int>))]
[InlineData(typeof(IntList))]
[InlineData(typeof(LinkedList<int>))]
public void CanGetCompatibleCollection_ReturnsTrue(Type destinationType)
{
// Arrange
var bindingContext = GetBindingContext(destinationType);
// Act
var result = ModelBindingHelper.CanGetCompatibleCollection<int>(bindingContext);
// Assert
Assert.True(result);
}
[Theory]
[InlineData(typeof(int))]
[InlineData(typeof(int[]))]
[InlineData(typeof(IEnumerable<int>))]
[InlineData(typeof(IReadOnlyCollection<int>))]
[InlineData(typeof(IReadOnlyList<int>))]
[InlineData(typeof(ICollection<int>))]
[InlineData(typeof(IList<int>))]
[InlineData(typeof(List<int>))]
public void GetCompatibleCollection_ReturnsList(Type destinationType)
{
// Arrange
var bindingContext = GetBindingContext(destinationType);
// Act
var result = ModelBindingHelper.GetCompatibleCollection<int>(bindingContext);
// Assert
Assert.IsType<List<int>>(result);
}
[Theory]
[InlineData(typeof(Collection<int>))]
[InlineData(typeof(IntList))]
[InlineData(typeof(LinkedList<int>))]
public void GetCompatibleCollection_ActivatesCollection(Type destinationType)
{
// Arrange
var bindingContext = GetBindingContext(destinationType);
// Act
var result = ModelBindingHelper.GetCompatibleCollection<int>(bindingContext);
// Assert
Assert.IsType(destinationType, result);
}
[Fact]
public void GetCompatibleCollection_SetsCapacity()
{
// Arrange
var bindingContext = GetBindingContext(typeof(IList<int>));
// Act
var result = ModelBindingHelper.GetCompatibleCollection<int>(bindingContext, capacity: 23);
// Assert
var list = Assert.IsType<List<int>>(result);
Assert.Equal(23, list.Capacity);
}
[Theory]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ArrayProperty))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ArrayPropertyWithValue))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerableProperty))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerablePropertyWithArrayValue))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ListProperty))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ScalarProperty))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ScalarPropertyWithValue))]
public void CanGetCompatibleCollection_ReturnsTrue_IfReadOnly(string propertyName)
{
// Arrange
var bindingContext = GetBindingContextForProperty(propertyName);
// Act
var result = ModelBindingHelper.CanGetCompatibleCollection<int>(bindingContext);
// Assert
Assert.True(result);
}
[Theory]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerablePropertyWithArrayValueAndSetter))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerablePropertyWithListValue))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ListPropertyWithValue))]
public void CanGetCompatibleCollection_ReturnsTrue_IfCollection(string propertyName)
{
// Arrange
var bindingContext = GetBindingContextForProperty(propertyName);
// Act
var result = ModelBindingHelper.CanGetCompatibleCollection<int>(bindingContext);
// Assert
Assert.True(result);
}
[Theory]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerablePropertyWithListValue))]
[InlineData(nameof(ModelWithReadOnlyAndSpecialCaseProperties.ListPropertyWithValue))]
public void GetCompatibleCollection_ReturnsExistingCollection(string propertyName)
{
// Arrange
var bindingContext = GetBindingContextForProperty(propertyName);
// Act
var result = ModelBindingHelper.GetCompatibleCollection<int>(bindingContext);
// Assert
Assert.Same(bindingContext.Model, result);
var list = Assert.IsType<List<int>>(result);
Assert.Empty(list);
}
[Fact]
public void CanGetCompatibleCollection_ReturnsNewCollection()
{
// Arrange
var bindingContext = GetBindingContextForProperty(
nameof(ModelWithReadOnlyAndSpecialCaseProperties.EnumerablePropertyWithArrayValueAndSetter));
// Act
var result = ModelBindingHelper.GetCompatibleCollection<int>(bindingContext);
// Assert
Assert.NotSame(bindingContext.Model, result);
var list = Assert.IsType<List<int>>(result);
Assert.Empty(list);
}
[Theory]
[InlineData(typeof(Collection<string>))]
[InlineData(typeof(List<long>))]
[InlineData(typeof(MyModel))]
[InlineData(typeof(AbstractIntList))]
[InlineData(typeof(ISet<int>))]
public void CanGetCompatibleCollection_ReturnsFalse(Type destinationType)
{
// Arrange
var bindingContext = GetBindingContext(destinationType);
// Act
var result = ModelBindingHelper.CanGetCompatibleCollection<int>(bindingContext);
// Assert
Assert.False(result);
}
private static ActionContext GetActionContext()
{
var services = new ServiceCollection();
services.AddSingleton<ILoggerFactory>(NullLoggerFactory.Instance);
return new ActionContext()
{
HttpContext = new DefaultHttpContext()
{
RequestServices = services.BuildServiceProvider()
}
};
}
private static DefaultModelBindingContext GetBindingContextForProperty(string propertyName)
{
var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider();
var modelMetadata = metadataProvider.GetMetadataForProperty(
typeof(ModelWithReadOnlyAndSpecialCaseProperties),
propertyName);
var bindingContext = GetBindingContext(modelMetadata);
var container = new ModelWithReadOnlyAndSpecialCaseProperties();
bindingContext.Model = modelMetadata.PropertyGetter(container);
return bindingContext;
}
private static DefaultModelBindingContext GetBindingContext(Type modelType)
{
var metadataProvider = new EmptyModelMetadataProvider();
var metadata = metadataProvider.GetMetadataForType(modelType);
return GetBindingContext(metadata);
}
private static DefaultModelBindingContext GetBindingContext(ModelMetadata metadata)
{
var bindingContext = new DefaultModelBindingContext
{
ModelMetadata = metadata,
};
return bindingContext;
}
private class ModelWithReadOnlyAndSpecialCaseProperties
{
public int[] ArrayProperty { get; }
public int[] ArrayPropertyWithValue { get; } = new int[4];
public IEnumerable<int> EnumerableProperty { get; }
public IEnumerable<int> EnumerablePropertyWithArrayValue { get; } = new int[4];
// Special case: Value cannot be used but property can be set.
public IEnumerable<int> EnumerablePropertyWithArrayValueAndSetter { get; set; } = new int[4];
public IEnumerable<int> EnumerablePropertyWithListValue { get; } = new List<int> { 23 };
public List<int> ListProperty { get; }
public List<int> ListPropertyWithValue { get; } = new List<int> { 23 };
public int ScalarProperty { get; }
public int ScalarPropertyWithValue { get; } = 23;
}
private class MyClassWithoutConverter
{
}
private class MySubClassWithoutConverter : MyClassWithoutConverter
{
}
private abstract class AbstractIntList : List<int>
{
}
private class IntList : List<int>
{
}
private enum IntEnum
{
Value0 = 0,
Value1 = 1,
MaxValue = int.MaxValue
}
private enum LongEnum : long
{
Value0 = 0L,
Value1 = 1L,
MaxValue = long.MaxValue
}
private enum UnsignedIntEnum : uint
{
Value0 = 0U,
Value1 = 1U,
MaxValue = uint.MaxValue
}
private enum ByteEnum : byte
{
Value0 = 0,
Value1 = 1,
MaxValue = byte.MaxValue
}
[Flags]
public enum FlagsEnum
{
Value1 = 1,
Value2 = 2,
Value4 = 4,
Value8 = 8
}
}
|