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

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

Related

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.

How to add a htmlAttributes in this HtmlHelper?

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

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

How to set disabled in MVC htmlAttribute

When using an HTML Helper, what is the best method to set an attribute based on a condition. For example
<%if (Page.User.IsInRole("administrator")) {%>
<%=Html.TextBoxFor(m => m.FirstName, new {#class='contactDetails'}%>
<%} else {%>
<%=Html.TextBoxFor(m => m.FirstName, new {#class='contactDetails', disabled = true}%>
<%}%>
There must be a better way to programmatically add just one additional KeyPair to the anonymous type? Can't use
new { .... disabled = Page.User.IsInRole("administrator") ... }
as the browser takes any disabled attribute value as making the input disabled
I could suggest you to use mvccontrib.FluentHtml.
You can do something like this
<%=this.TextBox(m=>m.FirstNam ).Disabled(Page.User.IsInRole("administrator"))%>
It works for me as well...
<%: Html.DropDownList("SportID", (SelectList)ViewData["SportsSelectList"], "-- Select --", new { #disabled = "disabled", #readonly = "readonly" })%>
<%= Html.CheckBoxFor(model => model.IsActive, new { #disabled = "disabled", #readonly = "readonly" })%>
Page.User.IsInRole("administrator") ? null : new { disabled = "disabled" }
Using #SLaks suggestion to use an Extension method, and using Jeremiah Clark's example Extension method I've written an extension method so I can now do
Html.TextBoxFor(m => m.FirstName,new{class='contactDetails', ...},Page.User.IsInRole("administrator"));
Not Sure if there's a better method though
public static class InputExtensions
{
public static IDictionary<string, object> TurnObjectIntoDictionary(object data)
{
var attr = BindingFlags.Public | BindingFlags.Instance;
var dict = new Dictionary<string, object>();
if (data == null)
return dict;
foreach (var property in data.GetType().GetProperties(attr))
{
if (property.CanRead)
{
dict.Add(property.Name, property.GetValue(data, null));
}
}
return dict;
}
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled","true");
return htmlHelper.TextBoxFor(expression, values);
}
public static MvcHtmlString TextAreaFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled", "true");
return htmlHelper.TextAreaFor(expression, values);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool>> expression, object htmlAttributes, bool disabled)
{
IDictionary<string, object> values = TurnObjectIntoDictionary(htmlAttributes);
if (disabled)
values.Add("disabled", "true");
return htmlHelper.CheckBoxFor(expression, values);
}
}
You'll need to pass a Dictionary<string, object>, and add the disabled key inside an if statement.
I recommend making an overload of the extension method that takes a bool disabled parameter and adds it to a RouteValueDictionary created from the attributes parameter if it's true. (You could also remove the disabled entry from the RouteValueDictionary if it's false, and not take another parameter)
You may also define this param that way:
Page.User.IsInRole("administrator")
? (object)new { #class='contactDetails'}
: (object)new { #class='contactDetails', disabled = true}
You may want to consider writing your own HtmlHelper Extension class with a new TextBox method:
public static class HtmlHelperExtensions
{
public static MvcHtmlString TextBoxFor(this HtmlHelper htmlHelper, Expression<Func<TModel, TProperty>> expression, string cssClass, bool disabled)
{
return disabled
? Html.TextBoxFor(expression, new {#class=cssClass, disabled="disabled"})
: Html.TextBoxFor(expression, new {#class=cssClass})
}
}
now (if this new class is in the same namespace, or you've imported the new namespace to your page header, or in the pages section of the web.config) you can do this on your aspx page:
<%=Html.TextBoxFor(m => m.FirstName, "contactDetails", Page.User.IsInRole("administrator")) %>
Created an extension method on Object that will create a copy of the input object excluding any properties that are null, and return it all as dictionary that makes it easily used in MVC HtmlHelpers:
public static Dictionary<string, object> StripAnonymousNulls(this object attributes)
{
var ret = new Dictionary<string, object>();
foreach (var prop in attributes.GetType().GetProperties())
{
var val = prop.GetValue(attributes, null);
if (val != null)
ret.Add(prop.Name, val);
}
return ret;
}
Not sure about performance implications of reflecting through properties twice, and don't like the name of the extension method much, but it seems to do the job well ...
new {
#class = "contactDetails",
disabled = Page.User.IsInRole("administrator") ? "true" : null
}.StripAnonymousNulls()

Categories

Resources