将 JSON 对象传递给 MVC 控制器时 string.empty 转换为 null

Posted

技术标签:

【中文标题】将 JSON 对象传递给 MVC 控制器时 string.empty 转换为 null【英文标题】:string.empty converted to null when passing JSON object to MVC Controller 【发布时间】:2012-09-25 21:23:51 【问题描述】:

我正在将一个对象从客户端传递到服务器。在此过程中,表示为 string.empty 的对象的属性将被转换为 null。我想知道当对象类型支持 string.empty 时如何防止这种情况发生。

console.log("DataToPost:", dataToPost);

$.ajax(
    type: "POST",
    contentType: 'application/json'
    url: "../../csweb/Orders/SaveOrderDetails/",
    data: dataToPost,
    success: function (result) 
        console.log(result);
    ,
    error: function (e) 
        console.error(e);
    
);

我的模型包括可为空的 DateTime 对象。我不能在服务器上强制所有 null 为 string.empty。

我正在使用 AutoMapper,因此我不想在服务器上单独检查属性。

【问题讨论】:

尝试对数据进行字符串化:data: JSON.stringify(dataToPost),,您还应该将 contentType 指定为:contentType: "application/json" @nemesv 感谢您的建议。应用 JSON.stringify(dataToPost) 并指定内容类型后,数据仍显示为 null。 【参考方案1】:

这是一个将空字符串绑定到nulls 的 MVC 功能。

此逻辑由DefaultModelBinder 使用的ModelMetadata.ConvertEmptyStringToNull 属性控制。

您可以使用DisplayFormat 属性设置ConvertEmptyStringToNull

public class OrderDetailsModel

    [DisplayFormat(ConvertEmptyStringToNull = false)]
    public string Comment  get; set; 

    //...

但是,如果您不想注释所有属性,您可以创建一个自定义模型绑定器,并将其设置为 false:

public class EmptyStringModelBinder : DefaultModelBinder 

    public override object BindModel(ControllerContext controllerContext,
                                     ModelBindingContext bindingContext)
    
        bindingContext.ModelMetadata.ConvertEmptyStringToNull = false;
        Binders = new ModelBinderDictionary()  DefaultBinder = this ;
        return base.BindModel(controllerContext, bindingContext);
    

您可以在操作中使用ModelBinderAttribute:

public ActionResult SaveOrderDetails([ModelBinder(typeof(EmptyStringModelBinder))] 
       OrderDetailsModel orderDetailsModel)


或者您可以在 Global.asax 中将其设置为全局默认 ModelBinder:

ModelBinders.Binders.DefaultBinder = new EmptyStringModelBinder();

您可以阅读有关此功能的更多信息here。

【讨论】:

这显然是问题的正确答案。谢谢你。更新:也像宣传的那样工作! 如果我只为一个参数设置了 ModelBinderAttribute 属性,如上所述,上述解决方案不起作用。似乎在绑定单个模型属性时使用了标准 DefaultModelBinder,这使得它们无论如何都为空。我对此的解决方法是将以下代码添加到重写的 BindModel() 方法的开头:Binders = new ModelBinderDictionary() DefaultBinder = this ; 确认:上面的例子在应用属性时不起作用 @Anders 感谢修复。我已经用这些信息更新了我的帖子。 使用 System.ComponentModel.DataAnnotations;【参考方案2】:

与其像一些答案建议的那样创建修改 ModelMetadata 的 ModelBinder,更简洁的替代方法是提供自定义 ModelMetadataProvider。

public class EmptyStringDataAnnotationsModelMetadataProvider : System.Web.Mvc.DataAnnotationsModelMetadataProvider 

    protected override ModelMetadata CreateMetadata(IEnumerable<Attribute> attributes, Type containerType, Func<object> modelAccessor, Type modelType, string propertyName)
    
        var modelMetadata = base.CreateMetadata(attributes, containerType, modelAccessor, modelType, propertyName);
        modelMetadata.ConvertEmptyStringToNull = false;
        return modelMetadata;
    

