How to make ModelBinder return null for a parameter?

I have a POCO which I use as an argument for action in MVC3. Something like that:

My type

public class SearchData { public string Property1 { get; set; } public string Property2 { get; set; } public string Property3 { get; set; } } 

My action

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

Currently, query is passed as an instance of SearchData with all properties as null . I would prefer that I get null for query , so I can just do the null check specified in the above code.

I could always look at ModelBinder.Any() or just the various keys in ModelBinder to see if it got any properties for query , but I don't want to use reflection to handle the properties of query . In addition, I can only use the ModelBinder.Any() tag if the request is my only parameter. As soon as I add additional parameters, this functionality breaks.

With the existing model binding functionality in MVC3, is it possible to get null return behavior for a POCO argument to an action?

+4
source share
6 answers

To do this, you will need to implement a custom device model. You can simply extend 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; } 

Concrete implementation

This is the actual binder implementation that will return null for the model if all properties are 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; } } 

Adding this as a binder in global.asax

 protected void Application_Start() { AreaRegistration.RegisterAllAreas(); ModelBinders.Binders.Add(typeof(SearchData), new SearchDataModelBinder()); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); MvcHandler.DisableMvcResponseHeader = true; } 
+6
source

try the route

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

Embed a custom model binder as a parameter attribute.

NOTE. All properties of your model must be NULL

  • The following is an example of a 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; } } 
  • Create attribute

     public class NullModelAttribute : CustomModelBinderAttribute { public override IModelBinder GetBinder() { return new NullModelBinder(); } } 
  • Use controller method attribute

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

I do not know the answer to your specific question, but I can come up with a workaround. Why not just add a method to the SearchData class?

 public bool IsEmpty(){ return Property1 == null && Property2 == null && Property3 == null; } 

Of course, if you have several types that you are trying to do, this can become tedious.

0
source

Deploy the user module, but use the interface to determine if the object is null. I prefer this template for two reasons:

  • Using reflection for each binding can be very expensive.
  • It encapsulates the logic of determining whether an object is null for that object.

     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(); } 
0
source

I found that SetProperty for DefaultModelBinder is only called when it finds a property and tries to set it.

With this in mind, this is my 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; } } 

So, only if the infrastructure has detected a property in the request and tries to set it to the model, I return the model created by BindModel .

Note:

My approach is different from the NullBinders of the previous answers, because it passes once through each property, and in the worst case, the other NullBinders go twice.

In this snnipet code:

 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 } 

When the call to base.BindModel is called .Net passes through each property of the model, trying to find them and set them when creating the model.

Then CustomModelBinder again returns to any property until it finds one of those present in the request, in which case it returns the model created by .NET, otherwise returns null.

Thus, if the property is not set, we would effectively go through each property of the model twice.

0
source

All Articles