File: ModelBinding\Binders\SimpleTypeModelBinder.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.ComponentModel;
using System.Runtime.ExceptionServices;
using Microsoft.Extensions.Logging;
 
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
 
/// <summary>
/// An <see cref="IModelBinder"/> for simple types.
/// </summary>
public class SimpleTypeModelBinder : IModelBinder
{
    private readonly TypeConverter _typeConverter;
    private readonly ILogger _logger;
 
    /// <summary>
    /// Initializes a new instance of <see cref="SimpleTypeModelBinder"/>.
    /// </summary>
    /// <param name="type">The type to create binder for.</param>
    /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
    public SimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory)
    {
        ArgumentNullException.ThrowIfNull(type);
        ArgumentNullException.ThrowIfNull(loggerFactory);
 
        _typeConverter = TypeDescriptor.GetConverter(type);
        _logger = loggerFactory.CreateLogger(typeof(SimpleTypeModelBinder));
    }
 
    /// <inheritdoc />
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        ArgumentNullException.ThrowIfNull(bindingContext);
 
        _logger.AttemptingToBindModel(bindingContext);
 
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult == ValueProviderResult.None)
        {
            _logger.FoundNoValueInRequest(bindingContext);
 
            // no entry
            _logger.DoneAttemptingToBindModel(bindingContext);
            return Task.CompletedTask;
        }
 
        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
 
        try
        {
            var value = bindingContext.ModelMetadata.IsFlagsEnum
                ? valueProviderResult.Values.ToString()
                : valueProviderResult.FirstValue;
 
            object? model;
            if (bindingContext.ModelType == typeof(string))
            {
                // Already have a string. No further conversion required but handle ConvertEmptyStringToNull.
                if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value))
                {
                    model = null;
                }
                else
                {
                    model = value;
                }
            }
            else if (string.IsNullOrWhiteSpace(value))
            {
                // Other than the StringConverter, converters Trim() the value then throw if the result is empty.
                model = null;
            }
            else
            {
                model = _typeConverter.ConvertFrom(
                    context: null,
                    culture: valueProviderResult.Culture,
                    value: value);
            }
 
            CheckModel(bindingContext, valueProviderResult, model);
        }
        catch (Exception exception)
        {
            var isFormatException = exception is FormatException;
            if (!isFormatException && exception.InnerException != null)
            {
                // TypeConverter throws System.Exception wrapping the FormatException,
                // so we capture the inner exception.
                exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException;
            }
 
            bindingContext.ModelState.TryAddModelError(
                bindingContext.ModelName,
                exception,
                bindingContext.ModelMetadata);
        }
 
        _logger.DoneAttemptingToBindModel(bindingContext);
        return Task.CompletedTask;
    }
 
    /// <summary>
    /// If the <paramref name="model" /> is <see langword="null" />, verifies that it is allowed to be <see langword="null" />,
    /// otherwise notifies the <see cref="P:ModelBindingContext.ModelState" /> about the invalid <paramref name="valueProviderResult" />.
    /// Sets the <see href="P:ModelBindingContext.Result" /> to the <paramref name="model" /> if successful.
    /// </summary>
    protected virtual void CheckModel(
        ModelBindingContext bindingContext,
        ValueProviderResult valueProviderResult,
        object? model)
    {
        // When converting newModel a null value 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 && !bindingContext.ModelMetadata.IsReferenceOrNullableType)
        {
            bindingContext.ModelState.TryAddModelError(
                bindingContext.ModelName,
                bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
                    valueProviderResult.ToString()));
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Success(model);
        }
    }
}