C# 模型绑定空间字符到字符属性

C# 模型绑定空间字符到字符属性,c#,asp.net-mvc,model-binding,C#,Asp.net Mvc,Model Binding,我有一个带有char属性的简单viewmodel public char Character1 { get; set; } 默认模型绑定似乎没有将空格字符(“”)转换为此属性,导致以下ModelState错误 The Character1 field is required. html输入元素是在javascript中创建的: var input = $('<input type="password" name="Character' + i + '" id="input-' + i

我有一个带有
char
属性的简单viewmodel

public char Character1 { get; set; }
默认模型绑定似乎没有将空格字符(“”)转换为此属性,导致以下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>');
var输入=$('');
  • 属性上没有
    [必需]
    属性
  • 在model error
    AttemptedValue
    属性中,要发布的值肯定是“”
  • 由于上述错误,
    ModelState.IsValid
    返回false
  • 绑定后,model属性的空字符值为
    \0
为什么空格字符没有绑定到
char
属性

更新:


char
属性更改为
string
将按预期绑定。

我认为这是DefaultModelBinder的失败。如果在操作中使用FormCollection,字符串将作为空格返回

此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
不可为空,并且必须有一个值

这就是为什么
DefaultModelBinder
(您可能正在使用)会自动将此属性的验证元数据设置为
required
,即使您没有添加
[required]
属性

以下是
ModelMetaData.cs
(第58行)的示例:

因此,将
Character1
属性的
ModelMetaData.Required
设置为
true

但是,您可以使用以下方法显式配置
DataAnnotationsModelValidatorProvider
以不自动将值类型设置为
required

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
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); } } }
请参见

好,我在
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;
      ...
}

我不确定这是否是一个bug。

最近在.NET Core中遇到了这个问题,因为SimpleTypeModelBinder具有相同的检查,所以添加了以下内容:

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;
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); } } } 使用制度; 使用Microsoft.AspNetCore.Mvc.ModelBinding; 公共类CharModelBinderProvider:IModelBinderProvider { /// 公共IModelBinder GetBinder( ModelBinderProviderContext(上下文) { if(上下文==null) { 抛出新ArgumentNullException(nameof(context)); } if(context.Metadata.ModelType==typeof(char)) { 返回新的CharModelBinder(); } 返回null; } } 使用制度; 使用系统组件模型; 使用System.Runtime.ExceptionServices; 使用System.Threading.Tasks; 使用Microsoft.AspNetCore.Mvc.ModelBinding; /// /// ///一个用于char的函数。 /// /// ///这里的区别是我们允许一个空格作为一个字符,而这个字符不允许。 /// 公共类CharModelBinder:IModelBinder { 专用只读类型转换器\u charConverter; 公共CharModelBinder() { 此._charConverter=新charConverter(); } /// 公共任务BindModelAsync( ModelBindingContext(绑定上下文) { if(bindingContext==null) { 抛出新ArgumentNullException(nameof(bindingContext)); } var valueProviderResult=bindingContext.ValueProvider.GetValue(bindingContext.ModelName); 如果(valueProviderResult==valueProviderResult.None) { //禁止进入 返回Task.CompletedTask; } bindingContext.ModelState.SetModelValue(bindingContext.ModelName,valueProviderResult); 尝试 { var值=valueProviderResult.FirstValue; var model=this.\u charConverter.ConvertFrom(null,valueProviderResult.Culture,value); this.CheckModel(bindingContext、valueProviderResult、model); 返回Task.CompletedTask; } 捕获(异常) { var isFormatException=异常为FormatException; 如果(!isFormatException&&exception.InnerException!=null) { //TypeConverter抛出System.Exception包装FormatException,因此我们捕获内部异常。 exception=ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; } bindingContext.ModelState.TryAddModelError(bindingContext.ModelName,异常,bindingContext.ModelMetadata); //无法找到该类型的转换器,但转换失败。 返回Task.CompletedTask; } } 受保护的虚拟void检查模型( ModelBindingContext bindingContext, ValueProviderResult ValueProviderResult, 对象模型) { //转换新模型时,空值可能表示转换其他所需模型失败(无法将ValueType设置为空)。 //这检测在给定当前bindingContext的情况下空模型值是否可接受。如果不可接受,则记录错误。 if(model==null&&!bindingContext.ModelMetadata.IsReferenceOrNullableType) { bindingContext.ModelState.TryAddModelError( bindingContext.ModelName, bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor(valueProviderResult.ToString()); } 其他的