razor template delegate, how the inside works? - c#

I'm reading this excellent article regarding razor template delegate. http://www.prideparrot.com/blog/archive/2012/9/simplifying_html_generation_using_razor_templates
While I understand how it's used, as in
Func<dynamic, HelperResult> variable = #<var>#item.ProductName</var>
My question is, how exactly razor engine translated "#<var>#item.ProductName</var>" into a delegate in the background? As in
Func<dynamic, HelperResult> variable = delegate(dynamic x)
{
(what goes on in here?)
}
is #item a reserved keyword that razor parses out? Can it be used in any other convention? say #column or #row or any other ways?
Thanks a lot. Like I said, i'm more interested in how razor view engine translated the template statements into actual code in the background.
[Edit]. Thanks to Brad for pointing out Andrew's article. so above statement "#<var>#item</var>" will translate into
Func<dynamic, HelperResult> variable = delegate(dynamic item)
{
return new Microsoft.WebPages.Helpers.HelperResult(__writer => {
#__writer.Write(" ");
#__writer.Write("<var>");
#__writer.Write(item.ProductName); <--- what's happening here?
#__writer.Write("</var>");
}
So I see razor automatically parses out #<var> and </var> into separate strings and such, my question in regards to "item.ProductName" is..suppose "item" is a "Proudct" type, then is the following what razor is trying to do?
First, razor parses "#item.ProductName" separated by comma ".", get "item" and "ProductName".
Then because of the "dynamic" parameter, in the background, .NET will attempt to find the value of the property "ProductName" of the item "Product"?
Thanks

Never mind. I don't know why i didn't make the connection.
I asked another question in regards to DynamicObject DynamicObject? How does the following code work? and #Alxandr explained this regarding "dynamic". So essentially, it becomes
dynamic item = new Product(...);
String ProductName = item.ProductName;
So in essence, "dynamic" parameter in the background use CSharpGetMemberBinder and through reflection, figure out the "ProductName" of the object "Product".
razor template has a pretty brilliant design

Related

What does DisplayNameFor do?

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.

What's the syntax for creating HandleBars helpers server side?

Using Handlebars.Net, I'd like to create a HandlebarsHelper that will replace carriage returns and newlines with <br> tags. It should look something like this:
string pattern = #"/(\r\n|\n|\r)/gm";
string replacement = "<br>";
Regex rgx = new Regex(pattern);
Handlebars.RegisterHelper("link_to", (string text) =>
{
text = rgx.Replace(text, replacement);
});
The compiler (or resharper) is telling me that it can't tell if I'm trying to use HandlebarsBlockHelper or HandlebarsHelper, and I'm missing arguments in either case.
What's the difference between the two?
I can't seem to find much documentation for any of this. Is there documentation for the above two mentioned objects as well as HelperOptions, and how to use TextWriter, the dymanic context and the argument object list?
It ended up looking like this with a little help from the C# Regex class:
var newlineRegx = new Regex("(\\r\\n|\\n|\\r)",RegexOptions.Multiline);
Handlebars.RegisterHelper("handleNewLines", (output, context, arguments) =>
{
var str = newlineRegx.Replace((string)arguments[0], "<br>");
output.Write(str);
});
To answer my questions:
HandleBarsBlockHelper provides a mechanism for invoking a helper with a block of the template. Block helpers can then invoke that block zero or more times with any context it chooses. Check out the description for Helpers for more info (at the bottom you'll see a button labeled "Learn More: Block Helpers").
TextWriter.Write is how you output your transformed text.
context will essentially be the JSON object you passed into the delegate you created with Handlebars.Compile().
The argument object lists contains the arguments that appear along side the helper you're defining, which you'll use in your HTML template
To better understand the argument object list, it'll help to see how I used this helper in my HTML template:
<div>
<p>{{{handleNewLines StringVariable}}}</p>
</div>
Where "StringVariable" is a member of the JSON object I passed into the delegate I created with Handlebars.Compile()

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.

How to pass in a lambda to a Razor helper method?

I have a razor helper method that needs to take in a Func<> that will return some HTML content to print out. This is what I originally had:
#helper node(string title, Func<HelperResult> descriptions)
{
....
<div>#descriptions()</div>
....
}
#node("title",
new Func<HelperResult>(() =>
{
return new HelperResult(
#<text>
<span>"desc1"</span>
<span>"desc2"</span>
</text>);
}))
Unfortunately with this my text never gets printed out. No error either.
So I learned about inline helpers, and changed the calling method to this:
#node("title",
#<text>
<span>"desc1"</span>
<span>"desc2"</span>
</text>)
However now I get a compilation error saying
"Delegate 'System.Func' does not
take 1 arguments".
But I'm not passing in any arguments.
So if I change it to Func<object,HelperResult> and then call it using #descriptions(null) I get the following error:
"Cannot use a lambda expression as an argument to a dynamically
dispatched operation without first casting it to a delegate or
expression tree type"
I'm sure I have something wrong somewhere, but I'm not sure what it is.
Edit: I think I may have solved that problem but it introduces some other issues.
What I did was to cast the lambda before passing into a dynamic method. I guess that's what the error was trying to say:
#node("title",
((Func<dynamic, HelperResult>)(#<text>
<span>"desc1"</span>
<span>"desc2"</span>
</text>))
That works and it prints out the span tags correctly. Unfortunately I have to pass in a useless parameter when calling this Func.
Now the issue I have is that my real function does a bit more than just write some spans. It's more like this:
#node("title",
((Func<dynamic, HelperResult>)(#<text>
<span>#Helpers.Format(resource.Description,"item")</span>
</text>))
Where #Helpers.Format is another helper and resource is a (dynamic) variable from the page model.
Of course now the code runs but nothing is printed out (inside the <span> tag). I put a breakpoint inside my Format helper function, and it hits it and all the parameters are correctly set, so I'm not sure why it wouldn't output correctly. Similarly if I just change it to
resource.Description
then nothing still gets output.
Since it works well outside of this context, I wonder does Razor's inline helpers not capture the outer variables?
Actually HelperResult is something Microsoft would rather you didn't use, as evidenced by documentation:
public class HelperResult : IHtmlString in namespace
System.Web.WebPages
Summary: This type/member supports the .NET Framework infrastructure
and is not intended to be used directly from your code.
A possible solution to your problem might be to wrap your description function in another helper and then pass that helper as a method group to your node helper, like this:
#helper Node(string title, Func<HelperResult> descriptions)
{
<div>#descriptions()</div>
}
#helper Description() {
<span>desc1</span>
<span>desc2</span>
}
#Node("title", Description)
In any case, your first idea shouldn't work because a parameter of type Func is in fact equal to a parameterless function, in which case you need to write the lambda expression like this:
myFunction( () => doSomething)
So your function call would have been:
#node("title", () =>
#<text>
<span>"desc1"</span>
<span>"desc2"</span>
</text>)
Since the future of these helpers is a bit dubious though, I would consider switching to either HtmlHelpers for small snippets of html or Partials for larger chunks.
#Test(new Func<object, HelperResult>[]{#<text>hello</text>})
#Test(new Func<object, HelperResult>[]{#<text>hello</text>,#<text>world</text>})
#helper Test(params Func<object, HelperResult>[] results)
{
foreach (var result in results)
{
#result(null);
}
}

