| File: ModelBinding\Binders\TryParseModelBinder.cs | Web Access |
| Project: src\aspnetcore\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. using System.Linq.Expressions; using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders; /// <summary> /// An <see cref="IModelBinder"/> for simple types. /// </summary> internal sealed class TryParseModelBinder : IModelBinder { private static readonly MethodInfo AddModelErrorMethod = typeof(TryParseModelBinder).GetMethod(nameof(AddModelError), BindingFlags.NonPublic | BindingFlags.Static)!; private static readonly MethodInfo SuccessBindingResultMethod = typeof(ModelBindingResult).GetMethod(nameof(ModelBindingResult.Success), BindingFlags.Public | BindingFlags.Static)!; private static readonly ParameterExpression BindingContextExpression = Expression.Parameter(typeof(ModelBindingContext), "bindingContext"); private static readonly ParameterExpression ValueProviderResultExpression = Expression.Parameter(typeof(ValueProviderResult), "valueProviderResult"); private static readonly MemberExpression BindingResultExpression = Expression.Property(BindingContextExpression, nameof(ModelBindingContext.Result)); private static readonly MemberExpression ValueExpression = Expression.Property(ValueProviderResultExpression, nameof(ValueProviderResult.FirstValue)); private static readonly MemberExpression CultureExpression = Expression.Property(ValueProviderResultExpression, nameof(ValueProviderResult.Culture)); private readonly Func<ValueProviderResult, ModelBindingContext, object?> _tryParseOperation; private readonly ILogger _logger; /// <summary> /// Initializes a new instance of <see cref="SimpleTypeModelBinder"/>. /// </summary> /// <param name="modelType">The model type.</param> /// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param> public TryParseModelBinder(Type modelType, ILoggerFactory loggerFactory) { ArgumentNullException.ThrowIfNull(modelType); ArgumentNullException.ThrowIfNull(loggerFactory); _tryParseOperation = CreateTryParseOperation(modelType); _logger = loggerFactory.CreateLogger(typeof(TryParseModelBinder)); } /// <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 = valueProviderResult.FirstValue; if (string.IsNullOrWhiteSpace(value)) { // Most TryParse() methods trim the value and fail if the result is empty. // When converting 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 (!bindingContext.ModelMetadata.IsReferenceOrNullableType) { bindingContext.ModelState.TryAddModelError( bindingContext.ModelName, bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( valueProviderResult.ToString())); } else { bindingContext.Result = ModelBindingResult.Success(null); } } else { _tryParseOperation(valueProviderResult, bindingContext); } } catch (Exception exception) { // Conversion failed. AddModelError(bindingContext, exception); } _logger.DoneAttemptingToBindModel(bindingContext); return Task.CompletedTask; } private static void AddModelError(ModelBindingContext bindingContext, Exception exception) { // Conversion failed. bindingContext.ModelState.TryAddModelError( bindingContext.ModelName, exception, bindingContext.ModelMetadata); } private static Func<ValueProviderResult, ModelBindingContext, object?> CreateTryParseOperation(Type modelType) { modelType = Nullable.GetUnderlyingType(modelType) ?? modelType; var tryParseMethodExpession = ModelMetadata.FindTryParseMethod(modelType) ?? throw new InvalidOperationException(Resources.FormatTryParseModelBinder_InvalidType(modelType, nameof(TryParseModelBinder))); // var tempSourceString = valueProviderResult.FirstValue; // object model = null; // if ([modeltype].TryParse(tempSourceString, [valueProviderResult.Culture,] out [modelType] parsedValue)) // { // model = (object)parsedValue; // bindingContext.Result = ModelBindingResult.Success(model); // } // else // { // AddModelError(bindingContext, new FormatException()); // } // return model; var parsedValue = Expression.Variable(modelType, "parsedValue"); var modelValue = Expression.Variable(typeof(object), "model"); var expression = Expression.Block( new[] { parsedValue, modelValue, ParameterBindingMethodCache.SharedExpressions.TempSourceStringExpr }, Expression.Assign(ParameterBindingMethodCache.SharedExpressions.TempSourceStringExpr, ValueExpression), Expression.IfThenElse(tryParseMethodExpession(parsedValue, CultureExpression), Expression.Block( Expression.Assign(modelValue, Expression.Convert(parsedValue, modelValue.Type)), Expression.Assign(BindingResultExpression, Expression.Call(SuccessBindingResultMethod, modelValue))), Expression.Call(AddModelErrorMethod, BindingContextExpression, Expression.Constant(new FormatException()))), modelValue); return Expression.Lambda<Func<ValueProviderResult, ModelBindingContext, object?>>(expression, new[] { ValueProviderResultExpression, BindingContextExpression }).Compile(); } }