Set disable attribute based on a condition for Html.TextBoxFor - asp.net-mvc

I want to set disable attribute based on a condition for Html.TextBoxFor in asp.net MVC like below
#Html.TextBoxFor(model => model.ExpireDate, new { style = "width: 70px;", maxlength = "10", id = "expire-date" disabled = (Model.ExpireDate == null ? "disable" : "") })
This helper has two output disabled="disabled " or disabled="". both of theme make the textbox disable.
I want to disable the textbox if Model.ExpireDate == null else I want to enable it

The valid way is:
disabled="disabled"
Browsers also might accept disabled="" but I would recommend you the first approach.
Now this being said I would recommend you writing a custom HTML helper in order to encapsulate this disabling functionality into a reusable piece of code:
using System;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
bool disabled
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
if (disabled)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
which you could use like this:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
},
Model.ExpireDate == null
)
and you could bring even more intelligence into this helper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes
)
{
var attributes = new RouteValueDictionary(htmlAttributes);
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (metaData.Model == null)
{
attributes["disabled"] = "disabled";
}
return htmlHelper.TextBoxFor(expression, attributes);
}
}
so that now you no longer need to specify the disabled condition:
#Html.MyTextBoxFor(
model => model.ExpireDate,
new {
style = "width: 70px;",
maxlength = "10",
id = "expire-date"
}
)

Actually, the internal behavior is translating the anonymous object to a dictionary.
So what I do in these scenarios is go for a dictionary:
#{
var htmlAttributes = new Dictionary<string, object>
{
{ "class" , "form-control"},
{ "placeholder", "Why?" }
};
if (Model.IsDisabled)
{
htmlAttributes.Add("disabled", "disabled");
}
}
#Html.EditorFor(m => m.Description, new { htmlAttributes = htmlAttributes })
Or, as Stephen commented here:
#Html.EditorFor(m => m.Description,
Model.IsDisabled ? (object)new { disabled = "disabled" } : (object)new { })

I like Darin method. But quick way to solve this,
Html.TextBox("Expiry", null, new { style = "width: 70px;", maxlength = "10", id = "expire-date", disabled = "disabled" }).ToString().Replace("disabled=\"disabled\"", (1 == 2 ? "" : "disabled=\"disabled\""))

One simple approach I have used is conditional rendering:
#(Model.ExpireDate == null ?
#Html.TextBoxFor(m => m.ExpireDate, new { #disabled = "disabled" }) :
#Html.TextBoxFor(m => m.ExpireDate)
)

If you don't use html helpers you may use simple ternary expression like this:
<input name="Field"
value="#Model.Field" tabindex="0"
#(Model.IsDisabledField ? "disabled=\"disabled\"" : "")>

I achieved it using some extension methods
private const string endFieldPattern = "^(.*?)>";
public static MvcHtmlString IsDisabled(this MvcHtmlString htmlString, bool disabled)
{
string rawString = htmlString.ToString();
if (disabled)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 disabled=\"disabled\">");
}
return new MvcHtmlString(rawString);
}
public static MvcHtmlString IsReadonly(this MvcHtmlString htmlString, bool #readonly)
{
string rawString = htmlString.ToString();
if (#readonly)
{
rawString = Regex.Replace(rawString, endFieldPattern, "$1 readonly=\"readonly\">");
}
return new MvcHtmlString(rawString);
}
and then....
#Html.TextBoxFor(model => model.Name, new { #class= "someclass"}).IsDisabled(Model.ExpireDate == null)

Is solved this using RouteValueDictionary (works fine as htmlAttributes as it's based on IDictionary) and an extension method:
public static RouteValueDictionary AddIf(this RouteValueDictionary dict, bool condition, string name, object value)
{
if (condition) dict.Add(name, value);
return dict;
}
Usage:
#Html.TextBoxFor(m => m.GovId, new RouteValueDictionary(new { #class = "form-control" })
.AddIf(Model.IsEntityFieldsLocked, "disabled", "disabled"))
Credit goes to https://stackoverflow.com/a/3481969/40939

This is late, but may be helpful to some people.
I have extended #DarinDimitrov's answer to allow for passing a second object that takes any number of boolean html attributes like disabled="disabled" checked="checked", selected="selected" etc.
It will render the attribute only if the property value is true, anything else and the attribute will not be rendered at all.
The custom reuseble HtmlHelper:
public static class HtmlExtensions
{
public static IHtmlString MyTextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
object htmlAttributes,
object booleanHtmlAttributes)
{
var attributes = new RouteValueDictionary(htmlAttributes);
//Reflect over the properties of the newly added booleanHtmlAttributes object
foreach (var prop in booleanHtmlAttributes.GetType().GetProperties())
{
//Find only the properties that are true and inject into the main attributes.
//and discard the rest.
if (ValueIsTrue(prop.GetValue(booleanHtmlAttributes, null)))
{
attributes[prop.Name] = prop.Name;
}
}
return htmlHelper.TextBoxFor(expression, attributes);
}
private static bool ValueIsTrue(object obj)
{
bool res = false;
try
{
res = Convert.ToBoolean(obj);
}
catch (FormatException)
{
res = false;
}
catch(InvalidCastException)
{
res = false;
}
return res;
}
}
Which you can use like so:
#Html.MyTextBoxFor(m => Model.Employee.Name
, new { #class = "x-large" , placeholder = "Type something…" }
, new { disabled = true})

if you dont want to use Html Helpers
take look it my solution
disabled="#(your Expression that returns true or false")"
that it
#{
bool isManager = (Session["User"] as User).IsManager;
}
<textarea rows="4" name="LetterManagerNotes" disabled="#(!isManager)"></textarea>
and I think the better way to do it is to do that check in the controller and save it within a variable that is accessible inside the view(Razor engine) in order to make the view free from business logic

Yet another solution would be to create a Dictionary<string, object> before calling TextBoxFor and pass that dictionary. In the dictionary, add "disabled" key only if the textbox is to be diabled. Not the neatest solution but simple and straightforward.

