0
votes

I am developing a web app using ASP.NET MVC 5. I have multiple partial views and their corresponding models, but only one controller corresponding to the main view, that uses below code to display each of the partial views.

@{Html.RenderPartial("PartialViewName");} 

My question is, how do I pass the form data from all the partial views (including the main view) to the main controller?

Initially, when I added only 1 partial view, I added it's model as a parameter to the main controller's public ActionResult Index(MainModelName model1, PartialModelName pmodel1). and that worked fine and I could access the data from the partial view in the main controller.

But, when I added the 2nd partial view and updated the public ActionResult Index(...) with an additional partial model parameter, it didn't help, as in the data is not being passed from the partial view to the main controller. though I don't get any error.

My main model class has the object references of the partial models as its data members. From what I read, I need to have a list of models as a parameter to the method in the main controller, but my models are not sub-types.

What's the idiomatic way of handling such a situation?

Thank you!

EDIT1 : Here's the code for a sample app, I get null reference error while accessing mainModel.PartialModel2.Pm2 in MainController.cs, while I can access mainModel.PartialModel1.Pm1 properly.

MainModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class MainModel
    {
        private int mm1;
        private PartialModel1 partialModel1;
        private PartialModel2 partialModel2;

        public int Mm1 { get => mm1; set => mm1 = value; }
        public PartialModel1 PartialModel1 { get => partialModel1; set => partialModel1 = value; }
        public PartialModel2 PartialModel2 { get => partialModel2; set => partialModel2 = value; }
    }
}

PartialModel1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class PartialModel1
    {
        private int pm1;

        public PartialModel1()
        {
        }

        public int Pm1 { get => pm1; set => pm1 = value; }
    }
}

PartialModel2.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class PartialModel2
    {
        private int pm2;

        public PartialModel2()
        {
        }

        public int Pm2 { get => pm2; set => pm2 = value; }
    }
}

Index.cshtml

@model TestApp.Models.MainModel

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            <h4>MainModel</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                @Html.LabelFor(model => model.Mm1, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Mm1, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Mm1, "", new { @class = "text-danger" })
                </div>
            </div>

            <div class="form-group">
                @{ Html.RenderPartial("Partial1", Model);  }
                @{ Html.RenderPartial("Partial2", Model);  }
            </div>

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
                    }

    @*<div>
            @Html.ActionLink("Back to List", "Index")
        </div>*@
</body>
</html>

Partial1.cshtml

@model TestApp.Models.MainModel

@using (Html.BeginForm("MainModel")) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>PartialModel1</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.PartialModel1.Pm1, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.PartialModel1.Pm1, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.PartialModel1.Pm1, "", new { @class = "text-danger" })
            </div>
        </div>

        @*<div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>*@
    </div>
}

@*<div>
    @Html.ActionLink("Back to List", "Index")
</div>*@

Partial2.cshtml

@model TestApp.Models.MainModel

@using (Html.BeginForm("MainModel")) 
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>PartialModel2</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="form-group">
            @Html.LabelFor(model => model.PartialModel2.Pm2, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.PartialModel2.Pm2, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.PartialModel2.Pm2, "", new { @class = "text-danger" })
            </div>
        </div>

        @*<div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>*@
    </div>
}

@*<div>
    @Html.ActionLink("Back to List", "Index")
</div>*@

MainController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestApp.Models;

namespace TestApp.Controllers
{
    public class MainController : Controller
    {
        // GET: Main

        [HttpGet]
        public ActionResult Index()
        {
            return View();
        }

        [HttpPost]
        public ActionResult Index(MainModel mainModel)
        {
            //Access mainModel.PartialModel1.Pm1
            //Access mainModel.PartialModel2.Pm2 

            string msg = mainModel.PartialModel1.Pm1 + " " + mainModel.PartialModel2.Pm2;
            ViewBag.message = msg;


            return View("Success");
        }
    }
}

EDIT 2: If I just keep it @{ Html.RenderPartial("Partial2"); }, I get this error - The model item passed into the dictionary is of type 'TestApp.Models.Main', but this dictionary requires a model item of type 'TestApp.Models.Partial2'.

MainController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestApp.Models;

namespace TestApp.Controllers
{
    public class MainController : Controller
    {
        // GET: Main

        [HttpGet]
        public ActionResult Index()
        {
            return View(new Main() { Partial1 = new Partial1(), Partial2 = new Partial2() });

        }

