Stack Overflow Exception in MVcHtmlString - c#

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)

Related

How to change method signature/properties to match given execution example?

So I have this:
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Expression<Func<TModel, ControlPermissionType>> mode)
{
MvcHtmlString value = null;
var modeIn = ModelMetadata.FromLambdaExpression(
mode, htmlHelper.ViewData
).Model;
switch ((ControlPermissionType)modeIn)
{
case ControlPermissionType.Read:
value = htmlHelper.TextBoxFor(expression, new { #readonly = "readonly" });
break;
case ControlPermissionType.Edit:
value = htmlHelper.TextBoxFor(expression);
break;
case ControlPermissionType.Deny:
value = new MvcHtmlString(string.Empty);
break;
}
return value;
}
and this is how I am calling it:
#Html.TextBoxFor(a => a.First().BirthDate, a => a.First().Mode)
but what I want is:
#Html.TextBoxFor(a => a.First().BirthDate, a.Mode)
how to do that?
EDIT:
or even
#Html.TextBoxFor(a => a.First().BirthDate) but in this way how to check if the a is implementing interface?
EDIT2:
#Html.TextBoxFor(a => a.First().BirthDate, a => a.First().Mode)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, Expression<Func<TModel, ControlPermissionType>> mode)
#Html.TextBoxFor(a => a.First().BirthDate, a.Mode)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, ControlPermissionType mode)
#Html.TextBoxFor(a => a.First().BirthDate)
Need method signature :
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
But in this last case, you miss the ControlPermissionType.
By the way, if you try to call your method with #Html.TextBoxFor(a => a.First().BirthDate), the compiler error should be self explanatory on which signature method is needed.

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)

.Net MVC 3 Razor example of using DdUovFor workaround to fix DropDownListFor validation?

I came across the following code in this post which is supposed to fix validation for DropDownListFor. However, I am unsure what value to pass from the view for the parameter:
this HtmlHelper<TModel> htmlHelper
What should be passed for this value? Could you provide an example of using this in the view? This is related to this question where no example is provided.
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Users cannot use anonymous methods with the LambdaExpression type")]
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString DdUovFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, string optionLabel, IDictionary<string, object> htmlAttributes)
{
if (expression == null)
{
throw new ArgumentNullException("expression");
}
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
IDictionary<string, object> validationAttributes = htmlHelper
.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(expression), metadata);
if (htmlAttributes == null)
htmlAttributes = validationAttributes;
else
htmlAttributes = htmlAttributes.Concat(validationAttributes).ToDictionary(k => k.Key, v => v.Value);
return SelectExtensions.DropDownListFor(htmlHelper, expression, selectList, optionLabel, htmlAttributes);
}
You don't need to pass anything, simply call #Html.DdUovFor ignoring that parameter.
See extension methods:
http://msdn.microsoft.com/en-us/library/bb383977.aspx
The htmlHelper is the instance of Html in #Html.. automatically that it refers to in the View.

How to extend the HtmlHelper with the IDictionary<string, object> overload?

I created an extension method for the HtmlHelper which works very well. Now I need to create the overload that receives an IDictionary so I can add a css class to it so I tried the following:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return EnumDropDownListFor(htmlHelper, expression, null);
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes)
{
var items = DoSomething();
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
When I tried to use it in my view I still got the following exception:
Compilation Error
Description: An error occurred during the compilation of a resource
required to service this request. Please review the following specific
error details and modify your source code appropriately.
Compiler Error Message: CS1928:
'System.Web.Mvc.HtmlHelper' does not
contain a definition for 'EnumDropDownListFor' and the best extension
method overload
'LIMM.Web.HtmlHelpers.HtmlDropDownExtensions.EnumDropDownListFor(System.Web.Mvc.HtmlHelper,
System.Linq.Expressions.Expression>,
System.Collections.Generic.IDictionary)' has some
invalid arguments
Obviously I'm not extending the method correctly but so google hasn't been my friend in finding a way to accomplish this. A little help will be appreciated.
Thanks.
UPDATE: As I type the code in the view, intellisense does give me both overloads. The error happens when I run the application.
Maybe you are trying use your helper using the commonest construction (pass html attributes as an anonymous object), so very probably you need an overload like this:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
return EnumDropDownListFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, IDictionary<string, object> htmlAttributes)
{
var items = DoSomething();
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}

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