Another approach is to disable the text box on the client side.
In your case you have only one textbox that you need to disable but consider the case where you have multiple input, select, and textarea fields that yout need to disable.
It is much easier to do it via jquery + (since we can not rely on data coming from the client) add some logic to the controller to prevent these fields from being saved.
Here is an example:
<input id="document_Status" name="document.Status" type="hidden" value="2" />
$(document).ready(function () {
disableAll();
}
function disableAll() {
var status = $('#document_Status').val();
if (status != 0) {
$("input").attr('disabled', true);
$("textarea").attr('disabled', true);
$("select").attr('disabled', true);
}
}

I like the extension method approach so you don't have to pass through all possible parameters.
However using Regular expressions can be quite tricky (and somewhat slower) so I used XDocument instead:
public static MvcHtmlString SetDisabled(this MvcHtmlString html, bool isDisabled)
{
var xDocument = XDocument.Parse(html.ToHtmlString());
if (!(xDocument.FirstNode is XElement element))
{
return html;
}
element.SetAttributeValue("disabled", isDisabled ? "disabled" : null);
return MvcHtmlString.Create(element.ToString());
}
Use the extension method like this:
#Html.EditorFor(m => m.MyProperty).SetDisabled(Model.ExpireDate == null)

Related

Get Display Name in Generic Enum Editor Template [duplicate]

I'm trying to use the Html.DropDownList extension method but can't figure out how to use it with an enumeration.
Let's say I have an enumeration like this:
public enum ItemTypes
{
Movie = 1,
Game = 2,
Book = 3
}
How do I go about creating a dropdown with these values using the Html.DropDownList extension method?
Or is my best bet to simply create a for loop and create the Html elements manually?
For MVC v5.1 use Html.EnumDropDownListFor
#Html.EnumDropDownListFor(
x => x.YourEnumField,
"Select My Type",
new { #class = "form-control" })
For MVC v5 use EnumHelper
#Html.DropDownList("MyType",
EnumHelper.GetSelectList(typeof(MyType)) ,
"Select My Type",
new { #class = "form-control" })
For MVC 5 and lower
I rolled Rune's answer into an extension method:
namespace MyApp.Common
{
public static class MyExtensions{
public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
where TEnum : struct, IComparable, IFormattable, IConvertible
{
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new { Id = e, Name = e.ToString() };
return new SelectList(values, "Id", "Name", enumObj);
}
}
}
This allows you to write:
ViewData["taskStatus"] = task.Status.ToSelectList();
by using MyApp.Common
I know I'm late to the party on this, but thought you might find this variant useful, as this one also allows you to use descriptive strings rather than enumeration constants in the drop down. To do this, decorate each enumeration entry with a [System.ComponentModel.Description] attribute.
For example:
public enum TestEnum
{
[Description("Full test")]
FullTest,
[Description("Incomplete or partial test")]
PartialTest,
[Description("No test performed")]
None
}
Here is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Reflection;
using System.ComponentModel;
using System.Linq.Expressions;
...
private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
{
Type realModelType = modelMetadata.ModelType;
Type underlyingType = Nullable.GetUnderlyingType(realModelType);
if (underlyingType != null)
{
realModelType = underlyingType;
}
return realModelType;
}
private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return EnumDropDownListFor(htmlHelper, expression, null);
}
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 = GetEnumDescription(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);
}
You can then do this in your view:
#Html.EnumDropDownListFor(model => model.MyEnumProperty)
**EDIT 2014-JAN-23: Microsoft have just released MVC 5.1, which now has an EnumDropDownListFor feature. Sadly it does not appear to respect the [Description] attribute so the code above still stands.See Enum section in Microsoft's release notes for MVC 5.1.
Update: It does support the Display attribute [Display(Name = "Sample")] though, so one can use that.
[Update - just noticed this, and the code looks like an extended version of the code here: https://blogs.msdn.microsoft.com/stuartleeks/2010/05/21/asp-net-mvc-creating-a-dropdownlist-helper-for-enums/, with a couple of additions. If so, attribution would seem fair ;-)]
In ASP.NET MVC 5.1, they added the EnumDropDownListFor() helper, so no need for custom extensions:
Model:
public enum MyEnum
{
[Display(Name = "First Value - desc..")]
FirstValue,
[Display(Name = "Second Value - desc...")]
SecondValue
}
View:
#Html.EnumDropDownListFor(model => model.MyEnum)
Using Tag Helper (ASP.NET MVC 6):
<select asp-for="#Model.SelectedValue" asp-items="Html.GetEnumSelectList<MyEnum>()">
I bumped into the same problem, found this question, and thought that the solution provided by Ash wasn't what I was looking for; Having to create the HTML myself means less flexibility compared to the built-in Html.DropDownList() function.
Turns out C#3 etc. makes this pretty easy. I have an enum called TaskStatus:
var statuses = from TaskStatus s in Enum.GetValues(typeof(TaskStatus))
select new { ID = s, Name = s.ToString() };
ViewData["taskStatus"] = new SelectList(statuses, "ID", "Name", task.Status);
This creates a good ol' SelectList that can be used like you're used to in the view:
<td><b>Status:</b></td><td><%=Html.DropDownList("taskStatus")%></td></tr>
The anonymous type and LINQ makes this so much more elegant IMHO. No offence intended, Ash. :)
Here is a better encapsulated solution:
https://www.spicelogic.com/Blog/enum-dropdownlistfor-asp-net-mvc-5
Say here is your model:
Sample Usage:
Generated UI:
And generated HTML
The Helper Extension Source Code snap shot:
You can download the sample project from the link I provided.
EDIT: Here's the code:
public static class EnumEditorHtmlHelper
{
/// <summary>
/// Creates the DropDown List (HTML Select Element) from LINQ
/// Expression where the expression returns an Enum type.
/// </summary>
/// <typeparam name="TModel">The type of the model.</typeparam>
/// <typeparam name="TProperty">The type of the property.</typeparam>
/// <param name="htmlHelper">The HTML helper.</param>
/// <param name="expression">The expression.</param>
/// <returns></returns>
public static MvcHtmlString DropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression)
where TModel : class
{
TProperty value = htmlHelper.ViewData.Model == null
? default(TProperty)
: expression.Compile()(htmlHelper.ViewData.Model);
string selected = value == null ? String.Empty : value.ToString();
return htmlHelper.DropDownListFor(expression, createSelectList(expression.ReturnType, selected));
}
/// <summary>
/// Creates the select list.
/// </summary>
/// <param name="enumType">Type of the enum.</param>
/// <param name="selectedItem">The selected item.</param>
/// <returns></returns>
private static IEnumerable<SelectListItem> createSelectList(Type enumType, string selectedItem)
{
return (from object item in Enum.GetValues(enumType)
let fi = enumType.GetField(item.ToString())
let attribute = fi.GetCustomAttributes(typeof (DescriptionAttribute), true).FirstOrDefault()
let title = attribute == null ? item.ToString() : ((DescriptionAttribute) attribute).Description
select new SelectListItem
{
Value = item.ToString(),
Text = title,
Selected = selectedItem == item.ToString()
}).ToList();
}
}
Html.DropDownListFor only requires an IEnumerable, so an alternative to Prise's solution is as follows. This will allow you to simply write:
#Html.DropDownListFor(m => m.SelectedItemType, Model.SelectedItemType.ToSelectList())
[Where SelectedItemType is a field on your model of type ItemTypes, and your model is non-null]
Also, you don't really need to genericize the extension method as you can use enumValue.GetType() rather than typeof(T).
EDIT: Integrated Simon's solution here as well, and included ToDescription extension method.
public static class EnumExtensions
{
public static IEnumerable<SelectListItem> ToSelectList(this Enum enumValue)
{
return from Enum e in Enum.GetValues(enumValue.GetType())
select new SelectListItem
{
Selected = e.Equals(enumValue),
Text = e.ToDescription(),
Value = e.ToString()
};
}
public static string ToDescription(this Enum value)
{
var attributes = (DescriptionAttribute[])value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
return attributes.Length > 0 ? attributes[0].Description : value.ToString();
}
}
So without Extension functions if you are looking for simple and easy.. This is what I did
<%= Html.DropDownListFor(x => x.CurrentAddress.State, new SelectList(Enum.GetValues(typeof(XXXXX.Sites.YYYY.Models.State))))%>
where XXXXX.Sites.YYYY.Models.State is an enum
Probably better to do helper function, but when time is short this will get the job done.
Expanding on Prise and Rune's answers, if you'd like to have the value attribute of your select list items map to the integer value of the Enumeration type, rather than the string value, use the following code:
public static SelectList ToSelectList<T, TU>(T enumObj)
where T : struct
where TU : struct
{
if(!typeof(T).IsEnum) throw new ArgumentException("Enum is required.", "enumObj");
var values = from T e in Enum.GetValues(typeof(T))
select new {
Value = (TU)Convert.ChangeType(e, typeof(TU)),
Text = e.ToString()
};
return new SelectList(values, "Value", "Text", enumObj);
}
Instead of treating each Enumeration value as a TEnum object, we can treat it as a object and then cast it to integer to get the unboxed value.
Note:
I also added a generic type constraint to restrict the types for which this extension is available to only structs (Enum's base type), and a run-time type validation which ensures that the struct passed in is indeed an Enum.
Update 10/23/12:
Added generic type parameter for underlying type and fixed non-compilation issue affecting .NET 4+.
In .NET Core you can just use this:
#Html.DropDownListFor(x => x.Foo, Html.GetEnumSelectList<MyEnum>())
To solve the problem of getting the number instead of text using Prise's extension method.
public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
{
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new { ID = (int)Enum.Parse(typeof(TEnum),e.ToString())
, Name = e.ToString() };
return new SelectList(values, "Id", "Name", enumObj);
}
A super easy way to get this done - without all the extension stuff that seems overkill is this:
Your enum:
public enum SelectedLevel
{
Level1,
Level2,
Level3,
Level4
}
Inside of your controller bind the Enum to a List:
List<SelectedLevel> myLevels = Enum.GetValues(typeof(SelectedLevel)).Cast<SelectedLevel>().ToList();
After that throw it into a ViewBag:
ViewBag.RequiredLevel = new SelectList(myLevels);
Finally simply bind it to the View:
#Html.DropDownList("selectedLevel", (SelectList)ViewBag.RequiredLevel, new { #class = "form-control" })
This is by far the easiest way I found and does not require any extensions or anything that crazy.
UPDATE: See Andrews comment below.
The best solution I found for this was combining this blog with Simon Goldstone's answer.
This allows use of the enum in the model. Essentially the idea is to use an integer property as well as the enum, and emulate the integer property.
Then use the [System.ComponentModel.Description] attribute for annotating the model with your display text, and use an "EnumDropDownListFor" extension in your view.
This makes both the view and model very readable and maintainable.
Model:
public enum YesPartialNoEnum
{
[Description("Yes")]
Yes,
[Description("Still undecided")]
Partial,
[Description("No")]
No
}
//........
[Display(Name = "The label for my dropdown list")]
public virtual Nullable<YesPartialNoEnum> CuriousQuestion{ get; set; }
public virtual Nullable<int> CuriousQuestionId
{
get { return (Nullable<int>)CuriousQuestion; }
set { CuriousQuestion = (Nullable<YesPartialNoEnum>)value; }
}
View:
#using MyProject.Extensions
{
//...
#Html.EnumDropDownListFor(model => model.CuriousQuestion)
//...
}
Extension (directly from Simon Goldstone's answer, included here for completeness):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.ComponentModel;
using System.Reflection;
using System.Linq.Expressions;
using System.Web.Mvc.Html;
namespace MyProject.Extensions
{
//Extension methods must be defined in a static class
public static class MvcExtensions
{
private static Type GetNonNullableModelType(ModelMetadata modelMetadata)
{
Type realModelType = modelMetadata.ModelType;
Type underlyingType = Nullable.GetUnderlyingType(realModelType);
if (underlyingType != null)
{
realModelType = underlyingType;
}
return realModelType;
}
private static readonly SelectListItem[] SingleEmptyItem = new[] { new SelectListItem { Text = "", Value = "" } };
public static string GetEnumDescription<TEnum>(TEnum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if ((attributes != null) && (attributes.Length > 0))
return attributes[0].Description;
else
return value.ToString();
}
public static MvcHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TEnum>> expression)
{
return EnumDropDownListFor(htmlHelper, expression, null);
}
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 = GetEnumDescription(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);
}
}
}
You want to look at using something like Enum.GetValues
#Html.DropDownListFor(model => model.Type, Enum.GetNames(typeof(Rewards.Models.PropertyType)).Select(e => new SelectListItem { Text = e }))
Now this feature is supported out-of-the-box in MVC 5.1 through #Html.EnumDropDownListFor()
Check the following link:
https://learn.microsoft.com/en-us/aspnet/mvc/overview/releases/mvc51-release-notes#Enum
It is really shame that it took Microsoft 5 years to implement such as feature which is so in demand according to the voting above!
This is Rune & Prise answers altered to use the Enum int value as the ID.
Sample Enum:
public enum ItemTypes
{
Movie = 1,
Game = 2,
Book = 3
}
Extension method:
public static SelectList ToSelectList<TEnum>(this TEnum enumObj)
{
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new { Id = (int)Enum.Parse(typeof(TEnum), e.ToString()), Name = e.ToString() };
return new SelectList(values, "Id", "Name", (int)Enum.Parse(typeof(TEnum), enumObj.ToString()));
}
Sample of usage:
<%= Html.DropDownList("MyEnumList", ItemTypes.Game.ToSelectList()) %>
Remember to Import the namespace containing the Extension method
<%# Import Namespace="MyNamespace.LocationOfExtensionMethod" %>
Sample of generated HTML:
<select id="MyEnumList" name="MyEnumList">
<option value="1">Movie</option>
<option selected="selected" value="2">Game</option>
<option value="3">Book </option>
</select>
Note that the item that you use to call the ToSelectList on is the selected item.
This is version for Razor:
#{
var itemTypesList = new List<SelectListItem>();
itemTypesList.AddRange(Enum.GetValues(typeof(ItemTypes)).Cast<ItemTypes>().Select(
(item, index) => new SelectListItem
{
Text = item.ToString(),
Value = (index).ToString(),
Selected = Model.ItemTypeId == index
}).ToList());
}
#Html.DropDownList("ItemTypeId", itemTypesList)
Building on Simon's answer, a similar approach is to get the Enum values to display from a Resource file, instead of in a description attribute within the Enum itself. This is helpful if your site needs to be rendered in more than one language and if you were to have a specific resource file for Enums, you could go one step further and have just Enum values, in your Enum and reference them from the extension by a convention such as [EnumName]_[EnumValue] - ultimately less typing!
The extension then looks like:
public static IHtmlString EnumDropDownListFor<TModel, TEnum>(this HtmlHelper<TModel> html, Expression<Func<TModel, TEnum>> expression)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var enumType = Nullable.GetUnderlyingType(metadata.ModelType) ?? metadata.ModelType;
var enumValues = Enum.GetValues(enumType).Cast<object>();
var items = from enumValue in enumValues
select new SelectListItem
{
Text = GetResourceValueForEnumValue(enumValue),
Value = ((int)enumValue).ToString(),
Selected = enumValue.Equals(metadata.Model)
};
return html.DropDownListFor(expression, items, string.Empty, null);
}
private static string GetResourceValueForEnumValue<TEnum>(TEnum enumValue)
{
var key = string.Format("{0}_{1}", enumValue.GetType().Name, enumValue);
return Enums.ResourceManager.GetString(key) ?? enumValue.ToString();
}
Resources in the Enums.Resx file looking like
ItemTypes_Movie : Film
One other thing I like to do is, instead of calling the extension method directly, I'd rather call it with a #Html.EditorFor(x => x.MyProperty), or ideally just have the whole form, in one neat #Html.EditorForModel(). To do this I change the string template to look like this
#using MVCProject.Extensions
#{
var type = Nullable.GetUnderlyingType(ViewData.ModelMetadata.ModelType) ?? ViewData.ModelMetadata.ModelType;
#(typeof (Enum).IsAssignableFrom(type) ? Html.EnumDropDownListFor(x => x) : Html.TextBoxFor(x => x))
}
If this interests you, I've put a much more detailed answer here on my blog:
http://paulthecyclist.com/2013/05/24/enum-dropdown/
Well I'm really late to the party, but for what it is worth, I have blogged about this very subject whereby I create a EnumHelper class that enables very easy transformation.
http://jnye.co/Posts/4/creating-a-dropdown-list-from-an-enum-in-mvc-and-c%23
In your controller:
//If you don't have an enum value use the type
ViewBag.DropDownList = EnumHelper.SelectListFor<MyEnum>();
//If you do have an enum value use the value (the value will be marked as selected)
ViewBag.DropDownList = EnumHelper.SelectListFor(MyEnum.MyEnumValue);
In your View:
#Html.DropDownList("DropDownList")
#* OR *#
#Html.DropDownListFor(m => m.Property, ViewBag.DropDownList as SelectList, null)
The helper class:
public static class EnumHelper
{
// Get the value of the description attribute if the
// enum has one, otherwise use the value.
public static string GetDescription<TEnum>(this TEnum value)
{
var fi = value.GetType().GetField(value.ToString());
if (fi != null)
{
var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Length > 0)
{
return attributes[0].Description;
}
}
return value.ToString();
}
/// <summary>
/// Build a select list for an enum
/// </summary>
public static SelectList SelectListFor<T>() where T : struct
{
Type t = typeof(T);
return !t.IsEnum ? null
: new SelectList(BuildSelectListItems(t), "Value", "Text");
}
/// <summary>
/// Build a select list for an enum with a particular value selected
/// </summary>
public static SelectList SelectListFor<T>(T selected) where T : struct
{
Type t = typeof(T);
return !t.IsEnum ? null
: new SelectList(BuildSelectListItems(t), "Text", "Value", selected.ToString());
}
private static IEnumerable<SelectListItem> BuildSelectListItems(Type t)
{
return Enum.GetValues(t)
.Cast<Enum>()
.Select(e => new SelectListItem { Value = e.ToString(), Text = e.GetDescription() });
}
}
I am very late on this one but I just found a really cool way to do this with one line of code, if you are happy to add the Unconstrained Melody NuGet package (a nice, small library from Jon Skeet).
This solution is better because:
It ensures (with generic type constraints) that the value really is an enum value (due to Unconstrained Melody)
It avoids unnecessary boxing (due to Unconstrained Melody)
It caches all the descriptions to avoid using reflection on every call (due to Unconstrained Melody)
It is less code than the other solutions!
So, here are the steps to get this working:
In Package Manager Console, "Install-Package UnconstrainedMelody"
Add a property on your model like so:
//Replace "YourEnum" with the type of your enum
public IEnumerable<SelectListItem> AllItems
{
get
{
return Enums.GetValues<YourEnum>().Select(enumValue => new SelectListItem { Value = enumValue.ToString(), Text = enumValue.GetDescription() });
}
}
Now that you have the List of SelectListItem exposed on your model, you can use the #Html.DropDownList or #Html.DropDownListFor using this property as the source.
I found an answer here. However, some of my enums have [Description(...)] attribute, so I've modified the code to provide support for that:
enum Abc
{
[Description("Cba")]
Abc,
Def
}
public static MvcHtmlString EnumDropDownList<TEnum>(this HtmlHelper htmlHelper, string name, TEnum selectedValue)
{
IEnumerable<TEnum> values = Enum.GetValues(typeof(TEnum))
.Cast<TEnum>();
List<SelectListItem> items = new List<SelectListItem>();
foreach (var value in values)
{
string text = value.ToString();
var member = typeof(TEnum).GetMember(value.ToString());
if (member.Count() > 0)
{
var customAttributes = member[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (customAttributes.Count() > 0)
{
text = ((DescriptionAttribute)customAttributes[0]).Description;
}
}
items.Add(new SelectListItem
{
Text = text,
Value = value.ToString(),
Selected = (value.Equals(selectedValue))
});
}
return htmlHelper.DropDownList(
name,
items
);
}
Hope that helps.
Another fix to this extension method - the current version didn't select the enum's current value. I fixed the last line:
public static SelectList ToSelectList<TEnum>(this TEnum enumObj) where TEnum : struct
{
if (!typeof(TEnum).IsEnum) throw new ArgumentException("An Enumeration type is required.", "enumObj");
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new
{
ID = (int)Enum.Parse(typeof(TEnum), e.ToString()),
Name = e.ToString()
};
return new SelectList(values, "ID", "Name", ((int)Enum.Parse(typeof(TEnum), enumObj.ToString())).ToString());
}
If you want to add localization support just change the s.toString() method to something like this:
ResourceManager rManager = new ResourceManager(typeof(Resources));
var dayTypes = from OperatorCalendarDay.OperatorDayType s in Enum.GetValues(typeof(OperatorCalendarDay.OperatorDayType))
select new { ID = s, Name = rManager.GetString(s.ToString()) };
In here the typeof(Resources) is the resource you want to load, and then you get the localized String, also useful if your enumerator has values with multiple words.
This is my version of helper method.
I use this:
var values = from int e in Enum.GetValues(typeof(TEnum))
select new { ID = e, Name = Enum.GetName(typeof(TEnum), e) };
Instead of that:
var values = from TEnum e in Enum.GetValues(typeof(TEnum))
select new { ID = (int)Enum.Parse(typeof(TEnum),e.ToString())
, Name = e.ToString() };
Here it is:
public static SelectList ToSelectList<TEnum>(this TEnum self) where TEnum : struct
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("self must be enum", "self");
}
Type t = typeof(TEnum);
var values = from int e in Enum.GetValues(typeof(TEnum))
select new { ID = e, Name = Enum.GetName(typeof(TEnum), e) };
return new SelectList(values, "ID", "Name", self);
}
You can also use my custom HtmlHelpers in Griffin.MvcContrib. The following code:
#Html2.CheckBoxesFor(model => model.InputType) <br />
#Html2.RadioButtonsFor(model => model.InputType) <br />
#Html2.DropdownFor(model => model.InputType) <br />
Generates:
https://github.com/jgauffin/griffin.mvccontrib
#Html.DropdownListFor(model=model->Gender,new List<SelectListItem>
{
new ListItem{Text="Male",Value="Male"},
new ListItem{Text="Female",Value="Female"},
new ListItem{Text="--- Select -----",Value="-----Select ----"}
}
)
I would like to answer this question in a different way where, user need not to do anything in controller or Linq expression. This way...
I have a ENUM
public enum AccessLevelEnum
{
/// <summary>
/// The user cannot access
/// </summary>
[EnumMember, Description("No Access")]
NoAccess = 0x0,
/// <summary>
/// The user can read the entire record in question
/// </summary>
[EnumMember, Description("Read Only")]
ReadOnly = 0x01,
/// <summary>
/// The user can read or write
/// </summary>
[EnumMember, Description("Read / Modify")]
ReadModify = 0x02,
/// <summary>
/// User can create new records, modify and read existing ones
/// </summary>
[EnumMember, Description("Create / Read / Modify")]
CreateReadModify = 0x04,
/// <summary>
/// User can read, write, or delete
/// </summary>
[EnumMember, Description("Create / Read / Modify / Delete")]
CreateReadModifyDelete = 0x08,
/*/// <summary>
/// User can read, write, or delete
/// </summary>
[EnumMember, Description("Create / Read / Modify / Delete / Verify / Edit Capture Value")]
CreateReadModifyDeleteVerify = 0x16*/
}
Now I canto simply create a dropdown by using this enum.
#Html.DropDownList("accessLevel",new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum))),new { #class = "form-control" })
OR
#Html.DropDownListFor(m=>m.accessLevel,new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum))),new { #class = "form-control" })
If you want to make a index selected then try this
#Html.DropDownListFor(m=>m.accessLevel,new SelectList(AccessLevelEnum.GetValues(typeof(AccessLevelEnum)) , AccessLevelEnum.NoAccess ),new { #class = "form-control" })
Here I have used AccessLevelEnum.NoAccess as an extra parameter for default selecting the dropdown.
#Simon Goldstone: Thanks for your solution, it can be perfectly applied in my case. The only problem is I had to translate it to VB. But now it is done and to save other people's time (in case they need it) I put it here:
Imports System.Runtime.CompilerServices
Imports System.ComponentModel
Imports System.Linq.Expressions
Public Module HtmlHelpers
Private Function GetNonNullableModelType(modelMetadata As ModelMetadata) As Type
Dim realModelType = modelMetadata.ModelType
Dim underlyingType = Nullable.GetUnderlyingType(realModelType)
If Not underlyingType Is Nothing Then
realModelType = underlyingType
End If
Return realModelType
End Function
Private ReadOnly SingleEmptyItem() As SelectListItem = {New SelectListItem() With {.Text = "", .Value = ""}}
Private Function GetEnumDescription(Of TEnum)(value As TEnum) As String
Dim fi = value.GetType().GetField(value.ToString())
Dim attributes = DirectCast(fi.GetCustomAttributes(GetType(DescriptionAttribute), False), DescriptionAttribute())
If Not attributes Is Nothing AndAlso attributes.Length > 0 Then
Return attributes(0).Description
Else
Return value.ToString()
End If
End Function
<Extension()>
Public Function EnumDropDownListFor(Of TModel, TEnum)(ByVal htmlHelper As HtmlHelper(Of TModel), expression As Expression(Of Func(Of TModel, TEnum))) As MvcHtmlString
Return EnumDropDownListFor(htmlHelper, expression, Nothing)
End Function
<Extension()>
Public Function EnumDropDownListFor(Of TModel, TEnum)(ByVal htmlHelper As HtmlHelper(Of TModel), expression As Expression(Of Func(Of TModel, TEnum)), htmlAttributes As Object) As MvcHtmlString
Dim metaData As ModelMetadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData)
Dim enumType As Type = GetNonNullableModelType(metaData)
Dim values As IEnumerable(Of TEnum) = [Enum].GetValues(enumType).Cast(Of TEnum)()
Dim items As IEnumerable(Of SelectListItem) = From value In values
Select New SelectListItem With
{
.Text = GetEnumDescription(value),
.Value = value.ToString(),
.Selected = value.Equals(metaData.Model)
}
' If the enum is nullable, add an 'empty' item to the collection
If metaData.IsNullableValueType Then
items = SingleEmptyItem.Concat(items)
End If
Return htmlHelper.DropDownListFor(expression, items, htmlAttributes)
End Function
End Module
End You use it like this:
#Html.EnumDropDownListFor(Function(model) (model.EnumField))
I ended up creating extention methods to do what is essentially the accept answer here. The last half of the Gist deals with Enum specifically.
https://gist.github.com/3813767
#Html.DropDownListFor(model => model.MaritalStatus, new List<SelectListItem>
{
new SelectListItem { Text = "----Select----", Value = "-1" },
new SelectListItem { Text = "Marrid", Value = "M" },
new SelectListItem { Text = "Single", Value = "S" }
})

