How to write a custom #Html.ControlFor(o => o.Property)? - c#

I want to create a custom method, to be able to call it as
#Html.PaginationFor(o => o.List)
I started looking at reflector, but I don't know exactly what it is doing over there. I tried:
public static MvcHtmlString PaginationFor<TModel, TProperty>(this Html<TModel> html,
Expression<Func<TModel, TProperty>> expression)
{
var propertyValue = ????????
return html.Partial("View", propertyValue);
}
How do I extract the property value from the expression to pass as a model of the partial view?

public static MvcHtmlString PaginationFor<TModel, TProperty>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression
)
{
TModel model = html.ViewData.Model;
var metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var propertyValue = metaData.Model; // This will be of type TProperty
return html.Partial("View", propertyValue);
}

public static MvcHtmlString PaginationFor<TModel, TProperty>(this HtmlHelper<TModel> html,
Expression<Func<TModel, TProperty>> expression) {
var func = expression.Compile();
var propertyValue = func(html.ViewData.Model);
return html.Partial("View", propertyValue);
}

Related

Passing data to EditorFor via ViewDataDictionary not working

I'm trying to pass data via a ViewDataDictionary to a template-file in an extension-method for HtmlHelper<TModel>
var vdd = new ViewDataDictionary(helper.ViewData);
vdd["someValue"] = true;
return helper.EditorFor(expression, "_MyTemplate", vdd);
In my _MyTemplate.cshtml I try to access it
#{
var myViewDataValue = ViewData["someValue"];
}
which is always null as my "someValue" is located under ViewData.Values where I cannot access it via it's name.
What am I missing here?
Using it directly in my view
ViewData["someValue"] = true;
#Html.EditorFor(m => m.Start, "_MyTemplate")
is working. The same inside the extension-method fails as of in the _MyTemplate the ViewData has no "someValue" at all.
public static MvcHtmlString MyExtension<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
helper.ViewData["someValue"] = true;
return helper.EditorFor(expression, "_MyTemplate", helper.ViewData);
}
throws an exception:
An item with the same key has already been added.
Changing
public static MvcHtmlString MyExtension<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
helper.ViewData["someValue"] = true;
return helper.EditorFor(expression, "_MyTemplate");
}
to
public static MvcHtmlString MyExtension<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
helper.ViewContext.ViewData["someValue"] = true;
return helper.EditorFor(expression, "_MyTemplate");
}
I thought HtmlHelper.ViewData and HtmlHelper.ViewContext.ViewData are the same if not overridden somehow.
Can anyone explain this behavior?

Extend Kendo HtmlHelpers for TextBoxFor

I'm looking how to extend Kendo HtmlHelpers to do things like
#Html.Kendo().TextBoxFor(model => model.field)
This is my suggestion
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using Kendo.Mvc.UI.Fluent;
namespace Kendo.Mvc.UI
{
public static class KendoExtensions
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
return htmlHelper.TextBoxFor(expression, format: null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format)
{
return htmlHelper.TextBoxFor(expression, format, null);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
return htmlHelper.TextBoxFor(expression, null, htmlAttributes);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, object htmlAttributes)
{
return htmlHelper.TextBoxFor(expression, format, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
return htmlHelper.TextBoxFor(expression, null, htmlAttributes);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an appropriate nesting of generic types")]
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this WidgetFactory<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, string format, IDictionary<string, object> htmlAttributes)
{
var lKWidget = new TagBuilder("span");
lKWidget.AddCssClass("k-widget k-numerictextbox");
var lKExpanding = new TagBuilder("span");
lKExpanding.AddCssClass("k-numeric-wrap k-expand-padding k-state-disabled");
if (htmlAttributes == null) htmlAttributes = new Dictionary<string, object>();
if (htmlAttributes.ContainsKey("class"))
{
htmlAttributes["class"] += "k-formatted-value k-input";
} else
{
htmlAttributes.Add("class", "k-formatted-value k-input");
}
var lTextBoxFor = htmlHelper.HtmlHelper.TextBoxFor(expression, format, htmlAttributes).ToHtmlString();
lKExpanding.InnerHtml += lTextBoxFor;
lKWidget.InnerHtml += lKExpanding;
lKWidget.InnerHtml += htmlHelper.HtmlHelper.ValidationMessageFor(expression);
return MvcHtmlString.Create(lKWidget.ToString(TagRenderMode.Normal));
}
}
}
New version of Kendo UI (2014.2.716) has this extension, and much more...
I use this with lable
#Html.EnhancedEditorField(model => model.Title)
in class InputExtensions
public static class InputExtensions
{
public const string noteTemplate = #"<span class=""inputNote"">{0}</span>";
public const string fieldClosing = #"</tr>";
public const string fieldOpenning = #"<tr>";
public const string fieldContent =
#"<td class=""labelWrp""> {0} </td> <td class=""inputWrp"" colspan=""{4}""> {1} <br /> {2} {3} </td>";
public static MvcHtmlString EnhancedEditorField<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression,
object htmlAttributes = null, string note = null)
{
return EnhancedField(htmlHelper, expression, htmlHelper.EditorFor(expression, htmlAttributes), note);
}
private static MvcHtmlString EnhancedField<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression, MvcHtmlString inputString,
string note = null, FieldGroupingSetting fieldGroupingSetting = FieldGroupingSetting.CloseBoth, bool checkIsRequired = true)
{
MvcHtmlString labelString = htmlHelper.EnhancedLabelFor(expression, checkIsRequired: checkIsRequired);
MvcHtmlString validationMessageString = htmlHelper.ValidationMessageFor(expression);
return CreateField(inputString.ToString(), note, labelString.ToString(), validationMessageString.ToString(),
fieldGroupingSetting);
}
private static MvcHtmlString CreateField(string inputString, string note, string labelString,
string validationMessageString,
FieldGroupingSetting fieldGroupingSetting = FieldGroupingSetting.CloseBoth, int valueCellCount = 1)
{
string noteString = string.Empty;
if (!string.IsNullOrWhiteSpace(note))
noteString = string.Format(noteTemplate, note);
string fieldTemplate =
(fieldGroupingSetting.HasFlag(FieldGroupingSetting.CloseStart) ? fieldOpenning : string.Empty) +
fieldContent +
(fieldGroupingSetting.HasFlag(FieldGroupingSetting.CloseEnd) ? fieldClosing : string.Empty);
string htmlString = string.Format(fieldTemplate, labelString, inputString, validationMessageString,
noteString, valueCellCount);
return MvcHtmlString.Create(htmlString);
}
}

