-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Description
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>();
});