asp net core controller datetime参数绕过模型有效检查
aspnet core controller datetime parameter bypasses modelvalid check
我有很多这种形式的控制器:
public IActionResult GetSomething(int id, DateTime from) {
...
}
id
和 from
参数在查询中作为查询参数给出。如果未提供 id
,则 ModelValid
状态设置为 false。但是,如果未提供 from
,则 ModelValid 为真且 from 设置为 1900-01-01 00:00:00 (DateTime.Min).
如果未提供所需的 DateTime 参数,如何使 ModelState 为假?
您可以通过创建一个具有 '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)
{
}
我决定去实施 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());
});
我有很多这种形式的控制器:
public IActionResult GetSomething(int id, DateTime from) {
...
}
id
和 from
参数在查询中作为查询参数给出。如果未提供 id
,则 ModelValid
状态设置为 false。但是,如果未提供 from
,则 ModelValid 为真且 from 设置为 1900-01-01 00:00:00 (DateTime.Min).
如果未提供所需的 DateTime 参数,如何使 ModelState 为假?
您可以通过创建一个具有 '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)
{
}
我决定去实施 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());
});