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
- 由于上述错误,
返回falseModelState.IsValid
- 绑定后,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());
}
其他的