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()
Related
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];
}
I've this code in razor page.
#{ var countryCode = Model.CountryCode;}
#if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
#Html.DropDownListFor(m => m.ProvState, Model.Provinces)
}
else
{
#Html.TextBoxFor(m => m.ProvState, Model.ProvState);
}
I wrote this helper in LocatioHelper.cshtml
#helper RenderProvince(LocationModel model)
{
var countryCode = model.CountryCode;
if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
#Html.DropDownListFor(model.ProvState, model.Provinces)
}
else
{
#Html.TextBoxFor(model.ProvState, model.ProvState);
}
}
I've error that I must specify type for dropDownListFor and TextBoxFor explicitly, but on view I don't specify type.
How to move this into helper ?
As per my opinion, you can pack your custom helper methods in a static class and declare your methods as simple extension methods on HtmlHelper<T>.
public static class CustomHelper
{
public static MvcHtmlString YourCustomHelper<TModel, TValue> (
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TValue>> expression ---- )
{
//Write your custom logic here.
}
}
You can import this class in your view and use this helper method using the usual syntax of #Html.YourCustomHelper()
Hope this helps.
EDIT:
In case you are using inline helper method keep the helper method in your View only where it is being used. Otherwise try creating an external html helper method as indicated above.
No, this is not possible. You could write a normal HTML helper with #Html.TextBoxFor because that your view is strongly typed.
So you need something like:
public class HelperExtentions
{
public static MvcHtmlString DefaultRenderer<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, LocationModel model, SelectList selectList, object htmlAttributes)
{
var sb = new StringBuilder();
var countryCode = string.IsNullOrEmpty(model.CountryCode):"":model.CountryCode;
var dtp = "";
if (countryCode.Equals("CA") || countryCode.Equals("US"))
{
dtp = htmlHelper.DropDownListFor(expression, selectList, htmlAttributes);
}
else
{
dtp = htmlHelper.TextBoxFor(expression, htmlAttributes).ToHtmlString();
}
sb.AppendFormat(dtp);
return MvcHtmlString.Create(sb.ToString());
}
}
Then you can use :
#html.DefaultRenderer((m => m.ProvState, Models, newSelectList(/*some list data*/) ,new { #class = "form-control" }
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)
);
}
}
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"))
})