Alex
Alex

Reputation: 77329

C# MVC: When to use a specific (non-default) ModelBinder?

It is my understanding that the default model binder is capable of turning

<input type="text" name="myType.property1" value="John Doe" />
<input type="text" name="myType.property2" value="22" />

into:

[AcceptVerbs(HttpVerbs.Post)]
public ActionResult SomeAction(MyType myType)
{
     // This will be "John Doe"
     String someName = myType.property1;
     // This will be 22
     Int32 someNumber = myType.property2;


     // 
     // irrelevant magic
}         

In what scenario would I use the non-default model binder? I can't think of a reason not to name the HTML input's any differently than class property names. Please clarify, with an example.

Thank you!

Upvotes: 1

Views: 629

Answers (3)

nkirkes
nkirkes

Reputation: 2665

Scott Hanselman has a simple case study for a custom model binder.

The point of the post isn't that what he's trying to do can't be done other ways with the default binding, but that he's attempting it allows him to re-use code that will do the job for him.

Upvotes: 2

fretje
fretje

Reputation: 8372

In the scenario that your MyType for instance doesn't have a default constructor (the default modelbinder needs a default constructor).
This can happen if you use the factory method pattern for creating a new object (very simple example just for illustration ;-):

public class MyType
{
    private MyType() // prevent direct creation through default constructor
    {  
    }

    public static MyType CreateNewMyType()
    {
        return new MyType();
    }
}

Then you would have to implement a custom modelbinder which calls the factory method CreateNewMyType() instead of creating a new MyType through reflection:

public class MyTypeBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext,
                                          ModelBindingContext bindingContext,
                                          Type modelType)
    {
        return MyType.CreateNewMyType();
    }
}

Also if you're not happy with the current functionality or implementation of the default modelbinder, then you can easily replace it with your own implementation.

Upvotes: 3

Robert Harvey
Robert Harvey

Reputation: 180798

Consider a custom type called TimeInterval that is stored as a double, but is displayed as hh.mm.ss.ffffff where ffffff is fractional seconds. With custom binding, it is possible to show the binder how to properly parse and display these numbers, without having to write custom code in the controller.

// This class implements a custom data type for data binding.
public class TimeInterval
{
    double _value;

    public TimeInterval(string value)
    {
        // Extension method parses string value to double.
        _value = value.ToSeconds();
    }
    public TimeInterval(double value)
    {
        _value = value;
    }
    public string GetText()
    {
        // Extension method formats double value as time string.
        return _value.ToTimeString();
    }
    public double? GetValue()
    {
        return _value;
    }
}

// This class implements custom binding for the TimeInterval custom type.  
// It is registered in Global.asax 
public class TimeIntervalBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string value = GetAttemptedValue(bindingContext);

        if (value == "")
            return null;

        return new TimeInterval(value);
    }

    private static string GetAttemptedValue(ModelBindingContext context)
    {
        ValueProviderResult value = context.ValueProvider[context.ModelName];
        return value.AttemptedValue ?? string.Empty;
    }
}

// in Global.asax:
protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ModelBinders.Binders.Add(typeof(TimeInterval), new TimeIntervalBinder());
}

Now binding happens automatically for the new custom type.

Upvotes: 2

Related Questions