I am a newbie to MVC 4, (after 10 yrs of webforms) and have a question that I have not been able to figure out.
When writing code in the cshtml file, I am walking through a tutorial that has the following line:
#Html.DisplayNameFor(model => model.City)
What does the model => model.City imply? Why can't I use #Html.DisplayNameFor(model.City) ? I understand this is Linq query, but I would like to understand why would I need the model goes to model.city ?
Generally, that is called a lambda expression.In your scenario, you are telling the DisplayNameFor method that "take my model, and create a display element for this property.".You can't use model.City, because it just returns the value of the property.The method needs more than that in order to create a display element for your property.For example, it needs to know it's type and also it's attributes (like DisplayName attribute) and then it creates a display element for your element(it should be label I guess) .
DisplayName method is doing that using Expression Trees.The method takes an Expression<Func<TModel, TValue>> and uses it to get the name, value and the metadata information (attributes) about your property.
If you want to use model.City you can still use it, but then you won't need the functionality that DisplayNameFor provides.If you just need to display value of the property you can always do it like this:
<label> #model.City </label>
I understand this is Linq query,
Btw, this is incorrect, that is not a LINQ query.That is just an extension method.
Related
I'm learning .NET Core with Razor pages, using one of the official tutorials here, and I'm having trouble with this code:
#Html.DisplayNameFor(model => model.Movie[0].Title)
The tutorial says:
The DisplayNameExtensions.DisplayNameFor HTML Helper inspects the Title property referenced in the lambda expression to determine the display name. The lambda expression is inspected rather than evaluated. That means there is no access violation when model, model.Movie, or model.Movie[0] is null or empty. When the lambda expression is evaluated, for example, with #Html.DisplayFor(modelItem => item.Title), the model's property values are evaluated.
Which I can't make heads or tails of. What does inspect mean here? Does it mean the lambda function runs in an try/catch, to prevent the access violation errors that the docs speak of? What does it mean exactly?
And in what important way is the second example (with DisplayFor) different? It uses DisplayFor instead of DisplayName, and another change is that it uses ModelItem instead of model. I don't know where it would get ModelItem from, model is made available by #model (...) at the op of the razor page but how ModelItem gets here is not clear to me.
The docs for DisplayNameForare here, but the tutorial links to the non-core docs here, both of which are too terse for me to make much sense of.
DisplayNameFor will look for the Name property of the Display property attribute and print it to your razor page (or the property name itself, if it can't find it). So, if your model is
class Foo
{
[Display(Name="My name")]
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
It will display My name for Prop1 and Prop2 for Prop2.
The part about it being inspected rather than evaluated, means that it will look at your model definition, it will not try to get a value, so if the model value is null, it will not throw.
On the other hand, DisplayFor will print the value of the selected property, applying any format described by DisplayFormatAttribute, so the model is evaluated and cannot be null.
Typically you will use DisplayNameFor to build the table headers and DisplayFor to build the table data.
What does inspect mean here?
DisplayNameFor is used to get the name of the property and not the value of it, so you will get Title and not Pets 2. In the second case the lambda expression would be evaluated - the first element of the movie list would be retrieved and then its title, that is not happening here.
The difference between evaluating and inspecting is similar to that between reading a book vs turning it around to find out who published it.
2)
And in what important way is the second example (with DisplayFor) different?
DisplayFor is used to format the data itself (Pets 2 and not the Title). In this case there is not much formatting involved because it is a string, but if it was a number for example you could specify how many digits you want to see ect.
3)
I don't know where it would get ModelItem from
ModelItem it is just the name of the variable to pass into the function. modelItem => item.Title is an equivalent of MyFunction(Movie modelItem){return item.Title;} modelItem can be called anything else, particularly as they don't even use it in the function itself and use item directly.
I've always used DropDownListFor like this
Html.DropDownListFor(m => m.PropertyOfTheModel, SelectionList)
this works fine as long as you know the exact name of the property you're trying to build a dropdown list on (in this case: PropertyOfTheModel).
Now I have a different task. My model contains a fixed property, declared as object and called FormModel. Using reflections assume I'd like to build a dropdown for everyone of the properties contained in FormModel. Thanks to the attributes I've managed to solve the SelectionList part, I now have to write the first argument but I have no idea on how to do it.
foreach (var property in Model.FormModel.GetType().GetProperties())
{
#Html.DropDownListFor(m => m.FormModel.GetType().GetProperty(property.Name), SelectList)
}
The code above is not working: how should I write it? I have no experience in writing lambda expressions: is there any alternative to generate a dropdownlist which automagically gets the correct selected attribute just by passing in the property and the selection list? Or do I have to write the expression? Thanks!
this is the error I get:
Templates can be used only with field access, property access,
single-dimension array index, or single-parameter custom indexer
expressions.
I haven't had time to try this out yet, but the stumbling block may be that the code is trying to bind PropertyInfo, not the actual property on the class. I would give this a shot:
foreach (var property in Model.FormModel.GetType().GetProperties())
{
#Html.DropDownListFor(m => property.Name, SelectList)
}
I think it is as simple as this:
#Html.DropDownList(property.Name, SelectList)
Don't use the DropDownListFor helper, but DropDownList instead.
I have found other topics regarding this, but none of them made me understand what I'm trying to figure out.
I am studying MVC 3 and I have problems wrapping my head around the Lambda expressions that go with #HTML.DisplayFor() in the scaffolding template.
I am working with the MvcMusicStore sample application, the view created by the scaffolding looks like this:
#model IEnumerable<MvcMusicStore.Models.Album>
...
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.Genre.Name)
</td>
I understand that DisplayFor is a extension method to HTML and that it takes an expression as a parameter. I researched Lambda expressions and understand them in other contexts such as with enumerable strings. For example:
cities.Where(s => s.StartsWith("L"));
What boggles my mind is, that in the second example, the first part of the expression (s) is actually used in the second part (s.startswith..), so it makes sense and I get what the program is doing with it. But this is not the case in the MVC 3 view. modelItem is not beeing used anywhere. I tried renaming just "modelItem" to whatever like so:
#Html.DisplayFor(iAmWearingShortPantsToday => item.Genre.Name)
This code works just as well, displaying all the items and values correctly. This makes me doubt that in this case "modelItem" is actually used for anything. But then, why is it there?
I have also tried to translate this Lambda expression into a delegate (which I believe it is the short form for), like this:
#(Html.DisplayFor(delegate(IEnumerable<MvcMusicStore.Models.Album> modelItem) { return item.Genre.Name; }))
This however, does not work. The resulting error is:
CS1946: An anonymous method expression cannot be converted to an expression tree
Hence my question:
What is "modelItem" good for? What does HTML.DisplayFor() do with modelItem? Are there other cases maybe, where this first part of the expression becomes significant?
If it's possible to translate this expression into a proper delegate, that might also help me to understand what's exactly going on.
Thanks very much
I understand your confusion. Actually modelItem variable is never used in the lambda expression. What is used is the item variable which is captured in a closure from the outer context. The item variable is simply the local variable defined in the foreach loop.
By the way when you see an expression like modelItem => item.Genre.Name in ASP.NET MVC view, that's usually a sign for a bad coding practice. Avoid it. The internet is swarming with such bad examples. Really. I am sick of seeing this. Please help me eradicate this practice.
Here's an alternative method in which the variable is actually used:
#model IList<MvcMusicStore.Models.Album>
...
#for (var i = 0; i < Model.Count; i++) {
<tr>
<td>
#Html.DisplayFor(x => x[i].Genre.Name)
</td>
...
</tr>
}
And even better. Why write loops at all? When we can directly use editor/display templates:
#model IEnumerable<MvcMusicStore.Models.Album>
...
#Html.DisplayForModel()
and then define a display template that will automatically be rendered by ASP.NET MVC for each element of the model collection (~/Views/Shared/DisplayTemplates/Album.cshtml)
#model Album
<tr>
<td>
#Html.DisplayFor(x => x.Genre.Name)
</td>
...
</tr>
Not sure I can answer your question, but the right part is used to tell DisplayFor which property is will create a display for. It is used to determine the type of the property (used to select how to display the value) and the value.
If used in a TextBoxFor, it is also used to retrieve the name of the property, so the textbox can get the proper name.
I think that the expression in a lot of the html-helper extension methods is actually a bit odd, but it's the easy way of letting the code get all these information, without you having to write it down as string parameters (that would fail if you changed anything, without giving a compile-time error).
I am trying to work around the fact that when they wrote asp.net MVC 3 they forgot to include code to add the unobtrusive validation attributes to select lists and their "fix" for this is to include it in MVC 4, which is no bloody use to anyone using MVC 3.
My proposed work around is to use Html.GetUnobtrusiveValidationAttributes() to add them myself, just like any other custom attributes, but i can't work out the correct syntax for calling the method. There are 2 overloads, one takes a string and the other takes a string and a ModelMetaData class. I understand the metadata param, I presume I just pass in ViewData.ModelMetadata but what should the string be? The MSDN documentation says it is "the specified HTML name attribute" which makes no sense to me. The HTML name attribute of what? The select list? Why would it need that and how does that help it know what property on my model i want the validation for? Looking at examples of usage they all seem to pass in the name of the property on my model that i want the validation attributes for, which makes sense. Unfortunately I can't get the method to return anything but an empty collection no matter what i pass in.
My model class is called Event and my property is called EventTypeID. I am using a slightly different viewmodel class as the basis for the view because i need to display a list of Events and also also allow a new event to be entered on the same view so i have a simple viewmodel class as below:
public class EventViewModel
{
public Model.Event NewEvent { get; set; }
public IEnumerable<Model.Event> Events { get; set; }
}
The dropdown list is mapped to the property like: #Html.DropDownListFor(model => model.NewEvent.EventTypeID what do I pass as the string to Html.GetUnobtrusiveValidationAttributes(string) or Html.GetUnobtrusiveValidationAttributes(string, ModelMetadata) to get the attributes for this property. I have tried:
Html.GetUnobtrusiveValidationAttributes("EventTypeID")
Html.GetUnobtrusiveValidationAttributes("EventTypeID",ViewData.ModelMetadata)
Html.GetUnobtrusiveValidationAttributes("NewEvent.EventTypeID")
Html.GetUnobtrusiveValidationAttributes("NewEvent.EventTypeID",ModelMetadata)
They all return an empty collection.
I know that my model is correct because if i change the call from Html.DropDownListFor to Html.TextBoxFor then the validation "just works" without me having to do anything other than add the validation attributes to my model class.
EDIT:
Just tried turning client side validation off, the validation works fine server side for all select lists.
For those still looking for an answer, this works for me:
public static IDictionary<string, object> UnobtrusiveValidationAttributesFor<TModel, TProperty>(this HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> propertyExpression)
{
var propertyName = html.NameFor(propertyExpression).ToString();
var metadata = ModelMetadata.FromLambdaExpression(propertyExpression, html.ViewData);
var attributes = html.GetUnobtrusiveValidationAttributes(propertyName, metadata);
return attributes;
}
Note that I'm using .Net MVC 4, you don't have the html.NameFor method in MVC 3. However, I believe this can be done in MVC 3 with the following method:
var propertyName = ExpressionHelper.GetExpressionText(propertyExpression);
You can use it inline
Example for select element
<select name="#Html.NameFor(m=> m.MyProperty)"
id="#Html.IdFor(m=> m.MyProperty)"
#Html.Raw(string.Join(" ", Html.GetUnobtrusiveValidationAttributes(Html.NameFor(m => m.MyProperty).ToString()).Select(x => x.Key.ToString() + "=\"" + x.Value + "\"")))
>
Here is a link to an answer I posted, showing an HtmlHelper I wrote to provide unobtrusive validation for dropdownlists: MVC 3 dropdownlist validation not working for complex view model
UPDATE
Are you trying to get the attributes in an HtmlHelper, or in-line in your view?
Assuming you are trying to get the attributes in your view, that is the problem.
First, you need to understand that ModelMetadata does not represent a single object available across your entire model. Rather, it represents the metadata for a particular element, be it your model, or any property within the model. A better descriptive name would be ObjectMetadata, since ModelMetadata is the metadata for a specified object, be it a model, a nested model, or a specific property.
ModelMetadata in the view is only the metadata for the top-level model. You must get the ModelMetadata for the property to which the dropdownlist is bound. If you use a helper, then the helper is passed the correct ModelMetadata as a matter of course. If you use your view, you need to engage in some gymnastics to get the correct ModelMetadata, see for example my answer here: Validating and editing a “Changeable”/optional type in Asp.net MVC 3
I have an ASP.NET MVC application. At a certain point I get a FormCollection in a Controller method that I want to use to update a model. In the collection not all of the values are properties of that model and the property to be updated is an item from a list, and that list is also an item from another list. Something like this (I hope this is clear):
propertyToUpdate --> model.Items[0].Subitems[0].SomePropertyClass.Value;
I tried this in my Controller:
UpdateModel(model);
The problem is that this is not working and I assume it has something to do with the fact that the reflection is not working. I went searching and stumbled upon this article. So I understand that using the prefix-parameter solves the problem. But not in my case, as the properties lie "deeper" in the model as items from a list.
Does anyone know how I can solve this?
Update:
Here's the EditorTemplate for the property:
#model Q95.Domain.Property
<li>
#Html.DisplayFor(p => p.Description) :
#Html.DisplayFor(p => p.Quantity.Value)
#Html.DisplayFor(p => p.Quantity.Unit.Description)
<br />
#Html.TextBoxFor(p => p.Quantity.Value)
</li>
This template is called like this:
<ul>
#Html.EditorFor(model => model.SegmentRequirement.MaterialRequirements[j].Properties)
</ul>
Is this enough code or is there something still missing?
Update2:
Ok, in all the sub-properties I defined parameterless constructors and now I call:
UpdateModel(segmentRequirement, "SegmentRequirement", form.ToValueProvider());
This updates the model, but everything from MaterialRequirements is re-instantiated... :S
UpdateModel works fine on "Deep properties".
The problem is probably the data in the collection you get isn't equal to the properties names.
Check 3 places to see the values you get from the page
The form values.
The route data
The query string
In exact that order.
The keys should match you model properties names.
Update:
How to match the keys to properties names?
The input id will be the key you will get, change the the ids to match your properties names, or even better, use the HtmlTextBoxFor helper: see this article:
Maybe you should create flattened ViewModel and then use that to populate the view, and later synchronize it with the real model.
Can you show us your model and your view, if you are not using htmlhelper, you then have to understand the naming convention very well in order to make the model binding work with your model. so the first thing in first is to show us your model and view.