FrankDev
FrankDev

Reputation: 41

Create MVC Custom Model Binder for model tuples

Need assistance in creating a MVC Custom Model Binder to post multiple model tuple to controller. Never worked with custom model binder. Have looked at other answers to this issue but, don't seem to come close in dealing with a tuple of models or provide desired solution. Any ideas are appreciated. - Thanks

View

@model Tuple<Contact, Communications, Addresses>
@using (Html.BeginForm()) {
  <div id="contact">
    <div class="editor-label">
         @Html.LabelFor(m => m.Item1.FirstName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Item1.FirstName)
    </div>
    <div class="editor-label">
        @Html.LabelFor(m => m.Item1.LastName)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Item1.LastName)
    </div>
    <div>
        <input type="submit" value="Create" />
    </div>
  </div>
  <div id="communication">
    <div class="editor-label">
        @Html.LabelFor(m => m.Item2.TelephoneValue)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Item2.TelephoneValue)
    </div>
  </div> 
  <div id="address">
    <div class="editor-label">
        @Html.LabelFor(m => m.Item3.Address1)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Item3.Address1
    </div>
    <div class="editor-label">
        @Html.LabelFor(m => m.Item3.City)
    </div>
    <div class="editor-field"> 
        @Html.TextBoxFor(m => m.Item3.City)
    </div>
    <div class="editor-label">
        @Html.LabelFor(m => m.Item3.StateProvince)
    </div>
    <div class="editor-field">
        @Html.TextBoxFor(m => m.Item3.StateProvince)
    </div>
    <div class="editor-label">
        @Html.LabelFor(m => m.Item3.PostalCode) 
    </div>
    <div class="editor-field"> 
        @Html.TextBoxFor(m => m.Item3.PostalCode, new { id = "zip", style = "width:90px;" })
    </div>
  </div> 
}

Contoller

[HttpPost]
public ActionResult CreateContact(Tuple<Contact, Communications, Addresses> tuple) {
      //…. Do tuple processing to transfer values to add values to App Service here.
}

Upvotes: 3

Views: 2870

Answers (3)

N-ate
N-ate

Reputation: 6933

The ease of using a tuple as a model is greatly diminished if it is necessary to add binding lines to a custom binder for each tuple.

Here is a custom model binder that will work with all tuples. It calls itself recursively so that type binders that are registered will still function. Now it is only necessary to change your tuple model in one place, which is ideal.

public class CustomModelBinder : DefaultModelBinder
{
    private static Type _tupleInterfaceType = Type.GetType("System.ITuple", true, false);

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        if (modelType.FindInterfaces((a, b) => a == (Type)b, _tupleInterfaceType).Length == 1)
        {
            object[] args = new object[modelType.GenericTypeArguments.Length];
            for (int i = 0; i < args.Length; i++)
            {
                if (modelType.GenericTypeArguments[i].IsValueType) args[i] = Activator.CreateInstance(modelType.GenericTypeArguments[i]);
                else args[i] = CreateModel(controllerContext, bindingContext, modelType.GenericTypeArguments[i]);
            }
            return Activator.CreateInstance(modelType, args);
        }
        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}

Note: It is necessary to create the System.ITuple interface type from a string, because it is a protected interface.

I hope this saves you some time. 8)

Upvotes: 0

FrankDev
FrankDev

Reputation: 41

For those who might be interested in the outcome of this implementation for this approach, I would like to report that it has worked out very well and exceeded all expectations in flexibility. Originally, it was used to solve a three model object tuple, it has since expanded to a four model object tuple successfully, Keep in mind that this approach does have a limit of 8 items though, there is a way of cascading the tuple to more items, not sure if it is even practical. Here are some code snippets that might be of use:

//Custom Model Binder
public class TupleModelBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext,
              ModelBindingContext bindingContext, Type modelType)
   {
      if (modelType == typeof(Tuple<ContactModel, CommunicationModel, AddressModel>))
            return new Tuple<ContactModel, CommunicationModel, AddressModel>(new ContactModel(), new CommunicationModel(), new AddressModel());
      if (modelType == typeof(Tuple<ContactModel, CommunicationModel, AddressModel, CustomerModel>))
            return new Tuple<ContactModel, CommunicationModel, AddressModel, CustomerModel>(new ContactModel(), new CommunicationModel(), new AddressModel(), new CustomerModel());

         return base.CreateModel(controllerContext, bindingContext, modelType);
      }
 }

 // In Global.asax Application_Start()
     ModelBinders.Binders.DefaultBinder = new TupleModelBinder();

Upvotes: 0

Moin
Moin

Reputation: 166

Why dont you try keeping the "Contact, Communications, Addresses" models inside a new model and bind it to the view.

It will make handling very simple.

Upvotes: 1

Related Questions