How to add a htmlAttributes in this HtmlHelper? - c#

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)
);
}
}

Related

C# Pass Class Property Name as parameter

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;
}

How can I added #readonly = readonly to an existing additionalViewData property?

I'm doing something like this:
public static MvcHtmlString DimensionEditorFor<TModel, TValue>(this HtmlHelper<TModel> a_html, Expression<Func<TModel, TValue>> a_expression, DimensionLock a_lock, object a_additionalViewData)
{
var dictionary = new RouteValueDictionary(a_additionalViewData);
if (a_lock.IsLocked)
{
object htmlAttributes;
if (dictionary.TryGetValue("htmlAttributes", out htmlAttributes))
{
var htmlAttributesDict = new RouteValueDictionary(htmlAttributes);
htmlAttributesDict["#readonly"] = "readonly";
htmlAttributes = htmlAttributesDict;
}
else
{
htmlAttributes = new {#readonly = "readonly"};
}
dictionary["htmlAttributes"] = htmlAttributes;
}
return a_html.EditorFor(a_expression, dictionary);
}
This doesn't work because RouteValueDictionary object are not allowable values for additionalViewData in the EditorFor extension method. I want to set readonly on the rendered text box if a_lock.IsLocked is true.
Updates:
I have tried the following just to see if ViewDataDictionary would work. It does not.
public static MvcHtmlString DimensionEditorFor<TModel, TValue>(this HtmlHelper<TModel> a_html, Expression<Func<TModel, TValue>> a_expression, DimensionLock a_lock, object a_additionalViewData)
{
var dictionary = new ViewDataDictionary();
foreach (KeyValuePair<string, object> pair in new RouteValueDictionary(a_additionalViewData))
dictionary.Add(pair.Key, pair.Value);
return a_html.EditorFor(a_expression, dictionary);
}
It's not exactly elegant, but if all you need is for the object to be a ViewDataDictionary instead of a RouteValueDictionary. You could always do:
var viewData = new ViewDataDictionary();
foreach (var key in dictionary.Keys)
{
viewData[key] = dictionary[key];
}

How to validate custom ASP.NET MVC helper

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.

Converting EditorTemplate into HtmlHelper

I originally created an editor template like this
#model MyModel
var items = // get items
#Html.DropDownListFor(m => m.Id, items, new { id = Html.IdFor(m => m) })
which was invoked by
#Html.EditorFor(m => m.SomeClass)
where SomeClass has an Id property. (The IdFor is one of my HTML helpers).
This would generate something like this
<select name="SomeClass.Id" id="SomeClass" />
Now I want to change the editor template into an HTML helper, so that my call looks like this
#Html.CustomEditorFor(m => m.SomeClass)
I'm changing this from a view to helper because its easier for reusability.
This is loosely what I have:
public static MvcHtmlString CustomEditorFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var idString = htmlHelper.IdFor(expression);
var propertyValue = expression.Compile()(htmlHelper.ViewData.Model);
var items = // get items
return htmlHelper.DropDownListFor(expression, items, new {id = idString});
}
However, when I call the editor, I get this HTML instead of what I want.
<select name="SomeClass" id="SomeClass" />
How can I modify the expression enough to allow it to "access" the Id property?
Try this:
public static MvcHtmlString CustomEditorFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression)
{
var idString = htmlHelper.IdFor(expression);
var items = // get items
var param = Expression.Parameter(typeof(TModel), "m");
var member = Expression.Property(
Expression.Property(param, ExpressionHelper.GetExpressionText(expression))
, "Id");
var isNullable = Nullable.GetUnderlyingType(member.Type);
if (isNullable != null) {
var expr2 = Expression.Lambda<Func<TModel, int?>>(
member, new[] { param }
);
return htmlHelper.DropDownListFor(expr2, items, new { id = idString });
}
var expr = Expression.Lambda<Func<TModel, int>>(
member, new[] { param }
);
return htmlHelper.DropDownListFor(expr, items, new { id = idString });
}

How can I change my extension method in C#

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));
}

Categories

Resources