ASP.NET MVC: Dynamically adding an existing View as a Partial View to a parent

Let’s say you have a pre-existing Model/View/Controller. For my purposes I will call it FirstController, FirstModel and the view, Details.

In this post I will explain how to reuse this form and model in another View/Controller which will become it’s parent. I will also show how to dynamically add this child view to the parent via Ajax. This way we can conditionally add the view based on the user’s input.

Here is our pre-existing MVC:

namespace ChildModel.Controllers
{
    public class FirstController : Controller
    {
        public ActionResult Details()
        {
            var model = new FirstModel { Name = "Gilles",
                                         Number = 42 };
            return View(model);
        }

        [HttpPost]
        public ActionResult Details(FirstModel model)
        {
            if (!ModelState.IsValid)
            {
                return View();
            }

            return RedirectToAction("Index", "Home");
        }
    }
}

namespace ChildModel.Models
{
    public class FirstModel
    {
        public string Name { get; set; }
        public int Number { get; set; }
    }
}

With this simple view:

@model ChildModel.Models.FirstModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>First - Details</title>
</head>
<body>
<div>
<h1>First - Details</h1>
@using (Html.BeginForm("Details", "First"))
        {
            @Html.LabelFor(model => model.Name, "Name")
            @Html.TextBoxFor(model => model.Name)

            @Html.LabelFor(model => model.Number, "Number")
            @Html.TextBoxFor(model => model.Number)

            <input type="submit" value="Submit" />
        }</div>
</body>
</html>

first1

Here is the existing Model/View/Controller which will eventually become the parent:

namespace ChildModel.Controllers
{
    public class ParentController : Controller
    {
        public ActionResult Index()
        {
            var model = new ParentModel { IsChecked = true };
            return View(model);
        }

        [HttpPost]
        public ActionResult Submit(ParentModel model)
        {
            if (!ModelState.IsValid)
            {
                return View();
            }

            return RedirectToAction("Index", "Home");
        }
    }
}

namespace ChildModel.Models
{
    public class ParentModel
    {
        public bool IsChecked { get; set; }
    }
}
@model ChildModel.Models.ParentModel

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Parent</title>
</head>
<body>
<div>
<h1>Parent</h1>
@using (Html.BeginForm("Submit", "Parent"))
        {
            @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
            @Html.CheckBoxFor(model => model.IsChecked)

            <input type="submit" value="Submit" />
        }</div>
</body>
</html>

parent1

Let’s start by including First into Parent. We will do the dynamic Ajax part afterwards.

Since we don’t want to include the form in our parent page, because our parent already has a form and submit button, we will take out the fields we want to reuse and put them in their own partial view.

_FirstFields.cshtml:

@model ChildModel.Models.FirstModel

@Html.LabelFor(model => model.Name, "Name")
@Html.TextBoxFor(model => model.Name)

@Html.LabelFor(model => model.Number, "Number")
@Html.TextBoxFor(model => model.Number)

And call this Partial View in First/Details.cshtml:

@using (Html.BeginForm("Details", "First"))
{
    Html.RenderPartial("_FirstFields");

    <input type="submit" value="Submit" />
}

In our ParentModel we will need to add a new property of type FirstModel:

public class ParentModel
{
    public bool IsChecked { get; set; }
    public FirstModel ChildModel { get; set; }
}

In our Parent/Index.cshtml view we can include the partial view. Notice the use of the ViewDataDictionary in RenderPartial. If we simply asked for the view, the model binder wouldn’t be able to get the data back on a Post. The HtmlFieldPrefix in our ViewDataDictionary is set to the name of our child model property.

This will have for effect of changing the Ids and names of the fields on the partial view allowing the model binder to correctly bind the fields.

@using (Html.BeginForm("Submit", "Parent"))
{
    @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
    @Html.CheckBoxFor(model => model.IsChecked)

    Html.RenderPartial("../First/_FirstFields",
                       Model.ChildModel,
        new ViewDataDictionary(Html.ViewData)
        {
            TemplateInfo = new TemplateInfo
            {
                HtmlFieldPrefix = "ChildModel"
            }
        });

    <input type="submit" value="Submit" />
}

Now we have completed the first part by including a child model/view in our parent. Let’s add the Ajax part.

We will remove our RenderPartial and replace it with a div element which will serve to host the dynamic html code. We will also add an Ajax action link to call a new method on our ParentController.