Generate label with HTML content

Using MVC and Razor, I want to create a label for a field that contains HTML, namely a hyperlink. However, when I use the Html.LabelFor() method, all HTML is encoded on output.
This screenshot shows the desired result and what MVC actually outputs instead:
Is there a way to generate a label for my model property that will correctly render HTML content?
My ViewModel:
[DisplayName("I accept the Terms & conditions")]
public bool AcceptedTermsAndConditions { get; set; }
My view:
#Html.EditorFor(m => m.AcceptedTermsAndConditions)
#Html.LabelFor(m => m.AcceptedTermsAndConditions)
I also tried to directly pass the content as a "labeltext" parameter, without success:
#Html.LabelFor(m => m.AcceptedTermsAndConditions, "I accept the Terms & conditions")
I ended up writing my own HtmlHelper method based on the decompiled sources of Html.LabelFor():
public static IHtmlString HtmlLabelFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, string labelText = null, object htmlAttributes = null)
{
string str = labelText;
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var htmlFieldName = ExpressionHelper.GetExpressionText(expression);
if (str == null)
{
string displayName = metadata.DisplayName;
if (displayName == null)
{
string propertyName = metadata.PropertyName;
str = propertyName ?? htmlFieldName.Split(new[] {'.'}).Last();
}
else
{
str = displayName;
}
}
string innerHtml = str;
if (string.IsNullOrEmpty(innerHtml))
return MvcHtmlString.Empty;
var tagBuilder = new TagBuilder("label");
tagBuilder.Attributes.Add("for", TagBuilder.CreateSanitizedId(html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName)));
tagBuilder.InnerHtml = innerHtml;
tagBuilder.MergeAttributes(HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), true);
return new MvcHtmlString(tagBuilder.ToString(TagRenderMode.Normal));
}
The major change is that I use
tagBuilder.InnerHtml = ..
instead of the original
tagBuilder.SetInnerText(..)
This way the provided labelText does not end up being HTML-encoded.
Usage for my case is:
#Html.HtmlLabelFor(m => m.AcceptedTermsAndConditions)
I got it. So instead of
#Html.LabelFor(m => m.AcceptedTermsAndConditions)
use
Html.GetDisplayName(x => x.AcceptedTermsAndConditions)
Not the cutest, but solution:
#Html.Raw(#HttpUtility.HtmlDecode( Html.LabelFor(x=>x.AcceptedTermsAndConditions).ToString()))
try to use this:
[DisplayName("I accept the <a href='Terms & conditions'>Terms & conditions</a>")]

