question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Consider adding support for `TryParse` as a way to bind primitives

See original GitHub issue

Current design

Types that do not contain a Converter from string available are considered Complex Types.

https://github.com/dotnet/aspnetcore/blob/85ac505b0a500acf5246fe48fe7e8eb400511c9d/src/Mvc/Mvc.Abstractions/src/ModelBinding/ModelMetadata.cs#L609

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:closed
  • Created 2 years ago
  • Comments:11 (11 by maintainers)

github_iconTop GitHub Comments

1reaction
pranavkmcommented, Feb 7, 2022

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.

1reaction
pranavkmcommented, Jan 22, 2022
  • I wouldn’t make ParameterBindingMethodCache public.
  • You could introduce a TryParseModelBinder instead that is registered to run after SimpleTypeModelBinder. Users can remove it if they’re don’t want it (thus avoiding the need for a option type).
Read more comments on GitHub >

github_iconTop Results From Across the Web

c# - Generic TryParse
<remarks> /// The method supports all the primitive types of the CLR /// such as int, boolean, double, guid etc. as well as...
Read more >
Getting a complex type as a simple type from the query ...
Using this new approach, the existence of a TryParse method with a signature like I showed earlier, as well as a BindAsync method,...
Read more >
TryParse and BindAsync methods are validated - .NET
NET Core now validates TryParse and BindAsync methods on parameter types for Map* methods. If no valid method is found, ASP.
Read more >
Example usage for com.google.common.primitives Longs ...
tryParse () doesn't support leading '+', so we need to trim it ourselves metricValueString = trimLeadingPlusOfLongString(metricValueString); Long v = Longs.
Read more >
Model Binding
Route<T>() method is only able to handle types that have a static TryParse() method. See here on how to add parsing support for...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found