Firstly you can only send a strongly typed view model back to your view using
return View(model);
View is a method on the base class, not a class to be instantiated with return new View(...
Then to your real question: Yes you can do this, but using a top level ViewModel which contains your different form items is far, far easier in the majority of use cases. The main problem the top level container ViewModel handles really well is value persistence and server-side validation and error messages between round trips.
If you are only worried about the perception of inefficiency from creating a top level ViewModel container, then don't. This is far more efficient than all the workarounds you may have to put in place in order to get well behaved forms working without the top level ViewModel.
There is some example code below. The code below should demonstrate that using models contained within the top level ViewModel is just simpler and neater: some of the forms deliberately don't round trip some of the state. Note the usage of HiddenFor and ModelState.Clear which are both related to what you are trying to do, but even these won't persist the value for inst4.Name for Form4Submit. The various options explored are:
- Use a query parameter to denote which form is being posted
- Use a different form name, but still with the view model.
- Use a redirect-only Action for the form (send new instances, and only part of the viewmodel)
- Use a mixture of the above
public class TestController : Controller
{
//
// GET: /Test/
[System.Web.Mvc.HttpGet]
public ActionResult Index(string msg = null)
{
var model = new MyViewModel
{
Inst1 = new ModelF1 { Name = "Name of F1" },
Inst2 = new ModelF2 (),
InstT = new ModelT {Name = "Name of T"},
PostNumber = 0,
Message = msg
};
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Index(MyViewModel model, int option = 1)
{
// process models after a form1/2 submit
model.Message = "You posted " +
((option == 1) ? model.Inst1.Name : model.Inst2.Name)
+ " to Index for "
+ ((option == 1) ? "inst1" : "inst2");
model.PostNumber ++;
// This, and the hiddenFor are required to allow us to update the PostNumber each time
ModelState.Clear();
return View(model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form2Submit(MyViewModel model)
{
// process models after a form2 submit
model.Message = "You posted " + model.Inst2.Name + " to Form2Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
[System.Web.Mvc.HttpPost]
public ActionResult Form3Submit(ModelF3 inst3, ModelT instT)
{
// process models after a form3 submit
var n = instT.Name;
var msg = "You posted " + inst3.Name + ", " + n + " to Form3Submit";
// We no longer have access to pass information back to the view, so lets redirect
return RedirectToAction("Index", new { msg = msg });
}
[System.Web.Mvc.HttpPost]
public ActionResult Form4Submit(ModelF4 inst4, MyViewModel model)
{
// process models after a form4 submit
var n = model.InstT.Name;
model.Message = "You posted " + inst4.Name + ", " + n + " to Form4Submit";
model.PostNumber++;
ModelState.Clear();
return View("Index", model);
}
public class MyViewModel
{
public int PostNumber { get; set; }
public string Message { get; set; }
public ModelF1 Inst1 { get; set; }
public ModelF2 Inst2 { get; set; }
public ModelT InstT { get; set; }
}
public class ModelBase { public string Name { get; set; } }
public class ModelF1 : ModelBase {}
public class ModelF2 : ModelBase { }
public class ModelF3 : ModelBase { }
public class ModelF4 : ModelBase { }
public class ModelT : ModelBase { }
}
Then for the multi-form view:
@using MyWebSite.Controllers;
@model TestController.MyViewModel
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<p>
@Html.Raw(Model.PostNumber) : @Html.Raw(Model.Message)
</p>
<p>
@Html.LabelFor(m => Model.InstT) : <br />
@Html.DisplayFor(m => Model.InstT)
</p>
<div>
<p>Default form submit</p>
@using (Html.BeginForm())
{
<div>
@Html.HiddenFor(m => m.PostNumber)
@Html.LabelFor(m => Model.Inst1.Name)
@Html.TextBoxFor(m => Model.Inst1.Name)
</div>
<input type="submit" value="Submit Index" />
}
</div>
<div>
<p>Use a parameter to denote the form being posted</p>
@using (Html.BeginForm("Index", "Test", new { option = 2 }))
{
<div>
@* Omitting these will not persist them between trips
@Html.HiddenFor(m => Model.Inst1.Name)
@Html.HiddenFor(m => Model.InstT.Name)*@
@Html.HiddenFor(m => m.PostNumber)
@Html.LabelFor(m => Model.Inst2.Name)
@Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit with option parameter" />
}
</div>
<div>
<p>Use a different form name, but still use the ViewModel</p>
@using (Html.BeginForm("Form2Submit", "Test"))
{
<div>
@Html.HiddenFor(m => Model.Inst1.Name)
@Html.HiddenFor(m => Model.InstT.Name)
@Html.HiddenFor(m => m.PostNumber)
@Html.LabelFor(m => Model.Inst2.Name)
@Html.TextBoxFor(m => Model.Inst2.Name)
</div>
<input type="submit" value="Submit F2" />
}
</div>
<div>
<p>Submit with a redirect, and no ViewModel usage.</p>
@using (Html.BeginForm("Form3Submit", "Test"))
{
var inst3 = new TestController.ModelF3();
<div>
@Html.HiddenFor(m => Model.InstT.Name)
@Html.LabelFor(m => inst3.Name)
@Html.TextBoxFor(m => inst3.Name)
</div>
<input type="submit" value="Submit F3" />
}
</div>
<div>
<p>Submit with a new class, and the ViewModel as well.</p>
@using (Html.BeginForm("Form4Submit", "Test"))
{
var inst4 = new TestController.ModelF4();
<div>
@Html.HiddenFor(m => Model.Message)
@Html.HiddenFor(m => Model.PostNumber)
@Html.HiddenFor(m => Model.Inst1.Name)
@Html.HiddenFor(m => Model.Inst2.Name)
@Html.HiddenFor(m => Model.InstT.Name)
@Html.LabelFor(m => inst4.Name)
@Html.TextBoxFor(m => inst4.Name)
</div>
<input type="submit" value="Submit F4" />
}
</div>
</body>
</html>