Current design
Types that do not contain a Converter from string available are considered Complex Types.
|
IsComplexType = !TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)); |
That means they will be bind using the ComplexObjectModelBinder when they are not special types that have your own binder (Eg. CancellationToken or Dictionary) or when the [FromBody] was not inferred, in this case the BodyModelBinder will be used.
On the other hand, when it is detected as a simple type (can be converted from string) the SimpleTypeModelBinder will be used, except for the types listed below:
DateTimeModelBinder => context.Metadata.UnderlyingOrModelType == typeof(DateTime)
DecimalModelBinder => modelType == typeof(decimal)
DoubleModelBinder => modelType == typeof(double)
FloatModelBinder => modelType == typeof(float)
EnumTypeModelBinder => context.Metadata.IsEnum
Today, this binder will basically use the actual value if the model type is string or call the converter when not.
Proposed Change
There is already the class Microsoft.AspNetCore.Http.ParameterBindingMethodCache exposing two methods HasTryParseMethod and FindTryParseMethod that will be used in this proposal, however, currently it works only with InvariantCulture instead of allowing the caller to provide a different Culture what is required in this proposal and will require a small change to this class.
With the capability to detect the TryParse method, the proposal is change ModelMetadata to include a new internal property HasTryParse, that will indicate that a TryParse method is available with the type.
namespace Microsoft.AspNetCore.Mvc.ModelBinding;
public abstract class ModelMetadata : IEquatable<ModelMetadata?>, IModelMetadataProvider
{
internal bool HasTryParse { get; private set; }
}
An important aspect of this change, the ModelMetadata will hold a static instance of the Microsoft.AspNetCore.Http.ParameterBindingMethodCache that will be used across all calls.
With this new property available the proposal is to add a new public available ModelBinderProvider that will create a ModelBinder only when ModelMetadata.HasTryParse == true
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
+ public class TryParseModelBinderProvider : IModelBinderProvider
+ {
+ public IModelBinder? GetBinder(ModelBinderProviderContext context!!)
+ {
+ if (context.Metadata.HasTryParse)
+ {
+ var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
+ return new TryParseModelBinder(context.Metadata.ModelType, loggerFactory);
+ }
+ return null;
+ }
+ }
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
+ public class TryParseModelBinder : IModelBinder
+ {
+ private readonly Func<ParameterExpression, IFormatProvider, Expression> _tryParseMethodExpession;
+ private readonly ILogger _logger;
+ public TryParseModelBinder(Type type!!, ILoggerFactory loggerFactory!!)
+ {
+ _tryParseMethodExpession = ModelMetadata.FindTryParseMethod(type)!;
+ _logger = loggerFactory.CreateLogger<SimpleTypeModelBinder>();
+ }
+ }
This new provider will be added to the list of the default ModelProviders and executed before the SimpleTypeModelBinderProvider. That means that for all types that contains a TryParse and was processed previously by the SimpleTypeModelBinderProvider because it has a Converter from string will now be bound using the new TryParseModelBinder. A very common example will be int type.
Also, this proposal will not change how types that have a TryParse method but not a Converter from string have their Binding Source inferred, so, for those types of the parameter will need to be explicitly defined the binding source, eg: FromQuery.
Usage Examples
The new behavior will be enabled without any code change, however, the following piece of code will rollback to the previous behavior if the users want to.
services.Configure<MvcOptions>(options => {
options.ModelBinderProviders.RemoveType<TryParseModelBinderProvider>();
});
Current design
Types that do not contain a
Converterfromstringavailable are consideredComplex Types.aspnetcore/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs
Line 609 in 85ac505
That means they will be bind using the
ComplexObjectModelBinderwhen they are not special types that have your own binder (Eg.CancellationTokenorDictionary) or when the[FromBody]was not inferred, in this case theBodyModelBinderwill be used.On the other hand, when it is detected as a simple type (can be converted from string) the
SimpleTypeModelBinderwill be used, except for the types listed below:DateTimeModelBinder=>context.Metadata.UnderlyingOrModelType == typeof(DateTime)DecimalModelBinder=>modelType == typeof(decimal)DoubleModelBinder=>modelType == typeof(double)FloatModelBinder=>modelType == typeof(float)EnumTypeModelBinder=>context.Metadata.IsEnumToday, this binder will basically use the actual value if the model type is
stringor call the converter when not.Proposed Change
There is already the class
Microsoft.AspNetCore.Http.ParameterBindingMethodCacheexposing two methodsHasTryParseMethodandFindTryParseMethodthat will be used in this proposal, however, currently it works only withInvariantCultureinstead of allowing the caller to provide a differentCulturewhat is required in this proposal and will require a small change to this class.With the capability to detect the
TryParsemethod, the proposal is changeModelMetadatato include a newinternalpropertyHasTryParse, that will indicate that aTryParsemethod is available with the type.An important aspect of this change, the
ModelMetadatawill hold a static instance of theMicrosoft.AspNetCore.Http.ParameterBindingMethodCachethat will be used across all calls.With this new property available the proposal is to add a new public available
ModelBinderProviderthat will create aModelBinderonly whenModelMetadata.HasTryParse == trueThis new provider will be added to the list of the default ModelProviders and executed before the
SimpleTypeModelBinderProvider. That means that for all types that contains aTryParseand was processed previously by theSimpleTypeModelBinderProviderbecause it has aConverterfromstringwill now be bound using the newTryParseModelBinder. A very common example will beinttype.Also, this proposal will not change how types that have a
TryParsemethod but not aConverterfromstringhave their Binding Source inferred, so, for those types of the parameter will need to be explicitly defined the binding source, eg:FromQuery.Usage Examples
The new behavior will be enabled without any code change, however, the following piece of code will rollback to the previous behavior if the users want to.