Razor DropDownListFor: Adding Extra Attribute To SelectList Option Tag

I'm trying to create a select list. I've created it just fine using a collection from my viewmodel that allows me to set each option's value and text with the following code:
#Html.DropDownListFor(model => model.Networks, new SelectList(Model.Networks, "NetworkID", "Name"), new { #class="form-control" })
Model.Networks contains another property called CountryId. I'd like to add an attribute to each option tag so it looks like:
<option value="[NetworkId]" data-countryId="[CountryId]">Canada</option>
Which way should I go about doing this?
You can create a Form Helper class to create a custom drop down list, and create a custom 'selectListItem' class that has an extra property 'itemsHtmlAttributes' of type IDictionary - see below. You may need to play around with the 'id' or 'name' attributes to get the default model binding working. Below is a bit messy, I would suggest using TagBuilder to build the 'select' and 'option' tags:
public class SelectListItemCustom : SelectListItem
{
public IDictionary<string, object> itemsHtmlAttributes { get; set; }
}
public static class FormHelper
{
public static MvcHtmlString DropDownListForCustom(this HtmlHelper htmlHelper, string id, List<SelectListItemCustom> selectListItems)
{
var selectListHtml = "";
foreach (var item in selectListItems)
{
var attributes = new List<string>();
foreach (KeyValuePair<string, string> dictItem in item.itemsHtmlAttributes)
{
attributes.Add(string.Format("{0}='{1}'", dictItem.Key, dictItem.Value));
}
// do this or some better way of tag building
selectListHtml += string.Format(
"<option value='{0}' {1} {2}>{3}</option>", item.Value,item.Selected ? "selected" : string.Empty,string.Join(" ", attributes.ToArray()),item.Text);
}
// do this or some better way of tag building
var html = string.Format("<select id='{0}' name='{0}'>{1}</select>", id, selectListHtml);
return new MvcHtmlString(html);
}
}
VIEW:
#{
var item = new SelectListItemCustom { Selected = true, Value = "123", Text = "Australia", itemsHtmlAttributes = new Dictionary<string, object> { { "countrycode", "au" } } };
var items = new List<SelectListItemCustom> { item };
Html.Raw(Html.DropDownListForCustom("insertIdHere", items))
}

