【问题标题】:aspnet core controller datetime parameter bypasses modelvalid checkasp net core 控制器 datetime 参数绕过模型有效检查
【发布时间】:2017-01-08 21:55:21
【问题描述】:

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

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?

【问题讨论】:

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


    【解决方案1】:

    您可以通过创建具有“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)
    {
    
    }
    

    【讨论】:

      【解决方案2】:

      我决定去实现一个 DateTime 模型绑定器。如果缺少 DateTime 参数,以下代码将不会在 ModelState 上设置 IsValid=true。约会时间? (nullable 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());
              });
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-04-18
        • 2018-10-27
        • 1970-01-01
        • 1970-01-01
        • 2021-11-11
        • 1970-01-01
        • 2022-07-27
        • 2012-06-24
        相关资源
        最近更新 更多