模型绑定 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>');

为什么 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.cssource(第 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()); })