        [HttpPost]
        public ActionResult Index(Main mainModel)
        {
            //Access mainModel.PartialModel1.Pm1
            //Access mainModel.PartialModel2.Pm2            

            string msg = mainModel.Partial1.Pm1 + " " + mainModel.Partial2.Pm2;
            ViewBag.message = msg;

            return View("Success");
        }
    }
}

Main.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class Main
    {
        public int Mm1 { get; set; }
        public Partial1 Partial1 { get; set; }
        public Partial2 Partial2 { get; set; }
    }
}

Partial1.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class Partial1
    {
        public Partial1()
        {
        }
        public int Pm1 { get; set; }
    }
}

Partial2.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace TestApp.Models
{
    public class Partial2
    {
        public Partial2()
        {
        }
        public int Pm2 { get; set; }
    }
}

Index.cshtml

@model TestApp.Models.Main

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    @using (Html.BeginForm())
    {
        @Html.AntiForgeryToken()

        <div class="form-horizontal">
            <h4>MainModel</h4>
            <hr />
            @Html.ValidationSummary(true, "", new { @class = "text-danger" })
            <div class="form-group">
                @Html.LabelFor(model => model.Mm1, htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.EditorFor(model => model.Mm1, new { htmlAttributes = new { @class = "form-control" } })
                    @Html.ValidationMessageFor(model => model.Mm1, "", new { @class = "text-danger" })
                </div>               

            </div>

            @{ Html.RenderPartial("Partial2");  }
            @{ Html.RenderPartial("Partial1");  }

            <div class="form-group">
                <div class="col-md-offset-2 col-md-10">
                    <input type="submit" value="Create" class="btn btn-default" />
                </div>
            </div>
        </div>
                    }

    @*<div>
            @Html.ActionLink("Back to List", "Index")
        </div>*@
</body>
</html>

Partial1.cshtml

@model TestApp.Models.Partial1


@Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>PartialModel1</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.Pm1, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Pm1, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Pm1, "", new { @class = "text-danger" })
        </div>
    </div>


</div>


@*<div>
        @Html.ActionLink("Back to List", "Index")
    </div>*@

Partial2.cshtml

@model TestApp.Models.Partial2


@Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>PartialModel2</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <div class="form-group">
        @Html.LabelFor(model => model.Pm2, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Pm2, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Pm2, "", new { @class = "text-danger" })
        </div>
    </div>
</div>


@*<div>
        @Html.ActionLink("Back to List", "Index")
    </div>*@
2
Pass model to your partial view? @{Html.RenderPartial("PartialViewName", Model);} - penleychan
Thanks for your response. but, I want to access the form data of the partial views in my main controller, and not render the partial views with data. - user7413
We need more information please provide relevant code on your main and partial form. etc. Normally you would do what I mentioned above and pass in your ViewModel then you would be able to access it from your controller - penleychan
you want to access the data on HttpPost? - akerra
I don't know if it's the idiomatic way but you can use a global viewmodel composing all partiel view viewmodels. MVC can recompose the global ViewModel if the input data names are prefixed with the partial view viewmodel name in the global viewmodel class. - Romain Deneau

2 Answers

0
votes

There's a bunch of stuff here you should change. For starters, you don't need to use the form of getter you're using.

Instead of

private int pm1;
public int Pm1 { get => pm1; set => pm1 = value; }

You only have to have this:

public int Pm1 { get; set; }

You don't need the private backing variable.

You only need to populate the get and set methods if you are going to actually provide methods that do something.

Second, you need to instantiate the PartialModel's. It doesn't matter a lot in this case, since you are not referencing any actual data, just metadata.. but as soon as you add real data it can cause issues.

[HttpGet]
public ActionResult Index()
{
    return View(new MainModel() { PartialModel1 = new PartialModel1(), PartialModel2 = new PartialModel2 });
}

Next, I'm not sure what you're trying to accomplish with this, but it's probably not what you think it's doing:

@using (Html.BeginForm("MainModel")) 

What you have here is nested forms, and that's not legal HTML.

Instead, you should remove the forms from the partials, even if it would be legal then a form will only submit the data within that form. This is your main problem.

also, remove the submit buttons in the partials. Just have the one over-arching form and submit button in the Index.cshtml.

0
votes

You can change your code:

public ActionResult Index(MainModelName model1, PartialModelName pmodel1)

To

public ActionResult Index(NewMainModelName  newModel)

In class NewMainModelName you can woke like this:

   public class NewMainModelName
    {
        public MainModelName model1 { get; set; }
        public PartialModelName pmodel1{ get; set; }
    }