1

I'm using Umbraco MVC to build a form four our webpage. I have a ViewModel which is actually a property to another ViewModel, and is populated using a partial-view in the razor-form below. For whatever reason, the sub-viewmodel (RequiredHealthInputData, se below) is always null after binding! The binder will successfully bind all the form values to my main viewmodel but my sub-viewmodel will remain null!

(I have research many similar questions regarding binding of complex types but I believe but none of the solutions I've found so far (prefixing, renaming my properties to support routing etc) works.

I have a PtjInsurancePickerViewModel.cs (the main viewmodel) which looks as follows:

public class PtjInsurancePickerViewModel : BaseViewModel, IValidatableObject
{
    [Required]
    public string InsuranceNameStr { get; set; } = "-1";

    public HealthDeclarationModel RequiredHealthInputData { get; set; }
}

HealthDeclarationModel is a ViewModel as well and has the following values:

public class HealthDeclarationModel : BaseViewModel, IValidatableObject
{
    [Display(Name = "Extra comment")]
    public string ExtraComments { get; set; }

    [Display(Name = "EnsurancePTName")]
    public string EnsurancePTName { get; set; }
}

And I have the a view InsurancePicker.cshtml with the following form:

@using (Html.BeginUmbracoForm<InsurancePickerSurfaceController>("ProcessSelections"))
{
    <h3>Select insurance</h3>
    @Html.Partial("../Partials/InsurancePicker/_vsFamilyInsuranceProtection", Model)

    <h3>Select extra options</h3>
    @Html.Partial("../Partials/_Healthdeclaration", Model.RequiredHealthInputData)

    <h3>Send!</h3>
    <input type="submit" class="btn btn-success" value="Send!" title="Confirm choices"/>
}

Note that the form consists of two partials, one which gets the PtjInsurancePickerViewModel and one which gets the HealthDeclarationModel which is a property of PtjInsurancePickerViewModel

Here is the partial for my HealthDeclarationModel:

        <div class="HealthDeclarationModel-Panel">

            <strong>2. Är du fullt arbetsför?</strong>
            @Html.DropDownListFor(x => x.EnsurancePTName, Model.YesNoLista, new { @class = "form-control", @id = "EnsurancePTNameSelect" })
            @Html.ValidationMessageFor(x => x.EnsurancePTName)


            @Html.TextAreaFor(x => x.ExtraComments, new { @class = "form-control", style = "max-width:100%; min-width: 100%;" })
            @Html.ValidationMessageFor(x => x.ExtraComments)

        </div>

(Note. I give the input fields in my partial special IDs (used by jquery for statemanagement). Perhaps this could be part of the issue? I've tried submitting with the Ids prefixed with the model name "HealthDeclarationModel .EnsurancePTNameSelect" instead of just "EnsurancePTNameSelect" for example, but to no avail)

The submit function leads to the follow method:

[Authorize(Roles = "Forsakrad")]
public class InsurancePickerSurfaceController : BaseSurfaceController
{

    [HttpPost]
    public ActionResult ProcessSelections(PtjInsurancePickerViewModel filledModel)
    {
        //Checks if the very important RequiredHealthInputData submodel is null

        if (filledModel.RequiredHealthInputData == null)
        {
            throw new ApplicationException("Critical data missing!!");
        }
        else
        {
            DoImportantStuffWithRequiredHealthInputData(filledModel.RequiredHealthInputData)
        }

        return CurrentUmbracoPage();
    }
}

Now here's my problem. RequiredHealthInputData will always be null as the MVC binder for can't bind HealthDeclarationModel to PtjInsurancePickerViewModel. The result of the default binder will look something like this:

    {IndividWebb.Models.PtjInsurancePickerViewModel}
       InsuranceNameStr: "0"
       HalsodeklarationDataModel: null

However, the form collection will look something like this:

    controllerContext.HttpContext.Request.Form.AllKeys
    {string[17]}
       [2]: "InsuranceNameStr"
       [9]: "EnsurancePTName"
       [12]: "ExtraComments"
        14]: "HalsodeklarationDataModel"

    (Some results have been excluded)

Also note that EnsurancePTName which is an input that belongs to HalsodeklarationDataModel has the id EnsurancePTName and not HalsodeklarationDataModel.EnsurancePTName.

I have really no idea how to proceed with this or even how to debug it. I've just a customed helper to verify that the binding does indeed exclude all values from HalsodeklarationDataModel

user1531921
  • 1,372
  • 4
  • 18
  • 36
  • 1
    You cannot use a partial unless you pass the HtmlFieldPrefix (refer [this answer](http://stackoverflow.com/questions/29808573/getting-the-values-from-a-nested-complex-object-that-is-passed-to-a-partial-view/29809907#29809907)). You should be using a custom `EditorTemplate` for typeof `HealthDeclarationModel ` which does generate the correct `name` attributes for model binding (and note that `id` attributes have nothing to do with it - they are for use by javascript/css selectors) –  Oct 05 '16 at 09:47
  • To make this an `EditorTemplate`, rename your partial to `HealthDeclarationModel.cshtml` and move it into the `/Views/Shared/EditorTemplates` folder, and use `@Html.EditorFor(m => m.RequiredHealthInputData)` –  Oct 05 '16 at 09:59
  • This produced a strange result whereby my partial view is displayed normally but now I also have all my controls printed out as editorfields in my html without styling. How do I prevent this? I updated my question above with the new code i entered. – user1531921 Oct 05 '16 at 10:47
  • If your using `@Html.EditorFor(m => m.RequiredHalsoInputData)` the you need to name the file correctly and place it in the correct folder (see previous comment - `/Views/Shared/EditorTemplates/HealthDeclarationModel.cshtm`) And its one or the other, not both. And if you use the partial option, then the prefix is `"RequiredHealthInputData"`, not `"HalsodeklarationModel"` –  Oct 05 '16 at 10:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/124981/discussion-between-stephen-muecke-and-user1531921). –  Oct 05 '16 at 11:09

2 Answers2

3

The reason that is does not bind is because your use of @Html.Partial("../Partials/_Healthdeclaration", Model.RequiredHealthInputData) is generating form controls with name attributes that do not relate to PtjInsurancePickerViewModel, for example, it generates

<textarea name="ExtraComments" ....>

where as it needs to be

<textarea name="RequiredHealthInputData.ExtraComments" ....>

One option is to pass the HtmlFieldPrefix to the partial as AdditionalViewData

@Html.Partial("../Partials/_Healthdeclaration", Model.RequiredHealthInputData, 
new ViewDataDictionary { TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "RequiredHealthInputData" }})

Refer also getting the values from a nested complex object that is passed to a partial view that include the code for a HtmlHelper extension method to make this simpler.

However the preferred approach is to use an EditorTemplate. Rename your partial to HealthDeclarationModel.cshtml (i.e. to match the name of the class) and move it to the /Views/Shared/EditorTemplates folder (or /Views/yourControllerName/EditorTemplates folder), and then in the view use

@Html.EditorFor(m => m.RequiredHealthInputData)

which will generate the correct name attributes.

Community
  • 1
  • 1
0

Change your _Healthdeclaration view so that it uses the PtjInsurancePickerViewModel as a view model. In short pass the same model to all partials to ensure correct input names.

mitsbits
  • 325
  • 3
  • 8
  • I could try this but the point using a separate model for Healthdeclaration is to be able to reuse it on other parts of the web. – user1531921 Oct 05 '16 at 10:47
  • ok, maybe use EditorTemplate for HealthDeclarationModel (http://www.growingwiththeweb.com/2012/12/aspnet-mvc-display-and-editor-templates.html) – mitsbits Oct 05 '16 at 11:01