aspnet核心控制器datetime参数绕过modelvalid检查

时间:2016-09-01 06:41:32

标签: datetime controller asp.net-core-mvc modelstate

我有很多这种形式的控制器:

public IActionResult GetSomething(int id, DateTime from) {
    ...
}

idfrom参数在查询中作为查询参数给出。如果未提供id,则ModelValid状态将设置为false。但是如果未提供from,则ModelValid为true,from设置为1900-01-01 00:00:00(DateTime.Min)。

如果未提供所需的DateTime参数,如何将ModelState设为false?

2 个答案:

答案 0 :(得分:1)

我决定去实现一个DateTime模型绑定器。如果缺少DateTime参数,则以下代码不会在ModelState上设置IsValid = true。约会时间? (可以为空的DateTime)处理得很好,但同样,如果缺少查询参数,则IsValid设置为false,而不是将参数设置为默认值。

首先是DateTimeModelBinderProvider:

public class DateTimeModelBinderProvider : IModelBinderProvider
{
    /// <inheritdoc />
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        if (!context.Metadata.IsComplexType)
        {
            // We can handle DateTime and nullable DateTime
            if ((context.Metadata.ModelType == typeof(DateTime)) ||
                (context.Metadata.IsNullableValueType && context.Metadata.UnderlyingOrModelType == typeof(DateTime)))
                return new DateTimeModelBinder(context.Metadata.ModelType);
        }

        return null;
    }
}

接下来是DateTimeModelBinder。大多数代码都是从github逐字复制的。其中一些可能被遗漏,但它的工作原理如下:

public class DateTimeModelBinder : IModelBinder
{
    private readonly TypeConverter _typeConverter;

    public DateTimeModelBinder(Type type)
    {
        if (type == null)
        {
            throw new ArgumentNullException(nameof(type));
        }

        _typeConverter = TypeDescriptor.GetConverter(type);
    }

    /// <inheritdoc />
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
        {
            throw new ArgumentNullException(nameof(bindingContext));
        }

        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult == ValueProviderResult.None)
        {
            // Do not accept an empty value provider result as being ok for DateTime (is ok for DateTime?)
            bindingContext.ModelState.TryAddModelError(
                    bindingContext.ModelName,
                    bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(
                        valueProviderResult.ToString()));

            // no entry
            return Task.CompletedTask;
        }

        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

        try
        {
            var value = valueProviderResult.FirstValue;

            object model = null;
            if (!string.IsNullOrWhiteSpace(value))
            {
                model = new DateTimeConverter().ConvertFrom(
                    context: null, 
                    culture: valueProviderResult.Culture,
                    value: value);
            }

            if (bindingContext.ModelType == typeof(string))
            {
                var modelAsString = model as string;
                if (bindingContext.ModelMetadata.ConvertEmptyStringToNull &&
                    string.IsNullOrEmpty(modelAsString))
                {
                    model = null;
                }
            }

            // 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()));

                return Task.CompletedTask;
            }
            else
            {
                bindingContext.Result = ModelBindingResult.Success(model);
                return Task.CompletedTask;
            }
        }
        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);

            // Were able to find a converter for the type but conversion failed.
            return Task.CompletedTask;
        }
    }
}

还要记得激活它。我将它插入提供程序列表的开头,以确保我的DateTime提供程序优先使用默认处理程序:

        var mvc = services.AddMvc(config => {
            config.ModelBinderProviders.Insert(0, new DateTimeModelBinderProvider());
        });

答案 1 :(得分:0)

您可以通过创建具有“From”属性的验证属性的模型来解决此问题。 我还没有测试出代码。但代码应该是:

public class Model
{
    public int Id { get; set; }
    [DateTimeShouldHaveValue]
    public DateTime From { get; set; }
}

public class DateTimeShouldHaveValueAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        if (value == null)
            return false;

        var dateTimeTmp = value.ToString();
        DateTime dateTime;
        DateTime.TryParse(dateTimeTmp, out dateTime);
        if (dateTime == DateTime.MinValue)
            return false;

        return true;
    }
}

public IActionResult GetSomething(Model model)
{

}