Skip to content

Commit 895737f

Browse files
committed
Merge in 'release/7.0' changes
2 parents 4832b80 + 254fce0 commit 895737f

File tree

4 files changed

+137
-3
lines changed

4 files changed

+137
-3
lines changed

src/Mvc/Mvc.Core/src/Infrastructure/MvcCoreMvcOptionsSetup.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ public void Configure(MvcOptions options)
6363
options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider());
6464
options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options));
6565
options.ModelBinderProviders.Add(new DateTimeModelBinderProvider());
66-
options.ModelBinderProviders.Add(new TryParseModelBinderProvider());
6766
options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider());
67+
options.ModelBinderProviders.Add(new TryParseModelBinderProvider());
6868
options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider());
6969
options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider());
7070
options.ModelBinderProviders.Add(new FormFileModelBinderProvider());

src/Mvc/Mvc.Core/src/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class SimpleTypeModelBinderProvider : IModelBinderProvider
2121
throw new ArgumentNullException(nameof(context));
2222
}
2323

24-
if (!context.Metadata.IsComplexType)
24+
if (context.Metadata.IsConvertibleType)
2525
{
2626
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
2727
return new SimpleTypeModelBinder(context.Metadata.ModelType, loggerFactory);

src/Mvc/Mvc/test/MvcOptionsSetupTest.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ public void Setup_SetsUpModelBinderProviders()
5252
binder => Assert.IsType<FloatingPointTypeModelBinderProvider>(binder),
5353
binder => Assert.IsType<EnumTypeModelBinderProvider>(binder),
5454
binder => Assert.IsType<DateTimeModelBinderProvider>(binder),
55-
binder => Assert.IsType<TryParseModelBinderProvider>(binder),
5655
binder => Assert.IsType<SimpleTypeModelBinderProvider>(binder),
56+
binder => Assert.IsType<TryParseModelBinderProvider>(binder),
5757
binder => Assert.IsType<CancellationTokenModelBinderProvider>(binder),
5858
binder => Assert.IsType<ByteArrayModelBinderProvider>(binder),
5959
binder => Assert.IsType<FormFileModelBinderProvider>(binder),

src/Mvc/test/Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs

+134
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.ComponentModel;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
47
using System.Text;
58
using Microsoft.AspNetCore.Http;
69
using Microsoft.AspNetCore.Mvc.Abstractions;
@@ -699,6 +702,94 @@ public async Task BindParameter_FromFormData_BindsCorrectly(Dictionary<string, S
699702
Assert.Equal(new[] { "line 1", "line 2" }, entry.RawValue);
700703
}
701704

705+
[Fact]
706+
public async Task BindParameter_PrefersTypeConverter_OverTryParse()
707+
{
708+
// Arrange
709+
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
710+
var parameter = new ParameterDescriptor()
711+
{
712+
Name = "Parameter1",
713+
BindingInfo = new BindingInfo(),
714+
ParameterType = typeof(SampleModel)
715+
};
716+
717+
var testContext = ModelBindingTestHelper.GetTestContext(request =>
718+
{
719+
request.QueryString = QueryString.Create("Parameter1", "someValue");
720+
});
721+
722+
var modelState = testContext.ModelState;
723+
724+
// Act
725+
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
726+
727+
// Assert
728+
729+
// ModelBindingResult
730+
Assert.True(modelBindingResult.IsModelSet);
731+
732+
// Model
733+
var model = Assert.IsType<SampleModel>(modelBindingResult.Model);
734+
Assert.Equal("someValue", model.Value);
735+
Assert.Equal("Converter", model.Source);
736+
737+
// ModelState
738+
Assert.True(modelState.IsValid);
739+
740+
Assert.Single(modelState.Keys);
741+
var key = Assert.Single(modelState.Keys);
742+
Assert.Equal("Parameter1", key);
743+
Assert.Equal("someValue", modelState[key].AttemptedValue);
744+
Assert.Equal("someValue", modelState[key].RawValue);
745+
Assert.Empty(modelState[key].Errors);
746+
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
747+
}
748+
749+
[Fact]
750+
public async Task BindParameter_BindsUsingTryParse()
751+
{
752+
// Arrange
753+
var parameterBinder = ModelBindingTestHelper.GetParameterBinder();
754+
var parameter = new ParameterDescriptor()
755+
{
756+
Name = "Parameter1",
757+
BindingInfo = new BindingInfo(),
758+
ParameterType = typeof(SampleTryParsableModel)
759+
};
760+
761+
var testContext = ModelBindingTestHelper.GetTestContext(request =>
762+
{
763+
request.QueryString = QueryString.Create("Parameter1", "someValue");
764+
});
765+
766+
var modelState = testContext.ModelState;
767+
768+
// Act
769+
var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext);
770+
771+
// Assert
772+
773+
// ModelBindingResult
774+
Assert.True(modelBindingResult.IsModelSet);
775+
776+
// Model
777+
var model = Assert.IsType<SampleTryParsableModel>(modelBindingResult.Model);
778+
Assert.Equal("someValue", model.Value);
779+
Assert.Equal("TryParse", model.Source);
780+
781+
// ModelState
782+
Assert.True(modelState.IsValid);
783+
784+
Assert.Single(modelState.Keys);
785+
var key = Assert.Single(modelState.Keys);
786+
Assert.Equal("Parameter1", key);
787+
Assert.Equal("someValue", modelState[key].AttemptedValue);
788+
Assert.Equal("someValue", modelState[key].RawValue);
789+
Assert.Empty(modelState[key].Errors);
790+
Assert.Equal(ModelValidationState.Valid, modelState[key].ValidationState);
791+
}
792+
702793
private class Person
703794
{
704795
public Address Address { get; set; }
@@ -712,4 +803,47 @@ private class Address
712803

713804
public int Zip { get; set; }
714805
}
806+
807+
[TypeConverter(typeof(SampleModelTypeConverter))]
808+
private class SampleModel
809+
{
810+
public string Value { get; set; }
811+
public string Source { get; set; }
812+
813+
public static bool TryParse([NotNullWhen(true)] string s, [MaybeNullWhen(false)] out SampleModel result)
814+
{
815+
result = new SampleModel() { Value = s, Source = "TryParse" };
816+
return true;
817+
}
818+
}
819+
820+
private class SampleTryParsableModel
821+
{
822+
public string Value { get; set; }
823+
public string Source { get; set; }
824+
825+
public static bool TryParse([NotNullWhen(true)] string s, [MaybeNullWhen(false)] out SampleTryParsableModel result)
826+
{
827+
result = new SampleTryParsableModel() { Value = s, Source = "TryParse" };
828+
return true;
829+
}
830+
}
831+
832+
private class SampleModelTypeConverter : TypeConverter
833+
{
834+
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
835+
{
836+
return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
837+
}
838+
839+
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
840+
{
841+
if (value is string s)
842+
{
843+
return new SampleModel() { Value = s, Source = "Converter" };
844+
}
845+
846+
return base.ConvertFrom(context, culture, value);
847+
}
848+
}
715849
}

0 commit comments

Comments
 (0)