将空格字符绑定到char属性的模型

时间:2015-03-03 14:05:34

标签: c# asp.net-mvc model-binding

我有一个简单的viewmodel,其中包含char属性...

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>');
  • 该属性没有[Required]属性。
  • 发布的价值肯定是&#34; &#34;在模型错误AttemptedValue属性。
  • 由于上述错误,
  • ModelState.IsValid返回false。
  • 绑定后,model属性的空字符值为\0

为什么空格字符没有绑定到char属性?

更新

char属性更改为string会按预期进行绑定。

4 个答案:

答案 0 :(得分:2)

我认为它是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());

答案 1 :(得分:2)

原因很简单,char被定义为值类型(struct),而string被定义为引用类型(class)。这意味着char不可为空,必须具有值。

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

此处为ModelMetaData.cs source(第58行):

_isRequired = !TypeHelpers.TypeAllowsNullValue(modelType);

因此,对于ModelMetaData.Required属性设置为Character1,您最终会得到true

但是,您可以使用以下内容明确配置DataAnnotationsModelValidatorProvider 而不是以自动将值类型设置为required

DataAnnotationsModelValidatorProvider.AddImplicitRequiredAttributeForValueTypes = false;

请参阅MSDN

答案 2 :(得分:1)

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

我不确定这是不是一个错误。

答案 3 :(得分:1)

最近在.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);
            }
        }
    }

在初创公司内:

serviceCollection.AddMvc(options => { options.ModelBinderProviders.Insert(0, new CharModelBinderProvider()); })