What does DisplayNameFor do? - c#

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.

Related

DisplayTemplate being ignored (covarient interface?)

This is a weird one. I have the following view file (Views/Search/Submit.cshtml):
#model IEnumerable<KeyValuePair<string, ISearchProvider>>
#foreach (var provider in Model)
{
var results = provider.Value.Results.Take(10);
if (results.Count() > 0)
{
<text><li class="dropdown-header">#provider.Key</li></text>
#Html.DisplayFor(x => results)
}
}
... where results is a System.Collections.Generic.IEnumerable<out T>, and T is ISearchMatch.
I have then defined a display template in Views/Search/DisplayTemplates/SiteSearchMatch.cshtml;
#model SiteSearchMatch
<li>#Html.ActionLink(Model.Name, "details", "site", new { Id = Model.Id }, null)</li>
... and SiteSearchMatch implements ISearchMatch like so;
public class SiteSearchMatch: ISearchMatch
{
public int Id { get; set; }
public string Name { get; set; }
}
I'd expect that my display template gets used; but it doesn't. Instead, the output I see being output is;
<li class="dropdown-header">sites</li>
11147166811481897189813271028
... where that string of numbers is the combination of all the Ids of the ISearchMatch's I wanted to render via the display template.
It seems Razor is simply rendering the ISearchMatch using the first attribute defined in the class; if I remove the definition of the Id property, I instead see the combination of all the Name's of the ISearchMatch's.
Does anyone know why this is happening, and how I can get Razor to use the display template I've specified?
Your expectation is wrong:
I'd expect that my display template gets used; but it doesn't.
The output you see is the ID's simply listed. I suspect your ISearchMatch-interface does only expose the Id-property, but this does not matter. What matters is the actual type of the instance of the result. In your case the following line:
#Html.DisplayFor(x => results)
can be implicitly evaluated as
HtmlHelper<IEnumerable<KeyValuePair<string, ISearchProvider>>>
.DisplayFor<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>>
(Func<IEnumerable<KeyValuePair<string, ISearchProvider>>, IEnumerable<ISiteMatch>> expression);
Looks pretty complex, but basically it's just a implicit substitution of your model and expression result. Your model is of type IEnumerable<KeyValuePair<string, ISearchProvider>>. That's also the type for the input of your lampda-expression. The result is of type IEnumerable<ISiteMatch>. And here come's the important thing!
The DisplayFor implementation checks, if the result type is enumerable or not. If not, it searches for a fitting template for the type, otherwise it will iterate through the elements and does this for all elements. 1
Searching for a template works based on the type name. In your case the template uses the name of the enumerated type, which is ISearchMatch. It does not find any display template, so it simply dumps the properties, resulting in what you see:
11147166811481897189813271028
To fix this problem, you need to convert your result set to the correct type first. You can do this in different ways. Either you cast the whole result of your provider results:
var results = provider.Value.Results
.Cast<SiteSearchMatch>()
.Take(10);
or you cast them individually within your lamda expression:
#Html.DisplayFor(x => (SiteSearchMatch)results)
The important thing is, that the scalar result type is the same as the model in your display template.
1 Note that this is a little bit more complex, for example the the extension also keeps track of an index and applys it to the output, so that the model could be bound for postback purposes.
The lame answer is that the "Build Action" on my View file Views/Search/DisplayTemplates/SiteSearchMatch.cshtml was set to "None", rather than "Content".
This meant the code worked fine when running in Debug mode within Visual Studio, but didn't work when any deployment was made.
Just to reiterate; this fix required no code changes. Simply change the "Build Action" back to "Content".

Using model objects in cshtml file

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.

What is the difference between #Html.ValueFor(x=>x.PropertyName) and #Model.PropertyName

#Html.ValueFor(x=>x.PropertyName)
#Model.PropertyName
It seems like these two Razor commands do the exact same thing. Is there any special circumstance or benefit of using one over the other?
#Html.ValueFor(x => x.PropertyName) invokes a lot a code and reflection under the hood.
It will allow you to customize the way the value is presented, and then have a consistent format across your whole site.
For example, if your property is decorated with DisplayFormatAttribute.
#Model.PropertyName is literally getting the value of the property directly, calling ToString() on it, and HTML escaping the result. No other formatting will take place.
To illustrate, you might see this:
[DisplayFormat(DataFormatString="{0:C}")]
public decimal PropertyName = 1234.56;
#Html.ValueFor(x => x.PropertyName) => "£1,234.56"
#Model.PropertyName => "1234.56"
ValueFor will invoke the template that exists for rendering the type that the property has. By default this template may be as simple as ToString(), but you can define anything as the template.
#Model.PropertyName will simply present the value as string.

