Consider adding support for `TryParse` as a way to bind primitives
See original GitHub issueCurrent design
Types that do not contain a Converter
from string
available are considered Complex Types
.
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>();
});
Issue Analytics
- State:
- Created 2 years ago
- Comments:11 (11 by maintainers)
Top GitHub Comments
API Review:
We could add a new
TryParseModelBinderFactory
that can be removed by app code. Let’s try that out and we can bring this back to API review.ParameterBindingMethodCache
public.TryParseModelBinder
instead that is registered to run afterSimpleTypeModelBinder
. Users can remove it if they’re don’t want it (thus avoiding the need for a option type).