So, the title should speak for itself.
To create re-usable components in ASP.NET MVC, we have 3 options (could be others i haven't mentioned):
Partial View:
#Html.Partial(Model.Foo, "SomePartial")
Custom Editor Template:
#Html.EditorFor(model => model.Foo)
Custom Display Template:
#Html.DisplayFor(model => model.Foo)
In terms of the actual View/HTML, all three implementations are identical:
#model WebApplications.Models.FooObject
<!-- Bunch of HTML -->
So, my question is - when/how do you decide which one of the three to use?
What i'm really looking for is a list of questions to ask yourself before creating one, for which the answers can be used to decide on which template to use.
Here's the 2 things i have found better with EditorFor/DisplayFor:
They respect model hierarchies when rendering HTML helpers (e.g if you have a "Bar" object on your "Foo" model, the HTML elements for "Bar" will be rendered with "Foo.Bar.ElementName", whilst a partial will have "ElementName").
More robust, e.g if you had a List<T> of something in your ViewModel, you could use #Html.DisplayFor(model => model.CollectionOfFoo), and MVC is smart enough to see it's a collection and render out the single display for each item (as opposed to a Partial, which would require an explicit for loop).
I've also heard DisplayFor renders a "read-only" template, but i don't understand that - couldn't i throw a form on there?
Can someone tell me some other reasons? Is there a list/article somewhere comparing the three?
EditorFor vs DisplayFor is simple. The semantics of the methods is to generate edit/insert and display/read only views (respectively). Use DisplayFor when displaying data (i.e. when you generate divs and spans that contain the model values). Use EditorFor when editing/inserting data (i.e. when you generate input tags inside a form).
The above methods are model-centric. This means that they will take the model metadata into account (for example you could annotate your model class with [UIHintAttribute] or [DisplayAttribute] and this would influence which template gets chosen to generate the UI for the model. They are also usually used for data models (i.e. models that represent rows in a database, etc)
On the other hand Partial is view-centric in that you are mostly concerned with choosing the correct partial view. The view doesn't necessarily need a model to function correctly. It can just have a common set of markup that gets reused throughout the site. Of course often times you want to affect the behavior of this partial in which case you might want to pass in an appropriate view model.
You did not ask about #Html.Action which also deserves a mention here. You could think of it as a more powerful version of Partial in that it executes a controller child action and then renders a view (which is usually a partial view). This is important because the child action can execute additional business logic that does not belong in a partial view. For example it could represent a shopping cart component. The reason to use it is to avoid performing the shopping cart-related work in every controller in your application.
Ultimately the choice depends on what is it that you are modelling in your application. Also remember that you can mix and match. For example you could have a partial view that calls the EditorFor helper. It really depends on what your application is and how to factor it to encourage maximum code reuse while avoiding repetition.
You certainly could customize DisplayFor to display an editable form. But the convention is for DisplayFor to be readonly and EditorFor to be for editing. Sticking with the convention will ensure that no matter what you pass into DisplayFor, it will do the same type of thing.
Just to give my 2c worth, our project is using a partial view with several jQuery tabs, and each tab rendering its fields with its own partial view. This worked fine until we added a feature whereby some of the tabs shared some common fields. Our first approach to this was to create another partial view with these common fields, but this got very clunky when using EditorFor and DropDownListFor to render fields and drop downs. In order to get the ids and names unique we had to render the fields with a prefix depending on the parent partial view that was rendering it:
<div id="div-#(idPrefix)2" class="toHide-#(idPrefix)" style="display:none">
<fieldset>
<label for="#(idPrefix).Frequency">Frequency<span style="color: #660000;"> *</span></label>
<input name="#(idPrefix).Frequency"
id="#(idPrefix)_Frequency"
style="width: 50%;"
type="text"
value="#(defaultTimePoint.Frequency)"
data-bind="value: viewState.#(viewStatePrefix).RecurringTimepoints.Frequency"
data-val="true"
data-val-required="The Frequency field is required."
data-val-number="The field Frequency must be a number."
data-val-range-min="1"
data-val-range-max="24"
data-val-range="The field Frequency must be between 1 and 24."
data-val-ignore="true"/>
#Html.ValidationMessage(idPrefix + ".Frequency")
... etc
</fieldset>
</div>
This got pretty ugly so we decided to use Editor Templates instead, which worked out much cleaner. We added a new View Model with the common fields, added a matching Editor Template, and rendered the fields using the Editor Template from different parent views. The Editor Template correctly renders the ids and names.
So in short, a compelling reason for us to use Editor Templates was the need to render some common fields in multiple tabs. Partial views aren't designed for this but Editor Templates handle the scenario perfectly.
Use _partial view approach if:
View Centric Logic
What to keep all _partial view related HTML in this view only. In the template method, you will have to keep some HTML outside the Template View like "Main Header or any outer border/settings.
Want to render partial view with logic (From controller) using URL.Action("action","controller").
Reasons to use Template:
Want to remove ForEach(Iterator). Template is well enough to identify Model as a list type. It will do it automatically.
Model Centric Logic. If multiple views are found in the same displayfor Template folder, then rendering will depend on Passed Model.
Another difference that hasn't been mentioned so far is that a partialview doesn't add model prefixes while a template does
Here is the issue
Related
I have a very large model for user profiles. There are about 15 different new user categories (e.g., young traveler, experienced traveler, captain, helmsman, ...). Depending on the user category, some data fields of the model are not applicable and remain null in the database.
I'm trying to populate the model through one big sign-up form. JavaScript determines the user category based on the user's previous answers and shows/hides applicable/non-applicable input fields in the form.
The following problem arose:
Depending on the user category, I need to show some input fields in a slightly different order in the form. I have solved this by creating multiple input fields for the same data field in the view (e.g., #Html.TextBoxFor(x => x.Age)) and then using JavaScript to show only one depending on the user category.
My plan was to submit only the visible input fields so that the server is not confused about which input fields to use to create the model.
Unfortunately, Visual Studio doesn't quite play along: First, there's there problem of having multiple input field with the same id; I could maybe live with that. But then, validation attributes in the HTML-source are generated only for the first occurrence of each data field in the view (e.g., #Html.TextBoxFor(x => x.Age)), not for any later occurrences.
E.g., the first occurrence generates
<input data-val="true" data-val-number="The field Age must be a number."
data-val-range="The field Age must be between 10 and 99." data-val-range-max="99"
data-val-range-min="10" id="Age" name="Age" type="text" value="">
The second occurrence generates merely
<input id="Age" name="Age" type="text" value="">
How can I solve this? My thinking:
Create all input fields hidden once at the beginning of the view and use Javascript to generate at the form dynamically. Downside: A lot of work, all page structure is now in JavaScript instead of view.
Use partial views for different user categories. Downside: When the form page is opened (in the controller), I retrieve several lists from the database and put them in the model in order to populate the form DropDown input fields and RadioButton input lists in the view. Is there any way to reuse this information in the partial views so that there are not many repetitive database queries?
Any thoughts on the situation are very much appreciated. I'm somewhat new to ASP.NET MVC.
Your view model should include a property (pehaps an enum) that defines you user category,
then in your view just render the html applicable to that category
/// Some common html
#if (model.UserCategory == "captain")
{
// controls specific to captain
}
else if (model.UserCategory = "helmsman")
{
// controls specific to helmsman
}
If you need to include this html on the same page you are determining the 'previous answers', put this in a partial view and all it using ajax (passing the user category).
Another option would be to create only one control for each property and then in the javascript that hides the control, also disable the control - disabled controls don't post back so the property will remain null
$('#MyProperty').prop('disabled', true);
Honestly, I would drop the idea of building such a beast in server technology in favor of using plain JS, KnockoutJS or AngularJS which seem to be built exact for this needs.
However, answering your questions:
You have to remember, that browser submits to the server only those fileds which are enabled. If you don wan't to "confuse" server just disable unneeded fields before submit.
Model binder of asp.net mvc finds values by name attribute not by id. You can have form with couple fields with different id attributes and one name for all of them. It will be just a bit more complex to declare them in the Razor View, but it should work.
I have an editor template and within that editor template i want to call another editor template with the same model (i.e. nested), but it does not seem to display.
ie. \EditorTemplates\Template1.cshtml
#model foo
// insert code here to edit the default fields.
// display extra fields via another editor template.
#Html.EditorForModel("Template2") // or #Html.EditorFor(m => m, "Template2")
and \EditorTemplates\Template2.cshtml
#model foo
#Html.TextBoxFor(m => m.Name)
I am sure someone will question why? Well, the nested template will only be displayed if a condition is met (ie. #if (#Model.IsConditionMet) { .... } ), but I have left that out of my prototype for simplicity.
Short answer:
Use Html.Partial instead.
So, in your Template1.cshtml file:
#model foo
// insert code here to edit the default fields.
// display extra fields via another editor template.
#Html.Partial("EditorTemplates/Template2", Model)
Long answer:
This sadly appears to be by-design. MVC tracks the models that have been rendered, and if your model has already been rendered by a template, it won't do it twice, even if the template is different. Hence why the second #Html.EditorForModel("Template2") just does nothing.
Specifically, it's tracked in ViewData.TemplateInfo.VisitedObjects, which is an internal field, so there's no hope in you modifying it after the fact. The intention of this field is to prevent infinite recursion. Noble, but annoying in that it doesn't take the template used into account.
I found this out by looking at the source code, which is great for finding these weird idiosyncrasies of MVC.
I am still trying to figure out how to create reusable partial views in MVC
Lets say I would like to create a partial view to display a form for submitting an address.
Then in my ViewModel I have two addresses (Home address & Work Address)
So I would think that in my view I call HTML.Partial for each one like this
#Html.Partial("Address", Model.HomeAddress)
#Html.Partial("Address", Model.WorkAddress)
but what happens is instead of the fields having names like HomeAddress.Street, HomeAddress.City etc. they just have the regular field names Street, City, etc. so the binder on the HTTPPost action has no idea what to do with them
Thanks in advance
Partial views where not designed to handle that scenario. What you are looking for are sub-editors. Take a look at Brad Wilson's excellent series on editor templates: http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-1-introduction.html
Instead of Partial you use the EditorFor and related methods:
#Html.EditorFor(m => m.HomeAddress)
You can then use the auto-generated templates or define your own using an approach similar to partial views.
In Eric Hexter's Input Builders, different templates use different strongly-typed models; for example String uses PropertyViewModel<object>, DateTime uses PropertyViewModel<DateTime>, Form uses PropertyViewModel[], and so forth. Spark View Engine doesn't seem to allow this, because all elements that compose the presentation (masters, views, partials, etc.) are compiled into a single class.
If I try to setup a view involving more than one template, I get the following exception:
Only one viewdata model can be declared. PropertyViewModel<DateTime> != PropertyViewModel<object>
If leave just one viewdata declaration, I get another exception about the passed model item mismatching the required one.
It seems like I will have to give up either the Input Builders or Spark, which is sad because I really love both. So I thought I'd ask here to see if anybody has already figured this out.
Thanks.
You can always use <% Html.RenderPartial() %> for partial view rendering with different model. This will create more than one view class.
My MVC application contains a parent model, which will contain 1 or more child models.
I have set up the main view to display properties from the parent model, and then loop through a collection of my child models (of various types, but all inheriting from the same base type). Each of the child models have a corresponding partial view.
My "parent" view iterates over the child models like this:
foreach (ChildBase child in ViewData.Model.Children)
{
Html.RenderPartial("Partials/"+child.ChildType.ToString()+"Child",
section);
}
My application has the appropriate /Partials/ChildType1.ascx, ChildType2.ascx, etc. Everything works great.
Is this an appropriate way to use Partial Views? It feels slightly off-kilter due to the dynamic names, but I don't know another way to perform dynamic selection of the proper view without resorting to a big switch statement.
Is it advisable to use the same view for multiple "modes" of the same model? I would like to use the same .ascx for displaying the "read only" view of the model, as well as an edit form, based on which Controller Action is used to return the view.
Iconic,
It's difficult to answer the questions without knowing exactly what you're trying to achieve.
I'll have a go though:
If you're familiar with web forms, think of your partial view as a webforms usercontrol for the moment and think of the part of your model that is relevant to your partial views as a 'fragment of information' that want to pass across to the partial view.
Natural choices for using a partial view would be for elements used in many views across your site.
So ... in answer:
1.Although what you are doing is valid, it doesn't seem quite correct. An example of a partial view I have used might be a row in a grid of data where you'd call the partial view passing in the row object as its model:
foreach (MyObject o in Model.objects)
{
Html.RenderPartial("Shared/gridRowForObject.ascx", o, ViewData);
}
You can strongly type your views also to expect a specific type to be passed through as the Model object.
Again another use might be a login box or a 'contact me form' etc.
2._Ultimately this is a personal design decision but I'd go for the option that requires the least application/presentation logic and the cleanest code in your view. I'd tend to avoid writing to many conditional calls in your view for example and by inferring a base type to pass across to all of your partial views as in your example, may well tie you down.
When learning the MVC framework I found the Oxite code to be useful.
I hope that helps.