C# 如何使ModelBinder为参数返回null?

C# 如何使ModelBinder为参数返回null?,c#,asp.net-mvc,model-binding,modelbinders,C#,Asp.net Mvc,Model Binding,Modelbinders,我有一个POCO,我用它作为MVC3中动作的参数。大概是这样的: 我的类型 我的行动 当前,query作为SearchData的实例传递,所有属性都为null。我更喜欢为query获取null,这样我就可以执行上面代码中的null检查 我可以随时查看ModelBinder.Any()或仅查看ModelBinder中的各个键,查看它是否获得了query的任何属性,但我不想使用反射来循环query的属性。此外,我只能使用ModelBinder.Any()检查查询是否是我的唯一参数。一旦我添加了其他参

我有一个POCO,我用它作为MVC3中动作的参数。大概是这样的:

我的类型 我的行动 当前,
query
作为
SearchData
的实例传递,所有属性都为
null
。我更喜欢为
query
获取
null
,这样我就可以执行上面代码中的null检查

我可以随时查看
ModelBinder.Any()
或仅查看
ModelBinder
中的各个键,查看它是否获得了
query
的任何属性,但我不想使用反射来循环
query
的属性。此外,我只能使用
ModelBinder.Any()
检查查询是否是我的唯一参数。一旦我添加了其他参数,该功能就会中断


使用MVC3中当前的模型绑定功能,是否有可能获得将POCO参数返回null的行为?

您需要实现自定义modelbinder来完成此操作。您只需扩展
DefaultModelBinder

public override object BindModel(
    ControllerContext controllerContext, 
    ModelBindingContext bindingContext)
{
    object model = base.BindModel(controllerContext, bindingCOntext);
    if (/* test for empty properties, or some other state */)
    {
        return null;
    }

    return model;
}
具体实施 这是绑定器的实际实现,如果所有属性都为null,那么绑定器将为模型返回null

/// <summary>
/// Model binder that will return null if all of the properties on a bound model come back as null
/// It inherits from DefaultModelBinder because it uses the default model binding functionality.
/// This implementation also needs to specifically have IModelBinder on it too, otherwise it wont get picked up as a Binder
/// </summary>
public class SearchDataModelBinder : DefaultModelBinder, IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        // use the default model binding functionality to build a model, we'll look at each property below
        object model = base.BindModel(controllerContext, bindingContext);

        // loop through every property for the model in the metadata
        foreach (ModelMetadata property in bindingContext.PropertyMetadata.Values)
        {
            // get the value of this property on the model
            var value = bindingContext.ModelType.GetProperty(property.PropertyName).GetValue(model, null);

            // if any property is not null, then we will want the model that the default model binder created
            if (value != null)
                return model;
        }

        // if we're here then there were either no properties or the properties were all null
        return null;
    }
}

我不知道你具体问题的答案,但我可以想出一个解决办法。为什么不向
SearchData
类添加一个方法呢

public bool IsEmpty(){
  return Property1 == null 
      && Property2 == null 
      && Property3 == null;
}
当然,如果您尝试在多个类型上执行此操作,则可能会变得单调乏味。

在路线中,请尝试

new { controller = "Articles", action = "Index", query = UrlParameter.Optional }

实现自定义modelbinder,但使用接口确定对象是否为null。我喜欢这种模式有两个原因:

  • 在每个绑定上使用反射可能会非常昂贵
  • 它封装了如何确定对象对该对象是否为空的逻辑

    public class NullValueModelBinder : DefaultModelBinder, IModelBinder {
    
      public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
    
         object model = base.BindModel(controllerContext, bindingContext);
    
         if (model is INullValueModelBindable && (model as INullValueModelBindable).IsNull()){
             return null;
         }
    
         return model;
      }
    }
    
    public interface INullValueModelBindable {
        bool IsNull();
    }
    

  • 将自定义模型绑定器作为参数的属性实现

    注意:模型上的所有属性都必须为空

  • 这是上面的ModelBinderClass

    public class NullModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
             // use the default model binding functionality to build a model, we'll look at each property below
             object model = base.BindModel(controllerContext, bindingContext);
    
             // loop through every property for the model in the metadata
             foreach (ModelMetadata property in bindingContext.PropertyMetadata.Values)
             {
                 // get the value of this property on the model
                 var value = bindingContext.ModelType.GetProperty(property.PropertyName).GetValue(model, null);
    
                 // if any property is not null, then we will want the model that the default model binder created
                 if (value != null) return model;
             }
    
             // if we're here then there were either no properties or the properties were all null
             return null;
         }
    }
    
  • 创建一个属性

    public class NullModelAttribute : CustomModelBinderAttribute
    {
        public override IModelBinder GetBinder()
        {
            return new NullModelBinder();
        }
    }
    
  • 在控制器方法上使用属性

    public ActionResult Index([NullModel] SearchData query)
    {
        // I'd like to be able to do this
        if (query == null)
        {
            // do something
        }
    }
    

  • 我发现
    DefaultModelBinder
    SetProperty
    只有在找到属性并尝试设置它时才会被调用

    记住这一点,这是我的NullModelBinder

    public class NullModelBinder : DefaultModelBinder
    {
        public bool PropertyWasSet { get; set; }
    
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object model = base.BindModel(controllerContext, bindingContext);
            if (!PropertyWasSet)
            {
                return null;
            }
    
            return model;
        }
    
        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
            PropertyWasSet = true;
        }
    }
    
    因此,只有当框架在请求中找到属性并尝试将其设置为模型时,我才会返回由
    BindModel
    创建的模型

    注意:

    我的方法不同于前面答案中的NullBinder,因为它只对每个属性执行一次,而在最坏的情况下,其他NullBinder执行两次

    在此代码中,snnipet:

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        object model = base.BindModel(controllerContext, bindingContext);
    
        // loop through every property for the model in the metadata
        //CODE HERE
    }
    
    当调用
    base.BindModel
    时,.Net将遍历模型上的每个属性,试图找到它们并在创建的模型上设置它们

    然后CustomModelBinder再次遍历每个属性,直到在请求中找到一个,在这种情况下返回由.Net创建的模型,否则返回null


    因此,如果没有设置属性,我们将有效地对模型的每个属性进行两次检查。

    我可以,但我真的不愿意,因为我必须保持这一点。啊,是的,这是解决方案。我用我的具体实现更新了你的答案。我最终也继承了DefaultModelBinder,就像你建议的那样。
    public ActionResult Index([NullModel] SearchData query)
    {
        // I'd like to be able to do this
        if (query == null)
        {
            // do something
        }
    }
    
    public class NullModelBinder : DefaultModelBinder
    {
        public bool PropertyWasSet { get; set; }
    
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            object model = base.BindModel(controllerContext, bindingContext);
            if (!PropertyWasSet)
            {
                return null;
            }
    
            return model;
        }
    
        protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, object value)
        {
            base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
            PropertyWasSet = true;
        }
    }
    
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        object model = base.BindModel(controllerContext, bindingContext);
    
        // loop through every property for the model in the metadata
        //CODE HERE
    }