然后在Application_Start()中

ModelMetadataProviders.Current = new EmptyStringDataAnnotationsModelMetadataProvider();

【讨论】:

在 MVC 5.2.3 中,ModelMetadataProviders.Current 默认返回CachedDataAnnotationsModelMetadataProvider 的实例。 CachedDataAnnotationsModelMetadataProvider 密封 CreateMetaData 方法,因此您不能像您在此处建议的那样覆盖它。我猜缓存的提供程序性能更高,所以我不确定这个答案是否是个好主意,因为它不使用缓存的提供程序(无论如何也不能)。【参考方案3】:

接受的答案对我使用 MVC4 无效。但是,以下解决方法确实可以,我认为它会对其他人有所帮助。

public class CustomModelBinder : DefaultModelBinder

    public bool ConvertEmptyStringToNull  get; set; 

    public CustomModelBinder ()
    
    

    public CustomModelBinder (bool convertEmptyStringToNull)
    
        this.ConvertEmptyStringToNull = convertEmptyStringToNull;
    

    protected override bool OnModelUpdating(ControllerContext controllerContext, ModelBindingContext bindingContext)
    
        // this little bit is required to override the ConvertEmptyStringToNull functionality that we do not want!

        foreach (string propertyKey in bindingContext.PropertyMetadata.Keys)
        
            if(bindingContext.PropertyMetadata[propertyKey] != null)
                    bindingContext.PropertyMetadata[propertyKey].ConvertEmptyStringToNull = this.ConvertEmptyStringToNull;
        
        return base.OnModelUpdating(controllerContext, bindingContext);
    



这将解决 MVC4+ 下的问题。似乎 bindingContext.ModelMetadata.ConvertEmptyStringToNull 被完全忽略,这是因为该设置存在于每个被绑定的属性的 PropertyMetadata 对象中。 PropertyMetadata 在 BindProperty() 中重新创建,因此如果您在该方法调用之前设置它,它将被覆盖,除非它作为被绑定对象的属性上的属性存在(例如 [DisplayFormat(ConvertEmptyStringToNull=false)])。没有人愿意在每个属性上都这样做,因为这很愚蠢。

【讨论】:

我在数组方面遇到了类似的问题。当涉及到 mvc-actions 的参数时,像 " foo: [] " 这样的 Json 字符串会导致 foo=null。有什么方法可以调整默认 mvc json-deserializer 的反序列化,以便它将给定的 json-string 转换为实际的空数组(也就是不为空)?【参考方案4】:

问题在于,当字符串为空时,AutoMapper 会将可空值变为空值。另一个问题的答案是我认为可以满足您的需求:Automapper null string to empty

【讨论】:

感谢您的回复。如果您查看我的第二个屏幕截图,您会看到我的监视窗口悬停在传递给 SaveOrderDetails 的参数上。 AutoMapper 还没有发挥作用,但也许 AutoMapper 可以解决这个问题。我会玩它的。【参考方案5】:

使用$.ajax 发布数据时,null 不是data 选项属性的可能值。如果您使用 http 调试器查看请求,您会看到它已转换为空字符串。

所以我猜你的 MVC 控制器正在应用相反的转换。

我在 ajax 应用程序中解决此问题的方法是我不使用 $.ajax()data 选项,但我将所有内容序列化为 JSON 并将其放入数据的单个字段“数据”中选项。像这样你没有空值的问题。当然,你必须在服务器端反序列化。

【讨论】:

以上是关于将 JSON 对象传递给 MVC 控制器时 string.empty 转换为 null的主要内容,如果未能解决你的问题,请参考以下文章

Angular2 http post没有将json对象传递给MVC 6控制器动作

将 JSON 对象传递给控制器

如何将 javascript 对象传递给 C# MVC 4 控制器

将多个 JSON 对象传递给 MVC3 操作方法

使用 jQuery Ajax 将单个对象传递给 MVC 控制器方法

Ajax 将“映射”对象传递给 Spring MVC 控制器