MS MVC3 Nested EditorFor and View Templates with same ViewModel - asp.net-mvc-3

I have a hierarchy of Models I need to render out editors for.
I want to have a nice ViewModel that contains all the info needed for each part of the hierarchy to render itself and have that ViewModel get passed down the chain of templates.
I have strongly typed View to that ViewModel, and I can get to the first level of nesting fine:
MyView.cshtml:
#model MyViewModel
#Html.EditorFor(x => x, "ViewTemplateA", "ViewTemplateA")
Within EditorTemplates/ViewTemplateA.cshtml:
#model MyViewModel
#Html.EditorFor(x => x, "ViewTemplateB", "ViewTemplateB")
---works OK up to here, ViewTemplateA gets rendered--
EditorTemplates/ViewTemplateB.cshtml:
#model MyViewModel
...etc...
-- ViewTemplateB never gets called. If I change it's #model to something else, and pass in a different object to match, such as x => x.SubModel, it gets called.
Any ideas??

Related

Can't bind model to Controller, MVC3

I have 2 Views and 1 EditorTemplate
The first view is the Cart.cshtml view and this view has a Model (CartModel)
Inside of this view there is a call to the second view using:
#Html.Partial("OrderSummary", Model.CartSummaryModel)
Inside of the OrderSummary.cshtml view I have this
#Html.EditorFor(m => m.OrderItems)
And inside the EditorTemplate (called OrderItemModel) I have
#Html.DropDownListFor(m => m.SelectedQuantity, Model.QuantityList)
The problem is when causing a post on the top most view (Cart.cshtml) the model isn't binding in the controller the "CartSummaryModel" is null. When swapping to a FormCollection there are 2 keys which are:
OrderItems[0].SelectedQuantity
OrderItems[1].SelectedQuantity
How do I bind the form collection data to the Controller's action method?
It's because of the partial:
#Html.Partial("OrderSummary", Model.CartSummaryModel)
Here you are loosing the CartSummaryModel prefix that is required in your input field names. So instead of using partials use editor templates.
Go ahead and move this OrderSummary.cshtml to EditorTemplates/OrderSummary.cshtml and then inside your view replace:
#Html.Partial("OrderSummary", Model.CartSummaryModel)
with:
#Html.EditorFor(x => x.CartSummaryModel, "OrderSummary")
and if the type of the CartSummaryModel property is OrderSummary you don't even need to specify the name of the editor template as ASP.NET MVC will find it by convention:
#Html.EditorFor(x => x.CartSummaryModel)
Now you are good to go and you will see the correct keys sent to the server:
CartSummaryModel.OrderItems[0].SelectedQuantity
CartSummaryModel.OrderItems[1].SelectedQuantity

How can I put a partial view of one type into a regular view of another type?

I’m trying to put a partial view in a regular view that is not of the same type. I create the partial view from my Assignment model and put it in the /Views/Shared folder. Then I tried to call the partial view in a regular view that was based on another model (InstructorIndexData ) and got the error message:
The model item passed into the dictionary is of type 'SchoolIn.ViewModels.InstructorIndexData', but this dictionary requires a model item of type 'SchoolIn.Models.Assignment'.
Here’s a bit of code from the partial view:
#model ...Models.Assignment
<div class="display-label">Grade</div>
<div class="display-field">
#Html.DisplayFor(model => model.Grade)
</div>
And here’s a bit of code from the regular view:
#model SchoolIn.ViewModels.InstructorIndexData
<td> #Html.Partial("_UpdateAttendance")</td>
How can I put a partial view of one type into a regular view of another type?
Thanks for any help.
If you are rendering the view using the Html.Partial method you could pass the model as second argument:
#Html.Partial("_PartialName", item.Grade)
If you are using display/editor templates, this is done automatically:
#Html.DisplayFor(x => x.Grade)
Now assuming that the Grade property is of type Assignment, then the ~/Views/Shared/DislpayTemplates/Assignment.cshtml will be rendered.
If on the other hand you have a collection property:
public IEnumerable<Assignment> Grades { get; set; }
then you could use:
#model SchoolIn.ViewModels.InstructorIndexData
<table>
<tr>
#Html.DisplayFor(x => x.Grades)
</tr>
</table>
and now the Assignment.cshtml display template will automatically be rendered for each element of the Grades collection so that you don't have to write any ugly loops in your view.

