How to pass property value to custom HtmlHelper in MVC Razor? - asp.net-mvc

I created a custom HTMLHelper extension method for Razor
public static MvcHtmlString BootstrapButtonGroupForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
// render stuff
}
And use it like this:
#Html.BootstrapButtonGroupForEnum(model => model.PriceType)
It works fine for adding records, but how to I pass the value of the model's method to it? So I can make a value preselected. Do I need to pass the value via an additional parameter or is there a more elegant way of doing this. I had a look in Watch and did not see the value there, did I miss something?
Thanks!
EDIT: The full implementation of the html helper:
public static MvcHtmlString BootstrapButtonGroupForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
string[] listOfNames = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
if (listOfNames != null)
{
sb = sb.Append("<div class=\"btn-group btn-group\" role=\"group\" >");
foreach (var value in listOfNames)
{
sb.Append("<button type=\"button\" class=\"btn btn-primary\" onclick=\"");
// onclick event hadler
sb.AppendFormat("$('#{0}').val('{1}');", metaData.PropertyName, value); // get the value to the hidden
sb.Append("$(this).addClass('active').siblings().removeClass('active');"); // retain selection
sb.Append("\">");
sb.Append(value.ToString().UnPascalCase());
sb.Append("</button>");
}
sb = sb.Append("</div>");
sb = sb.AppendFormat("<input type=\"hidden\" id=\"{0}\" name=\"{0}\" />", metaData.PropertyName);
}
return MvcHtmlString.Create(sb.ToString());
}
I suppose I could try using #HiddenFor(...) instead of rendering it directly as html?

I needed to assign the value of the hidden to metaData.Model and take it from there.
sb = sb.AppendFormat("<input type=\"hidden\" id=\"{0}\" name=\"{0}\" value=\"" + metaData.Model + "\" />", metaData.PropertyName);
CORRECTION: After following Stephens advice and looking at MVC source code I concluded that the hidden should be rendered like this:
var hidden = InputExtensions.HiddenFor(htmlHelper, expression, null);
sb.Append(hidden );

Related

Customizing Html.LabelFor

I'd like to add some classes to my labels, so I added Label.cshtml Under Shared/EditorTemplates folder. It seems Razor is ignoring it. I've tried DisplayTemplatesfolder too, but it didn't work neither.
Can I have editor template for labels? If no, what's the best way to customize them?
Label.cshtml:
#model object
#Html.LabelFor(m => m, new { #class = "col-xs-4 control-label" })
UPDATE:
This is my code that I took from this post and with some changes from this useful link. It still doesn't work, I don't know what's going on. Can anyone help?
public static class Extensions
{
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)
{
return LabelFor(html, expression, null);
}
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes){
ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string FieldName = ExpressionHelper.GetExpressionText(expression);
string LabelText = metaData.DisplayName ?? metaData.PropertyName ?? FieldName.Split('.').Last();
if (string.IsNullOrEmpty(LabelText))
return MvcHtmlString.Empty;
TagBuilder tag = new TagBuilder("label");
tag.MergeAttributes(htmlAttributes);
tag.SetInnerText(LabelText);
tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(FieldName));
tag.Attributes.Add("class", "control-label col-xs-2");
return MvcHtmlString.Create(tag.ToString());
}
}
EditorTemplate's are based on the type of a property (not on the html element they generate). You would need to create your own html helper
public static MvcHtmlString BootstrapLabelFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
var attributes = new { #class = "col-xs-4 control-label" };
return helper.LabelFor(expression, attributes);
}
and use it in the view as
#Html.BootstrapLabelFor(m => m.yourProperty)
However this seems an inflexible way to use a helper just to save adding the html attributes in the standard LabelFor() helper. Perhaps it might be more useful to combine the associated textbox and validation message in the one helper as per this answer (Note this answer also shows how to add the namespace to the web.config file
EditorTemplate and DisplayTemplate are based on property type. You'd do this by creating your own HTML helper.
public static class LabelExtensions
{
public static string Label(this HtmlHelper helper, string target, string text)
{
return String.Format("<label for='{0}'>{1}</label>", target, text);
}
}

How can I convert anonymous object HTML attributes to a Dictionary<string, object>

I'm providing an extra overload to RadioButtonFor and want to add a Key Value pair to the HTML Attributes that are passed in.
As an example I am passing in something like:
new { id = "someID" }
When i the use the HtmlHelper.AnonymousObjectToHtmlAttributes method as seems to be the suggestions I'm finding), its resulting in a dictionary with 4 items with Keys of "Comparer", "Count", "Keys", "Values". I then try to use Reflection to iterate over the values in both "Keys" and "Values", but cannot get that to work either.
Essentially all I want to do is to be able to cast the htmlAttributes to an IDictionary , add a item and then pass it on to a regular RadioButtonFor method.
Edit:
Heres what Im actually trying to do. Provide an overload called isDisabled to be able to set the disabled state of the radio button as this cant be easily done directly using HTML attributes because disabled = false stillr esults in disabled being rendered to tag and disables the radio.
public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, bool isDisabled, object htmlAttributes)
{
var linkAttributes = System.Web.Mvc.HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
Dictionary<string, object> htmlAttributesDictionary = new Dictionary<string, object>();
foreach (var a in linkAttributes)
{
if (a.Key.ToLower() != "disabled")
{
htmlAttributesDictionary.Add(a.Key, a.Value);
}
}
if (isDisabled)
{
htmlAttributesDictionary.Add("disabled", "disabled");
}
return InputExtensions.RadioButtonFor<TModel, TProperty>(htmlHelper, expression, value, htmlAttributesDictionary);
}
Looks like you might be applying the AnonymousObjectToHtmlAttributes either twice or to the wrong item.
Without more of your code, it's hard to tell
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(new { id = "someID" });
attributes.Count = 1
attributes.Keys.First() = id
compared with
var attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(new { id = "someID" }));
attributes.Count = 3
attributes.Keys.Join = Count,Keys,Values
When writing your overload, make sure your parameter is: object htmlAttributes for the new { } part with an overload with the IDictionary, eg:
Public static MvcHtmlString MyRadioButtonFor(..., object htmlAttributes)
{
return MyRadioButtonFor(...., HtmlHelper.AnonymousObjectToHtmlAttrbites(htmlAttributes);
}
public static MvcHtmlString MyRadioButtonFor(..., IDictionary<string, object> htmlAttributes)
{
htmlAttributes.Add("item", item);
return RadioButtonFor(..., htmlAttributes);
}
(just to be clear, never use My... - it's just for illustration)
Its unclear why you would not just use and existing overload that accepts object htmlAttributes to add the disabled="disabled" attribute, however the following should work
public static MvcHtmlString RadioButtonFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object value, bool isDisabled, object htmlAttributes)
{
IDictionary<string, object> attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
if (isDisabled && !attributes.ContainsKey("disabled"))
{
attributes.Add("disabled", "disabled");
}
return InputExtensions.RadioButtonFor<TModel, TProperty>(htmlHelper, expression, value, attributes);
}

ASP MVC strongly typed html without razor

I would like to be able to do things like:
<input name=#Model.SomeProperty (as the actual property name and not its value) value=#Model.SomeProperty type="text/>
so that model binding still works, but I don't need to use the Razor helpers.
You can add a Helper Method that return your DisplayName:
public static MvcHtmlString GetPropertyName<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression )
{
var metaData = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
string value = metaData.PropertyName ?? expressionHelper.GetExpressionText(expression);
return MvcHtmlString.Create(value);
}
And then you just use: #Html.GetPropertyName(m => m.SomeProperty)

