I want to do something like this:
[Display(Name = "Plain text. <span class=\"red strong\">Red text bolded.</span>")]
Is this possible (to style the text within the Display Attribute)? Currently it is just displaying the literal text.
Is this possible (to style the text within the Display Attribute)?
The problem doesn't lie within the [Display] attribute. It lies within the Html.LabelFor helper which you used to display. This attribute always HTML encodes the value. If you don't like this behavior you could write a custom helper that will not encode the value:
public static class HtmlExtensions
{
public static IHtmlString MyLabelFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metadata = ModelMetadata.FromLambdaExpression<TModel, TProperty>(expression, htmlHelper.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var labelText = (metadata.DisplayName ?? (metadata.PropertyName ?? htmlFieldName.Split(new char[] { '.' }).Last<string>()));
if (string.IsNullOrEmpty(labelText))
{
return MvcHtmlString.Empty;
}
var label = new TagBuilder("label");
label.Attributes.Add("for", TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
label.InnerHtml = labelText;
return new HtmlString(label.ToString());
}
}
and then:
#Html.MyLabelFor(x => x.Foo)
Related
I'm trying to build an HtmlHelper to add functionality to an MVC RadioButtonFor control. I simply want to add a class and style attribute to the radio button given certain conditions. I can't seem to get my extension to include attributes. I've followed several examples I've found online, but no luck.
Here is my code:
public static MvcHtmlString GCSRadioButtonForHelper<TModel>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool?>> expression,
Object htmlAttributes,
object value )
{
bool isRequired = false;
bool isHidden = false;
object styleList;
object classList;
Dictionary<string, object> attributes = new Dictionary<string, object>();
attributes.TryGetValue("class", out classList);
attributes.TryGetValue("style", out styleList);
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var categoryName = string.Empty;
if (metadata.ContainerType != null)
categoryName = metadata.ContainerType.Name + "." + metadata.PropertyName;
isHidden = GetIsHidden(metadata);
isRequired = GetIsRequired(metadata);
if (isHidden)
classList = classList + " GCSHidden ";
if (isRequired)
{
classList = classList + " GCSRequired ";
}
attributes.Remove("class");
attributes.Remove("style");
attributes.Add("class", classList);
attributes.Add("style", styleList);
//Render HTML String
MvcHtmlString html = System.Web.Mvc.Html.InputExtensions.RadioButtonFor(htmlHelper, expression, attributes, value);
return html;
}
The problem is that the result of InputExtensions.RadioButtonFor() never includes the attributes class or style as I have specified. Any ideas?
TIA
I have a enum that holds countries:
public enum CountryEnum
{
[Display(Name = "AF", ResourceType = typeof(Global))]
AF,
[Display(Name = "AL", ResourceType = typeof(Global))]
AL,
[Display(Name = "DZ", ResourceType = typeof(Global))]
DZ,
};
As you can see I make use of the DataAnnotations to localise the values.
Now I want to display a dropdownlist with all of the localised country names. I come up with this code:
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes =
(DisplayAttribute[])fi.GetCustomAttributes(
typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Name;
else
return value.ToString();
}
I have a Html helper that makes use of the above method:
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression, object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
Type enumType = GetNonNullableModelType(metadata);
IEnumerable<TEnum> values = Enum.GetValues(enumType).Cast<TEnum>();
IEnumerable<SelectListItem> items = from value in values
select new SelectListItem
{
Text = GetDisplayName(value),
Value = value.ToString(),
Selected = value.Equals(metadata.Model)
};
// If the enum is nullable, add an 'empty' item to the collection
if (metadata.IsNullableValueType)
items = SingleEmptyItem.Concat(items);
return htmlHelper.DropDownListFor(expression, items, htmlAttributes);
}
The DropDown renders correctly, however, GetDisplayName doesn't return the localised value, it just display the name attribute (e.g. AF for the first entry).
How can I modify GetDisplayName method to return the localised value?
You need to update your GetDisplayName method to use the GetName() method rather than the Name property of the DisplayAttribute.
Like this:
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes = (DisplayAttribute[])fi.
GetCustomAttributes(typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].GetName();
else
return value.ToString();
}
From the MSDN documentation for DisplayAttribute.Name:
Do not use this property to get the value of the Name property. Use
the GetName method instead.
The MSDN documentation for the GetName() method has this to say:
Returns the localized string for the Name property, if the
ResourceType property has been specified and the Name property
represents a resource key; otherwise, the non-localized value of the
Name property.
I was having a similar problem, having setup the Display Attribute as you did (easier since classes use the same attribute to load localizations), so took your initial code and tweaked it a bit, now it is displaying the localization string as expected. I don't think it is the most clever or optimized way of doing it, but it works. Hopefully that's what you intended.
public static string GetDisplayName<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DisplayAttribute[] attributes = (DisplayAttribute[])fi.
GetCustomAttributes(typeof(DisplayAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
{
string key = attributes[0].Name;
string localizedString = attributes[0].ResourceType.GetProperty(key).GetValue("").ToString();
return localizedString;
}
else
return value.ToString();
}
I'm trying to show a link as part of the validation message for a field.
I'm using data attributes with custom error messages to set it:
[Required(ErrorMessage = "Message <a href='#'>link</a>")]
public string Field{ get; set; }
But when it renders the tags are escaped and literally prints:
Message <a href='#'>link</a>
Is it possible to have the link as part of the validation message but render correctly?
In case anyone's interested, here's how I accomplished it
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression)
{
return ValidationHTMLMessageFor(helper, expression, (object)null);
}
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes)
{
return ValidationHTMLMessageFor(helper, expression, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
string propertyName = ExpressionHelper.GetExpressionText(expression);
string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));
if (helper.ViewData.ModelState[name] == null ||
helper.ViewData.ModelState[name].Errors == null ||
helper.ViewData.ModelState[name].Errors.Count == 0)
{
return MvcHtmlString.Empty;
}
string errors = "";
foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
{
TagBuilder tag = new TagBuilder("span");
tag.Attributes.Add("class", HtmlHelper.ValidationMessageCssClassName);
tag.MergeAttributes(htmlAttributes);
tag.Attributes.Add("data-valmsg-for", name);
tag.Attributes.Add("data-valmsg-replace", "true");
var text = tag.ToString(TagRenderMode.StartTag);
text += error.ErrorMessage;
text += tag.ToString(TagRenderMode.EndTag);
errors += text;
}
return MvcHtmlString.Create(errors);
}
Thanks Darin for pointing me in the right direction. I also found this that I used as a template Customize Html.ValidationMessageFor doesn't work in client side.
I'm new to this, so if anyone has any suggestions, please post.
Thanks!
The code above doesn't work with client-side validation since it doesn't produce the tags required for client-side validation.
Here's an improvement on it that does that:
public static MvcHtmlString ValidationHTMLMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, IDictionary<string, object> htmlAttributes)
{
string propertyName = ExpressionHelper.GetExpressionText(expression);
string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));
TagBuilder tag = new TagBuilder("span");
tag.Attributes.Add("class", HtmlHelper.ValidationMessageCssClassName);
tag.MergeAttributes(htmlAttributes);
tag.Attributes.Add("data-valmsg-for", name);
tag.Attributes.Add("data-valmsg-replace", "true");
var returnTag = new StringBuilder(tag.ToString(TagRenderMode.StartTag));
if (helper.ViewData.ModelState[name] != null &&
helper.ViewData.ModelState[name].Errors != null &&
helper.ViewData.ModelState[name].Errors.Count > 0)
{
foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
{
returnTag.Append(error.ErrorMessage);
}
}
returnTag.Append(tag.ToString(TagRenderMode.EndTag));
return MvcHtmlString.Create(returnTag.ToString());
}
Thanks for the original post - it was very helpful!
Yes, it is possible but not with the standard helpers (ValidationSummary and ValidationMessageFor). You will have to write a custom helper to render those messages if you want to achieve that. You may take a look at the following post for an example of how to write a custom ValidationSummary helper that doesn't HTML encode the error messages as the standard one.
I'm using MVC4 so I can't say for sure about MVC3.
The only way I was able to insert a <br> tag (in my case), was to start with Jerode's method, but I also had to put a marker in the message string and replace it in the razor. So the sent message was "Line 1[BR]Line2". The razor was #Html.Raw(Html.ValidationMessageFor(x => Model.MyProperty).ToString().Replace("[BR]", "<br>"))
Hope that helps.
Another approach would be to just put the validation item inside of the Html.Raw.
#Html.Raw(Html.ValidationMessageFor(x => Model.MyProperty))
This is not as nice as the approach as the suggestions but should accomplish what you are looking for.
Building on the answer from #shycohen:
public static MvcHtmlString HtmlValidationMessageFor<TModel, TProperty>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes = null)
{
string propertyName = ExpressionHelper.GetExpressionText(expression);
string name = helper.AttributeEncode(helper.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName));
TagBuilder tag = new TagBuilder("span");
tag.Attributes.Add("data-valmsg-for", name);
tag.Attributes.Add("data-valmsg-replace", "true");
if (htmlAttributes != null)
{
tag.MergeAttributes((IDictionary<string, object>)HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
tag.AddCssClass(HtmlHelper.ValidationMessageCssClassName);
var returnTag = new StringBuilder(tag.ToString(TagRenderMode.StartTag));
if (helper.ViewData.ModelState[name] != null &&
helper.ViewData.ModelState[name].Errors != null &&
helper.ViewData.ModelState[name].Errors.Count > 0)
{
foreach (ModelError error in helper.ViewData.ModelState[name].Errors)
{
returnTag.Append(error.ErrorMessage);
}
}
returnTag.Append(tag.ToString(TagRenderMode.EndTag));
return MvcHtmlString.Create(returnTag.ToString());
}
The htmlAttributes parameter is now optional and passed as an anonymous object to closer match ValidationMessageFor.
The other issue was not being able to add extra classes via htmlAttributes, as TagBuilder.MergeAttributes doesn't merge attribute values. This is solved by using TagBuilder.AddCssClass after merging the attributes to set the validation class.
I know there is several threads on this topic. I see a lot of solutions but most of them demands the knowledge of which type the enum is. Since Enum by default renders uses the template for string, it's more difficult to find out the exact type.
Has someone got a complete example on how to solve this?
Edit. Based on Roys suggestion, here's my code.
HtmlHelpers.cs
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
var type = htmlHelper.ViewData.ModelMetadata.ModelType;
var inputName = type.Name;
var value = htmlHelper.ViewData.Model == null ? default(TProperty) : expression.Compile()(htmlHelper.ViewData.Model);
var selected = value == null ? String.Empty : value.ToString();
return htmlHelper.DropDownList(inputName, ToSelectList(type, selected));
}
private static IEnumerable<SelectListItem> ToSelectList(Type enumType, string selectedItem)
{
var items = new List<SelectListItem>();
foreach (var item in Enum.GetValues(enumType))
{
var fi = enumType.GetField(item.ToString());
var attribute = fi.GetCustomAttributes(typeof(DescriptionAttribute), true).FirstOrDefault();
var title = attribute == null ? item.ToString() : ((DescriptionAttribute)attribute).Description;
var listItem = new SelectListItem
{
Value = ((int)item).ToString(),
Text = title,
Selected = selectedItem == ((int)item).ToString()
};
items.Add(listItem);
}
return new SelectList(items, "Value", "Text");
}
Viewmodel
public class AddTestForm
{
[UIHint("Enum")]
public EnumType Type { get; set; }
public string Description { get; set; }
public enum EnumType
{
One = 1,
Two = 2
}
EditorTemplates/Enum.cshtml
#model object
#Html.EnumDropDownListFor(x => x)
And in views...
#Html.EditorForModel()
The problem now is that I can't get the enum to fill after the form has been posted.
Your helper generates wrong names to the generated select elements:
<select id="Type_EnumType" name="Type.EnumType">
The DefaultModelBinder uses the name attribute to match the property names. Your helper generated name "Type.EnumType" doesn't match with the Model property name "Type". To make it work instead of the Type name you need to get the property name from the expression. It's quite easy with the ExpressionHelper class:
public static MvcHtmlString EnumDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression) where TModel : class
{
...
return htmlHelper.DropDownList(ExpressionHelper.GetExpressionText(expression), ToSelectList(type, selected));
}
I'm using Disable autocomplete on html helper textbox in MVC to add autocomplete to "off" on text boxes:
public static MvcHtmlString NoAutoCompleteTextBoxFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, object htmlAttributes)
{
return html.TextBoxFor(expression, new { autocomplete = "off" });
}
This is fine if there are no htmlAttributes on the view. Some of the textfields also have an extra CSS class added to them:
#Html.NoAutoCompleteTextBoxFor(m => m.UserName, new { #class = "ignore" })
Rather than specifically create another helper for this, is there a way to merge the sets of htmlAttributes?
Thanks in advance...
You can use RouteValueDictionary.
var attrs = new RouteValueDictionary(htmlAttributes);
attrs ["autocomplete"] = "off";
return html.TextBoxFor(expression, attrs);
Yes of cause. When passing an object into the helper, you can use reflection to read all the properties (which correspond to the html attributes you want to set) and pass the combined attributes as Dictionary to the helper:
Dictionary<string, object> attrs = new Dictionary<string, object>();
if (htmlAttributes != null)
{
foreach (var prop in htmlAttributes.GetType().GetProperties())
{
attrs[prop.Name] = prop.GetValue(htmlAttributes, null);
}
}
// now add your own attribute(s):
htmlAttributes["autocomplete"] = "off";
return html.TextBoxFor(expression, htmlAttributes);