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
Related
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 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);
}
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
I use an anonymous object to pass my Html Attributes to some helper methods.
If the consumer didn't add an ID attribute, I want to add it in my helper method.
How can I add an attribute to this anonymous object?
The following extension class would get you what you need.
public static class ObjectExtensions
{
public static IDictionary<string, object> AddProperty(this object obj, string name, object value)
{
var dictionary = obj.ToDictionary();
dictionary.Add(name, value);
return dictionary;
}
// helper
public static IDictionary<string, object> ToDictionary(this object obj)
{
IDictionary<string, object> result = new Dictionary<string, object>();
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(obj);
foreach (PropertyDescriptor property in properties){
result.Add(property.Name, property.GetValue(obj));
}
return result;
}
}
I assume you mean anonymous types here, e.g. new { Name1=value1, Name2=value2} etc. If so, you're out of luck - anonymous types are normal types in that they're fixed, compiled code. They just happen to be autogenerated.
What you could do is write new { old.Name1, old.Name2, ID=myId } but I don't know if that's really what you want. Some more details on the situation (including code samples) would be ideal.
Alternatively, you could create a container object which always had an ID and whatever other object contained the rest of the properties.
If you're trying to extend this method:
public static MvcHtmlString ActionLink(this HtmlHelper htmlHelper, string linkText, string actionName, object routeValues);
Although I'm sure Khaja's Object extensions would work, you might get better performance by creating a RouteValueDictionary and passing in the routeValues object, add your additional parameters from the Context, then return using the ActionLink overload that takes a RouteValueDictionary instead of an object:
This should do the trick:
public static MvcHtmlString MyLink(this HtmlHelper helper, string linkText, string actionName, object routeValues)
{
RouteValueDictionary routeValueDictionary = new RouteValueDictionary(routeValues);
// Add more parameters
foreach (string parameter in helper.ViewContext.RequestContext.HttpContext.Request.QueryString.AllKeys)
{
routeValueDictionary.Add(parameter, helper.ViewContext.RequestContext.HttpContext.Request.QueryString[parameter]);
}
return helper.ActionLink(linkText, actionName, routeValueDictionary);
}
public static string TextBox(this HtmlHelper html, string value, string labelText, string textBoxId, object textBoxHtmlAttributes, object labelHtmlAttributes){}
This would accept the id value the textbox should have and the label should refer to.
If the consumer now doesn't include the "id" property in the textBoxHtmlAttributes, the method will create an incorrect label.
I can check through reflection if this attribute is added in the labelHtmlAttributes object. If so, I want to add it or create a new anonymous object that has it added.
But because I can't create a new anonymous type by walking through the old attributes and adding my own "id" attribute, I'm kind of stuck.
A container with a strongly typed ID property and then an anonymous typed "attributes" property would require code rewrites that don't weigh up to the "add an id field" requirement.
Hope this response is understandable. It's the end of the day, can't get my brains in line anymore..