Related
In my View, I'm making an entire html table editable. Each field in the table has validation. Here is some sample code...
<tbody>
foreach (Item item in Model.Items)
{
<tr>
<td>
#Html.TextBoxFor(x => em.Value)
#Html.ValidationMessageFor(x => em.Value)
</td>
</tr>
}
</tbody>
This is creating two problems on the browser.
1) Since the name attribute for all the controls are the same, when one field is invalid, the error messages are displayed on all fields of the same name.
2) Probably related to the names as well, when validating the form, if the control in the first row is invalid, the form is invalid. But controls in all other rows will not invalidate the form (even though the error messages are displayed).
if (!$(form).valid()) {
return false;
}
Any thoughts would be helpful.
UPDATE: THE SOLUTION
Per bobek suggestion, I created custom extensions for TextBoxInLoopFor and ValidationMessageInLoop. Below is the solution:
public static MvcHtmlString TextBoxInLoopFor<TModel, IItem, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IItem item, object htmlAttributes)
{
var model = item as IInLoopForModel;
if (model == null)
return MvcHtmlString.Empty;
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
var fieldName = ExpressionHelper.GetExpressionText(expression);
var name = model.Name + fieldName;
var tag = new TagBuilder("input");
tag.MergeAttributes(new RouteValueDictionary(htmlAttributes));
tag.Attributes.Add("id", name);
tag.Attributes.Add("for", name);
tag.Attributes.Add("name", name);
tag.Attributes.Add("value", metadata.Model.ToString());
// Add the validation attributes
ModelState modelState;
if (html.ViewData.ModelState.TryGetValue(name, out modelState))
{
if (modelState.Errors.Count > 0)
tag.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
tag.MergeAttributes(html.GetUnobtrusiveValidationAttributes(name, metadata));
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
public static MvcHtmlString ValidationMessageInLoopFor<TModel, IItem, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IItem item)
{
var model = item as IInLoopForModel;
if (model == null)
return MvcHtmlString.Empty;
var fieldName = ExpressionHelper.GetExpressionText(expression);
var name = model.Name + fieldName;
var tag = new TagBuilder("span");
tag.Attributes.Add("class", "field-validation-valid");
tag.Attributes.Add("data-valmsg-replace", "true");
tag.Attributes.Add("data-valmsg-for", name);
return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
}
This is not valid at all. MVC3 helpers for input are adding an ID for each one so here you will have multiple textboxes with same ID - it's not valid in HTML.
You should manually create a TextBoxFor and ValidationFor your each property on the model, or if it's possible try:
foreach (Item item in Model.Items)
{
<tr>
<td>
#Html.TextBoxFor(x => item.Value)
#Html.ValidationMessageFor(x => item.Value)
</td>
</tr>
}
I would suggest using metadata in your domain.model . Here is a small example for you to understand the process :
namespace yourDomain.Domain.Model
{
[MetadataType(typeof(fooEntityMeta))]
public partial class fooEntity
{
}
and pick the value field from your entity; make it required
public class fooEntityMeta
{
[Required(ErrorMessage = "Value is required")]
public string Value;
...
}
here's a link that will help you through the process : http://msdn.microsoft.com/en-us/magazine/ee336030.aspx
I wrote an HtmlHelper expression I use a lot of the time to put title tags into my dropdown lists like so:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
string idPropertyName,
string displayPropertyName,
string titlePropertyName,
object htmlAttributes) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">\n";
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == idPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var titleValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == titlePropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
var displayValue = enumeratedType.GetProperties()
.FirstOrDefault(p => p.Name == displayPropertyName)
.GetValue(listItem, null).ToStringOrEmpty();
returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected=\"selected\"";
}
returnText += string.Format(">{0}</option>\n", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
...this works swimmingly, but there are times when I want to go further. I'd like to customize the id, display, and title pieces of this beast without having to write out the html. For example, if I have some classes in a model like so:
public class item
{
public int itemId { get; set; }
public string itemName { get; set; }
public string itemDescription { get; set; }
}
public class model
{
public IEnumerable<item> items { get; set; }
public int itemId { get; set; }
}
In my view I can write:
#Html.SelectFor(m => m.itemId, Model.items, "itemId", "itemName", "itemDescription", null)
...and I'll get a nice dropdown with title attributes etc. This is great as long as the enumerated items have properties exactly as I'd like to display them. But what I'd really like to do is something like:
#Html.SelectFor(m => m.itemId, Model.items, id=>id.itemId, disp=>disp.itemName, title=>title.itemName + " " + title.itemDescription, null)
...and have, in this case, the title attribute on the options be a concatenation of the itemName property and the itemDescription property. I confess the meta-level of lambda expressions and Linq functions has got me a little dizzy. Can someone point me in the right direction?
FINAL RESULT For those who are curious, the following code gives me complete control over the select list's ID, Title, and DisplayText properties using lambda expressions:
public static HtmlString SelectFor<TModel, TProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> forExpression,
IEnumerable<TListItem> enumeratedItems,
Attribute<TListItem> idExpression,
Attribute<TListItem> displayExpression,
Attribute<TListItem> titleExpression,
object htmlAttributes,
bool blankFirstLine) where TModel : class
{
//initialize values
var metaData = ModelMetadata.FromLambdaExpression(forExpression, htmlHelper.ViewData);
var propertyName = metaData.PropertyName;
var propertyValue = htmlHelper.ViewData.Eval(propertyName).ToStringOrEmpty();
var enumeratedType = typeof(TListItem);
//build the select tag
var returnText = string.Format("<select id=\"{0}\" name=\"{0}\"", HttpUtility.HtmlEncode(propertyName));
if (htmlAttributes != null)
{
foreach (var kvp in htmlAttributes.GetType().GetProperties()
.ToDictionary(p => p.Name, p => p.GetValue(htmlAttributes, null)))
{
returnText += string.Format(" {0}=\"{1}\"", HttpUtility.HtmlEncode(kvp.Key),
HttpUtility.HtmlEncode(kvp.Value.ToStringOrEmpty()));
}
}
returnText += ">\n";
if (blankFirstLine)
{
returnText += "<option value=\"\"></option>";
}
//build the options tags
foreach (TListItem listItem in enumeratedItems)
{
var idValue = idExpression(listItem).ToStringOrEmpty();
var displayValue = displayExpression(listItem).ToStringOrEmpty();
var titleValue = titleExpression(listItem).ToStringOrEmpty();
returnText += string.Format("<option value=\"{0}\" title=\"{1}\"",
HttpUtility.HtmlEncode(idValue), HttpUtility.HtmlEncode(titleValue));
if (idValue == propertyValue)
{
returnText += " selected=\"selected\"";
}
returnText += string.Format(">{0}</option>\n", displayValue);
}
//close the select tag
returnText += "</select>";
return new HtmlString(returnText);
}
public delegate object Attribute<T>(T listItem);
If you don't need the title attribute on individual options your code could be simplified to:
public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
Expression<Func<TListItem, TIdProperty>> idProperty,
Expression<Func<TListItem, TDisplayProperty>> displayProperty,
object htmlAttributes
) where TModel : class
{
var id = (idProperty.Body as MemberExpression).Member.Name;
var display = (displayProperty.Body as MemberExpression).Member.Name;
var selectList = new SelectList(enumeratedItems, id, display);
var attributes = new RouteValueDictionary(htmlAttributes);
return htmlHelper.DropDownListFor(expression, selectList, attributes);
}
and used like this:
#Html.SelectFor(
m => m.itemId,
Model.items,
id => id.itemId,
disp => disp.itemName,
null
)
And if you need the title attribute, well, you will have to implement everything that the DropDownList helper does manually which could be quite of a pain. Here's an example of only a small portion of all the functionality:
public static class HtmlExtensions
{
private class MySelectListItem : SelectListItem
{
public string Title { get; set; }
}
public static HtmlString SelectFor<TModel, TProperty, TIdProperty, TDisplayProperty, TListItem>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<TListItem> enumeratedItems,
Expression<Func<TListItem, TIdProperty>> idProperty,
Expression<Func<TListItem, TDisplayProperty>> displayProperty,
Func<TListItem, string> titleProperty,
object htmlAttributes
) where TModel : class
{
var name = ExpressionHelper.GetExpressionText(expression);
var fullHtmlName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
var select = new TagBuilder("select");
var compiledDisplayProperty = displayProperty.Compile();
var compiledIdProperty = idProperty.Compile();
select.GenerateId(fullHtmlName);
select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
select.Attributes["name"] = fullHtmlName;
var selectedValue = htmlHelper.ViewData.Eval(fullHtmlName);
var options =
from i in enumeratedItems
select ListItemToOption(
ItemToSelectItem(i, selectedValue, compiledIdProperty, compiledDisplayProperty, titleProperty)
);
select.InnerHtml = string.Join(Environment.NewLine, options);
return new HtmlString(select.ToString(TagRenderMode.Normal));
}
private static MySelectListItem ItemToSelectItem<TListItem, TIdProperty, TDisplayProperty>(TListItem i, object selectedValue, Func<TListItem, TIdProperty> idProperty, Func<TListItem, TDisplayProperty> displayProperty, Func<TListItem, string> titleProperty)
{
var value = Convert.ToString(idProperty(i));
return new MySelectListItem
{
Value = value,
Text = Convert.ToString(displayProperty(i)),
Title = titleProperty(i),
Selected = Convert.ToString(selectedValue) == value
};
}
private static string ListItemToOption(MySelectListItem item)
{
var builder = new TagBuilder("option");
builder.Attributes["value"] = item.Value;
builder.Attributes["title"] = item.Title;
builder.SetInnerText(item.Text);
if (item.Selected)
{
builder.Attributes["selected"] = "selected";
}
return builder.ToString();
}
}
and then use like this:
#Html.SelectFor(
m => m.itemId,
Model.items,
id => id.itemId,
disp => disp.itemName,
title => title.itemName + " " + title.itemDescription,
null
)
Hi so I'm pretty new to MVC3 and Razor and I've been trying to get my head around it the past few days. I've been given a task by my project architect to create a helper method that sorts a drop down list in an MVC View. I have a View that retrieves various data from a Controller and I'm returning some values that I want to appear in a drop down list. I've been told not to sort it in the Controller and also to pass the field that we want to sort by into the helper method. I could do it like below but the architect wants to keep the view free of c sharp code:
#Html.DropDownListFor(model => model.StudyName, new SelectList(ViewBag.StudyTypes, "Value", "Text").OrderBy(l => l.Text))
So I've created some sample code and some extension methods to try and get it to work. My idea is to replicate the existing Html.DropDownList method and allow the passing of 'object htmlAttributes' so I can set the style as part of the method call.
Here's my code so far. I'm returning the data for the drop down in ViewBag.StudyTypes in the Edit Controller method:
public ActionResult Edit(int id)
{
IEnumerable<SelectListItem> mySelectList = new List<SelectListItem>();
IList<SelectListItem> myList = new List<SelectListItem>();
for (int i = 0; i < 5; i++)
{
myList.Add(new SelectListItem()
{ Value = i.ToString(), Text = "My Item " + i.ToString(), Selected = i == 2 }
);
}
mySelectList = myList;
ViewBag.StudyTypes = mySelectList;
StudyDefinition studydefinition = db.StudyDefinitions.Find(id);
return View(studydefinition);
}
Here's my View code:
#model MyStudyWeb.Models.StudyDefinition
#using MyStudyWeb.Helpers
#{
ViewBag.Mode = "Edit";
}
<div>
#Html.DropDownListSorted(new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
#Html.DropDownListSorted("MyList", new SelectList(ViewBag.StudyTypes, "Value", "Text"))<br />
</div>
Finally below are the extension methods I'm trying to get to work. The first extension method does nothing, I just get a blank space at that point in the View. The second method kind of works but it's ugly. For the 3rd method I don't know how to specify an 'order by' parameter as the OrderBy on an IEnumerable expects a Linq expression.
namespace StudyDefinition.Helpers
{
public static class HtmlHelperExtensions
{
// 1st sort method: sort the passed in list and return a new sorted list
public static SelectList DropDownListSorted(this HtmlHelper helper, IEnumerable<SelectListItem> selectList)
{
var x = new SelectList(selectList.ToList()).OrderBy(l => l.Text);
return x as SelectList;
}
// 2nd sort method: return IHtml string and create <select> list manually
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList)
{
StringBuilder output = new StringBuilder();
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
// 3rd sort method: pass in order by parameter - how do I use this?
public static IHtmlString DropDownListSorted(this HtmlHelper helper, string name, SelectList selectList, string orderBy)
{
StringBuilder output = new StringBuilder();
//How do I use the orderBy parameter?
(selectList).OrderBy(l => l.Text);
output.Append("<select id=" + name + " name=" + name + ">");
foreach (var item in selectList)
{
output.Append("<option value=" + item.Value.ToString() + ">" + item.Text + "</option>");
}
output.Append("</select>");
return MvcHtmlString.Create(output.ToString());
}
}
}
I really don't know the best approach to take, there may be a much simpler way that I'm totally missing and I might be at the point where I can't see the wood for the trees anymore. Some questions
Should I return a SelectList or an MvcHtmlString, or something else entirely?
For the first extension method how do I get the returned SelectList to render in the View?
How to I pass in a parameter to my extension methods that specifies the sort order?
How do I pass an 'object htmlAttributes' parameter, and how do I apply this object / parameter to the SelectList?
If anyone has some ideas or suggestions then I'd appreciate some feedback :)
The first and most important part of your code would be to get rid of any ViewBag/ViewData (which I personally consider as cancer for MVC applications) and use view models and strongly typed views.
So let's start by defining a view model which would represent the data our view will be working with (a dropdownlistg in this example):
public class MyViewModel
{
public string SelectedItem { get; set; }
public IEnumerable<SelectListItem> Items { get; set; }
}
then we could have a controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// I am explicitly putting some items out of order
Items = new[]
{
new SelectListItem { Value = "5", Text = "Item 5" },
new SelectListItem { Value = "1", Text = "Item 1" },
new SelectListItem { Value = "3", Text = "Item 3" },
new SelectListItem { Value = "4", Text = "Item 4" },
}
};
return View(model);
}
}
and a view:
#model MyViewModel
#Html.DropDownListForSorted(
x => x.SelectedItem,
Model.Items,
new { #class = "foo" }
)
and finally the last piece is the helper method which will sort the dropdown by value (you could adapt it to sort by text):
public static class HtmlExtensions
{
public static IHtmlString DropDownListForSorted<TModel, TProperty>(
this HtmlHelper<TModel> helper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> items,
object htmlAttributes
)
{
var model = helper.ViewData.Model;
var orderedItems = items.OrderBy(x => x.Value);
return helper.DropDownListFor(
expression,
new SelectList(orderedItems, "Value", "Text"),
htmlAttributes
);
}
}
Just add in the sorting before you return the items to the dropdown list.
Do this:
Models: StudyViewModel.cs
public class StudyViewModel {
public string StudyName { get; set; }
public string StudyTypes { get; set; }
}
Controller: StudyController.cs
using System.Web.Mvc;
public class StudyController
{
public List<SelectListItem> studyTypes()
{
List<SelectListItem> itemList = new List<SelectListItem>();
for (var i=0; i<5; i++)
{
itemList.Add = new SelectListItem({
Value = i.ToString();
Text = "My Item";
});
}
// You can sort here....
List<SelectListItem> sortedList = itemList.OrderBy(x=>x.Text);
return sortedList;
}
public ActionResult Edit(int id)
{
//You won't need this because you get it using your
//controller's routine, instead
//ViewBag.StudyTypes = studySlots.OrderBy(e => e.Value);
//-- unless you need to add these values to the model for
// some reason (outside of filling the ddl), in which case....
// StudyViewModel svm = new StudyViewModel();
// svm.StudyTypes = studySlots.OrderBy(e => e.Value);
// svm.StudyName = "My Item";
// return View(svm);
// Otherwise, just....
return View();
}
}
View: Edit.cshtml
#Html.DropDownListFor(model => model.StudyName)
.OptionLabel('Select...')
.DataTextField('Text')
.DataValueField('Value')
.Datasource(source =>
{
// This is where you populate your data from the controller
source.Read(read =>
{
read.Action("studyTypes", "Study");
});
})
.Value(Model.StudyName != null ? Model.StudyName.ToString() : "")
)
This way will avoid ViewBags and just use a function to fill in the values, directly.
If you are using a database you can use a query to define the sort element
using (BDMMContext dataContext = new BDMMContext())
{
foreach (Arquiteto arq in dataContext.Arquitetos.SqlQuery("SELECT * FROM Arquitetos ORDER BY Nome"))
{
SelectListItem selectItem = new SelectListItem { Text = arq.Nome, Value = arq.Arquiteto_Id.ToString() };
//
list.Add(selectItem);
}
}
My model has a boolean that has to be nullable
public bool? Foo
{
get;
set;
}
so in my Razor cshtml I have
#Html.CheckBoxFor(m => m.Foo)
except that doesn't work. Neither does casting it with (bool). If I do
#Html.CheckBoxFor(m => m.Foo.Value)
that doesn't create an error, but it doesn't bind to my model when posted and foo is set to null. Whats the best way to display Foo on the page and make it bind to my model on a post?
I got it to work with
#Html.EditorFor(model => model.Foo)
and then making a file at Views/Shared/EditorTemplates/Boolean.cshtml with the following:
#model bool?
#Html.CheckBox("", Model.GetValueOrDefault())
Found answer in similar question - Rendering Nullable Bool as CheckBox.
It's very straightforward and just works:
#Html.CheckBox("RFP.DatesFlexible", Model.RFP.DatesFlexible ?? false)
#Html.Label("RFP.DatesFlexible", "My Dates are Flexible")
It's like accepted answer from #afinkelstein except we don't need special 'editor template'
I have bool? IsDisabled { get; set; } in Model. Inserted if in View.
<div class="inputClass" id="disabled">
<div>
#if(Model.IsDisabled==null)
{
Model.IsDisabled = false;
}
#Html.CheckBoxFor(model => model.IsDisabled.Value)
</div>
</div>
My model has a boolean that has to be nullable
Why? This doesn't make sense. A checkbox has two states: checked/unchecked, or True/False if you will. There is no third state.
Or wait you are using your domain models in your views instead of view models? That's your problem. So the solution for me is to use a view model in which you will define a simple boolean property:
public class MyViewModel
{
public bool Foo { get; set; }
}
and now you will have your controller action pass this view model to the view and generate the proper checkbox.
Complicating a primitive with hidden fields to clarify whether False or Null is not recommended.
Checkbox isn't what you should be using -- it really only has one state: Checked. Otherwise, it could be anything.
When your database field is a nullable boolean (bool?), the UX should use 3-Radio Buttons, where the first button represents your "Checked", the second button represents "Not Checked" and the third button represents your null, whatever the semantics of null means. You could use a <select><option> drop down list to save real estate, but the user has to click twice and the choices aren't nearly as instantaneously clear.
1 0 null
True False Not Set
Yes No Undecided
Male Female Unknown
On Off Not Detected
The RadioButtonList, defined as an extension named RadioButtonForSelectList, builds the radio buttons for you, including the selected/checked value, and sets the <div class="RBxxxx"> so you can use css to make your radio buttons go horizontal (display: inline-block), vertical, or in a table fashion (display: inline-block; width:100px;)
In the model (I'm using string, string for the dictionary definition as a pedagogical example. You can use bool?, string)
public IEnumerable<SelectListItem> Sexsli { get; set; }
SexDict = new Dictionary<string, string>()
{
{ "M", "Male"},
{ "F", "Female" },
{ "U", "Undecided" },
};
//Convert the Dictionary Type into a SelectListItem Type
Sexsli = SexDict.Select(k =>
new SelectListItem
{
Selected = (k.Key == "U"),
Text = k.Value,
Value = k.Key.ToString()
});
<fieldset id="Gender">
<legend id="GenderLegend" title="Gender - Sex">I am a</legend>
#Html.RadioButtonForSelectList(m => m.Sexsli, Model.Sexsli, "Sex")
#Html.ValidationMessageFor(m => m.Sexsli)
</fieldset>
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForSelectList<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression,
IEnumerable<SelectListItem> listOfValues,
String rbClassName = "Horizontal")
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var sb = new StringBuilder();
if (listOfValues != null)
{
// Create a radio button for each item in the list
foreach (SelectListItem item in listOfValues)
{
// Generate an id to be given to the radio button field
var id = string.Format("{0}_{1}", metaData.PropertyName, item.Value);
// Create and populate a radio button using the existing html helpers
var label = htmlHelper.Label(id, HttpUtility.HtmlEncode(item.Text));
var radio = String.Empty;
if (item.Selected == true)
{
radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id, #checked = "checked" }).ToHtmlString();
}
else
{
radio = htmlHelper.RadioButtonFor(expression, item.Value, new { id = id }).ToHtmlString();
}// Create the html string to return to client browser
// e.g. <input data-val="true" data-val-required="You must select an option" id="RB_1" name="RB" type="radio" value="1" /><label for="RB_1">Choice 1</label>
sb.AppendFormat("<div class=\"RB{2}\">{0}{1}</div>", radio, label, rbClassName);
}
}
return MvcHtmlString.Create(sb.ToString());
}
}
For me the solution was to change the view model. Consider you are searching for invoices. These invoices can be paid or not. So your search has three options: Paid, Unpaid, or "I don't Care".
I had this originally set as a bool? field:
public bool? PaidInvoices { get; set; }
This made me stumble onto this question. I ended up created an Enum type and I handled this as follows:
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.NotSpecified, new { #checked = true })
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.Yes)
#Html.RadioButtonFor(m => m.PaidInvoices, PaidStatus.No)
Of course I had them wrapped in labels and had text specified, I just mean here's another option to consider.
Checkbox only offer you 2 values (true, false). Nullable boolean has 3 values (true, false, null) so it's impossible to do it with a checkbox.
A good option is to use a drop down instead.
Model
public bool? myValue;
public List<SelectListItem> valueList;
Controller
model.valueList = new List<SelectListItem>();
model.valueList.Add(new SelectListItem() { Text = "", Value = "" });
model.valueList.Add(new SelectListItem() { Text = "Yes", Value = "true" });
model.valueList.Add(new SelectListItem() { Text = "No", Value = "false" });
View
#Html.DropDownListFor(m => m.myValue, valueList)
I would actually create a template for it and use that template with an EditorFor().
Here is how I did it:
Create My template, which is basically a partial view I created in the EditorTemplates directory, under Shared, under Views name it as (for example): CheckboxTemplate:
#using System.Globalization
#using System.Web.Mvc.Html
#model bool?
#{
bool? myModel = false;
if (Model.HasValue)
{
myModel = Model.Value;
}
}
<input type="checkbox" checked="#(myModel)" name="#ViewData.TemplateInfo.HtmlFieldPrefix" value="True" style="width:20px;" />
Use it like this (in any view):
#Html.EditorFor(x => x.MyNullableBooleanCheckboxToBe, "CheckboxTemplate")
Thats all.
Templates are so powerful in MVC, use them.. You can create an entire page as a template, which you would use with the #Html.EditorFor(); provided that you pass its view model in the lambda expression..
The cleanest approach I could come up with is to expand the extensions available to HtmlHelper while still reusing functionality provided by the framework.
public static MvcHtmlString CheckBoxFor<T>(this HtmlHelper<T> htmlHelper, Expression<Func<T, bool?>> expression, IDictionary<string, object> htmlAttributes) {
ModelMetadata modelMeta = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
bool? value = (modelMeta.Model as bool?);
string name = ExpressionHelper.GetExpressionText(expression);
return htmlHelper.CheckBox(name, value ?? false, htmlAttributes);
}
I experimented with 'shaping' the expression to allow a straight pass through to the native CheckBoxFor<Expression<Func<T, bool>>> but I don't think it's possible.
I had a similar issue in the past.
Create a Checkbox input in HTML, and set the attribute name="Foo" This should still post properly.
<input type="checkbox" name="Foo" checked="#model.Foo.Value" /> Foo Checkbox<br />
If checked = True and not checked = null
Model
public NotNullFoo
{
get { return this.Foo?? false; }
set { this.Foo= (value == false ? null : true as bool?); }
}
public bool? Foo
{
get;
set;
}
View
#Html.CheckBoxFor(x => Model.NotNullFoo })
Just check for the null value and return false to it:
#{ bool nullableValue = ((Model.nullableValue == null) || (Model.nullableValue == false)) ? false : true; }
#Html.CheckBoxFor(model => nullableValue)
I also faced the same issue. I tried the following approach to solve the issue
because i don't want to change the DB and again generate the EDMX.
#{
bool testVar = (Model.MYVar ? true : false);
}
<label>#Html.CheckBoxFor(m => testVar)testVar</label><br />
When making an EditorTemplate for a model which contains a nullable bool...
Split the nullable bool into 2 booleans:
// Foo is still a nullable boolean.
public bool? Foo
{
get
{
if (FooIsNull)
return null;
return FooCheckbox;
}
set
{
FooIsNull = (value == null);
FooCheckbox = (value ?? false);
}
}
// These contain the value of Foo. Public only so they are visible in Razor views.
public bool FooIsNull { get; set; }
public bool FooCheckbox { get; set; }
Within the editor template:
#Html.HiddenFor(m => m.FooIsNull)
#if (Model.FooIsNull)
{
// Null means "checkbox is hidden"
#Html.HiddenFor(m => m.FooCheckbox)
}
else
{
#Html.CheckBoxFor(m => m.FooCheckbox)
}
Do not postback the original property Foo, because that is now calculated from FooIsNull and FooCheckbox.
All the answers above came with it's own issues. Easiest/cleanest way IMO is to create a helper
MVC5 Razor
App_Code/Helpers.cshtml
#helper CheckBoxFor(WebViewPage page, string propertyName, bool? value, string htmlAttributes = null)
{
if (value == null)
{
<div class="checkbox-nullable">
<input type="checkbox" #page.Html.Raw(htmlAttributes)>
</div>
}
else if (value == true)
{
<input type="checkbox" value="true" #page.Html.Raw(htmlAttributes) checked>
}
else
{
<input type="checkbox" value="false" #page.Html.Raw(htmlAttributes)>
}
}
Usage
#Helpers.CheckBoxFor(this, "IsPaymentRecordOk", Model.IsPaymentRecordOk)
In my scenario, a nullable checkbox means that a staff member had not yet asked the question to the client, so it's wrapped in a .checkbox-nullable so that you may style appropriately and help the end-user identify that it is neither true nor false
CSS
.checkbox-nullable {
border: 1px solid red;
padding: 3px;
display: inline-block;
}
Extension methods:
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression)
{
return htmlHelper.CheckBoxFor<TModel>(expression, null);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, bool?>> expression, object htmlAttributes)
{
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
bool? isChecked = null;
if (metadata.Model != null)
{
bool modelChecked;
if (Boolean.TryParse(metadata.Model.ToString(), out modelChecked))
{
isChecked = modelChecked;
}
}
return htmlHelper.CheckBox(ExpressionHelper.GetExpressionText(expression), isChecked??false , htmlAttributes);
}
Radio buttons are useful, but they don't allow you to deselect. If you want this behaviour, then consider using a drop-down/select. The following code will generate the SelectList and this binds successfully to a nullable boolean:
public static SelectList GetBoolTriState()
{
var items = new List<SelectListItem>
{
new SelectListItem {Value = "", Text = ""},
new SelectListItem {Value = "True", Text = "Yes"},
new SelectListItem {Value = "False", Text = "No"},
};
return new SelectList(items, "Value", "Text");
}
#{ bool testVar = ((bool)item.testVar ? true : false); }
#Html.DisplayFor(modelItem => testVar)
This is an old question, and the existing answers describe most of the alternatives. But there's one simple option, if you have bool? in your viewmodel, and you don't care about null in your UI:
#Html.CheckBoxFor(m => m.boolValue ?? false);
i have seen other posts with answers to this but i cannot get any of them to work for me.. so. this is in my controller->
ViewData["TARGET_DATE"] = new SelectList((from n in _db.ACTION_PLANs select n).ToList(), "TARGET_DATE", "TARGET_DATE");
i want to be able to apply a format on the dates that come back from the db, my DDL looks like this
<td><%=Html.DropDownList("TARGET_DATE", "All")%></td>
does anyone know if there is a way to loop through and format each date, or apply a format to them all. or what would be the best way to do this, if u need more code i can provide, what i really want is to display the date without the time with it. thanks in advance.
I know this post is old, but I ran across it while searching for an answer to the same problem. I ended up writing a FormattableSelectList class that takes a format string as a parameter, and then formats that output of the select list. Here's a sample of how to use it.
#Html.DropDownFor(
o => o.SomeProperty,
new FormattableSelectList(ViewBag.MyItems, "ID", "Time", "{0:f}"),
"Choose a Time"
)
I wrote a short post about it at http://www.jpolete.me/2011/11/16/a-formattable-selectlist-for-net-mvc/. Here's the code for it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Collections;
using System.Web.UI;
using System.Globalization;
public class FormattableSelectList : SelectList
{
private string _FormatString;
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString)
: base(items, dataValueField, dataTextField)
{
_FormatString = formatString;
}
public FormattableSelectList(IEnumerable items, string dataValueField, string dataTextField, string formatString, object selectedValue)
: base(items, dataValueField, dataTextField, selectedValue)
{
_FormatString = formatString;
}
public override IEnumerator<SelectListItem> GetEnumerator()
{
return ((!String.IsNullOrEmpty(DataValueField)) ?
GetListItemsWithValueField() :
GetListItemsWithoutValueField()).GetEnumerator();
}
private IList<SelectListItem> GetListItemsWithValueField()
{
HashSet<string> selectedValues = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
if (SelectedValues != null)
{
selectedValues.UnionWith(from object value in SelectedValues select Convert.ToString(value, CultureInfo.CurrentCulture));
}
var listItems = from object item in Items
let value = Eval(item, DataValueField, null)
select new SelectListItem
{
Value = value,
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(value)
};
return listItems.ToList();
}
private IList<SelectListItem> GetListItemsWithoutValueField()
{
HashSet<object> selectedValues = new HashSet<object>();
if (SelectedValues != null)
{
selectedValues.UnionWith(SelectedValues.Cast<object>());
}
var listItems = from object item in Items
select new SelectListItem
{
Text = Eval(item, DataTextField, _FormatString),
Selected = selectedValues.Contains(item)
};
return listItems.ToList();
}
private static string Eval(object container, string expression, string formatString)
{
object value = container;
if (!String.IsNullOrEmpty(expression))
{
value = DataBinder.Eval(container, expression);
}
string stringValue;
if (formatString == null)
{
stringValue = Convert.ToString(value, CultureInfo.CurrentCulture);
}
else
{
stringValue = String.Format(formatString, value);
}
return stringValue;
}
}
Here is how I implemented the class above. My client was looking for two dropdownlists containing Start time and End time. In MVC4
Model:
[Display(Name = "Time (From):")]
public int FromTimeId { get; set; }
[Display(Name = "Time (To):")]
public int ToTimeId { get; set; }
public virtual FromTime FromTime { get; set; }
public virtual ToTime ToTime { get; set; }
Controller:
public ActionResult Create()
{
ViewBag.FromTimes = db.FromTimes;
ViewBag.ToTimes = db.ToTimes;
}
View:
<div class="editor-field">
#Html.DropDownListFor(model => model.FromTimeId, new Namespace.FormattableSelectList(ViewBag.FromTimes, "FromTimeId", "FromTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.FromTimeId)
</div>
</td>
<td>
<div class="editor-label">
#Html.LabelFor(model => model.ToTimeId, "To Time:")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.ToTimeId, new Namespace.FormattableSelectList(ViewBag.ToTimes, "ToTimeId", "ToTimeName", "{0:h:mm tt}"))<br />
#Html.ValidationMessageFor(model => model.ToTimeId)
</div>