模型绑定 space 字符到 char 属性
Model binding space character to char property
我有一个简单的视图模型 char
属性...
public char Character1 { get; set; }
默认模型绑定似乎没有将 space 字符 (" ") 转换成此 属性,导致以下 ModelState 错误...
The Character1 field is required.
html 输入元素创建于 javascript:
var input = $('<input type="password" name="Character' + i + '" id="input-' + i + '" data-val="true" data-val-custom maxlength="1"></input>');
- 属性 上没有
[Required]
属性。
- 在模型错误中发布的值肯定是 " "
AttemptedValue
属性。
ModelState.IsValid
returns false 由于上述错误。
- 模型属性绑定后空字符值为
[=19=]
。
为什么 space 字符未绑定到 char
属性?
更新:
将 char
属性 更改为 string
按预期绑定。
我认为这是 DefaultModelBinder 的失败。如果您在操作中使用 FormCollection,则字符串返回为 space.
此 IModelBinder 实现展示了默认模型绑定器的行为方式,并提供了可能的解决方案:
public class CharModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var dmb = new DefaultModelBinder();
var result = dmb.BindModel(controllerContext, bindingContext);
// ^^ result == null
var rawValueAsChar = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(char));
// ^^ rawValueAsChar == null
var rawValueAsString = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
if(!string.IsNullOrEmpty(rawValueAsString))
return rawValueAsString.ToCharArray()[0];
return null;
}
}
在您的 Global.asax 中注册:
ModelBinders.Binders.Add(typeof(char), new CharModelBinder());
原因很简单,char
被定义为值类型(struct
),而string
被定义为引用类型(class
)。这意味着 char
不可为 null,并且必须有一个值。
这就是为什么 DefaultModelBinder
(您可能正在使用)会自动将此 属性 的验证元数据设置为 required
,即使您没有添加 [Required]
属性。
这是 ModelMetaData.cs
的 source(第 58 行):
_isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);
因此,您最终将 Character1
属性 的 ModelMetaData.Required
设置为 true
。
但是,您可以明确配置 DataAnnotationsModelValidatorProvider
not 以自动将值类型设置为 required
使用以下内容:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
见MSDN
好的,我在 System.Web.Mvc.ValueProviderResult
中找到了违规代码:
private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
{
if (value == null || destinationType.IsInstanceOfType(value))
return value;
string str = value as string;
if (str != null && string.IsNullOrWhiteSpace(str))
return (object) null;
...
}
我不确定这是否是错误。
最近在 .NET Core 中遇到这个问题,因为 SimpleTypeModelBinder 有相同的检查,所以添加了以下内容:
using System;
using Microsoft.AspNetCore.Mvc.ModelBinding;
public class CharModelBinderProvider : IModelBinderProvider
{
/// <inheritdoc />
public IModelBinder GetBinder(
ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(char))
{
return new CharModelBinder();
}
return null;
}
}
using System;
using System.ComponentModel;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.ModelBinding;
/// <inheritdoc />
/// <summary>
/// An <see cref="T:Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" /> for char.
/// </summary>
/// <remarks>
/// Difference here is that we allow for a space as a character which the <see cref="T:Microsoft.AspNetCore.Mvc.ModelBinding.SimpleTypeModelBinder" /> does not.
/// </remarks>
public class CharModelBinder : IModelBinder
{
private readonly TypeConverter _charConverter;
public CharModelBinder()
{
this._charConverter = new CharConverter();
}
/// <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)
{
// no entry
return Task.CompletedTask;
}
bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);
try
{
var value = valueProviderResult.FirstValue;
var model = this._charConverter.ConvertFrom(null, valueProviderResult.Culture, value);
this.CheckModel(bindingContext, valueProviderResult, 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;
}
}
protected virtual void CheckModel(
ModelBindingContext bindingContext,
ValueProviderResult valueProviderResult,
object model)
{
// 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()));
}
else
{
bindingContext.Result = ModelBindingResult.Success(model);
}
}
}
并且在 Startup 中:
serviceCollection.AddMvc(options => { options.ModelBinderProviders.Insert(0, new CharModelBinderProvider()); })
我有一个简单的视图模型 char
属性...
public char Character1 { get; set; }
默认模型绑定似乎没有将 space 字符 (" ") 转换成此 属性,导致以下 ModelState 错误...
The Character1 field is required.
html 输入元素创建于 javascript:
var input = $('<input type="password" name="Character' + i + '" id="input-' + i + '" data-val="true" data-val-custom maxlength="1"></input>');
- 属性 上没有
[Required]
属性。 - 在模型错误中发布的值肯定是 " "
AttemptedValue
属性。 ModelState.IsValid
returns false 由于上述错误。- 模型属性绑定后空字符值为
[=19=]
。
为什么 space 字符未绑定到 char
属性?
更新:
将 char
属性 更改为 string
按预期绑定。
我认为这是 DefaultModelBinder 的失败。如果您在操作中使用 FormCollection,则字符串返回为 space.
此 IModelBinder 实现展示了默认模型绑定器的行为方式,并提供了可能的解决方案:
public class CharModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var dmb = new DefaultModelBinder();
var result = dmb.BindModel(controllerContext, bindingContext);
// ^^ result == null
var rawValueAsChar = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).ConvertTo(typeof(char));
// ^^ rawValueAsChar == null
var rawValueAsString = bindingContext.ValueProvider.GetValue(bindingContext.ModelName).AttemptedValue;
if(!string.IsNullOrEmpty(rawValueAsString))
return rawValueAsString.ToCharArray()[0];
return null;
}
}
在您的 Global.asax 中注册:
ModelBinders.Binders.Add(typeof(char), new CharModelBinder());
原因很简单,char
被定义为值类型(struct
),而string
被定义为引用类型(class
)。这意味着 char
不可为 null,并且必须有一个值。
这就是为什么 DefaultModelBinder
(您可能正在使用)会自动将此 属性 的验证元数据设置为 required
,即使您没有添加 [Required]
属性。
这是 ModelMetaData.cs
的 source(第 58 行):
_isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);
因此,您最终将 Character1
属性 的 ModelMetaData.Required
设置为 true
。
但是,您可以明确配置 DataAnnotationsModelValidatorProvider
not 以自动将值类型设置为 required
使用以下内容:
DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
见MSDN
好的,我在 System.Web.Mvc.ValueProviderResult
中找到了违规代码:
private static object ConvertSimpleType(CultureInfo culture, object value, Type destinationType)
{
if (value == null || destinationType.IsInstanceOfType(value))
return value;
string str = value as string;
if (str != null && string.IsNullOrWhiteSpace(str))
return (object) null;
...
}
我不确定这是否是错误。
最近在 .NET Core 中遇到这个问题,因为 SimpleTypeModelBinder 有相同的检查,所以添加了以下内容:
using System; using Microsoft.AspNetCore.Mvc.ModelBinding; public class CharModelBinderProvider : IModelBinderProvider { /// <inheritdoc /> public IModelBinder GetBinder( ModelBinderProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (context.Metadata.ModelType == typeof(char)) { return new CharModelBinder(); } return null; } } using System; using System.ComponentModel; using System.Runtime.ExceptionServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; /// <inheritdoc /> /// <summary> /// An <see cref="T:Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" /> for char. /// </summary> /// <remarks> /// Difference here is that we allow for a space as a character which the <see cref="T:Microsoft.AspNetCore.Mvc.ModelBinding.SimpleTypeModelBinder" /> does not. /// </remarks> public class CharModelBinder : IModelBinder { private readonly TypeConverter _charConverter; public CharModelBinder() { this._charConverter = new CharConverter(); } /// <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) { // no entry return Task.CompletedTask; } bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); try { var value = valueProviderResult.FirstValue; var model = this._charConverter.ConvertFrom(null, valueProviderResult.Culture, value); this.CheckModel(bindingContext, valueProviderResult, 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; } } protected virtual void CheckModel( ModelBindingContext bindingContext, ValueProviderResult valueProviderResult, object model) { // 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())); } else { bindingContext.Result = ModelBindingResult.Success(model); } } }
并且在 Startup 中:
serviceCollection.AddMvc(options => { options.ModelBinderProviders.Insert(0, new CharModelBinderProvider()); })