Passing different models to RenderPartial() - asp.net-mvc

I am chaining some partial views together, and I require a way to use different models in my call to RenderPartial(). Most answers on SO state to cast to the model type, but this will not be known at compile-time.
The website has different categories such as Cars, Planes, Helicopters and Boats. When the user clicks on say the Cars link, the Car controller's Index page displays all cars in a pretty table. Same for Planes etc.
All the tables are identical, so I want to use a partial view to reuse the code.
#model IEnumerable<MyNamespace.Entities.Car>
#{Html.RenderPartial("Partials/_TableList", Model);}
Inside _TableList.cshtml is my pretty table. So I now want to call another partial view, which takes a list of models of type Car, and output the table head and body. Likewise if the user had clicked Planes, this would load a partial that takes a list of Plane models.
<table class="table prettyTable">
#{Html.RenderPartial((string)ViewBag.PartialToLoad, Model);}
</table>
And in _CarList.cshtml
#model IEnumerable<MyNamespace.Entities.Car>
<thead>...</thead>
<tbody>
#foreach (var item in Model)
{ ... }
</tbody>
Can this be done? Or must I put the model in the ViewBag and pass that around?
EDIT: Thanks to #Adas, I was able to solve this by adding
#model IEnumerable<dynamic>
to the top of _TableList.cshtml. Now, when I call RenderPartial() it does not complain that "Extension methods cannot be dynamically dispatched". This now works perfectly:
<table class="table prettyTable">
#{Html.RenderPartial((string)ViewBag.PartialToLoad, Model);}
</table>
FURTHER EDIT: I found that although the above worked with IEnumerable it did not with #model dynamic.
I discovered that one can call RenderPartial this way, which works in this instance.
#{RenderPartialExtensions.RenderPartial(Html, (string)ViewBag.PartialToLoad, Model);}
where PartialToLoad in the ViewBag is set in the controller.

You have 3 options here:
use dynamic model, i.e. #model IEnumerable<dynamic>
Implement some common interface on your models and use it #model IEnumerable<IYourCommonInterface>
Inherit all your models from some common class, like emre nevayeshirazi commented, #model IEnumerable<YourBaseClass>.
It does not matter will you pass your model like Model, or you put it in the ViewBag, you will need one of the above methods to access common methods.

Related

Render a partial view with single model from a partial view using an IEnumerable

I am trying to figure out how to render a single model from a view that is using an IEnumerable of that model. I can't seem to figure out how to send it using the razor
Right now I am getting the error:
MyApp.Models.DefectsVM' is a 'type' but is being used like a 'variable'
On this line in my main view(Under DefectsVM model I am trying to pass in):
#{Html.RenderPartial("~/Views/Defect/defectsPartial.cshtml", DefectsVM);}
My partial view has this in it to use a model:
#model MyApp.Models.DefectsVM
And my main view is using this:
#model IEnumerable<MyApp.Models.DefectsVM>
I am not sure what all other information is needed, let me know if I need to edit. But thank you for reading and taking your time to help.
You need to refer to your model with Model. But in your case, Model will return an IEnumerable<MyApp.Models.DefectsVM>, so you will need to iterate over that, and then render your partial.
Something like:
foreach(var defectsVM in Model)
{
#{Html.RenderPartial("~/Views/Defect/defectsPartial.cshtml", defectsVM);}
}
Regardless of whether the IEnumerable contains one or more than one, it should work.

EditorFor not rendering enumerable without #foreach