@using (Html.BeginForm("Submit", "Parent"))
{
    @Html.LabelFor(model => model.IsChecked, "Is Checked ?")
    @Html.CheckBoxFor(model => model.IsChecked)

    @Ajax.ActionLink("Add Child Form", "GetChildElement",
                     "Parent",
                    new AjaxOptions
                    {
                        AllowCache = false,
                        InsertionMode =
                                InsertionMode.Replace,
                        HttpMethod = "Get",
                        UpdateTargetId = "childContainer",
                    })
<div id="childContainer"></div>
<input type="submit" value="Submit" />
}

In our ParentController we add the Ajax action method GetChildElement this time setting the prefix manually:

public PartialViewResult GetChildElement()
{
    ViewData.TemplateInfo.HtmlFieldPrefix = "ChildModel";
    return PartialView("../First/_FirstFields");
}

Don’t forget to add Ajax capabilities to your project like detailed here.

And everything should be working.

parent2

6 thoughts on “ASP.NET MVC: Dynamically adding an existing View as a Partial View to a parent

  1. This is a really helpful post and I appreciate it! I just have one question though – how would you bind to a list of FirstModel objects if I were to want to do that?

    1. In the scenario of the blog post the FirstController and it’s view stand alone on their own and I wanted to reuse them in another view.

      If I understand correctly you have a list of FirstModels in your ParentModel of child models which you need to reuse (because otherwise you don’t have to go to all this trouble). I’ll assume that the list is fixed and that you don’t want to add to it via ajax.

      In that case change ParentModel to take a List of FirstModel:
      public List ChildModel { get; set; }

      In ParentController instantiate that list:
      public ActionResult Index()
      {
      var model = new ParentModel { IsChecked = true };

      model.ChildModel = new List
      {
      new FirstModel { Name = “Test1” },
      new FirstModel { Name = “Test2” },
      };

      return View(model);
      }

      And finally in the view loop over the items rendering the partial view:

      for (int i = 0; i < Model.ChildModel.Count; i++)
      {
      Html.RenderPartial("../First/_FirstFields", Model.ChildModel[i],
      new ViewDataDictionary(Html.ViewData)
      {
      TemplateInfo = new TemplateInfo
      {
      HtmlFieldPrefix = "ChildModel"
      }
      });
      }

      The problem your then left with is binding the data on the postback. Normally if you weren't working with a partial view that would be easy but because of the partial view things are more complicated.

      For this I would use the ViewDataDictionary that is already being used and pass the i variable in a new key/value pair for the dictionary. Bind this i variable in the partial view and then try to get it to work with MVC's model binding.

      1. Thanks for your reply! I did manage to work this out I believe – I wrote up a quick test controller method to submit the entire thing as a form as you have demonstrated in your original article. I am not 100% sure what you mean by a fixed list that I don’t want to add to via Ajax but I assume you mean that the list length will not grow or shrink. I actually do want to allow the user to add additional forms dynamically and I wrote a method on my controller to handle this similar to GetChildElement

        In my equivalent of your “GetChildElement” controller, I wrote essentially the same thing but instead of assigning the HtmlFieldPrefix = “ChildModel” I instead passed in the count of which partial view that I was adding and did as follows:

        public PartialViewResult GetChildElement(int index)
        {
        ViewData.TemplateInfo.HtmlFieldPrefix = “ChildModel[” + index + “]”;
        return PartialView(“../First/_FirstFields”);
        }

        Does that make sense or should I take a little more time to explain it better? It’s essentially what you already told me but I think it handles the MVC model binding.. I’m pretty new to ViewData and model binding as a whole.

        And as far as reuse goes, what do you define reuse to be? I guess I considered having multiple of the same fields that would be dynamically added to be reuse even if I’m not using those fields elsewhere in my application.

        1. It’s a great solution passing the index in the ViewData.

          What I meant by if the list was fixed is whether you could add elements at runtime on the client in the list. And then submit these additional elements to the controller.

          What I meant by reuse was that in the original project I used this technique, I had already created FirstController and I was using it on the site. A new requirement came in to use the exact same fields and logic (view, controller and model) in another view. Additionally this FirstView needed to be added conditionally at run time via Ajax only when it was requested by the client.

        2. Hi,

          So in the project that I’m working on that I utilized this the client now wants the additional option to remove these dynamically added fields. I was staring at the code and basically I think using Javascript I could remove the HTML if I know what form I’m removing but how do I ensure that the model binding doesn’t end up incorrect? Will it handle it automatically?

Leave a comment