romeozor
romeozor

Reputation: 941

Bind only a subset of a model in an MVC Action

I would like to have multiple forms on a single page showing each one on a fancy tab. I thought I would create a container model which would hold the models the work would actually happen on. Then I would create handlers for each form(/tab) in the controller accepting the specific model as its parameter I want to work with.

Consider the following models:

public class FormCollection
{
    public FormsContainer()
    {
        Form1 = new Form1();
        Form2 = new Form2();
    }
    public Form1 Form1 { get; set; }
    public Form2 Form2 { get; set; }
}

public class Form1
{
    public string PropNameCollision { get; set; }

    public DateTime? Form1Date { get; set; }
}

public class Form2
{
    public string PropNameCollision { get; set; }

    public DateTime? Form2Date { get; set; }
}

In the FormController controller:

public ActionResult Form1Handler(Form1 model)
{
    return Content("Doing Form1");
}
public ActionResult Form2Handler(Form2 model)
{
    return Content("Doing Form2");
}

And a view:

@model MvcApp.Models.FormCollection
<section id="tab1">
@using (Html.BeingForm("Form1Handler", "Form"))
{
    @Html.TextboxFor(m => m.Form1.PropNameCollision)
    @Html.TextboxFor(m => m.Form1.Form1Date)
    <input type="submit"/>
}
</section>
<section id="tab2">
@using (Html.BeingForm("Form2Handler", "Form"))
{
    @Html.TextboxFor(m => m.Form2.PropNameCollision)
    @Html.TextboxFor(m => m.Form2.Form2Date)
    <input type="submit"/>
}
</section>

When I submit either form, the default model binder can't match up the model and what arrived in the context because e.g. to bind Form1's PropNameCollision it would expect a value for PropNameCollision but instead Form1.PropNameCollision arrives, because that's what the raw HTML markup generated by the helper:

<input type="text" id="Form1_PropNameCollision" name="Form1.PropNameCollision" ... />

The question: Is there a smart way to create a binder that looks for a specific type in the context and binds+returns only that? I've doodled a bit with overriding the default binder's BindModel, managed to bind primitives with Reflection, but the path did not seem favourable (accounting for complex types, nullables, etc.).

Edit: I would like to avoid accepting FormCollection models, because I would like to keep my hands tied, meaning I don't want to accidentally work with data I'm not supposed to work with. Say someone else needs to work with the code, or I'm coming back to it 6 months from now and I forgot everything about needing to separate down the sub-class.

Upvotes: 1

Views: 1078

Answers (2)

anaximander
anaximander

Reputation: 7140

If I understand your situation correctly, then you might want to try a custom model binder as described in the accepted answer to this question. To summarise:

  • Inherit the different models from a base class
  • Give the base class a method which returns a string identifying which class a particular instance is of
  • Set up a custom model binder for the base class which calls the identifying method on incoming instances of the class(es) and uses the result to determine which derived class it actually is, and return an instance of it, with values bound appropriately.
  • (if necessary) provide EditorTemplates for the various derived classes and use @Html.EditorFor to display the form sections related to the various class instances.

Depending on your controller logic, you may still have to receive them as base class instances and either do some logic to work out what to cast them to (not very pretty, but it works) or give the base class the relevant methods so that the derived classes can provide their own implementations and take advantage of a little polymorphism (a more elegant approach, but sometimes trickier).

Upvotes: 0

Coding Flow
Coding Flow

Reputation: 21881

Put your forms in partial views then bind them to your properties like so

@Html.Partial("PartialViewForm1", model.Form1)
@Html.Partial("PartialViewForm2", model.Form2)

Then your main view can be strongly typed to FormsContainer and your partial view can be strngly typed to Form1 and Form2.

Although in your case i would have only a single class called form, as the properties are identical on both classes and simply have 2 properties that are of this type.

Upvotes: 1

Related Questions