In my razor view I am using an HTML Helper Extension method in order to use the Display Name attribute of my Model Property to display a placeholder value in a textbox. See example below.
My Helper Extension Method:
public static MvcHtmlString TextBoxPlaceHolderFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string labelText = metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
if (!String.IsNullOrEmpty(labelText))
{
if (htmlAttributes == null)
{
htmlAttributes = new Dictionary<string, object>();
}
htmlAttributes.Add("placeholder", labelText);
}
return html.TextBoxFor(expression, htmlAttributes);
}
My Model Property:
[Display(Name = "First Name", Description = "Enter Display Name")]
public string FirstName { get; set; }
Current Outcome:
Display Name of property displays inside the field as a placeholder.
However, sometimes I might want to use another property on the model for the placeholder text (like the Description property in the model above).
How can I make it so a Model data annotation property can be passed in the razor view and the the placeholder uses the value of that property instead?
Example code of what I want is below.
The razor view with meta-data property passed:
#Html.TextBoxPlaceHolderFor(model => model.FirstName, new { #class = "form-control" }, MODELMETADATAPROPERTY HERE)
The HTML Helper Extension (what I imagine the code would do, probably need to use reflection or something?)
public static MvcHtmlString TextBoxPlaceHolderFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes, MODELMETADATAPROPERTY HERE)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string labelText = metadata.MODELMETADATAPROPERTY HERE;
if (!String.IsNullOrEmpty(labelText))
{
if (htmlAttributes == null)
{
htmlAttributes = new Dictionary<string, object>();
}
htmlAttributes.Add("placeholder", labelText);
}
return html.TextBoxFor(expression, htmlAttributes);
}
you can use the following
public static MvcHtmlString TextBoxPlaceHolderFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
string labelText = metadata.DisplayName ?? Proper(metadata.PropertyName?? htmlFieldName.Split('.').Last());
if (!String.IsNullOrEmpty(labelText))
{
if (htmlAttributes == null)
{
htmlAttributes = new Dictionary<string, object>();
}
htmlAttributes.Add("placeholder", modeldata.Description??Proper(metadata.PropertyName?? htmlFieldName.Split('.').Last())); // to get the description value
}
return html.TextBoxFor(expression, labelText);
}
// this function will convert FullName to Full Name with space
private static string Proper(string value)
{
var newValue = Regex.Replace(value, "([a-z])([A-Z])", "$1 $2");
return newValue;
}
Related
I need to get working validation of the custom ASP.NET MVC helper.
Helper
public static class AutocompleteHelper
{
public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl)
{
return CreateAutocomplete(helper, expression, actionUrl, null, null);
}
public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl, bool? isRequired, string placeholder)
{
return CreateAutocomplete(helper, expression, actionUrl, placeholder, isRequired);
}
private static MvcHtmlString CreateAutocomplete<TModel, TValue>(HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string actionUrl, string placeholder, bool? isRequired)
{
var attributes = new Dictionary<string, object>
{
{ "data-autocomplete", true },
{ "data-action", actionUrl }
};
if (!string.IsNullOrWhiteSpace(placeholder))
{
attributes.Add("placeholder", placeholder);
}
if (isRequired.HasValue && isRequired.Value)
{
attributes.Add("required", "required");
}
attributes.Add("class", "form-control formControlAutocomplete");
attributes.Add("maxlength", "45");
Func<TModel, TValue> method = expression.Compile();
var value = method((TModel)helper.ViewData.Model);
var baseProperty = ((MemberExpression)expression.Body).Member.Name;
var hidden = helper.Hidden(baseProperty, value);
attributes.Add("data-value-name", baseProperty);
var automcompleteName = baseProperty + "_autocomplete";
var textBox = helper.TextBox(automcompleteName, null, string.Empty, attributes);
var builder = new StringBuilder();
builder.AppendLine(hidden.ToHtmlString());
builder.AppendLine(textBox.ToHtmlString());
return new MvcHtmlString(builder.ToString());
}
}
HTML
#Html.AutocompleteFor(x => x.ProductUID, Url.Action("AutocompleteProducts", "Requisition"), true, "Start typing Product name...")
#Html.ValidationMessageFor(x => x.ProductUID)
I seems like validating but no message appears.
Any clue?
The name of your text field is ProductUID_autocomplete but your ValidationMessageFor which is supposed to display the error message is bound to ProductUID.
So make sure that you are binding your error message to the same property:
#Html.ValidationMessage("ProductUID_autocomplete")
It appears that whatever custom logic you might have to validate this field is injecting the error under the ProductUID_autocomplete key in the ModelState.
This being said, why not just invoke the ValidationMessage helper inside your custom helper? This way you will have less things to type in your view and the logic with those names being suffixed with _autocomplete will stay inside the helper only.
I'm using right now this code to implement a RadioButtonList using MVC4.
And as you can see, that function does not have htmlAttributes parameter. So I'd like to add it and here is the problem. Check please that the htmlAttributes for RadioButtonFor() is occupied by the id.
I was trying to add it but throws me errors because the id already exists for the loop.
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues)
{
return htmlHelper.RadioButtonForSelectList(expression, listOfValues, null);
}
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
object htmlAttributes)
{
return htmlHelper.RadioButtonForSelectList(expression, listOfValues, new RouteValueDictionary(htmlAttributes));
}
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
IDictionary<string, object> htmlAttributes)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
foreach (SelectListItem item in listOfValues)
{
var id = string.Format(
"{0}_{1}",
metaData.PropertyName,
item.Value
);
var radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
sb.AppendFormat(
"{0}<label for=\"{1}\">{2}</label>",
radio,
id,
HttpUtility.HtmlEncode(item.Text)
);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
In the third method, it looks like the html attributes being passed to the radion button being created is new { id = id }. Try to replace that with the parameter from the method.
UPDATED
Include id in the html attributes and assign a new value to id in each loop iteration.
if (listOfValues != null)
{
if (!htmlAttributes.ContainsKey("id"))
{
htmlAttributes.Add("id", null);
}
foreach (SelectListItem item in listOfValues)
{
var id = string.Format(
"{0}_{1}",
metaData.PropertyName,
item.Value
);
htmlAttributes["id"] = id;
var radio = htmlHelper.RadioButtonFor(expression, item.Value, htmlAttributes).ToHtmlString();
sb.AppendFormat(
"{0}<label for=\"{1}\">{2}</label>",
radio,
id,
HttpUtility.HtmlEncode(item.Text)
);
}
}
This is different approach to translation of page. It is not meant to be localization.
I created class TransModel which all my ViewModels are inheriting from.
This class fetches string pairs relevant for current ViewModel from database and stores them in "labels" Dictionary. Key for that pair is the value of string here "User Name" and value is translated value.
[Display(Name = "User Name")]
public string UserName { get; set; }
Instead of using Html.LabelFor in View I use extension of it call it TransLabelFor
public static MvcHtmlString TransLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression,TransModel model)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string trans = "";
model.labels.TryGetValue(metadata.DisplayName, out trans);
if (trans == null)
{
trans = metadata.DisplayName;
}
return MvcHtmlString.Create(String.Format("<label for='{0}'>{1}</label>", metadata.DisplayName, trans));
}
Now I want to replace what I return by it. I want the original tag as returned by this:
MvcHtmlString originalTag = System.Web.Mvc.Html.LabelExtensions.LabelFor(html, expression);
but with my translation.
Are there any neat ways of doing it instead of string find/replace? I don't like that I have to pass Model around either, any better ideas?
Or "improvement" to your solution (also including reflection, but this will avoid to put TransModel in your helpers)
public static IHtmlString TransLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
var modelType = typeof (TModel);
var resolvedLabelText = metadata.DisplayName ?? metadata.PropertyName;
if (String.IsNullOrEmpty(resolvedLabelText))
return MvcHtmlString.Empty;
var labelProperty = modelType.GetProperty("labels");
if (labelProperty != null)
{
var labels = labelProperty.GetValue(html.ViewData.Model, null) as Dictionary<string, string>;
if (labels != null && labels.ContainsKey(resolvedLabelText))
resolvedLabelText = labels[resolvedLabelText];
}
var tag = new TagBuilder("label");
tag.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tag.SetInnerText(resolvedLabelText);
return MvcHtmlString.Create(tag.ToString());
}
but this will mean you'll have to change all your LabelFor to TransLableFor (string replace in all solution) : but at least you don't have to add a "TransModel" during the replacement.
I have a field called W2_Sent which is defined as (bit,null)
In my view I have the following which shows it as a checkbox:
<div class="editor-label" style="width: 10em">
#Html.Label("W2 Sent")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.W2_Sent)
#Html.ValidationMessageFor(model => model.W2_Sent)
</div>
If I check it, I get an error
The value 'checked' is not valid for W2_Sent
[HttpPost]
public ActionResult Create(Employee emp)
{
foreach (ModelState modelState in ViewData.ModelState.Values)
{
foreach (ModelError error in modelState.Errors)
{
string s = "error";
}
}
I am able to trap the error within the foreach loop you see above..
Why am I getting value 'checked' is invalid though
For displaying checkboxes in forms you should always use #Html.CheckBox/CheckBoxFor instead of <input type="checkbox" name="gender" />. When you use #Html.CheckBox/CheckBoxFor ASP.NET MVC generates a hidden field which has a boolean value and that is what will be binded to your model property.
When you directly use the html part then browsers posts the value of the field as string "checked" if it is, and in model binding that throws the error.
I've done it like this. Write your own ExtensionMethod for CheckBoxFor.
The trick is the static values for "value" = "true" in the checkbox and the "value" "false" in the hidden field.
As mentioned before, a checkbox with a value false will not be sent back. In this case the value of the hidden field will be taken. When the checkbox is checked by the user, the new "true"-value will override the "false" from the hidden field.
public static MvcHtmlString CheckboxForMetro<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, int offset = 3)
{
TagBuilder tblabel = new TagBuilder("label");
tblabel.AddCssClass("checkbox offset" + offset.ToString());
TagBuilder tbinput = new TagBuilder("input");
tbinput.Attributes.Add("type", "checkbox");
tbinput.Attributes.Add("id", GetPropertyNameFromLambdaExpression(html, expression));
tbinput.Attributes.Add("name", GetPropertyNameFromLambdaExpression(html, expression));
tbinput.Attributes.Add("value", "true");
tbinput.MergeAttributes(GetPropertyValidationAttributes(html, expression, null));
if (GetPropertyValueFromLambdaExpression(html, expression) == "True") tbinput.Attributes.Add("checked", "checked");
TagBuilder tbhidden = new TagBuilder("input");
tbhidden.Attributes.Add("type", "hidden");
tbhidden.Attributes.Add("value", "false");
tbhidden.Attributes.Add("name", GetPropertyNameFromLambdaExpression(html, expression));
TagBuilder tbspan = new TagBuilder("span");
//tbspan.AddCssClass("span" + spanLabel.ToString());
tbspan.InnerHtml = GetPropertyDisplayNameFromLambdaExpression(html, expression);
tblabel.InnerHtml = tbinput.ToString() + tbspan.ToString() + tbhidden.ToString();
return new MvcHtmlString(tblabel.ToString());
}
It's an ExtensionMethod for the Metro UI CSS at http://metroui.org.ua
This is the code for getting the value, displayname, propertyname and validationAttributes
private static string GetPropertyDisplayNameFromLambdaExpression<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
return metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last() ?? "Geen tekst";
}
private static string GetPropertyValueFromLambdaExpression<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
string value = string.Empty;
TModel model = html.ViewData.Model;
if (model != null)
{
var expr = expression.Compile().Invoke(model);
if (expr != null)
{
value = expr.ToString();
}
}
return value;
}
private static string GetPropertyNameFromLambdaExpression<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
return metadata.PropertyName;
}
private static IDictionary<string, object> GetPropertyValidationAttributes<TModel, TValue>(HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IDictionary<string, object> htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
IDictionary<string, object> validationAttributes = html.GetUnobtrusiveValidationAttributes(ExpressionHelper.GetExpressionText(expression), metadata);
if (htmlAttributes == null)
{
htmlAttributes = validationAttributes;
}
else
{
htmlAttributes = htmlAttributes.Concat(validationAttributes).ToDictionary(k => k.Key, v => v.Value);
}
return htmlAttributes;
}
I hope this will help someone else.
In my case, I was using some styling library that did not allow me to use the HTMLhelper. So, if it is useful for anyone, a straightforward solution was using jQuery when submitting the form, simply assigning the check value to the input, as shown below.
$("#myForm").submit(function (e) {
$("#myCheckBoxInput").val($("#myCheckBoxInput").prop("checked"))
})
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));
}