This is rather baffling. Imagine a Company entity with an .Employees enumerable property, for the sake of simplicity. I've seen elsewhere on SO that the trick to getting EditorTemplates to render enumerables sensibly is to use syntax like this:
In Index.cshtml:
#Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate")
EmployeeEditorTemplate.cshtml expects a single employee:
#model MyNamespace.Models.Employee
//...
#Html.EditorFor(x => x.FullName)
// ...etc.
Posts like this one: Viewmodels List Is Null in Action ...indicate that the modelbinder is smart enough to figure out the binding and index the enumerable on the fly. I'm getting different results (exactly what I'd expect if the modelbinder wasn't supposed to be so mageriffic):
The model item passed into the dictionary is of type
System.Collections.Generic.List`1[MyNamespace.Models.Employee]',
but this dictionary requires a model item of type
'MyNamespace.Models.Employee'.
Enumerating it myself renders the collection just fine, but emits the same id for every row's controls (which is less than optimal, of course, but it tells me the EditorTemplate is functionally correct):
#foreach (var item in Model.Employees)
{
#Html.EditorFor(x => item, "EmployeeEditorTemplate")
}
This construct also fails to post the company.Employees collection back to my other controller action.
Any ideas on what's required to achieve the following?
Render the child collection with unique Ids per instance in the list
Post the child collection back with the model
I don't care if I have to enumerate it myself, nor whether it's inside or outside the EditorTemplate; I've just seen it said that this is unnecessary and that MVC works better if you let it handle things. Thanks.
Per #David-Tansey (from this answer - see for a much more detailed explanation):
(changed to suit this question):
When you use #Html.EditorFor(c => c.Employees) MVC convention chooses the default template for IEnumerable. This template is part of the MVC framework, and what it does is generate Html.EditorFor() for each item in the enumeration. That template then generates the appropriate editor template for each item in the list individually - in your case they're all instances of Employee, so, the Employee template is used for each item.
To sum up his explanation on when you use the named template, you are, in effect, passing the entire IEnumerable<Employee> to a template expecting a single Employee by using #Html.EditorFor(company => company.Employees, "EmployeeEditorTemplate").
note: I really only answered this because it had no answer and I stumbled here first. At least now someone doesn't close it and potentially miss an answer without having to go through the rigmarole of getting duplicate question votes, blah blah.
You can read about it here: Hanselman's article
In short, you should write in your view (index.cshtml):
int i=0;
#foreach (var item in Model.Employees)
{
#Html.TextBox(string.Format("Model[{0}].FullName", i), item.FullName)
i++;
}
foreach can be used with #HTML.EditorFor, for an example see below syntax:
#foreach(var emp in #Model)
{
#Html.EditorFor(d => emp.EmployeeName)
#Html.EditorFor(d => emp.Designation)
<br />
}

In a big and complex ASP.NET MVC application is created a model of all other model classes?

I'll explain my point:
The best practice is to create views strongly typed with a Model. You only can stronly type one Model.
If you need two models in a view you can created two views and use Partial Render, but it seems not to be the very best option.
Another approach is to create another type model that encapsulates the other pieces of the model what you need; this make much more sense for me.
Then, my question is, in a complex proyect when a page needs to communicate with all the models and they are not direct realted, developers create a type that encapsulates all the other things?
For non-related parts of your view, you may use Html.Action() to invoke an action that returns a partial view.
This way, the logic of the "area" will be encapsulated in its own action and/or controller.
Update: I don't know if it's really the best practice, but I prefer composition over complex views & view models. Even for related information, I prefer to break it to smaller partial views and child actions. As I see it, it has the following flexibility:
Ability to easily move some of the partial views/child actions to a layout page
Load the partial view asynchronously via AJAX query
Reduced controller action complexity and increased maintainability.
Better support for conditioned rendering
Separation of concerns
In (4) I mean that you can easily do the following without complicating your view model:
<div class="header">
#if (loggedInUser.ShowAds) {
#Html.Action("Header", "Ads")
}
</div>
Answering the question in your comment.
Considering twitter. There's the content pane and the users box on the left.
So here's our TweetsController:
public class TweetsController: Controller {
public ActionResult Index() {
var tweets = ...;
return View(tweets);
}
}
The Tweets/Index view may look like:
#model Tweet[]
<div class="leftPane">
#Html.Action("Index", "Users");
</div>
<div class="mainContent">
#foreach var t in Model {
#t.User - #t.Text
}
</div>
Note that the left pane just calls the Index action in UsersController to display the users list.
Here's how it may look like:
public class UsersController: Controller {
public ActionResult Index() {
var users = ...;
return PartialView(users);
}
}
And here's the partial view (Users/Index):
#model User[]
#foreach var u in Model {
<img src="#u.IconUrl"/> #u.Name
}
So what will actually happen, when the Tweets view will be rendered Html.Action will put the partial view returned by UsersController.Index in the left pane.
Of course you may move this logic into a layout if this behavior is common for a number of pages.
Hope that helps.
Then, my question is, in a complex proyect when a page needs to
communicate with all the models and they are not direct realted,
developers create a type that encapsulates all the other things?
Yes some of them do, and the name for these is ViewModels

Using multiple partial views in MVC3 razor forms

I have an insurance entry form that has contact information for two people. I have created a Razor partial view for the contact entry and put it in the form twice. The 'master' view model (VmApplicationForm) contains two instances of a subsidiary view model (VmPolicyHolder) corresponding to the two contacts as well as some properties common to both contacts. I am calling #Html.RenderPartial("_CreateOrEdit", Model.contactInfo1) and #Html.RenderPartial("_CreateOrEdit", Model.contactInfo2) in the page. With this arrangement (no surprises) the rendered code has duplicate IDs for the form input elements.
Is there any way of getting RenderPartial to prefix the IDs and Name attributes? I couldn't see this in the documentation, but perhaps I've missed something.
Sorry, I don't have time yet to give you the example code, but i'll give you the idea. You should first create EditorTemplate for that probably called ContactInfo class. And then, in the base class(Holding that two contacts) edit view, you should write
#Html.EditorFor(model => model.contactInfo1)
#Html.EditorFor(model => model.contactInfo2)
This way, it will render that EditorTemplate and generate correct ids and names to inputs within it.
What you are doing is trying to post a collection of items in a form- this can indeed be done in MVC (as well as in any web page/application that uses a FORM tag), however it requires some special handling to avoid id collisions and to correctly format the post data. Steve Sanderson has a great post on how to accomplish this:
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Essentially its a wrapper to append unique guids to element ids (in your case for each contactInfo), and create the proper array format in the tags. eg <input name="ContactInfo[f2cc4d6b-fc32-45e9-9d3d-fce54c3fede3].FirstName">
if your model is something like ContactInfo, you will end up posting like
[HttpPost]
public ActionResult Index(IEnumerable<ContactInfo> contacts)
{
// To do: do whatever you want with the data
}

Parsing Form Post Values From a Table in ASP.NET MVC?

Ok, I'm an MVC newbie coming from a webforms background, so please excuse any ignorance here. Here is my scenario. I've got a table consisting of a list of applications and associated permissions. Each table row consists of 3 pieces of information: a checkbox, some text describing the row, and a dropdown list allowing the user to select the appropriate permission for the application. I want to post this data and only work with the rows in the table which were checked (the id of the row is embedded as the checkbox name). From there, I want to grab the selected value from the DropDownList, and call the necessary code to update the DB. Here is my View page's code:
<%foreach (var app in newApps)
{ %>
<tr>
<td><input type="checkbox" name="AddApps" value="<%=app.ApplicationId %>" /></td>
<td><%=Html.Encode(app.ApplicationName)%></td>
<td><%=Html.DropDownList("AppRole", new SelectList(app.Roles, "RoleId", "RoleDescription"))%></td>
</tr>
<%} %>
How would I retrieve the appropriate values from the FormCollection when I get to the controller on form post? I have done this in the past when I only had checkbox values to retrieve by just calling Request.Form["CheckBoxName"] and parsing the string.
Or am I going about this entirely wrong?
You are halfway right in order to post your data that the controller can read the info it must be inside a form as so :
<% using(Html.BeginForm("Retrieve", "Home")) %>//Retrieve is the name of the action while Home is the name of the controller
<% { %>
<%foreach (var app in newApps) { %>
<tr>
<td><%=Html.CheckBox(""+app.ApplicationId )%></td>
<td><%=Html.Encode(app.ApplicationName)%></td>
<td><%=Html.DropDownList("AppRole", new SelectList(app.Roles, "RoleId", "RoleDescription"))%></td>
</tr>
<%} %>
<input type"submit"/>
<% } %>
and on your controller :
public ActionResult Retrieve()
{
//since all variables are dynamically bound you must load your DB into strings in a for loop as so:
List<app>=newApps;
for(int i=0; i<app.Count;i++)
{
var checkobx=Request.Form[""+app[i].ApplicationId];
// the reason you check for false because the Html checkbox helper does some kind of freaky thing for value true: it makes the string read "true, false"
if(checkbox!="false")
{
//etc...almost same for other parameters you want that are in thr form
}
}
//of course return your view
return View("Index");//this vaires by the name of your view ex: if Index.aspx
}
This site gives more details on how to handle the dropdownlist helper:
http://quickstarts.asp.net/previews/mvc/mvc_HowToRenderFormUsingHtmlHelpers.htm
Request.Form will still work, except that your checkboxes all have the same name.
So one way would be to give the checkboxes distinct names, e.g. "AddApps-app.id", and use Request.Form.
However, a more elegant and testable way is to use list binding.
In this model, you give your form elements a certain structured name, and the default model binder will wrap each set of form elements into a list of typed records in the controller. This is fully explained in this blog post.
The advantage here is that your controller deals only with instances of the application type, and hence has no implicit dependency on the way the view is structured. Therefore, it is very easy to unit test.

Resources