Using viewmodel to pass different views to partial views - asp.net-mvc

I have a view that renders 2 partial views. One partial is a list of people. The other partial is a modal edit form to edit a single person. I have a viewmodel that contains a person model and a list of person model. I pass the viewmodel to the view and specify the specific models for the partials. I get an error saying the view requires the PersonModel instead of the PersonViewModel. However, the view needs the viewmodel or it wont work.
The Viewmodel:
public class PersonViewModel
{
public Person Person { get; set; }
public List<Person> PeopleList { get; set; }
}
The view (Index):
#model App.Models.ViewModels.PersonViewModel
<h1>Manage People</h1>
#Html.Partial("_personEditForm", Model.Person)
#Html.Partial("_PersonGrid", Model.PersonList)
The person edit partial calls the Person model:
#model App.Models.Person
and the grid partial calls the same model as a list:
#model List<App.Models.Person>
This works fine if the page has only one partial, but this is the first time I have tried it with 2 and it fails. The Index view must have the ViewModel for the partials to work, so not sure why it wont take it.

I was able to fix this by passing the ViewModel to the view, and passing it to both partials:
#model App.Models.ViewModels.PersonViewModel
<h1>Manage People</h1>
#Html.Partial("_PersonEditForm", Model)
#Html.Partial("_PersonGrid", Model)
Then inside the partials, I specified the models within the viewmodel. For example, in the form partial, the textbox helpers went from:
#Html.TextBoxFor(m => m.FirstName, new { #class = "form-control" })
To
#Html.TextBoxFor(m => m.Person.FirstName, new { #class = "form-control" })

Just wanted to add my 2 cents since this has also been driving me crazy. What I have done is created separate viewmodels for each of the partial views. One similar to PersonEdit and one similar to PersonList.
So you end up with your main VM, and 2 smaller ones.
public class PersonIndexViewModel
{
public PersonEditViewModel Person { get; set; }
public List<PersonListViewModel> PeopleList { get; set; }
}
public class PersonEditViewModel
{
// ...whatever info you want in your edit view
}
public class PersonListViewModel
{
// ... whatever info you want in your table grid
}
Then you call it like so
#model App.Models.ViewModels.PersonViewModel
<h1>Manage People</h1>
#Html.Partial("_PersonEditForm", Model.Person)
#Html.Partial("_PersonGrid", Model.PeopleList)

Related

MVC page that displays one model but let's you create a different one

What is the right way to create an input of a different model type for a MVC page? I could just put in the HTML that I needed, but how do you do it the idiomatic way with "Html.EditorFor" etc.
For example, in Stack overflow, you are looking at a post, but at the bottom you are creating an answer. Should an IEnumerable be part of the post model?
You can include both models in the view model for the page. For example, let's say you are viewing a post and want to allow users to comment on the post. Your view model for the page would look like this:
public class ViewPostViewModel
{
// The view model for the post and replies
public PostViewModel Post { get; set; }
// The view model for the comment form
public AddCommentViewModel Comment { get; set; }
}
This is my preferred method of including two view models in the same page, especially if you will need to reuse one of the view models across several different action methods.
Then in your view you can use the standard #Html.EditorFor(m => m.Comment.Message) helpers to create the form inputs, or render the comment form using a partial view.
Use partials views, then you can have something like this:
Your partial:
#model stackoverflow.answer
#HTML.desplaynamefor(u => u.name)
Then in another view you can do this
#model stackoverflow.post
#HTML.displaynamefor(u => u.comment)
#partial("mypartial",new answer())
If you only create a new answer without data coming from he server side (no server side validation, editing, etc.), you could just instantiate the model and then use a partial view for the answer form like this:
QuestionView:
#model ViewModel.Question
#{ var answer = new ViewModel.Answer(); }
<h3>Question:</h3>
#Html.DisplayTextFor(m => m.Text)
#Html.Partial("AnswerFormView", answer)
AnswerFormView:
#model ViewModel.Answer
#using (var form = Html.BeginForm(...))
{
<h3>Answer:</h3>
#Html.EditorFor(m => m.Text)
}
If you however need data from server side (validation, default values, editing, etc.) you'll need to get the data from the controller and use a composition view.
ViewModel.Composed:
public class Composed
{
public ViewModel.Question Question { get; set; }
public ViewModel.Answer Answer { get; set; }
}
ComposedView:
#model ViewModel.Composed
#Html.Partial("QuestionView", Model.Question)
#Html.Partial("AnswerFormView", Model.Answer)
QuestionView:
#model ViewModel.Question
<h3>Question:</h3>
#Html.DisplayTextFor(m => m.Text)
AnswerFormView:
#model ViewModel.Answer
#using (var form = Html.BeginForm(...))
{
<h3>Answer:</h3>
#Html.EditorFor(m => m.Text)
}

Single strongly Typed Partial View for two similar classes of different types

