using System; using System.Globalization; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.ModelBinding { /// /// Custom floating point ModelBinder as the team of Microsoft is not capable of fixing their issue with other cultures than en-US. /// /// /// Initializes a new instance of . /// /// The . /// The . /// The . [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public class InvariantFloatingPointModelBinder(NumberStyles supportedStyles, CultureInfo cultureInfo, ILoggerFactory loggerFactory) : IModelBinder { private readonly NumberStyles _supportedNumberStyles = supportedStyles; private readonly ILogger _logger = loggerFactory?.CreateLogger(); private readonly CultureInfo _cultureInfo = cultureInfo ?? throw new ArgumentNullException(nameof(cultureInfo)); /// public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); _logger?.AttemptingToBindModel(bindingContext); string modelName = bindingContext.ModelName; var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); if (valueProviderResult == ValueProviderResult.None) { _logger?.FoundNoValueInRequest(bindingContext); // no entry _logger?.DoneAttemptingToBindModel(bindingContext); return Task.CompletedTask; } var modelState = bindingContext.ModelState; modelState.SetModelValue(modelName, valueProviderResult); var metadata = bindingContext.ModelMetadata; var type = metadata.UnderlyingOrModelType; try { string value = valueProviderResult.FirstValue; var culture = _cultureInfo ?? valueProviderResult.Culture; object model; if (string.IsNullOrWhiteSpace(value)) { // Parse() method trims the value (with common NumberStyles) then throws if the result is empty. model = null; } else if (type == typeof(float)) { model = float.Parse(value, _supportedNumberStyles, culture); } else if (type == typeof(double)) { model = double.Parse(value, _supportedNumberStyles, culture); } else if (type == typeof(decimal)) { model = decimal.Parse(value, _supportedNumberStyles, culture); } else { // unreachable throw new NotSupportedException(); } // When converting value, a null model may indicate a failed conversion for an otherwise required // model (can't set a ValueType to null). This detects if a null model value is acceptable given the // current bindingContext. If not, an error is logged. if (model == null && !metadata.IsReferenceOrNullableType) { modelState.TryAddModelError( modelName, metadata .ModelBindingMessageProvider .ValueMustNotBeNullAccessor(valueProviderResult.ToString()) ); } else { bindingContext.Result = ModelBindingResult.Success(model); } } catch (Exception exception) { bool isFormatException = exception is FormatException; if (!isFormatException && exception.InnerException != null) { // Unlike TypeConverters, floating point types do not seem to wrap FormatExceptions. Preserve // this code in case a cursory review of the CoreFx code missed something. exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; } modelState.TryAddModelError(modelName, exception, metadata); // Conversion failed. } _logger?.DoneAttemptingToBindModel(bindingContext); return Task.CompletedTask; } } }