Passing data in ASP.NET MVC using LINQ - nuttiness

Allow me to start with: I am a n00b on ASP.NET MVC. I love it, but I am a n00b.
I am trying to pass "complex" data back from a LINQ query. I understand how to use the data context and then just cast that data when I send it back, but when I do a more complicated LINQ query which returns an anonymous type, things break down.
I saw someone ask a similar question (MVC LINQ to SQL Table Join Record Display), and the answer seemed to be to create a new data type to capture the data from the LINQ query. I don't get that I can create a var type in the controller, and access the member fields within the controller, but if I want to pass that var back to my View, I need to create an entire new class for that.
Here’s my Controller code:
var vrGoodResults1 = from records in db.Words
group records by records.word1 into g
select new
{
strWord = g.Key,
intWordCount = g.Count()
};
ViewData["GoodWords"] = vrGoodResults1;
return View();
And the View looks like this:
<% foreach (var kvp in (IEnumerable)ViewData["GoodWords"]) %>
<% { %>
<%= String.Format("{0} was used times", kvp) %> <br />
<% } %>
Which outputs:
{strWord = cool, intWordCount = 2 } was used times
{strWord = educated, intWordCount = 1 } was used times
{strWord = great, intWordCount = 1 } was used times
{strWord = smart, intWordCount = 6 } was used times
{strWord = strong, intWordCount = 2 } was used times
{strWord = super smart, intWordCount = 2 } was used times
So the data is getting to the View, but I cannot refer to the data by the field names I assigned in the LINQ query. When I try dumping kvp.GetType(), I get:
<>f__AnonymousType1`2[System.String,System.Int32]
All I want to do is something along the lines of:
<%= String.Format("{0} was used {1} times", kvp.strWord, kvp.intWordCount) %> <br />
But I am getting a compile error on the kvp.strWord.
error CS1061: 'object' does not contain a definition for 'strWord' and no extension method 'strWord' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
If I insert the following code into my Controller:
foreach (var kvp in vrGoodResults1)
{
string strNew = kvp.strWord;
}
I can reference the fields of my variable kvp without a compile error. So something is getting lost when passing from the Controller to the View. Am I forgetting to include something somewhere? Perhaps a “using” or in the “<# Page” directive, am I forgetting to inherit something?
When you are using LINQ for clear data contexts, you just set the IEnumerable<”datatype”> where “datatype” = your data context type, and you are all good. When you reduce your data set into something new in LINQ, I can't believe that the best answer is to create a new class so that I can use it in my View.
var is a compiler shortcut for declaring a type in the current scope. It doesn't add any dynamic functionality to the .NET runtime, so code outside the scope sees the object as "System.Object", since that is the most-specific type in the inheritance chain the view code is aware of.
You should create a real class if you want to pass tuple objects around; that's what you had to do before var, so it's not like you're losing anything by having to do it now :)
The best answer here simply is to create a new type. You can do get anonymous types back from object (see here) but it is ugly and brittle. Don't do it!
You could use reflection (kvp.GetType().GetProperty("strWord").GetValue(kvp, null)) - but that also isn't a great idea.
In this case - perhaps use the existing KeyValuePair<string,int> from the original select? Or your own Tuple<T1,T2>?
Don't be afraid of new types. They give lots of other benefits too especially when testing. Use the new property syntax and your class will only be as many lines long as you have properties. You can lose that awful Hungarian notation too. Int and str - yuk

Categories

Resources