I have a Register Primary View which shows two different types of Addresses 1. Home Address 2. Mailing Address
public class RegisterModel
{
public AddressModel HomeAddress { get; set; }
public AddressModel MailAddress { get; set; }
}
public class AddressModel
{
public string Street1 { get; set; }
public string Street2 { get; set; }
public string State { get; set; }
public string City { get; set; }
}
My main Register View is Strongly Typed to RegisterModel as follows
#model MyNamespace.Models.RegisterModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.Action("MyAddressPartial")
#Html.Action("MyAddressPartial")
</div>
}
MyAddressPartialView as follows : -
#model MyNamespace.Models.AddressModel
#{
Layout = "~/Views/_Layout.cshtml";
}
<div id="Address">
#Html.TextBoxFor(m=>m.Street1 ,new { #id="Street1 "})
#Html.TextBoxFor(m=>m.Street2,new { #id="Street2"})
#Html.TextBoxFor(m=>m.State ,new { #id="State "})
#Html.TextBoxFor(m=>m.City,new { #id="City"})
</div>
My RegisterController:-
// Have to instantiate the strongly Typed partial view when my form first loads
// and then pass it as parameter to "Register" post action method.
// As you can see the #Html.Action("MyAddressPartial") above in main
// Register View calls this.
public ActionResult MyAddressPartial()
{
return PartialView("MyAddressPartialView", new AddressModel());
}
I submit my Main Form to below mentioned action method in same Register Controller.
[HttpPost]
public ActionResult Register(RegisterModel model,
AddressModel homeAddress,
AddressModel mailingAddress)
{
//I want to access homeAddress and mailingAddress contents which should
//be different, but as if now it comes same.
}
I don't want to create a separate class one for MailingAddress and one for HomeAddress. if I do that then I will have to create two separate strongly typed partial views one for each address.
Any ideas on how to reuse the classes and partial views and make them dynamic and read their separate values in Action Method Post.
Edit 1 Reply to scott-pascoe:-
In DisplayTemplates Folder, I added following AddressModel.cshtml
<div>
#Html.DisplayFor(m => m.Street1);
#Html.DisplayFor(m => m.Street2);
#Html.DisplayFor(m => m.State);
#Html.DisplayFor(m => m.City);
</div>
Also In EditorTemplate Folder, I added following AddressModel.cshtml but with EditorFor
<div>
#Html.EditorFor(m => m.Street1);
#Html.EditorFor(m => m.Street2);
#Html.EditorFor(m => m.State);
#Html.EditorFor(m => m.City);
</div>
Now how do i use them in RegisterView and also how i read values in Controller's post Action Method ? What else would have to be modified ? I have added almost entire code above. I am pretty beginner to MVC.
The typical ASP.NET MVC method for doing this is to use EditorTemplates and DisplayTemplates for your custom types.
In ~/Views/Shared, Create two folders, DisplayTemplates, and EditorTemplates.
In the DisplayTemplates folder create a partial view with the name of your Model, ie (AddressModel), and create a DisplayFor Template.
In the EditorTemplates folder create another partial view named AddressModel.cshtml and create an EditorFor Template.
MVC will then automatically use your templates and give you the data that you are asking for.
Use #Html.EditorFor (or #Html.DisplayFor, for display) in your view:
#model MyNamespace.Models.RegisterModel
#{
Layout = "~/Views/_Layout.cshtml";
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { id = "myForm" }))
{
<div id="form">
#Html.EditorFor(m => m.HomeAddress)
#Html.EditorFor(m => MailAddress)
</div>
}
You will not need to have a separate controller action for the parts, just populate the addresses in the RegisterModel before in your controller. Like this:
[HttpGet]
public ActionResult Register() // this will be the page people see first
{
var model = new RegisterModel();
return View(model); // assuming your view is called Register.cshtml
}
[HttpPost]
public ActionResult Register(RegisterModel model){
DosomethingWithHomeAddress(model.HomeAddress);
DosomethingWithMailAddress(model.MailAddress);
model.IsSaved = true; // some way to let the user knwo that save was successful;
// if this is true, display a paragraph on the view
return View(model);
}

MVC ASP.Net how do I add a second model to a view? i.e. Add a dropdown list to a page with a html grid?

