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);
}
}
Related
Is it possible to override ASP.NET MVC's EditorFor and DisplayFor.
I want to apply some logic as to whether or not to actually display the content or simply output "".
So I want to do something like this (pseudocode):
public static MyEditorFor(this html)
{
if(ICanRender)
{
call HtmlEditorFor(original);
}
else
{
return EmtpyString;
}
}
Is this possible?
It sound like you want an extension:
public static MvcHtmlString MyEditorFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression) {
if(ICanRender)
{
return System.Web.Mvc.Html.EditorExtensions.EditorFor(html, expression);
}
else
{
return MvcHtmlString.Create(string.Empty);
}
}
That codesnipped is untested but it should work.
You can create your own Html helpers as extensions methods for HtmlHelper or HtmlHelper<TModel>. Simply add extension method like below:
public static MvcHtmlString MyEditorFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
}
Of course, you can add additional parameters as needed. Inside this method access all methods of htmlHelper. Return ordinary EditorFor for example:
public static MvcHtmlString MyEditorFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
return htmlHelper.EditorFor(expression);
}
Or you can create your own element (or combination of them) as you need:
public static MvcHtmlString MyEditorFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
{
TagBuilder div = new TagBuilder("div");
div.AddCssClass("some-css");
MvcHtmlString editor = htmlHelper.TextBoxFor(expression);
div.InnerHtml = editor.ToHtmlString();
return new MvcHtmlString(div.ToString());
}
Read more about TagBuilder here.
You could put the custon models under the "views/shared/EditorTemplates" or "views/shared/DisplayTemplates", and put the files with the [Model name].cshtml.
Ex: DateTime.cshtml
That way all editors and displays with DateTime type defined in your models properties will follow the DateTime.cshtml logic.
The problem is you will have to deal with the HTML in the ViewData.TemplateInfo if you want to retrieve the fields you want.
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 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)
I am working with legacy models that wrap data and meta-data up in a single property. For the purpose of this question, suppose the interface is:
pubic interface ILegacyCheckbox
{
bool Value { get; set; }
bool Editable { get; set; }
}
I want to wrap the CheckBoxFor() extension method with my own,
public static MvcHtmlString LegacyCheckboxFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, ILegacyCheckbox>> expression)
{
// wrap html.CheckBoxFor() method here by extracting the Value
// property and check if Editable is false, in which case add
// an htmlAttribute of "disabled=true"
}
Is there a way to do something like this? Where would I start?
Any help would be appreciated,
Thanks,
Alex
You could try something like this:
public static MvcHtmlString LegacyCheckboxFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, ILegacyCheckbox>> expression)
{
var parameterName = ((MemberExpression)expression.Body).Member.Name;
var compiled = expression.Compile().Invoke(html.ViewData.Model);
if (editable)
return html.CheckBox(parameterName, compiled.Value);
else
return html.CheckBox(parameterName, compiled.Value, new {disabled = "disabled"});
}
You may also wish to cache the compiled expression.
My example uses Html.CheckBox(); I'm not sure how to go about utilising CheckBoxFor(). I haven't got time to investigate it either, but atleast this is somewhere to start.
public static MvcHtmlString LegacyCheckboxFor<TModel>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, ILegacyCheckbox>> expression)
{
MemberExpression memberExpression = expression.Body as MemberExpression;
string parameterName = memberExpression.Member.Name;
var checkbox = expression.Compile().Invoke(html.ViewData.Model);
return new MvcHtmlString(
string.Format(
"<input type=\"checkbox\" name=\"{0}\" id=\"{0}\" value=\"{1}\" {2} />",
parameterName,
checkbox.Value,
checkbox.Editable ? "disabled=true" : string.Empty));
}
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));
}