ASP.Net MVC Html.Label with Attribute List?

How do I create an ASP.Net MVC Helper for an Html.Label which takes in attributes?
Currently when I define an Html.TextBox I am able to pass in a list of attributes. Sort of like below:
new {disabled="disabled", #class="pcTextBoxWithoutPaddingDisabled"})%>
However, it does not look as though the Html.Label has this feature. As a result, I have to define my labels using the label tag. Sort of like below:
<label class="pcLabelBlackWithoutPadding">
I would like to be consistent I how my Html element get created.
So, how do I create an Html.Label that will take in a list of attributes?
Thanks for your help.
This is updated version for MVC3:
public static MvcHtmlString Label(this HtmlHelper helper, String htmlFieldName, String labelText, Object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromStringExpression(htmlFieldName, helper.ViewData);
String innerText = labelText ?? (metadata.DisplayName ?? (metadata.PropertyName ?? htmlFieldName.Split('.').Last()));
if (String.IsNullOrEmpty(innerText))
{
return MvcHtmlString.Empty;
}
TagBuilder tagBuilder = new TagBuilder("label");
tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
tagBuilder.SetInnerText(innerText);
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
I have modified Alexandr code a bit with lambda expression, in case anyone needed the lambda expression.
usage:
#Html.LabelFor(model => model.Property , new { #class = "bigFont" })
code:
public static MvcHtmlString LabelFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression, Object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var innerText = metadata.DisplayName ?? metadata.PropertyName;
if (String.IsNullOrEmpty(innerText))
{
return MvcHtmlString.Empty;
}
var tagBuilder = new TagBuilder("label");
tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(metadata.PropertyName)));
tagBuilder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
tagBuilder.SetInnerText(innerText);
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
I'd suggest creating your own HtmlHelper extension method and using a TagBuilder to create the label.
public static HtmlHelperExtensions
{
public static Label( this HtmlHelper helper, string labelText, object properties )
{
var builder = new TagBuilder("label");
builder.MergeAttributes( new RouteValueDictionary( properties ) );
builder.SetInnerText( labelText );
return builder.ToString( TagRenderMode.Normal );
}
}
See the MVC source code for ideas on how to create a strongly-typed label helper. Note that you'll need to add the namespace containing your extensions either to the page or the web.config to be able to use it.

Asp.net Mvc2 LabelFor and DisplayName from prop

Context =>
${Html.CheckBoxFor(x => x.Foos[i].Checked)}
${Html.LabelFor(x => x.Foos[i].Checked)}
Problem is - i can't provide label text based on Foo.Name.
Is there out of the box method of how to modify metadata, passing in lambda x=>x.Name to change it?
Is creating another HmtlHelper extension method
LabelFor(this htmlhelper, [lambda], value, htmlattribs)
the only way?
Related issue
Eh, whatever. Workaround is acceptable. This issue ain't showstopper =>
public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression,
string labelText, object htmlAttributes) where TModel : class
{
TagBuilder builder = new TagBuilder("label");
builder.MergeAttributes(new RouteValueDictionary(htmlAttributes)); // to convert an object into an IDictionary
string value = ExpressionHelper.GetExpressionText(expression); ;
builder.InnerHtml = labelText;
//the replaces shouldnt be necessary in the next statement, but there is a bug in the MVC framework that makes them necessary.
builder.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(value).Replace('[', '_').Replace(']', '_'));
return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
}

Resources