Binding conflict between a property named Title in my Model and View.Title in my View (in MVC)

My Model contains a property named Title, and in my Create view I set the page title using ViewBag.Title.
This creates the following problem: the form generated by Html.Editor will display the text from ViewBag.Title, instead of the model's Title value.
The only workaround I have found is first calling Html.Editor, and then setting the View.Title.
Does anyone have a better solution?
Edit 1: I am using MVC 3.
Edit 2: This is my DisplayTemplates/Object.cshtml:
#model dynamic
#using Iconum.VS10CS040.Library.Web.MVC3.Helpers
#if (ViewData.TemplateInfo.TemplateDepth > 1) {
<span class="editor-object simple">#ViewData.ModelMetadata.SimpleDisplayText</span>
} else {
foreach (var prop in ViewData.ModelMetadata.Properties.Where(
pm =>
pm.ShowForEdit
&& !ViewData.TemplateInfo.Visited(pm)
&& pm.ModelType != typeof(System.Data.EntityState)
&& !pm.IsComplexType
)
)
{
if (prop.HideSurroundingHtml) {
<text>#Html.Editor(prop.PropertyName)</text>
} else {
string css = "";
if (prop.Model != null && prop.Model.GetType() != null)
{
css += " " + prop.Model.GetType().ToString().ToLower().Replace('.', '-');
}
if (prop.DataTypeName != null)
{
css += " " + prop.DataTypeName.ToLower();
}
if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
{
css += " required";
}
<div class="editor-container #css">
<div class="editor-label">
#if (!String.IsNullOrEmpty(Html.Label(prop.PropertyName).ToHtmlString()))
{
// Use LabelWithForThatMatchesTheIdOfTheInput instead of Label because of a bug (fixed in MVC 3)
#Html.LabelWithForThatMatchesTheIdOfTheInput(prop.PropertyName)
}
#if (prop.IsRequired && prop.ModelType.FullName != "System.Boolean")
{
#Html.Raw(" <span class=\"required\">*<span>");
}
</div>
<div class="editor-field">
#* This the line that causes my problem *#
#Html.Editor(prop.PropertyName)
#Html.ValidationMessage(prop.PropertyName)
</div>
</div>
}
} //foreach
// Loop though all items in the Model with an TemplateHint (UIHint)
foreach (var prop in ViewData.ModelMetadata.Properties.Where(
pm => pm.ShowForEdit
&& !ViewData.TemplateInfo.Visited(pm)
&& pm.ModelType != typeof(System.Data.EntityState)
&& !pm.IsComplexType
&& pm.TemplateHint != null
&& (
pm.TemplateHint == "jWYSIWYG0093"
||
pm.TemplateHint == "jQueryUIDatepicker"
||
pm.TemplateHint == "CKEditor"
)
)
)
{
// TODO: check for duplicate js file includes
#Html.Editor(prop.PropertyName, prop.TemplateHint + "-Script")
}
}
I would recommend using EditorFor instead of Editor.
Html.EditorFor(x => x.Title)
instead of:
Html.Editor("Title")
This way not only that the view takes advantage of your view model but it behaves as expected in this case.
Example with ASP.NET MVC 3.0 RTM (Razor):
Model:
public class MyViewModel
{
public string Title { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Title = "ViewBag title";
ViewData["Title"] = "ViewData title";
var model = new MyViewModel
{
Title = "Model title"
};
return View(model);
}
}
View:
#model AppName.Models.MyViewModel
#{
ViewBag.Title = "Home Page";
}
#Html.EditorFor(x => x.Title)
#{
ViewBag.Title = "Some other title";
}
So no matter how much we try to abuse here the editor template uses the correct model title (which is not the case if we used Html.Editor("Title")).
As suggested by the other answers, using EditorFor instead of Editor seems to work around the problem. However, using EditorFor requires knowledge of the model type and property type at compile-time, which isn't the case for Object.cshtml.
You can still do this by building up and calling the correct generically-constructed EditorFor method using reflection. The code to do this is really messy, so here are some re-usable extension methods to do it for you.
Use them like this in Object.cshtml where prop is an instance of ModelMetadata like in the question:
#Html.DisplayFor(prop)
#Html.LabelFor(prop)
#Html.EditorFor(prop)
#Html.ValidationMessageFor(prop)
Here are the extension methods:
using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
namespace ASP
{
public static class NonStronglyTypedStronglyTypedHtmlHelpers
{
public static MvcHtmlString DisplayFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
{
return StronglyTypedHelper(html, h => h.DisplayFor, prop);
}
public static MvcHtmlString EditorFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
{
return StronglyTypedHelper(html, h => h.EditorFor, prop);
}
public static MvcHtmlString LabelFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
{
return StronglyTypedHelper(html, h => h.LabelFor, prop);
}
public static MvcHtmlString ValidationMessageFor<TModel>(this HtmlHelper<TModel> html, ModelMetadata prop)
{
return StronglyTypedHelper(html, h => h.ValidationMessageFor, prop);
}
private static MvcHtmlString StronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
{
var constructedMethod = MakeStronglyTypedHelper(html, accessMethod, prop);
var genericPropertyExpression = MakePropertyExpression(prop);
var typedHtmlHelper = MakeStronglyTypedHtmlHelper(html, prop.ContainerType);
return (MvcHtmlString)constructedMethod.Invoke(null, new object[] { typedHtmlHelper, genericPropertyExpression });
}
private static MethodInfo MakeStronglyTypedHelper(HtmlHelper html, Func<HtmlHelper<object>, GenericHelper<object>> accessMethod, ModelMetadata prop)
{
var objectTypeHelper = new HtmlHelper<object>(html.ViewContext, html.ViewDataContainer, html.RouteCollection);
var runMethod = accessMethod(objectTypeHelper);
var constructedMehtod = runMethod.Method;
var genericHelperDefinition = constructedMehtod.GetGenericMethodDefinition();
return genericHelperDefinition.MakeGenericMethod(prop.ContainerType, prop.ModelType);
}
private static object MakeStronglyTypedHtmlHelper(HtmlHelper html, Type type)
{
var genericTypeDefinition = typeof(HtmlHelper<>);
var constructedType = genericTypeDefinition.MakeGenericType(type);
var constructor = constructedType.GetConstructor(new[] { typeof(ViewContext), typeof(IViewDataContainer), typeof(RouteCollection) });
return constructor.Invoke(new object[] { html.ViewContext, html.ViewDataContainer, html.RouteCollection });
}
private static LambdaExpression MakePropertyExpression(ModelMetadata prop)
{
var propertyInfo = prop.ContainerType.GetProperty(prop.PropertyName);
var expressionParameter = Expression.Parameter(prop.ContainerType);
var propertyExpression = Expression.MakeMemberAccess(expressionParameter, propertyInfo);
return Expression.Lambda(propertyExpression, expressionParameter);
}
private delegate MvcHtmlString GenericHelper<TModel>(Expression<Func<TModel, object>> expression);
}
}
I found partial solution myself.
Just use:
#Html.EditorForModel()
instead of:
#foreach (var property in Model.GetMetadata().Properties)
{
<div class="editor-label">
#Html.Label(property.PropertyName)
</div>
<div class="editor-field">
#Html.Editor(property.PropertyName)
#Html.ValidationMessage(property.PropertyName)
</div>
}
Html.EditorForModel() method return same results, but without described problem.
I solve same problem. Use this syntax instead Html.Editor
#(Html.EditorFor(p => property.Model))

