Adding HtmlHelper to translate field name into column header using resource file - c#

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.

Related

.NET MVC HtmlHelper extension problems

.NET MVC HtmlHelper extension
I have this custom HtmlHelper extension, it gets a lambda expression that must be a child collection of the model, (for example model.childs), and a string that contains the name of a property of the collection items (for example "child_name")
And it creates a <span> whose content is the concatenation of the value of that property on every item of the collection. (for example "-- Mary -- Paul -- Rick")
public static MvcHtmlString SpanCollectionFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string nomPropToUse, IDictionary<string, object> htmlAttributes)
{
var collection = expression.Compile()(html.ViewData.Model).ToList();
string text = "";
if (collection != null) {
foreach (var ent in collection) {
var val = ent.GetType().GetProperty(nomPropToUse).GetValue(ent, null).ToString();
text += " -- " + val;
}
}
//creates span using tagbuilder
var span = new TagBuilder("span");
span.SetInnerText(text);
span.MergeAttributes(new RouteValueDictionary(htmlAttributes));
return MvcHtmlString.Create(span.ToString());
}
That I can use in a Razor view like:
Html.SpanCollectionFor(modelItem => item.childs, "child_name")
So this creates a <span> whose content is the concatenation of the name of every child in the collection.
This is working, but I have 2 problems:
First problem: This works for simple cases, where the nomPropToUse is a property of the collection items. but wont work if I try to pass a property of a granchild. so for example, imagine that every child contains a child Type, with a property type_name.
I would use it with:
Html.SpanCollectionFor(modelItem => item.childs, "type.type_name")
but this does not work.
Second problem: I would like to have also an overload that receives the second parameter as a lambda expression too, instead of a string.
something like
public static MvcHtmlString SpanCollectionFor<TModel, TValue, KValue>(this HtmlHelper<TModel> html,
Expression<Func<TModel, IEnumerable<TValue>>> expression,
Expression<Func<TModel, KValue>> expression2,
IDictionary<string, object> htmlAttributes)
{
Any help?

Multiple Lambda Expressions as MVC Html Helper Parameters?

I am trying to create a Html Helper which takes two model properties. In my example below my model has two fields Height and HeightUnit. The code in the helper will render a Bootstrap textbox with a drop down list of units in an input group. The first model property is bound to the textbox and the second is bound to the drop down list. The code does not error on compile but when it gets the display name of the 2nd expression it fails with the following error:
"Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."
Here is the Html Helper declaration:
public static MvcHtmlString MaterialTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> textBoxExpression, Expression<Func<TModel, TValue>> dropDownListExpression, object htmlAttributes = null)
{
string Id = htmlHelper.IdFor(textBoxExpression).ToString();
string DisplayName = htmlHelper.DisplayNameFor(textBoxExpression).ToString();
// this is coming out as blank
string DDId = htmlHelper.IdFor(dropDownListExpression).ToString();
// this is causing the error message displayed
string DDDisplayName = htmlHelper.DisplayNameFor(dropDownListExpression).ToString();
}
Here is the razor code I am trying to use to call the helper:
#Html.MaterialTextBoxFor(m => m.Height, m => m.HeightUnit)
Does anyone know how to make this work?
I eventually did find a solution. The key was to not use a second expression for the data source. Instead to create the source as an item in the model but pass it directly to the helper.
public static MvcHtmlString CustomDropDownListFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, List<SelectListItem> DataSource, object htmlAttributes = null)
Inside the model you need to have two fields, one to store the selected value and one for holding the source data:
public string MyField{ get; set; }
public List<SelectListItem> MyFieldSource { get; set; }
You then call the helper as follows:
#Html.CustomDropDownListFor(m => m.MyField, Model.MyFieldSource)
I populate the "source" fields in the model constructor.

How do I tell the type of a field passed into a custom Html.DisplayFor helper?

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.

Update Label Text in ASP.NET MVC Html Helper

I have an ASP.NET MVC app. I am rendering the view with the help of Html Helpers. For example, my .cshtml file looks like the following:
<div>
#Html.Label(model => model.Price)
#Html.TextPrice(model => model.Price)
</div>
The helpers are defined like the following in Extensions.cs:
public static MvcHtmlString Label<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string classes = "control-label")
{
var attr = new Dictionary<string, object>();
attr .Add("class", classes);
return System.Web.Mvc.Html.LabelExtensions.LabelFor(html, expression, attr);
}
public static MvcHtmlString TextPrice<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, byte decimalPlaces = 2, string classes = "form-control")
{
return Text(html, expression, classes + " decimal-" + decimalPlaces.ToString());
}
Eventually, I want to translate the labels into other languages. For now, I need to do an intermediary translation. My question is, when I print a label, how do I grab the text, alter it, then use the new text for the label? I do NOT want to add a bunch of Display attributes on my model at this time. I just need to do a quick-and-dirty search and replace in my Label extension method. However, I'm just not sure how to grab the text and update it.
Thank you!
You can get the property name from the expression yourself and then pass the translation, as an argument for the labelText parameter, to LabelExtensions.LabelFor():
public static MvcHtmlString Label<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string classes = "control-label")
{
var attr = new Dictionary<string, object>();
attr .Add("class", classes);
string propertyName = ((MemberExpression)expression.Body).Member.Name;
string labelText = translate(propertyName);
return System.Web.Mvc.Html.LabelExtensions.LabelFor(html, expression, labelText, attr);
}
You may use the DisplayName attribute at the Model. E.g.:
[Display(Name="Character_FirstName", ResourceType=typeof(ClassLib1.Resources))]
public string FirstName {get; set;}
See for example: http://haacked.com/archive/2011/07/14/model-metadata-and-validation-localization-using-conventions.aspx/ or https://stackoverflow.com/a/3877154/2298807 (related to Localization of DisplayNameAttribute)

How do I get the value from an anonymous expression?

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

Categories