File: ModelBinding\Binders\DecimalModelBinder.cs
Web Access
Project: src\src\Mvc\Mvc.Core\src\Microsoft.AspNetCore.Mvc.Core.csproj (Microsoft.AspNetCore.Mvc.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable enable
 
using System.Globalization;
using System.Runtime.ExceptionServices;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
 
/// <summary>
/// An <see cref="IModelBinder"/> for <see cref="decimal"/> and <see cref="Nullable{T}"/> where <c>T</c> is
/// <see cref="decimal"/>.
/// </summary>
public class DecimalModelBinder : IModelBinder
{
    private readonly NumberStyles _supportedStyles;
    private readonly ILogger _logger;
 
    /// <summary>
    /// Initializes a new instance of <see cref="DecimalModelBinder"/>.
    /// </summary>
    /// <param name="supportedStyles">The <see cref="NumberStyles"/>.</param>
    /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
    public DecimalModelBinder(NumberStyles supportedStyles, ILoggerFactory loggerFactory)
    {
        ArgumentNullException.ThrowIfNull(loggerFactory);
 
        _supportedStyles = supportedStyles;
        _logger = loggerFactory.CreateLogger(typeof(DecimalModelBinder));
    }
 
    /// <inheritdoc />
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        ArgumentNullException.ThrowIfNull(bindingContext);
 
        _logger.AttemptingToBindModel(bindingContext);
 
        var 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
        {
            var value = valueProviderResult.FirstValue;
 
            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(decimal))
            {
                model = decimal.Parse(value, _supportedStyles, valueProviderResult.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)
        {
            var 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;
    }
}