Converting from Expression<Func<TModel, TProperty>> to Expression<Func<TModel, bool>>

The following code is generating an error:
public static MvcHtmlString EditControlFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
ModelMetadata metadata = GetModelMetaData(htmlHelper, expression);
Type propertyType = GetPropertyType(metadata);
if (propertyType.IsEnum)
{
return DropDownListForEnum(htmlHelper, expression);
}
else if (propertyType == typeof(bool))
{
return htmlHelper.CheckBoxFor(expression);
}
else
{
return htmlHelper.TextBoxFor(expression);
}
}
The compile time error is below:
Error: cannot convert from
'System.Linq.Expressions.Expression<System.Func<TModel,TProperty>>'
to 'System.Linq.Expressions.Expression<System.Func<TModel,bool>>'.
It looks like CheckBoxFor() is expecting a parameter of type :
Expression<Func<TModel, bool>>
Because of the propertyType == typeof(bool) test, it should already be the right thing - you just need to convince the compiler that your intentions are honest. In this case, via:
var typedExpression = (System.Linq.Expressions.Expression<System.Func<TModel,bool>>)(object)expression;
return htmlHelper.CheckBoxFor(typedExpression);

Custom HtmlHelper method that generates an <input type="range"> HtmlString

As far as I know there are no (extension) methods in the HtmlHelper class that can generate a HTML5 input element of type range so I am trying to implement my own by extending the HtmlHelper class:
public static class MvcHtmlHelper
{
public static HtmlString RangeFor<TModel, TProperty>
(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes)
{
var name = ExpressionHelper.GetExpressionText(expression);
var metadata = ModelMetadata.FromLambdaExpression(expression,
htmlHelper.ViewData);
//var min = (string)((ViewDataDictionary<TModel>)htmlAttributes)["min"];
//var max = (string)((ViewDataDictionary<TModel>)htmlAttributes)["max"];
//var value = (string)((ViewDataDictionary<TModel>)htmlAttributes)["value"];
return Range(htmlHelper, min, max, value);
}
public static HtmlString Range(this HtmlHelper htmlHelper,
string name, string min, string max, string value = "0")
{
var builder = new TagBuilder("input");
builder.Attributes["type"] = "range";
builder.Attributes["name"] = name;
builder.Attributes["min"] = min;
builder.Attributes["max"] = max;
builder.Attributes["value"] = value;
return new HtmlString(builder.ToString(TagRenderMode.SelfClosing));
}
}
I am trying to emulate the existing TextBoxFor extension method which allows callers to specify extra htmlAttributes via an anonymous object. However I am getting an InvalidCastException on the commented lines above.
Can anyone point me to the correct way of obtaining the values from the anonymous htmlAttributes object (similar to how TextBoxFor handles htmlAttributes?
InputExtensions.TextBoxFor handles attributes the following way:
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
return InputExtensions.TextBoxFor<TModel, TProperty>(htmlHelper, expression, (IDictionary<string, object>) HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
So you have to create attributes dictionary from anomymous object before you use it

How can I change my extension method in C#

I started to use this extension that I found on the web:
public static class NewLabelExtensions
{
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
return LabelFor(html, expression, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(labelText))
{
return MvcHtmlString.Empty;
}
var tag = new TagBuilder("label");
tag.MergeAttributes(htmlAttributes);
tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
tag.SetInnerText(labelText);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
}
I use it like this:
#Html.LabelFor(m => m.Login.RememberMe, new { #class = "adm" })
The result is like this:
<label class="adm" for="Login_RememberMe">Remember me?</label>
However I would like to style this label. I don't really understand the code that I am using. Can anyone suggest a change to the code above that would make the LabelFor method generate?
<label class="adm" id="Login_RememberMe" for="Login_RememberMe">Remember me?</label>
Thanks
you just need to add the below line :
tag.Attributes.Add("id", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
code:
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (String.IsNullOrEmpty(labelText))
{
return MvcHtmlString.Empty;
}
var tag = new TagBuilder("label");
tag.MergeAttributes(htmlAttributes);
tag.Attributes.Add("id", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
tag.SetInnerText(labelText);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}

Categories

Resources