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

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);
}

Related

Stack Overflow Exception in MVcHtmlString

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)

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.

.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 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

Declaring parameter that i don't want to pass

public Jquery Extra(this HtmlHelper htmlhelper,
string message,
IDictionary<string, object> htmlAttributes)
if i declare the this Htmlhelper htmlhelper when i declare my method, but i don't want to pass that parameter in when i call the method??
am i making sense
I believe you are trying to write an Extension Method. You define it like so
namespace ExtensionMethods
{
public static class MyExtensions
{
public static Jquery Extra(this HtmlHelper htmlhelper, string message, IDictionary htmlAttributes)
{
//do work
return Jquery;
}
}
}
And then use it like this:
HtmlHelper helper = new HtmlHelper();
Jquery jq = helper.Extra(message, htmlAttributes);
EDIT: It sounds like you want to be able to call this method without any HtmlHelper object at all.
If the method needs an HtmlHelper, you will not be able to call it without one.
You should rewrite the method so that it doesn't need an HtmlHelper.
You can make an overload with fewer parameters:
public static Jquery Extra(this HtmlHelper htmlhelper, string message) {
return htmlHelper.Extra(message, null);
}
In C# 4, you can also use an optional parameter:
public Jquery Extra(this HtmlHelper htmlhelper, string message, IDictionary<string, object> htmlAttributes = null) {
I highly recommend that you also add an overload that takes an anonymous type:
public static Jquery Extra(this HtmlHelper htmlhelper, string message, object htmlAttributes) {
return htmlHelper.Extra(message, null, new RouteValueDictionary(htmlAttributes));
}
Who is the author of this function? If it's you then do not include the first parameter.
public Jquery Extra(string message, IDictionary<string, object> htmlAttributes).
If it's code you have not written yourself, then the HtmlHelper variable is probably necessary, and you shoud not attempt to remove it from the function prototype.
One of your comments said that you cannot initialize a HtmlHelper, that is not technically true. See the [msdn reference].1

Categories