Creating a SelectListItem with the disabled="disabled" attribute

I'm not seeing a way to create, via the HtmlHelper, a SelectListItem that will spit out the following HTML:
<option disabled="disabled">don't click this</option>
The only properties SelectListItem has are:
new SelectListItem{
Name = "don't click this",
Value = string.Empty,
Selected = false
}
The only option I see is to
Subclass the SelectListItem to add an Enabled property to get the value to the view
Not use the HTML helper for DropDownList
Create a new HtmlHelper extension that accepts my new EnablableSelectList and adds my disabled attribute.
The Disabled property is supported since ASP.NET MVC 5.2:
new SelectListItem {
// ...
Disabled = true
}
See the API reference.
This is something I might try before recreating the helper completely. The basic idea is that the Html you get from the helper should be well formed, so it should be safe to parse. So you can build on that idea by making your own extension that uses the existing extension but adds the functionality to disable the items.
Something like this might do (totally untested)
public class CustomSelectItem : SelectListItem
{
public bool Enabled { get; set; }
}
public static class CustomHtmlHelpers
{
public static MvcHtmlString MyDropDownList(this HtmlHelper html, IEnumerable<CustomSelectItem> selectList)
{
var selectDoc = XDocument.Parse(html.DropDownList("", (IEnumerable<SelectListItem>)selectList).ToString());
var options = from XElement el in selectDoc.Element("select").Descendants()
select el;
foreach (var item in options)
{
var itemValue = item.Attribute("value");
if (!selectList.Where(x => x.Value == itemValue.Value).Single().Enabled)
item.SetAttributeValue("disabled", "disabled");
}
// rebuild the control, resetting the options with the ones you modified
selectDoc.Root.ReplaceNodes(options.ToArray());
return MvcHtmlString.Create(selectDoc.ToString());
}
}
Clientside option: if you for example give your dropdownlist a class 'custom' and the items that should be unselectable the value -1 (for example), then you can do something like:
$('select.custom option[value=-1]').each(function () {
$(this).attr("disabled", "disabled");
});
If all you are trying to do is prevent a user from selecting a certain value from the list, it seems like the simpler and more time-efficient way to do it is to use input validation. Which you may quite possibly be doing anyways, if you want to verify they've made a selection to begin with.
-----Option 1
Controller:
var ExpectedShipmentsRange = new List();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "Selected number of shipments", Value="0", Disabled = true, Selected = true });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = ExpectedShipmentsRange;
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (IEnumerable<SelectListItem>)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
-----Option 2
Controller:
ViewBag.citiesSa = _dbContext.Countries.ToList();
View:
#Html.DropDownListFor(m => m.City, new SelectList(#ViewBag.citiesSa, "Id", "Name"), "Select your city", new { #class = "form-control" })
-----Option 3 does not support disabled option:
List<SelectListItem> ExpectedShipmentsRange = new List<SelectListItem>();
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "0 to 20 shipments", Value = "0-20" });
ExpectedShipmentsRange.Add(new SelectListItem() { Text = "20 to 40 shipments", Value = "20-40" });
ViewBag.ExpectedShipmentsRange = new SelectList(ExpectedShipmentsRange, "Value", "Text");
View:
#Html.DropDownListFor(m => m.ExpectedShipments, (SelectList)#ViewBag.ExpectedShipmentsRange, new { #class = "form-control" })
I've noticed that while using SelectList to populate the DropDownListFor() method the Disabled & Selected parameters are not respected. They are only honored when populating using List<SelectListItem>. However I've run into other odd bugs when populating the DropDownListFor() using List<SelectListItem> and found that using SelectList is the correct option for populating a DropDownListFor() list. Here's an example of how to create a SelectList list:
public static SelectList States = new SelectList(new[]
{
new SelectListItem { Text = "AL", Value = "AL" },
new SelectListItem { Text = "AK", Value = "AK" },
...
}, "Value", "Text", 1);
In my case I needed to disable the first item of the select list, the only way I was able to do so using the SelectList was by creating an extension method for the DropDownListFor() method. Here is the class I used:
public static class HtmlHelperExtensions
{
private static MvcHtmlString DisableFirstItemDropDownListFor(MvcHtmlString source, string sourceItemName, string sourceItemValue = "", string targetItemValue = "")
{
string htmlString = source.ToHtmlString();
if (string.IsNullOrEmpty(sourceItemValue))
{
htmlString = htmlString.Replace("value=\"\"", "value=\"\" disabled=\"disabled\" selected=\"selected\"");
}
return new MvcHtmlString(htmlString);
}
public static MvcHtmlString DisableFirstItemDropDownListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression, IEnumerable<SelectListItem> selectList, object htmlAttributes)
{
return DisableFirstItemDropDownListFor(htmlHelper.DropDownListFor(expression, selectList, htmlAttributes), string.Empty);
}
}
You can then use this method in your .cshtml file like so:
#Html.DisableFirstItemDropDownListFor(x => x.YourFieldType, Model.YourModel, new { #class = "YourClass" })

Resources