I am trying to get below expression value by compiling and invoking but i get some errors and no success till now.
public static void TextEditorFor<TModel, TProperty>(this System.Web.Mvc.HtmlHelper<TModel> html, Expression<Func<TModel, TProperty>> expression)
{
var value = expression.Compile().Invoke(html.ViewData.Model);//problem that is value is null
}
(applies to the original question)
Given the signature, you should just need:
return Convert.ToString(
expression.Compile().Invoke(modelInstance)
);
You can also do this by inspection of the expression if absolutely needed.
Use ModelMetadata.FromLambdaExpression Method and then its property Model
Related
I am trying to extend the HtmlHelper so it can translate field names from the Linq entities using a simple resource file.
The problem is that i can't get the extended method signature right. Here the code:
public static class HtmlHelperExtension
{
public static MvcHtmlString HeaderFromResource<TModel,
TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
...
return (MvcHtmlString)html.Raw(something)
}
}
When i try to use it inside the view, like this:
#model IEnumerable<WebApp.Models.TransferInConfig>
...
<th>
#Html.HeaderFromResource(model => model.RemotePath)
</th>
i get the following error:
'IEnumerable' does not contain a definition for
'RemotePath' and no extension method 'RemotePath'
Well to start your model is an IEnumerable, it indeed does not have RemotePath definition, but the items in it does (probably). So you will have to loop through your model list first and use each items. Something like this
foreach (var item in model){
#Html.HeaderFromResource(item => item.RemotePath)
}
public static MvcHtmlString HeaderFromResource<TModel, TValue>( this HtmlHelper<IEnumerable<TModel>> html, Expression<Func<TModel, TValue>> expression)
{
var headerName = expression.Body.ToString() //return model.Connection.RemotePath
.Split('.')
.Last();
if (Properties.Resources.ResourceManager.GetString(headerName) != null)
headerName = Properties.Resources.ResourceManager.GetString(headerName);
return MvcHtmlString.Create(headerName);
The only thing i needed to do was to look up on the manual for the signature of DisplayForName, but i am not use it anymore. Should be a better way to get the name of the field from the expression, anyway this one works for me.
If I write a custom DisplayFor helper, for example:
public static HtmlString MyDisplayFieldFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, object additionalViewData = null)
{
//...
}
How do I determine the type of the field being passed into it such that different types can have custom display logic?
For example, I can all this method with three types:
#Html.MyDisplayFieldFor(e=>e.Name) //string
#Html.MyDisplayFieldFor(e=>e.DepartmentSelectList) //SelectList
#Html.MyDisplayFieldFor(e=>e.CupsOfTeaPerDay) // int
What's the best way to access this type information inside the Helper method?
Get the value type from the expression
public static HtmlString MyDisplayFieldFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, object additionalViewData = null) {
var valueType = typeof(TValue);
//...other code
}
You don't need to check the type of the passed value. ASP.NET MVC lets you create display templates that are automatically selected based on the type of the field.
You could for example create this template for the SelectList in your example and place it in the Views/Shared/DisplayTemplates/SelectList.cshtml project folder.
#model SelectList
<!-- Replace this with the display code you prefer -->
<div class="select-list">#Model.ToString()</div>
This lets you keep the HTML code within the views.
I have created my own Html Helper which adds red asterisks to any required field.
It successfully works with both
#Html.myLabelFor(model => model.Description)
//and
#Html.myLabelFor(model => model.Description, new { /*stuff*/ })
However, some of the code lines are like following
#Html.myLabelFor(model => model.Description, "Deletion Reason", new { /*stuff*/ })
My method was not designed to handle 3 parameters, so I added a caller which would handle 3 parameters
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, string labelText, Object htmlAttributes)
{
return myLabelFor(html, expression, labelText, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
Below are other methods that are working properly (including internal, which contains all necessary code and whose structure I used as a reference)
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, IDictionary<String, Object> htmlAttributes)
{
return LabelHelper(html, ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression), null, htmlAttributes);
}
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression)
{
return LabelHelper(html, ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression), null);
}
public static MvcHtmlString myLabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> expression, Object htmlAttributes)
{
return myLabelFor(html, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
//USED ITS STRUCTURE AS A REFERENCE
internal static MvcHtmlString LabelHelper(HtmlHelper html, ModelMetadata metadata, String htmlFieldName,
String labelText = null, IDictionary<String, Object> htmlAttributes = null)
Logically, I was expecting that parameter labelText would take a value of "Deletion Reason" from the line of code above. However, instead it had thrown a StackOverflowException inside my 3-parameter method. Microsoft description was vague, additional explanation did not help, and additional solution was using
Expression<Func<TModel, string>> expression instead of my Expression<Func<TModel, TValue>> expression
I do not understand what I am doing wrong. At this point I can only think of "fiddle with parameters until it works", but I am hopeful there is more elegant solution to that problem.
PS: Please let me know if my code for internal helper will help to solve the problem.
You getting an exception on the first overload, because the method is recursively calling itself, and keeps doing so until the execution stack overflows. Rather than calling itself you need to change
return myLabelFor(html,
expression,
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
to
return LabelHelper(html,
ModelMetadata.FromLambdaExpression(expression, html.ViewData),
ExpressionHelper.GetExpressionText(expression),
labelText,
HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
From your comments, the reason your 4th overload which uses return myLabelFor(...) does not throw the exception is because it calls your 2nd overload which in turn calls return LabelHelper(...)
I recommend that you change the 4th overload to call LabelHelper() directly, and change all the public overloads to explicitly call LabelHelper(), passing all 4 parameters, which is the pattern used by the in-built `HtmlHelper extension methods (you can view the source code for LabelFor() here)
I have an extension generic method
public static void AddError<TModel>(
this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression,
string resourceKey,
string defaultValue)
{
// How can I get a reference to TModel object from expression here?
}
I need to get the reference to TModel object from expression.
This method called by the following code:
ModelState.AddError<AccountLogOnModel>(
x => x.Login, "resourceKey", "defaultValue")
You cannot get to the TModel object itself without passing it into the method. The expression you are passing in is only saying "take this property from a TModel". It isn't actually providing a TModel to operate on. So, I would refactor the code to something like this:
public static void AddError<TModel>(
this ModelStateDictionary modelState,
TModel item,
Expression<Func<TModel, object>> expression,
string resourceKey,
string defaultValue)
{
// TModel's instance is accessible through `item`.
}
Then your calling code would look something like this:
ModelState.AddError<AccountLogOnModel>(
currentAccountLogOnModel, x => x.Login, "resourceKey", "defaultValue")
I imagine you really want the text "Login" to use to add a new model error to the ModelStateDictionary.
public static void AddError<TModel>(this ModelStateDictionary modelState,
Expression<Func<TModel, object>> expression, string resourceKey, string defaultValue)
{
var propName = ExpressionHelper.GetExpressionText(expression);
modelState.AddModelError(propName, GetResource("resourceKey") ?? defaultValue);
}
Assume you have some resource factory/method that returns null if the resource isn't found, that's just for illustration.
For sake of simplicity, imagine the following code:
I want to create a Foo:
public class Foo
{
public string Bar { get; set; }
}
And pass it to a special Html Helper method:
Html.SomeFunction(f => f.Bar);
Which is defined as:
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
I want to get the value of Bar inside of this function, but have absolutely no idea how to get it.
Simply compile the expression and get the value.
Func<TModel, TValue> method = expression.Compile();
TValue value = method(html.ViewData.Model);
// might be a slightly different property, but you can get the ViewModel
// from the HtmlHelper object.
You will need to call Compile() on the expression to get the Func and then execute that.
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
TValue valueOfBar = expression.Compile()(html.Model); // Assumes Model is accessible from html.
// Do stuff
}
Side note: If there isn't any need for the dynamic expressions or expression analysis you might as well pass the Func directly in instead.
For those that are using expression without MVT Model, one would obtain name and value of property in a following way.
public static string Meth<T>(Expression<Func<T>> expression)
{
var name = ((MemberExpression)expression.Body).Member.Name;
var value = expression.Compile()();
return string.Format("{0} - {1}", name, value);
}
use:
Meth(() => YourObject.Property);
in Microsoft.AspNetCore.Mvc.Rendering there is helpfull valuefor method;
public static string ValueFor<TModel, TResult>(this IHtmlHelper htmlHelper, Expression<Func<TModel, TResult>> expression);
public string SomeFunction<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression){
var valueOfExpression = html.ValueFor(expression);
//do your stuff
}
Using Compile() will use the Roslyn compiler-framework and will emit MSIL-code that will be dynamically loaded into your application. This executable code takes up memory, and in contrast to "normal" memory it is not subject to garbage collection nor can you free it yourself. If you do this too frequently (like regularly during SQL generation) you will run out of memory eventually. I ran into this issue and open-sourced my solutions as an open-source library:
https://www.nuget.org/packages/MiaPlaza.ExpressionUtils