Nested EditorFor/DisplayFor override field name - asp.net-mvc

Short of overriding ViewData.TemplateInfo.HtmlFieldPrefix with an empty string, is there a way to keep the prefix from coming through on a nested set of strongly-typed EditorFor or DisplayFor helper calls?
Here's the ugly markup/code I have working so far:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<System.Web.Mvc.SelectList>" %>
<% ViewData.TemplateInfo.HtmlFieldPrefix = ""; %>
<%=Html.DropDownList("sort", Model)%>
I've tried the *For overload that allows specifying htmlFieldName but that only changes the immediate level. If I have a prefix at the point of that call, I just change what is appended to the prefix.
I could write the template markup by hand, but doing so for a SelectList object seems like I will just end up copying over the MVC source with a single tweak since it involves object data binding logic.

If you have a property on your view model, rather than just doing dropdown list directly on the model, you can put a DataAnnontation attribute on it.
public class MyModel
{
[Display(Name="Your Favorite Choices")]
public string[] Choices {get; set;}
}
then in your code
<%= Html.LableFor(m => m.Choices) %><br />
<%=Html.DropDownList("sort", Model.Choices)%>
Should use that name.
[Hope I got it right from memory. :)]
Here's the MSDN link:
http://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.displayattribute.displayattribute.aspx

Not sure if I understand your question, but, if you render the "child" views as partials instead of EditorFor then the fields will not be prefixed.

Related

ASP.NET MVC: Accessing ViewModel Attributes on the view

Is there any way to access any attributes (be it data annotation attributes, validation attributes or custom attributes) on ViewModel properties from the view? One of the things I would like to add a little required indicator next to fields whose property has a [Required] attribute.
For example if my ViewModel looked like this:
public class MyViewModel
{
[Required]
public int MyRequiredField { get; set; }
}
I would want to do something in the EditorFor template like so:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<int?>" %>
<div class="label-container">
<%: Html.Label("") %>
<% if (PROPERTY_HAS_REQUIRED_ATTRIBUTE) { %>
<span class="required">*</span>
<% } %>
</div>
<div class="field-container">
<%: Html.TextBox("") %>
<%: Html.ValidationMessage("") %>
</div>
The information you're looking for is in ViewData.ModelMetadata. Brad Wilson's blog post series on Templates should explain it all, especially the post on ModelMetadata.
As far as the other ValidationAttributes go, you can access them via the ModelMetadata.GetValidators() method.
ModelMetadata.IsRequired will tell you if a complex type (or value type wrapped in Nullable<T>) is required by a RequiredAttribute, but it will give you false positives for value types that are not nullable (because they are implicitly required). You can work around this with the following:
bool isReallyRequired = metadata.IsRequired
&& (!metadata.ModelType.IsValueType || metadata.IsNullableValueType);
Note: You need to use !metadata.ModelType.IsValueType instead of model.IsComplexType, because ModelMetadata.IsComplexType returns false for MVC does not consider to be a complex type, which includes strings.
I would suggest not doing that way because you're adding logic in the view which is a bad practice.
Why don't you create a HtmlHelper or LabelExtension, you can call ModelMetaProvider inside the method and find out whether the property has Required attribute decorated?

What's the point of Html.DisplayTextFor()?

Is there a good reason to use the strongly typed html helper...
<%: Html.DisplayTextFor(model => model.Email) %>
As opposed to...
<%: Model.Email %>
Consider the following Model:
public class MyModel
{
public string Name { get; set; }
[DisplayFormat(NullDisplayText = "No value available!")]
public string Email { get; set; }
}
in my view:
<%= Html.DisplayTextFor(m => m.Email) %>
<%: Model.Email %>
The first line will display "No value available" if we leave the Email to be 'null' whereas the second line will not display anything.
Conclusion:
Html.DisplayTextFor will take into consideration the DataAnnotations on your properties, <%: Model.Email %> will not.
Also <%: Model.Email %> will throw an "Object reference error" when the value is null, but <%= Html.DisplayTextFor %> wont.
DisplayTextFor will also get called during "DisplayFor" and "EditFor" execution. This will ensure that any templated helpers will display the text using the correct Templated Helpers if set ... so a change to the single templated helper will propogate through all displays of that text item ... simple display, edit form, create forms etc etc.
Well, DisplayTextFor will not break if you do not pass in a model (or pass null). Model.Email will throw an exception on null. So, DisplayTextFor is more robust.
The point of the strongly typed helpers is to allow greater compile time checking. This is very handy when refactoring.
DisplayTextFor allows using a consistent construct (with the other strongly typed helpers) throughout the page. Some people might find that more appealing.
DisplayTextFor also allows you to pass the names of templates and fields as parameters.

How to format a date in a view for two-way binding in MVC 2?

So let's say I have a view that access a date:
<%= Html.TextBoxFor(model => Model.Birthday) %>
How do I have my view dictate how that date is formatted? I'd like to use common formatting options, and it's important that this is 2-way (both for display and data-entry).
In your model, you could use System.ComponentModel.DataAnnotations metadata to specify the format you want the property in. e.g.
[DisplayFormat(DataFormatString="{0:MM/dd/yyyy}")]
public DateTime Birthday { get; set; }
Then, in your view, use:
<%= Html.EditorFor(model => model.Birthday) %>
Unfortunately, the regular HTML Helpers don't respect the metadata, so you have to use Html.EditorFor.
Use a specific ViewModel class that has a string in the format you want for the birthday and use that to display in the textbox. When you post the form, if you are using default binding for the domain object itself, it should parse the string for you. If you are using a ViewModel, you'll have to parse it upon posting the form.
I was kind of surprised to see that you can't just throw a format string in Html.EditorFor(). I've sinced starting using EditorTemplates.
Create a directory structure in your solution.
Views > Shared > EditorTemplates
Add a new MVC 2 User Control in the EditorTemplates folder and name it DateTime.ascx. Html.EditorFor(DateTime) will now use this user control for display.
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %>
<%= Html.TextBox(string.Empty, (Model.HasValue ? Model.Value.ToShortDateString() : string.Empty)) %>

