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.
Related
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.
In my MVC4 website, I have a page where I'm looping through a list of items that are part of my model and putting them in a table.
My model is named JobModel and it has a list of TaskModel objects accessible in a property named Tasks.
I'm using syntax like this for each item in the list...
#Html.DisplayFor(m => m.Tasks[i].TaskName)
#Html.DisplayFor(m => m.Tasks[i].TaskName)
This works fine.
However, I have a need to create a Custom DisplayFor extension, that I'll name DisplayFor2 for this discussion.
My code looks like this...
public static IHtmlString DisplayFor2<TProperty>(this HtmlHelper<TaskModel> html, Expression<Func<TaskModel, TProperty>> ex) {
//Build my custom html here...
}
When I attempt this code,
#Html.DisplayFor2(m => m.Tasks[i].TaskName)
I get the compiler complaining that:
The type arguments for method 'IHtmlString MySite.HtmlHelpers.HelperExtensions.DisplayFor2<TProperty>(this HtmlHelper<TaskModel>, Expression<Func<TaskModel,TProperty>>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
What do I need to do?
*UPDATE*
I changed my method to this, and now it works.
public static IHtmlString TableCellFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> ex)
But when I do this, I don't know the index of the child item, so it's proven useless.
I trying to use a Razor Helper that does some if statements for me. I'm passing it a list with certain rules and based on that rule a label and input text field are created.
The problem I have is I can't get it to work with a Lambda expression as parameter. It won't recognize the TModel part.
The helper method is as follows:
#helper CreateCheckbox(Expression<Func<TModel, bool>> expression, object htmlAttributes, List<Rule> ruleList)
{
}
The error I get is: The type of namespace 'TModel' can not be found.
You have to pick a type for TModel. Because your view doesn't have any generic type parameters, there is no way for it to figure out what type it should substitute in for TModel. You have to give it an actual type to work with.
If that's not an option, you might just make an extension method for the HtmlHelper class, and make it a generic method. See this question for an example of how to do that.
The default DisplayFor helper just throws some escaped text on the page. Is there a way to wrap the default in a <span> or <div> without writing custom DisplayFor templates for each property you want to display? Instead of it spewing out
Foo
I want it to spit out
<span>Foo</span>
I've seen a lot of articles for making the custom stuff; I use those quite often. But most of my display stuff I just want to handle with simple CSS where the content is wrapped in a tag, and I can't find a reference on how to do this, or even if it's possible.
Sure use the 'object' template and override as follows from ViewData.ModelMetadata.Properties
Object template - Html.Display using ViewBag
By having your display template named object.cshtml you will apply to all types with numerable properties
There's a link to brad wilsons entry that contains the primary details
http://bradwilson.typepad.com/blog/2009/10/aspnet-mvc-2-templates-part-3-default-templates.html
You could create your own HtmlHelper extension
public static class HtmlHelperExtensions
{
public static MvcHtmlString EnhancedDisplayFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var stringBuilder = new StringBuilder("<span>");
stringBuilder.Append(html.DisplayFor(expression));
stringBuilder.Append("</span>");
return new MvcHtmlString(stringBuilder.ToString());
}
}
and then use it like this
#Html.EnhancedDisplayFor(model => model.MyProperty);
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?