Write additional HTML comments in razor

I'm struggling with the following problem and I can't find an acceptable way to solve it.
My challenge: write out HTML comments just before the actual property value in a Razor view.
This is my (simplyfied) Viewmodel:
public class Article
{
public string Title {get;set;}
}
To write out this title I simply do this in my Razor view:
<h2>#Model.Title</h2>
Now I want to write out a html comment just before the actual title so the generated HTML looks like this (simplyfied):
<h2><!-- some parameters for a 3th party system --> This is my title</h2>
The HTML comment comes from an Attribute I applied to the 'Title' attribute. It's value is generated, so the attribute-value is added at runtime using the TypeDescriptor from the .NET framework.
Now I know I could achieve this by simply writing out all my properties using an HTML helper. Like this: #MyHelper.Write(m => m.Title)
But since potentially ALL my properties need this HTML comment I want to avoid the use of an HTML helper since it clutters the View and doesn't make the view look nice and (more) readable.
This is what I have tried:
Created a custom Razor base page (Inheriting from WebViewPage<TModel>). And overwriting it's 'Write' method.
This kind of works but the BIGGEST problem here is that I don't know which property is been written out at that moment. There is no way of getting the current property name in the 'Write' method. So now I dynamically search my Model to find a property with the value that's been written out and prepend the HTML comment from the attribute.
My question: is there another approach to accomplish what I want. As sais before: I want to avoid using an HTML helper to write out all my properties. (Think about loops, etc. It's just not nice).
Also, adding this HTML comment in my Controller is no option since:
it's not part of the actual value. Is a sort of metadata.
The HTML comment should be added to int's, double's and DateTime's. There is no way to adjust a double property to include a string. (Image a List<DateTime>. All date's need this HTML comment)
the HTML comment should be added based on a web.config setting. Yes or No. (The actual HTML comment is different for each value of a property)
I realize this question is rather long. Sorry for that. Any thoughts are appreciated.
You can use the existing #Html.Raw(Model.Title)
Alternatively you can use a display templates. Add a UIHintAttribute to the properties you wish to behave this way.
public class MyModel
{
[UIHint("Raw")]
public string MyString { get; set; }
}
Create a new display template called Raw.cshtml that accepts model of type string:
#model string
#Html.Raw(model)
Then in your view you can use:
#Html.DisplayFor(m => m.MyString)
This still requires that you use a helper (DisplayFor). This is a recommended practice that allows you to easily change the behavior of one or many fields with minimal code changes.

MVC Strongly typed IQueryable<IGrouping<TKey, TElement>> model

I'm trying to create a strongly typed model for one of my Views in MVC. The model is the result of a LINQ GroupBy query so it is the type shown below (grouping employees by first letter of surname).
#model IQueryable<IGrouping<string, Employee>>
I'm unsure why but it doesn't let me have a model of this type. The error message I get is:
An opening "<" is missing the corresponding closing ">". Which is incorrect.
I know I can create a view specific model and populate that instead but I'd like to know why this model doesn't seem to work?
By default, a very limited set of namespaces are available for direct use in razor views. Try to expand it to fully qualified names and see if the problem persists:
#model System.Linq.IQueryable<System.Linq.IGrouping<string, Name.Space.Employee>>
I don't know why you'd be getting this error, since you appear to be using correct Razor code. It's possible that there's actually a bug elsewhere in the page that is being made manifest through this incorrect error message.
A workaround, which may help you determine the real source of the bug, would be to create your own strongly-typed model class, which could have this data as its property:
public class EmployeeListViewModel
{
public IQueryable<IGrouping<string, Employee>> EmployeesByCompanyTitle {get;set;}
}
(There are those who would argue that this is a better approach anyway, since you can now add information to your view model more easily.)

Categories

Resources