|
// 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 Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
/// <summary>
/// An <see cref="IModelBinder"/> for <see cref="DateTime"/> and nullable <see cref="DateTime"/> models.
/// </summary>
public class DateTimeModelBinder : IModelBinder
{
private readonly DateTimeStyles _supportedStyles;
private readonly ILogger _logger;
/// <summary>
/// Initializes a new instance of <see cref="DateTimeModelBinder"/>.
/// </summary>
/// <param name="supportedStyles">The <see cref="DateTimeStyles"/>.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/>.</param>
public DateTimeModelBinder(DateTimeStyles supportedStyles, ILoggerFactory loggerFactory)
{
ArgumentNullException.ThrowIfNull(loggerFactory);
_supportedStyles = supportedStyles;
_logger = loggerFactory.CreateLogger(typeof(DateTimeModelBinder));
}
/// <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 DateTimeSyles) then throws if the result is empty.
model = null;
}
else if (type == typeof(DateTime))
{
model = DateTime.Parse(value, valueProviderResult.Culture, _supportedStyles);
}
else
{
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)
{
// Conversion failed.
modelState.TryAddModelError(modelName, exception, metadata);
}
_logger.DoneAttemptingToBindModel(bindingContext);
return Task.CompletedTask;
}
}
|