Binding, Prefixes and generated HTML

MVC newbie question re binders. Supposing I have two strongly typed partial actions that happen to have a model attributes with the same name, and are rendered in the same containing page i.e.:
Class Friend {string Name {get; set ;} DateTime DOB {get; set ;}}
Class Foe {string Name {get; set ;} string ReasonForDislike {get; set ;}}
Both partials will have a line:
<%= Html.TextBoxFor(model => model.Name) %>
And associated controller actions:
public ActionResult SaveFriend(Friend friend)
public ActionResult SaveFoe(Foe foe)
My problem is that both will render on my containing page with the same id (of course, bad for lots of reasons). I’m aware of the [Bind] attribute that allows me add a prefix, resulting in code:
public ActionResult SaveFriend([Bind(Prefix = “friend”)] Friend friend)
<%= Html.TextBox("friend.Name", Model. Name) %> //Boo, no TextBoxFor :(
But this still doesn’t cut it. I can just about tolerate the loss of the strongly typed TextBoxFor helpers but I’ve yet to get clientside validation to work with prefixes:
I’ve tried:
<%= Html.ValidationMessage("friend.Name") %>
...and every other variant I can think of.
I seem to need the model to be aware of the prefix in both directions but bind only applies when mapping the inbound request. It seems (to me) a common scenario but I’m struggling to find examples out there. What am I missing!
Thanks in advance.
The prefix is there so you can wrap your objects in an "outer" ViewModel.
Suppose we have:
public class MyViewModel
{
public Friend friend;
public Foe foe;
}
If you use this class as your ViewModel and as the base of your strongly-typed Views, then your strongly-typed Textboxes will be named thusly:
friend.Name
foe.Name
You can then use the Prefix attribute you refer to in your question to disambiguate between your Friend and Foe classes.

ASP.Net MVC - Editing a collecion - is iterating through a FormCollection the best way?

This same question was asked here and an answer was given which is workable, but with the finalization of the ASP.Net MVC framework, I wondered if there was a better solution.
If I have the following class structure how do create the view page and more importantly return the data back to the controller.
public class Person {
public int Id {get;set;}
public string Name {get;set;}
public IList<TelNos> TelNos {get;set;}
}
public class TelNos{
public string Type {get;set;}
public string Number {get;set;}
}
My understanding is that within the page I would could include the following (assuming strongly typed view):
<% foreach (var telNo in Model.Product.TelNos)
{%>
<p><label for="telNo.Type">Type of Number</label>
<%= Html.TextBox("telNo.Type")%>
<%= Html.ValidationMessage("telNo.Type", "*")%>
</p>
<p><label for="telNo.Number">Type of Number</label>
<%= Html.TextBox("telNo.Number")%>
<%= Html.ValidationMessage("telNo.Number", "*")%>
</p>
<%} %>
Assuming that I initiated 2 TelNos objects I would then see 2 sets of text boxes within the view.
When that form is posted back, the suggestion on the previous post was to iterate through the FormCollection within the post method:
[AcceptVerbs( HttpVerb.POST )]
public ActionResult Whatever( FormCollection form )
{
....
}
However is that now the best approach, or have the further updates to MVC provided a better solution?
Thanks, Richard
There have not been any updates or best practices (as far as I'm aware of) to handling dynamic forms on post. The still tried and true ways are to either databind your information, or iterate through it in the FormCollection. If you take a look here it might help you with the databinding. OR in the latter case, you could iterate through the forms collection calling up the various values with their string name. Although this could possibly have some conflicts since they are all going to have the same id of
"telNo.Type"
"telNo.Number"
You might have to do some manipulation to have it be something like
"telNo.Type[i]"
"telNo.Number[i]"
where i is a number in sequence for that object in the list. You could also have it be something other string combination that generates a unique id for that object so that you can get the type and the number.
"object[i].telNo.Type"
"object[i].telNo.Number"
It really depends on how you think you can best implement it. Sometimes getting databinding to work for dynamic forms can be a pain and it's easier to just iterate through the collection, then using something like LINQ to get the ones you want/group them/etc.
Thanks for your answer. Your answer did give me an idea, which I think is different to what you are suggesting.
If I include in the view an index to the object, then the Default Model builder takes the values and assigns them to the associated objects. The view code is as follows:
<% int i=0;
foreach (var telNo in Model.Product.TelNos)
{%>
<p><label for="telNo.Type">Type of Number</label>
<%= Html.TextBox("telNo"+i.ToString()+".Type")%>
<%= Html.ValidationMessage("telNo"+i.ToString()+".Type", "*")%>
</p>
<p><label for="telNo.Number">Type of Number</label>
<%= Html.TextBox("telNo"+i.ToString()+".Number")%>
<%= Html.ValidationMessage("telNo"+i.ToString()+".Number", "*")%>
</p>
<%i++;
} %>
Richard
I like the idea at ASP.NET MVC Partial View with Form which in turn links to Model Binding To A List
I think it's better for two reason:
Going through FormCollection violates the idea of separating view form controller. Controller will have too much knowledge on how the data is displayed.
Writing a unit test will be painful. you'll need to stick all the values into the form controller manually

Resources