Using model data in a form in a partial view

I have the following code in a partial view:
#model MyOrganization.MyApp.Models.ProductListing
#using (Ajax.BeginForm("TagProduct", new AjaxOptions() { UpdateTargetId = "FormContainer" , OnSuccess = "$.validator.unobtrusive.parse('form');" }))
{
<p>
#Html.LabelFor(m => m.ModelNumber):
#Html.EditorFor(m => m.ModelNumber)
Tag Product with This Model Number
#(Html.ValidationMessageFor(m => m.ModelNumber))
</p>
}
The viewmodel that this partial view gets is instantiated and many of its properties are hydrated by the view that contains this partial view. However, when the submit is called here, the viewmodel that the controller gets has only the ModelNumber property hydrated. All the other properties are null as if a new instance is created by the partial view with only the property that was edited (ModelNumber) getting a value.
I know that the viewmodel instance passed to the partial view has all the other property values, because if I add an #Html.EditorFor(m => m.SerialNumber) I can see the SerialNumber value that the containing view had rendered in the browser's textbox and I also get it back in the controller when the form is submitted. However, I don't want an editor for the SerialNumber property on the form - I just want it back in the controller when it is submitted.
How can I can I get the entire model back to the controller as it was passed in to the partial view with only the changes that the partial view made?
When it posts the form, it DOES create a new copy of the model with whatever values you posted to it. If you need the other values, put #Html.HiddenFor the other elements you need posted.

Is it possible to pass a partial view a different model than the model used by the view it is in?

I tried to do it but I get an error saying that model x was expected but y was passed in.
Yes. In fact you can use any class, but it has to match the #model declaration of your partial view.
Partial View:
#model partialViewModel
<h2>#Model.partialViewModelProperty</h2>
Main View:
#model mainViewModel
<h1>Model.mainViewModelProperty</h1>
#Html.Partial("_PartialView", new partialViewModel()
{
partialViewModelProperty = "A title"
})
No, that is the point of a strongly-typed View. It requires a certain type. A partial view would handle this the same as any other view.

EditorFor is not getting the right Editor in ASP.NET MVC 3.0

I have a situation where I want to use a custom EditorTemplate with a ViewModel. So I have my ViewModel...
class Aspect {
}
class AspectViewModel {
}
then my EditorTemplate
Views
Shared
EditorTemplates
Aspect.cshtml
Aspect.cshtml
#model AspectViewModel
// other html
Then in another view that takes AspectViewModel, I call #Html.EditorFor(model => model), but it does not work. It only works if I use a hard-coded string #Html.EditorForModel("Aspect").
Any idea why it isn't being called?
You should name the editor template AspectViewModel.cshtml if it is strongly typed to AspectViewModel. Then all you have to do is:
#model AspectViewModel
#Html.EditorForModel()
or
#model SomeViewModel
#Html.EditorFor(x => x.Aspect)
where the Aspect property of SomeViewModel is of type AspectViewModel.
The convention is that the editor/display should be named as the type of the property you are calling it on and not the name of this property.
They also work greatly with collections. For example if you have the following property:
public class SomeViewModel
{
public IEnumerable<AspectViewModel> Aspects { get; set; }
}
and you use:
#model SomeViewModel
#Html.EditorFor(x => x.Aspects)
then the ~/Views/Shared/EditorTemplates/AspectViewModel.cshtml editor template wil be rendered for each element of this collection. Thanks to this you really no longer need to ever write for/foreach loops in your views.
This is because your model is AspectViewModel, but your view name is Aspect. They must match exactly.

Resources