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.
Related
I have a custom html Helper class like this below,
public static class LabelWithAstrickHelper
{
public static MvcHtmlString LabelWithAstrick<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string resolvedLabelText = metadata.DisplayName ?? metadata.PropertyName;
if (metadata.IsRequired)
{
resolvedLabelText += "*";
// resolvedLabelText += "<span style='color:red;'>*</span>";
}
return LabelExtensions.LabelFor<TModel, TValue>(html, expression, resolvedLabelText, htmlAttributes);
}
}
I used it like below in my view.
#Html.LabelWithAstrick(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
Now this will Show 'astrick' in black color, but I want to display this 'astrick' sign in red color.
please suggest How to do this.
Since you want to style the label text and the associated asterisk differently, then you cannot use the inbuilt LabelFor() method (the HtmlHelper methods encode the vale of the label text.
Instead you need to generate you own html in the method.
public static MvcHtmlString LabelWithAstrick<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var name = ExpressionHelper.GetExpressionText(expression);
string text = metadata.DisplayName ?? metadata.PropertyName ?? name.Split('.').Last();
// Build 2 span elements containing text and the styled asterisk
StringBuilder innerHtml = new StringBuilder();
TagBuilder span = new TagBuilder("span");
span.InnerHtml = text;
innerHtml.Append(span.ToString());
if (metadata.IsRequired)
{
span = new TagBuilder("span");
span.InnerHtml = "*";
span.MergeAttribute("style", "color:red;"); // see notes
innerHtml.Append(span.ToString());
}
// Create the label element and add the 2 span elements
TagBuilder label = new TagBuilder("label");
label.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name)));
RouteValueDictionary attributes = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
label.MergeAttributes(attributes, replaceExisting: true);
label.InnerHtml = innerHtml.ToString();
return new MvcHtmlString(label.ToString());
}
Note that I hard-coded an inline style for the asterisk as per your code, however I would recommend you use a class name instead using span.AddCssClass("...."); and create a css definition for that class name to give you more flexibility.
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 );
I would like to create a custom htmlhelper(Extension Method) for dropdownlist to accept custom attributes in the Option tag of the selectlistitem.
I have a property in my model class, that I would like to include as an attribute in the option tag of the selectlist.
i.e <option value ="" modelproperty =""></option>
I have come across various examples but non quite specific to what I would want.
Try this:
public static MvcHtmlString CustomDropdown<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
string classPropName)
{
var model = htmlHelper.ViewData.Model;
var metaData = ModelMetadata
.FromLambdaExpression(expression, htmlHelper.ViewData);
var tb = new TagBuilder("select");
if (listOfValues != null)
{
tb.MergeAttribute("id", metaData.PropertyName);
var prop = model
.GetType()
.GetProperties()
.FirstOrDefault(x => x.Name == classPropName);
foreach (var item in listOfValues)
{
var option = new TagBuilder("option");
option.MergeAttribute("value", item.Value);
option.InnerHtml = item.Text;
if (prop != null)
{
// if the prop's value cannot be converted to string
// then this will throw a run-time exception
// so you better handle this, put inside a try-catch
option.MergeAttribute(classPropName,
(string)prop.GetValue(model));
}
tb.InnerHtml += option.ToString();
}
}
return MvcHtmlString.Create(tb.ToString());
}
Yeah you can create it by your own.
Create one Extension method which will accept a list of Object which contains all required properties of it. Use TagBuilder to create Tags and use MergeAttribute method of it to add your own attribute to it.
Cheers
I was thinking it would be very useful to have an extended version of
the EditorFor HTML helper which automatically writes out value data bindings for Knockout JS.
This would be for where the client side view model and the server side view model are the same - I've already autogenerated the client side view model by using ko mapping and getting the viewmodel via AJAX.
Has anyone else attempted something like this, or are there any projects which include something similar to what I'm thinking here?
The advantage of this would be that when refactoring there would be no danger of the data bound values being missed.
We have done something along these lines, its far from perfect, and we have much more in our custom extensions, but I extracted the essence.
using System.Web.Mvc.Html;
namespace System.Web.Mvc
{
public static class EditorForExtensions
{
public static MvcHtmlString TextBoxForViewModel<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var htmlAttributes = HtmlAttributesForKnockout(metadata);
return htmlHelper.TextBoxFor(expression, htmlAttributes);
}
private static Dictionary<string, object> HtmlAttributesForKnockout(ModelMetadata metadata)
{
var htmlAttributes = new Dictionary<string, object>();
var knockoutParameter = String.Format("value: {0}", metadata.PropertyName);
htmlAttributes.Add("data-bind", knockoutParameter);
return htmlAttributes;
}
}
}
This can then be used like:
#Html.TextBoxForViewModel(m => m.Name)
Wanted to add the htmlAttributes overload to Chad's answer above for anyone looking to drop it in and have it work. All the other helpers can be built from these examples pretty easily. (Thanks Chad, your extension helped ease my transition into using knockout!)
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Web.Mvc.Html;
namespace System.Web.Mvc {
public static class KnockoutExtensions {
public static MvcHtmlString KnockoutTextBoxFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression ) {
var metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData );
var htmlAttributes = HtmlAttributesForKnockout( metadata );
return htmlHelper.TextBoxFor( expression, htmlAttributes );
}
public static MvcHtmlString KnockoutTextBoxFor<TModel, TProperty>( this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object attributes ) {
// convert passed anonymous object (attributes) into IDictionary<string,object> to pass into attribute parser
var attrs = HtmlHelper.AnonymousObjectToHtmlAttributes( attributes ) as IDictionary<string, object>;
var metadata = ModelMetadata.FromLambdaExpression( expression, htmlHelper.ViewData );
var htmlAttributes = HtmlAttributesForKnockout( metadata, attrs );
return htmlHelper.TextBoxFor( expression, htmlAttributes );
}
private static Dictionary<string, object> HtmlAttributesForKnockout( ModelMetadata metadata, IEnumerable<KeyValuePair<string, object>> attributes = null ) {
var htmlAttributes = new Dictionary<string, object>();
var knockoutParameter = String.Format( "value: {0}", metadata.PropertyName );
htmlAttributes.Add( "data-bind", knockoutParameter );
if ( attributes != null ) foreach ( var attr in attributes ) htmlAttributes.Add( attr.Key, attr.Value );
return htmlAttributes;
}
}
}
I need a form on my ASP.NET MVC Razor page. My preference would be to use the following syntax:
#using (Html.BeginForm())
{
}
However, I need several attributes added to the form. So I ended up with something like the following:
#using (Html.BeginForm(null, null, FormMethod.Post, new { name = "value" }))
{
}
However, this has an undesired side effect. If there are query arguments in this page's request, the first form passes them along when the form is submitted. However, the second version does not.
I really don't know why BeginForm() doesn't support attributes, but is there a straight-forward way to add attributes to BeginForm() and still pass along any query arguments when the for is submitted?
EDIT:
After looking into this, it would seem the best solution is something like this:
<form action="#Request.RawUrl" method="post" name="value">
</form>
However, when using this syntax, client-side validation is disabled. It seems there is no good solution to this situation without more complicated and potentially unreliable constructs.
That's indeed true, but I would go with a custom helper in order to preserve the form context inside which is used for client side validation:
public static class FormExtensions
{
private static object _lastFormNumKey = new object();
public static IDisposable BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
string rawUrl = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return htmlHelper.FormHelper(rawUrl, FormMethod.Post, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
private static int IncrementFormCount(IDictionary items)
{
object obj2 = items[_lastFormNumKey];
int num = (obj2 != null) ? (((int)obj2) + 1) : 0;
items[_lastFormNumKey] = num;
return num;
}
private static string DefaultFormIdGenerator(this HtmlHelper htmlhelper)
{
int num = IncrementFormCount(htmlhelper.ViewContext.HttpContext.Items);
return string.Format(CultureInfo.InvariantCulture, "form{0}", new object[] { num });
}
private static IDisposable FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
var builder = new TagBuilder("form");
builder.MergeAttributes<string, object>(htmlAttributes);
builder.MergeAttribute("action", formAction);
builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
bool flag = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
if (flag)
{
builder.GenerateId(htmlHelper.DefaultFormIdGenerator());
}
htmlHelper.ViewContext.Writer.Write(builder.ToString(TagRenderMode.StartTag));
var form = new MvcForm(htmlHelper.ViewContext);
if (flag)
{
htmlHelper.ViewContext.FormContext.FormId = builder.Attributes["id"];
}
return form;
}
}
which could be used like this:
#using (Html.BeginForm(htmlAttributes: new { name = "value" }))
{
...
}
I had a similar problem and here is quick solution (it works with MVC4).
Declare the extension method:
public static MvcForm BeginForm(this HtmlHelper helper, object htmlAttributes)
{
return helper.BeginForm(helper.ViewContext.RouteData.Values["Action"].ToString(),
helper.ViewContext.RouteData.Values["Controller"].ToString(),
FormMethod.Post, htmlAttributes);
}
and use it in your page:
#using (Html.BeginForm(htmlAttributes: new {#class="form-horizontal"}))
{
...
}
Small modification to source code:
http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/Html/FormExtensions.cs
public static MvcForm BeginForm(this HtmlHelper htmlHelper, object htmlAttributes)
{
// generates <form action="{current url}" method="post">...</form>
string formAction = htmlHelper.ViewContext.HttpContext.Request.RawUrl;
return FormHelper(htmlHelper, formAction, FormMethod.Post, new RouteValueDictionary(htmlAttributes));
}
private static MvcForm FormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
{
TagBuilder tagBuilder = new TagBuilder("form");
tagBuilder.MergeAttributes(htmlAttributes);
// action is implicitly generated, so htmlAttributes take precedence.
tagBuilder.MergeAttribute("action", formAction);
// method is an explicit parameter, so it takes precedence over the htmlAttributes.
tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
bool traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled
&& !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled;
if (traditionalJavascriptEnabled)
{
// forms must have an ID for client validation
tagBuilder.GenerateId(htmlHelper.ViewContext.FormIdGenerator());
}
htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag));
MvcForm theForm = new MvcForm(htmlHelper.ViewContext);
if (traditionalJavascriptEnabled)
{
htmlHelper.ViewContext.FormContext.FormId = tagBuilder.Attributes["id"];
}
return theForm;
}