I have been able to find lots of examples of adding a Dropdown list to a view but I need to add a dropdown list to a view that also has a Webgrid on it. This entails two different models and from what I see I can only have one per view.
The DDL will be filled from one model and the grid from the other.
I'm just trying to filter the displayed data in the grid with the data selected in the ddl.
Any examples or articles would be greatly appreciated.
TIA
Create a ViewModel that has the data for both your grid and your DropDownList. Use this ViewModel object as the model for your view.
See Steve Michelotti's post for different strategies on implementing the ViewModel pattern.
Something like this, for example:
public class MyViewModel
{
public List<Row> RowsForGrid { get; set; }
public SelectList ItemsForDropdown { get; set; }
}
Since you're trying to filter the displayed data in the grid, I'd do it this way:
In the main view I'd call a partial view. In your case the partial view will hold the DropDownList data. Something like this:
#Html.Partial("DropDownView", ViewBag.DropDownViewModel as DropDownViewModel)
In your controller action you'd fill the DropDownViewModel with the DropDownList data and would pass the DropDownViewModel to the ViewBag like this:
DropDownViewModel dropDownViewModel = new DropDownViewModel();
DropDownViewModel.Items = GetDropDownData(); // Fetch the items...
ViewBag.DropDownViewModel = dropDownViewModel;
ViewModel (DropDownViewModel.cs)
public class DropDownViewModel
{
public SelectList Items { get; set; }
}
Partial View (DropDownView.cshtml)
#model DropDownViewModel
#using (Html.BeginForm("YourControllerAction", "YourControllerName", FormMethod.Get))
{
#Html.Label("Search") #Html.DropDownList("YourDataId", Model.Items, String.Empty)
<input type="submit" value="Search" id="submit"/>
}
"YourDataId" will be a parameter for the action method and will contain the value selected by the user like this:
public virtual ActionResult Index(int? YourDataId, GridSortOptions sort)
{
...
}

.NET MVC3 Stongly Typed Form Helper not passing the data to the action?

I have a Model Wrapper class that wrap another model:
public class LogicPage {
public MyPage Pg { get; set; }
}
public class MyPage {
public string name { get; set; }
}
In my create form view I use strongly typed form helper:
#model MyAppCore.Components.LogicPage
#using (Html.BeginForm("Create","PageAdmin",FormMethod.Post)) {
<div class="editor-field">
#Html.EditorFor(model => model.Pg.name)
#Html.ValidationMessageFor(model => model.Pg.name)
</div>
...
}
In my controller, I define the action as:
[HttpPost]
public ActionResult Create(LogicPage logicpg)
{
return View("View",logicpg.Pg);
}
However, when the MyPage Pg is displayed in the "View" which list details of the model (MyPage), no user inputted data are shown, the details of the page shown are all MyPage default values (initialized with default no parameter constructor of LogicPage). Looks like data in the form are not passing to the model in the action. Can someone please help me why the data is not passing?
To clarify more, I have two views "Create" form view of model LogicPage and the "View" detail view for model MyPage
Create.cshtml
#model MyAppCore.Components.LogicPage
View.cshtml
#model MyAppCore.Components.MyPage
Thanks
You say you're doing
[HttpPost]
public ActionResult Create(LogicPage logicpg)
{
return View("View",logicpg.Pg);
}
But given that MyPage doesn't inherit from LogicPage, and your view expects an instance of LogicPage, I think you should be doing:
[HttpPost]
public ActionResult Create(LogicPage logicpg)
{
return View("View",logicpg);
}

Passing View Model to ASP.NET MVC 3 Edit Controller

When passing a view model (as below) to a view, how can I ensure that the checkboxes I'm creating (mapped to item "Product" in here) get passed back to the controller?
I've included my view model and "post" product controller below.
Unfortunately, when posted back to the controller, "Products" is null.
namespace MyProject.Models
{
public class ChartViewModel
{
public Chart ChartItem { get; set; }
public IEnumerable<Product> Products { get; set; }
}
}
Controller:
[Authorize]
[ValidateInput(false)]
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(ChartViewModel objChartViewModel)
{
if (!TryUpdateModel(objChartViewModel))
{
return View(objChartViewModel);
}
else
{
} return View("Details", objChartViewModel);
}
How the checkboxes are added to my view, mapped to the "Product" object within my view model:
#{
foreach (MyProject.Models.Product objProduct in Model.Products)
{
#Html.CheckBox("product" + objProduct.Id, Model.ChartItem.ChartProducts.Select(t => t.ProductId).Contains(objProduct.Id));
#String.Format("{0} {1}", objProduct.Manufacturer.Name, objProduct.Name);<br />
}
}
You can send your lists (IEnumerable<T>) down to the view but when they don't come back up to the controller. The only properties of your ViewModel that have values are those with exactly matching items in the forms collection. So add a properties to your ViewModel like SelectedProductID.
This sets up a drop down list that sends its selected value back to the controller:
<div class="editor-field">
#Html.DropDownListFor(m => m.SelectedEmployeeID,
new SelectList(Model.EmployeeList, "EmployeeID", "EmployeeName"), "--Please select an Employee--")
#Html.ValidationMessageFor(model => model.SelectedEmployeeID)
</div>
Notice: the property being set is SelectedEmployeeID but this comes from the EmployeeList.
In your case the values many be in the collection (only "checked" checkboxes get sent in a post) so you could do:
string value = collection["myProductID"];
if its there its checked.
sorry for the messiness, this was in a bit of a rush. See this for more info:
MVC 3 form post and persisting model data

Resources