I have an extension method
public static HelperResult List<T> (this IEnumerable<T> items, Func<T, HelperResult> template) {
return new HelperResult(writer =>{
foreach (var item in items)
template(item).WriteTo(writer);
});
}
When I try to use this method like this
<ol>
#Model.List(t=> {#<li>#t.Title</li>});
</ol>
I get an error "; expected"
But if I do
<ol>
#Model.List( #<li>#item.Title</li>)
</ol>
it's OK. (what is the variable "item"? Where does it define?)
Why does the first example throws an error?
The one solution is to declare razor helper like this
#helper ItemWriter(string item)
{
<li>#item.Title</li>
}
And then pass this to your extension function
#Model.List(ItemWriter)
I know this code can be made better, but this works. Main idea is to use Razor Helpers
The syntax #<tagname>...</tagname> declares a lambda expression that takes a parameter named item and returns a HelperResult.
You cannot use it as a statement; your first example cannot work.
That's pretty much the reason why I had to create Castle.Blade. It supports #=> p ... as an expression to create a lambda with named args. It also supports nesting these declarations, which razor doesn't.
Related
I want to create a custom HTML Helper where I can pass a LINQ expression as a parameter, like this:
#Html.GetBackgroundColor(model => model.RiskAssessment)
I want to use it to display some custom css in an MVC view, depending on what the RiskAssessment property is.
So I created a helper method like this:
public static string GetBackgroundColor<T, TResult>(this HtmlHelper<T> htmlHelper, Expression<Func<T, TResult>> expression)
{
...
}
However, that won't compile, the error is IEnumerable does not contain a definition for 'RiskAssessment'
So I changed the method to
public static string GetBackgroundColor<T, TResult>(this HtmlHelper<IEnumerable<T>> htmlHelper, Expression<Func<T, TResult>> expression)
{
...
}
which compiles, so now I presumably have all the objects in the collection but I have no idea how to get the object I want as I can't use use the expression on the IEnumerable, there is no Where() method available. I would have thought I could do something like this:
IEnumerable<T> collection = htmlHelper.ViewData.Model;
T obj = collection.Where(expression)
but I just don't know what I am doing wrong here.
Figured it out, simple mistake. The table header row is set up with #Html.DisplayNameFor(model => modelType), and I was trying to call my custom HTML helper with these parameters. I should have been calling the method on each table row, using #Html.GetBackgroundColor(modelItem => item.RiskAssessment), and this works because I can use htmlHelper.ValueFor(expression) within the method to get the property value.
That said, I have no idea how the header row is generated as Html.DisplayNameFor uses the same method signature as my custom method but Intellisense reports that one of the Expression types is unknown. But that is not an issue for me.
Thanks.
I think my understanding of lambda notation is lacking because I do not understand why it is even needed in Razor. For example:
instead of this:
#Html.DisplayFor(modelItem => item.FirstName)
why can't we just have this:
#Html.DisplayFor(item.FirstName)
What is the purpose of the lambda syntax and why do we need to add in all of the extra work of typing out the lambda notation?
Can someone help me to understand why this is needed and what benefit it offers?
The purpose of lambda expression on Razor view is returning value given by model from anonymous function (i.e. nameless function). Take a look on your first example:
#Html.DisplayFor(modelItem => item.FirstName)
would be translated to something like:
#Html.DisplayFor(String Function(Model modelItem)
{
return item.FirstName;
})
Here modelItem given as function parameter declared as Model, and return statement as function body to return property value depending on get/set operation.
If we look further to the DisplayFor helper declaration:
public static MvcHtmlString DisplayFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression,
string templateName
)
As stated by #SLaks before, expression tree can be parsed as a parameter to generate proper HTML tag into view based from type of your model defined by #model directive instead of executing it.
The second argument, Expression<Func<TModel, TValue>> is a declaration that ensures any given function parameter will always have same type as your model. This way eliminates reflection code that using GetProperty and GetValue required by HTML helper to retrieve given property value at appropriate time with strongly typed model as a benefit.
Here is an example of reflection code inside HTML helper declaration which can be eliminated by lambda syntax:
var model = html.ViewData.Model;
var value = String.Empty;
if (model != null)
{
var type = typeof(TModel);
var propertyInfo = type.GetProperty(templateName);
var propertyValue = propertyInfo.GetValue(model);
value = propertyValue.ToString();
}
Afterwards, let's examine the second example:
#Html.DisplayFor(item.FirstName)
Here DisplayFor will use Object as parameter type, consider we can't determine exactly what type should be pre-assigned thus it sets to System.Object. Since the method doesn't provide model definition type as TModel in generic code, the method probably requires reflection when dealing with property value.
Any improvements and suggestions welcome.
References:
https://msdn.microsoft.com/en-us/library/hh833706%28v=vs.118%29.aspx
http://odetocode.com/blogs/scott/archive/2012/11/26/why-all-the-lambdas.aspx
I want to understand the lambda expression in #Html.DisplayFor(modelItem => item.FirstName)
The HtmlHelper methods take an expression tree as a parameter.
This lets them see the actual property that you pass so that they can observe its attributes.
I would like to create a html helper that would receive as a parameter content of the view, like this:
<% Html.MyTextArea("txt1", () => {%>
content of the view
<%=Html.TextBox("Name") %>
...
<% }) %>
Since you've tagged it as MVC, I'm going to propose you could do something like I posted on my blog as a way to get syntax highlighting for templates as the solution would be very similar, IF you don't need to manipulate the inner content and are simply interested in 'wrapping' it in some way (like in a containing element that requires some extra logic).
Using the technique, the HtmlHelper method receives the context of the block. The syntax is slightly different from your suggested technique though.
For example, you could have something like:
#using(Html.MyTextArea("txt1"))
{
<some HTML content here>
}
The context is passed to an IDisposable object which includes a Writer (for writing to the current output stream). There, it can output multiple elements or do other manipulation as needed. The Dispose is used to write a close element (if needed).
So, you could end up with something like this:
<textarea><some HTML content here></textarea>
However, as I mentioned this does not provide the inner content to the function itself.
As Razor pages render inside out, there's not an effective method to grab the output in the way you're wanting. There are some posts around about caching the output of a Partial to a string (which would mean the inner content in your example would be in another file, an .ascx file), so you might want to look at those.
One approach is,
public static MvcHtmlString TextArea(this HtmlHelper htmlHelper, string name, Action<TextWriter> action)
{
var writer = new StringWriter();
action(writer);
// here writer contains the html
return htmlHelper.TextArea(name);
}
<%:Html.TextArea("txt1",(writer) => {
writer.Write("content of the view");
writer.Write(HttpUtility.HtmlEncode(Html.TextBox("Name")));
}) %>
Do you mean something like this?
namespace System.Web.Mvc {
public static class HtmlHelperExtensions {
public static MvcHtmlString MyTextArea(this HtmlHelper htmlHelper, string id, Func<MvcHtmlString> helperFunc) {
return new MvcHtmlString(string.Format("<div id=\"{0}\">{1}</div>", id, helperFunc()));
}
}
}
You can use this way:
<%: Html.MyTextArea("txt1", () => Html.TextBox("Name", "abc")) %>
When I try to render a partial view whose model type is specified as:
#model dynamic
by using the following code:
#{Html.RenderPartial("PartialView", Model.UserProfile);}
I get the following exception:
'System.Web.Mvc.HtmlHelper<dynamic>' has no applicable method named 'RenderPartial' but appears to have an extension method by that name. Extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax.
However, the same code in a .aspx file works flawlessly. Any thoughts?
Just found the answer, it appears that the view where I was placing the RenderPartial code had a dynamic model, and thus, MVC couldn't choose the correct method to use. Casting the model in the RenderPartial call to the correct type fixed the issue.
source: Using Html.RenderPartial() in ascx files
Instead of casting the model in the RenderPartial call, and since you're using razor, you can modify the first line in your view from
#model dynamic
to
#model YourNamespace.YourModelType
This has the advantage of working on every #Html.Partial call you have in the view, and also gives you intellisense for the properties.
Can also be called as
#Html.Partial("_PartialView", (ModelClass)View.Data)
There's another reason that this can be thrown, even if you're not using dynamic/ExpandoObject. If you are doing a loop, like this:
#foreach (var folder in ViewBag.RootFolder.ChildFolders.ToList())
{
#Html.Partial("ContentFolderTreeViewItems", folder)
}
In that case, the "var" instead of the type declaration will throw the same error, despite the fact that RootFolder is of type "Folder. By changing the var to the actual type, the problem goes away.
#foreach (ContentFolder folder in ViewBag.RootFolder.ChildFolders.ToList())
{
#Html.Partial("ContentFolderTreeViewItems", folder)
}
Here's a way to pass a dynamic object to a view (or partial view)
Add the following class anywhere in your solution (use System namespace, so its ready to use without having to add any references) -
namespace System
{
public static class ExpandoHelper
{
public static ExpandoObject ToExpando(this object anonymousObject)
{
IDictionary<string, object> anonymousDictionary = HtmlHelper.AnonymousObjectToHtmlAttributes(anonymousObject);
IDictionary<string, object> expando = new ExpandoObject();
foreach (var item in anonymousDictionary)
expando.Add(item);
return (ExpandoObject)expando;
}
}
}
When you send the model to the view, convert it to Expando :
return View(new {x=4, y=6}.ToExpando());
Cheers
I had the same problem & in my case this is what I did
#Html.Partial("~/Views/Cabinets/_List.cshtml", (List<Shop>)ViewBag.cabinets)
and in Partial view
#foreach (Shop cabinet in Model)
{
//...
}
I was playing around with C# code an I accidentally found the solution to your problem haha
This is the code for the Principal view:
`#model dynamic
#Html.Partial("_Partial", Model as IDictionary<string, object>)`
Then in the Partial view:
`#model dynamic
#if (Model != null) {
foreach (var item in Model)
{
<div>#item.text</div>
}
}`
It worked for me, I hope this will help you too!!
This throws error:
public static void RenderPartialForEach<T>
(this HtmlHelper helper, string partialName, IList<T> list)
{
foreach (var item in list)
helper.RenderPartial(partialName, item);
}
=>
Error Message: CS1519: Invalid token '(' in class, struct, or interface member declaration
Line 283: #line default
Line 284: #line hidden
Line 285: #__w.Write("\r\n \r\n\r\n\r\n");
Line 286: }
Line 287:
Is it possible to create clean htmlhelper
which is able to render partial views for every item in list passing it as model?
Edit:
That was just a blunder from my side. I forgot to add '<% } %>'.
And got confused cause of error message. ^^
You need to declare the method in a class. It's not obvious that you are doing that, but it would certainly cause the type of error that you are seeing.
public static class CustomHtmlHelperExtensions
{
public static void RenderPartialForEach<T>(
this HtmlHelper helper,
...
}
EDIT: In retrospect, given the text of the error, I suspect that the error lies elsewhere in your markup. Perhaps, you're missing a parenthesis around an if statement or foreach clause.
Where are you declaring such a thing? Try writing that extension method in a separate static class in a code file, not inline in .aspx.
Just a guess... you are implementing a generic method (of T) but you are not actually replacing the generic parameter (T) with a type argument?