CheckBoxFor(t => t.boolValue, new { disabled="disabled" }) method to render a checkbox, in disabled mode.
The method renders a hidden field as well.
My question is why does this hidden field has a false value for disabled check box? I believe the purpose of the hidden field is to have some extra behavior over the default check box behavior
Is there a way to override default MVC functionality so that the value of this hidden field is based on the state of the checkbox even in disabled mode?
The hidden field is used to bind the checkbox value to a boolean property. The thing is that if a checkbox is not checked, nothing is sent to the server, so ASP.NET MVC uses this hidden field to send false and bind to the corresponding boolean field. You cannot modify this behavior other than writing a custom helper.
This being said, instead of using disabled="disabled"
use readonly="readonly"
on the checkbox. This way you will keep the same desired behavior that the user cannot modify its value but in addition to that its value will be sent to the server when the form is submitted:
@Html.CheckBoxFor(x => x.Something, new { @readonly = "readonly" })
UPDATE:
As pointed out in the comments section the readonly
attribute doesn't work with Google Chrome. Another possibility is to use yet another hidden field and disable the checkbox:
@Html.HiddenFor(x => x.Something)
@Html.CheckBoxFor(x => x.Something, new { disabled = "disabled" })
UPDATE 2:
Here's a full testcase with the additional hidden field.
Model:
public class MyViewModel
{
public bool Foo { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel { Foo = true });
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return Content(model.Foo.ToString());
}
}
View:
@model MyViewModel
@using (Html.BeginForm())
{
@Html.HiddenFor(x => x.Foo)
@Html.CheckBoxFor(x => x.Foo, new { disabled = "disabled" })
<button type="submit">OK</button>
}
When the form is submitted the value of the Foo property is true
. I have tested with all major browsers (Chrome, FF, IE).
I find the added parameter in the query string looks a mess and makes people think that we as developers have done something wrong. So I've resorted to the extreme method of creating my own InputExtensions class which allows me to decide on whether I want the hidden input to be rendered or not.
The following is the InputExtensions class I've created (based on the existing MVC code to maintain full compatibility):
public static class InputExtensions
{
//
// Checkboxes
//
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool>> expression, bool renderHiddenInput)
{
if (renderHiddenInput)
{
return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression);
}
return CheckBoxFor(htmlHelper, expression, false);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool>> expression, object htmlAttributes, bool renderHiddenInput)
{
if (renderHiddenInput)
{
return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression, htmlAttributes);
}
return CheckBoxFor(htmlHelper, expression, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), false);
}
public static MvcHtmlString CheckBoxFor<TModel>(this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, bool>> expression, IDictionary<string, object> htmlAttributes,
bool renderHiddenInput)
{
if (renderHiddenInput)
{
return System.Web.Mvc.Html.InputExtensions.CheckBoxFor(htmlHelper, expression, htmlAttributes);
}
if (expression == null)
{
throw new ArgumentNullException("expression");
}
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 CheckBoxHelper(htmlHelper, metadata, ExpressionHelper.GetExpressionText(expression), isChecked, htmlAttributes);
}
private static MvcHtmlString CheckBoxHelper(HtmlHelper htmlHelper, ModelMetadata metadata, string name, bool? isChecked, IDictionary<string, object> htmlAttributes)
{
RouteValueDictionary attributes = ToRouteValueDictionary(htmlAttributes);
bool explicitValue = isChecked.HasValue;
if (explicitValue)
{
attributes.Remove("checked"); // Explicit value must override dictionary
}
return InputHelper(htmlHelper,
InputType.CheckBox,
metadata,
name,
value: "true",
useViewData: !explicitValue,
isChecked: isChecked ?? false,
setId: true,
isExplicitValue: false,
format: null,
htmlAttributes: attributes);
}
//
// Helper methods
//
private static MvcHtmlString InputHelper(HtmlHelper htmlHelper, InputType inputType, ModelMetadata metadata, string name, object value, bool useViewData, bool isChecked, bool setId, bool isExplicitValue, string format, IDictionary<string, object> htmlAttributes)
{
string fullName = htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
if (string.IsNullOrEmpty(fullName))
{
throw new ArgumentException("Value cannot be null or empty.", "name");
}
var tagBuilder = new TagBuilder("input");
tagBuilder.MergeAttributes(htmlAttributes);
tagBuilder.MergeAttribute("type", HtmlHelper.GetInputTypeString(inputType));
tagBuilder.MergeAttribute("name", fullName, true);
string valueParameter = htmlHelper.FormatValue(value, format);
var usedModelState = false;
bool? modelStateWasChecked = GetModelStateValue(htmlHelper.ViewData, fullName, typeof(bool)) as bool?;
if (modelStateWasChecked.HasValue)
{
isChecked = modelStateWasChecked.Value;
usedModelState = true;
}
if (!usedModelState)
{
string modelStateValue = GetModelStateValue(htmlHelper.ViewData, fullName, typeof(string)) as string;
if (modelStateValue != null)
{
isChecked = string.Equals(modelStateValue, valueParameter, StringComparison.Ordinal);
usedModelState = true;
}
}
if (!usedModelState && useViewData)
{
isChecked = EvalBoolean(htmlHelper.ViewData, fullName);
}
if (isChecked)
{
tagBuilder.MergeAttribute("checked", "checked");
}
tagBuilder.MergeAttribute("value", valueParameter, isExplicitValue);
if (setId)
{
tagBuilder.GenerateId(fullName);
}
// If there are any errors for a named field, we add the css attribute.
ModelState modelState;
if (htmlHelper.ViewData.ModelState.TryGetValue(fullName, out modelState))
{
if (modelState.Errors.Count > 0)
{
tagBuilder.AddCssClass(HtmlHelper.ValidationInputCssClassName);
}
}
tagBuilder.MergeAttributes(htmlHelper.GetUnobtrusiveValidationAttributes(name, metadata));
return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.SelfClosing));
}
private static RouteValueDictionary ToRouteValueDictionary(IDictionary<string, object> dictionary)
{
return dictionary == null ? new RouteValueDictionary() : new RouteValueDictionary(dictionary);
}
private static object GetModelStateValue(ViewDataDictionary viewData, string key, Type destinationType)
{
ModelState modelState;
if (viewData.ModelState.TryGetValue(key, out modelState))
{
if (modelState.Value != null)
{
return modelState.Value.ConvertTo(destinationType, culture: null);
}
}
return null;
}
private static bool EvalBoolean(ViewDataDictionary viewData, string key)
{
return Convert.ToBoolean(viewData.Eval(key), CultureInfo.InvariantCulture);
}
}
Then you can call the method like so:
@Html.CheckBoxFor(t => t.boolValue, new { disabled="disabled" }, false)
Here is a quick tip I discovered : If you set a default value for the boolean value on the object. It's possible to write out the tag with the value as true/false (this value should be the opposite of the default boolean value on the object)
<input type="checkbox" name="IsAllDayEvent" value="true" id="IsAllDayEvent" />
The value is only sent if the checkbox is checked, so it works.
Although Darin's answer is perfectly fine, there is another way of making sure that disabled checkbox post backs as expected.
This solution will also work in the scenarios where checkbox is conditionally disabled based on a property in a model, or enabled/disabled client-side based on user selection or client-side retrieved data.
You can keep your view simple: (setting disabled here is optional)
@Html.CheckBoxFor(m => m.Something, new { id = "somethingCheckbox", disabled = "disabled" })
This will, as Darin mentioned generate an extra hidden input field set to false, so that the checkbox value will post-back also when checkbox is not checked. Therefore we know that Something
field will always post-back regardless if checkbox is checked or not, disabled or not. The only problem that if checkbox is disabled it will always post back as False
.
We can fix that with some javaScript post processing before submitting. In my case the code looks something along those lines:
var dataToPost = $form.serializeArray();
if (dataToPost != null) {
for (var i = 0; i < dataToPost.length; i++) {
if (dataToPost[i].name === "Something") {
dataToPost[i].value = $("#somethingCheckbox").attr("checked") === "checked";
}
}
}
$.post($form.attr("action"), dataToPost)
You may need to modify it for your scenario, but you should get the idea.
来源:https://stackoverflow.com/questions/11011300/asp-net-mvc-3-checkboxfor-method-outputs-hidden-field-that-hidden-field-value