Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I make the ModelBinder return null for a parameter?

I've got a POCO that I'm using as an argument to an action in MVC3. Something like this:

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 of the properties as null. I'd prefer that i get a null for query so I can just do the null check that I have in the above code.

I could always look at ModelBinder.Any() or just the various keys in ModelBinder to see if it got any of the properties for query, but I don't want to have to use reflection to loop over the properties of query. Also, I can only use the ModelBinder.Any() check if query is my only parameter. As soon as I add additional parameters, that functionality breaks.

With the current model binding functionality in MVC3, is it possible to get the behavior of returning null for POCO argument to an action?

like image 669
Allen Rice Avatar asked Feb 20 '23 21:02

Allen Rice


2 Answers

You'll need to implement a custom modelbinder to do this. You can just 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;
}

Specific Implementation

This is the actual implementation of the binder that will return null for the model if all of the 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;
}
like image 133
bhamlin Avatar answered Feb 22 '23 09:02

bhamlin


in the route try

new { controller = "Articles", action = "Index", query = UrlParameter.Optional }
like image 21
viperguynaz Avatar answered Feb 22 '23 09:02

viperguynaz