How to bind checkbox to a string property in asp net mvc - asp.net-mvc

I had in my classes some fields that represents true or false, but it has string values "T" or "F".
How to bind the checkbox to these fields?

You can do this in ASP.Net MVC Razor
<input type="checkbox" name="someName" value="#Model.SomeValue"
#(Model.IsChecked == "T" ? "checked='checked'" : "") />
However, this may not be what you are looking for, since you would have to do some manual work on the server to figure out values that had been "unchecked".
By default only your checked values will get sent to the server.
UPDATE:
The Html.CheckBox and Html.CheckBoxFor helper methods both emit hidden fields in order to make sure everything binds correctly back to your model. You can imitate this behavior pretty easily.
First you need to emit a hidden field that will be bound to your model on the server.
#Html.HiddenFor(model => model.StringBool, new { id = "_stringBool" })
Next you create a plain jane checkbox and set the initial state to reflect your model.
<input type="checkbox" id="myCheckBox" #(Model.StringBool == "T" ? "checked='checked'" : "") />
This checkbox's only purpose is to proxy values to and from the hidden field so your model will automatically get bound on the server. This can be achieved with some simple jQuery.
$("#myCheckBox").click(function () {
var isChecked = $(this).is(":checked");
$("#_stringBool").val(isChecked ? "T" : "F");
});
Now checking and unchecking the box will set the value of your bound hidden field accordingly. When you post to the server your values will be preserved via model binding.
Some things to note
This does not take into account validation. It is very easy to change the value of the hidden field so make sure you properly validate on the server side!

When you set the checkstate of the new checkbox items, just put an if statement:
checkBox1.Checked = (stringValue == "T") ? true : false;

I made my own extension to solve this problem. It is a little tidyer in the razorview, helps minimizing the c# / razor mixture. Here's the class:
public static class CheckBoxExtensions {
public static MvcHtmlString ValueCheckbox<TModel>(this HtmlHelper<TModel> htmlHelper, string name, string value, bool isChecked, IDictionary<string, object> htmlAttributes, bool disabled = false)
{
string result = string.Format(CultureInfo.InvariantCulture,
"<input type=\"checkbox\" name=\"{0}\" value=\"{1}\" {2} {3} ",
name, value, (isChecked) ? "checked=\"checked\"" : "", (disabled) ? "disabled=\"disabled\"" : "");
if (htmlAttributes != null && htmlAttributes.Count > 0)
{
foreach (KeyValuePair<string, object> item in htmlAttributes)
{
result += string.Format(CultureInfo.InvariantCulture, "{0}=\"{1}\" ", item.Key, item.Value.ToString());
}
}
result += " />";
return new MvcHtmlString(result);
}
}
And this is what I do in the razorview:
Html.ValueCheckbox("SelectedItems",
item.Number,
(Model.SelectedItems.Contains(item.Number)),
new Dictionary<string, object>() {
{ "data-disable-bubble", "true" },
{ "data-forms-enable-any-checked-checkbox", "true" }
}
)
Dont mind the weir HtmlAttributes, I figured I would just let them in there :-)

Related

Required field validator for a textbox in MVC depend on a dynamically generated drop down list

I'm generating some controls with the help of a for-each loop. I would like to validate the textbox depend on the answer of the dropdown. For some questions, validator should be enabled if the selected item in dropdown is "yes" and for some questions validator should be enabled if the answer from the dropdown is "no". At the moment it fires for both because I don't how to control it as the controls are dynamically generated
View
#for (int a = 0; a < Model.ServiceQaVisits.Count(); a++)
{
if (Model.ServiceQaQuestions[a].sqqQuestionID == Model.ServiceQaVisits[a].sqvQuestionID)
{
<div>
#Html.DisplayTextFor(m => m.ServiceQaVisits[a].sqvQuestionID)
#Html.DisplayTextFor(m => m.ServiceQaQuestions[a].sqqQuestion)
#Html.HiddenFor(m => m.ServiceQaVisits[a].sqvQAID)
#if (Model.ServiceQaQuestions[a].sqqQuestionTypeID == 1)
{
#Html.TextBoxFor(m => m.ServiceQaVisits[a].sqvAnswer)
}
else if (Model.ServiceQaQuestions[a].sqqQuestionTypeID == 2)
{
List<string> lista = new List<string>() { "Yes", "No" };
#Html.DropDownListFor(m => m.ServiceQaVisits[a].sqvAnswer, new SelectList(lista), "Select Answer")
}
else
{
List<string> listb = new List<string>() { "Yes", "No", "N/A" };
#Html.DropDownListFor(m => m.ServiceQaVisits[a].sqvAnswer, new SelectList(listb), "Select Answer")
}
#if (Model.ServiceQaQuestions[a].sqqNegativeAnswer != null)
{
#Html.TextBoxFor(m => m.ServiceQaVisits[a].sqvComment)
#Html.ValidationMessageFor(m => m.ServiceQaVisits[a].sqvComment,"", new {#class = "text-danger"});
}
</div>
}
}
Model
[Required(ErrorMessage = "Please enter the reason")]
public string sqvComment { get; set; }
Any help is highly appreciated.
There are two parts to the solution, as validation should happen both on server and on client.
On the server, you should use [CustomValidation] attribute on your sqvComment property. For example, your validation method can be
public static ValidationResult ValidateSqvComment(string value, ValidationContext context)
{
ValidationResult result = ValidationResult.Success;
MyModel model = context.ObjectInstance as MyModel;
if (model.sqvAnswer == "Yes" && String.IsNullOrEmpty(value))
{
result = new ValidationResult("sqvComment is required");
}
return result;
}
and decorate your property with
[CustomValidation(typeof(MyModel), nameof(MyModel.ValidateSqvComment))]
public string sqvComment { get; set; }
On the client things are more complicated. What MVC does, if you look at the generated source, is add special properties to the input elements, that it then parses in client side to add the jQuery validation. in particular, it adds a data-val="true" to enable validation, and then data-val-*=..., depending on the type of validation. you need to dynamically add these attributes, and then call the parsing function to parse them. In your case, you need to add the data-val-required="sqvComment is required" attribute to your sqvComment field (through the htmlAttribute argument of the Html.TextBoxFor() method), and then set the value of data-val to true only if the answer to the drop-down is "Yes" (or whatever your logic is), using the onchange event in client code. Then, you need to clear the validation that jQuery already picked up, and recreate it:
jqForm.removeData("unobtrusiveValidation");
jqForm.removeData("validator");
$.validator.unobtrusive.parse(formId);
where jqForm is a jQuery object with your form, and formId is a selector to the form.

Convert Y/N values of razor html checkbox in MVC

Im reading a Y/N value from my model in a razor for loop.
I want to change the value from Y/N to true/false.
<td>#Html.CheckBoxFor(modelItem => (item.ReqDowngrade == "Y" ? true : false))</td>
I keep geting this error: System.InvalidOperationException: 'Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions.'
Is there any way I can do this without creating a server view model?
This declaration is totally wrong:
#Html.CheckBoxFor(modelItem => (item.ReqDowngrade == "Y" ? true : false))
CheckBoxFor helper only accepts viewmodel properties with either bool or Nullable<bool> type for model binding, hence the conditional expression above should not be used. If you want to assign Y or N value to a new bool viewmodel property bound with CheckBoxFor, do this instead:
Viewmodel
// assumed both 'ReqDownGrade' & new bool property are in same viewmodel class
public bool CheckReqDownGrade
{
get
{
// returns false if ReqDownGrade is 'N'
return (ReqDownGrade == "Y");
}
set
{
CheckReqDownGrade = value;
CheckReqDownGrade == true ? ReqDownGrade = "Y" : ReqDownGrade = "N";
}
}
View
#Html.CheckBoxFor(model => model.CheckReqDownGrade)
If you still insist to not adding bool viewmodel property, you can use a HiddenFor, standard HTML input element with type="checkbox" attribute and simple JS trick:
View
#Html.HiddenFor(modelItem => item.ReqDowngrade, new { id = "ReqDownGrade" })
<input id="CheckReqDownGrade" type="checkbox" #(item.ReqDowngrade == "Y" ? "checked='checked'" : "") />
JS (jQuery)
<script>
$(function() {
$('#CheckReqDownGrade').click(function() {
var isChecked = $(this).is(':checked');
$('#ReqDownGrade').val(isChecked ? 'Y' : 'N');
});
});
</script>

Is there a way to automatically display “*” required icon beside the [Required] fields

I have finished my asp.net MVC web application, and I have been using the data annotation [Required] to mention that the field is required. But currently the required fields does not have any indication that they are required, unless the user tried to submit the form. So is there way to force my Razor view to display a red “” beside any field that have [Required] defined on it? OR I need to manually add the “” icon ?
Thanks
After I got burned by the Bootstrap 2 to 3 upgrade, where they pretty much completely changed the HTML for form controls, I've been putting the entire form group in editor templates instead of just the field. Here's an example:
<div class="form-group">
#Html.Label("", new { #class = string.Format("control-label col-md-2{0}", ViewData.ModelMetadata.IsRequired ? string.Empty : " optional") })
<div class="col-md-6">
#Html.TextBox("", ViewData.TemplateInfo.FormattedModelValue.ToString(), new { type = "email", #class = "form-control" })
#Html.ValidationMessage("")
</div>
</div>
What's important here for you is the string.Format in Html.Label. I'm using the ViewData.ModelMetadata.IsRequired to add an "optional" class if it's false. Bootstrap makes the labels bold by default, so as a required indicator, I make optional field labels normal (non-bold). However, adding a * is a little more difficult. You could use the same thing I'm doing here to add an additional span tag:
#if (ViewData.ModelMetadata.IsRequired)
{
<span class="requiredIndicator>*</span>
}
#Html.Label(...)
...
The potential problem is that that won't actually be inside your <label> tag, so you might have to do some extra styling work to make it look right depending on the styles you apply to the labels.
An alternative is to create your own HtmlHelper to return a label with a required indicator. Here's some sample code for that:
public static MvcHtmlString RequiredIndicatorLabelFor<TModel, TValue>(
this HtmlHelper<TModel> html,
Expression<Func<TModel, TValue>> modelProperty,
object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(modelProperty, html.ViewData);
var labelText = metadata.IsRequired ? string.Format("* {0}", metadata.GetDisplayName()) : metadata.GetDisplayName();
return html.LabelFor(modelProperty, labelText, htmlAttributes);
}
You can also write a custom label helper for this purpose
public static MvcHtmlString CustomLabelFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression,
IDictionary<string, object> htmlAttributes = null )
{
var metadata = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
var propertyName = ExpressionHelper.GetExpressionText(expression);
var builder = new TagBuilder("label");
builder.Attributes.Add("for", TagBuilder.CreateSanitizedId(htmlHelper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(propertyName)));
var labelText = string.Format("{0}{1}", metadata.IsRequired ? "*" : string.Empty,
string.IsNullOrEmpty(metadata.DisplayName)
? metadata.PropertyName
: metadata.DisplayName);
builder.SetInnerText(labelText);
builder.MergeAttributes<string, object>(htmlAttributes, true);
return new MvcHtmlString(builder.ToString());
}
Now when used CustomLabelFor on a property with Required attribute, it will append * in fort of the label text.
#Html.CustomLabelFor(m => m.YourRequiredField)

Radio Button Enum Helper for model with resource file

Problem:
Need to bind a strongly typed model which has a Gender as enum property. Also i like to show a Display text from a Resource file.
My Model is
public enum GenderViewModel
{
[Display(Name = "Male", ResourceType = typeof(Resources.Global), Order = 0)]
Male,
[Display(Name = "Female", ResourceType = typeof(Resources.Global), Order = 1)]
Female
}
Initially, I tried following http://romikoderbynew.com/2012/02/23/asp-net-mvc-rendering-enum-dropdownlists-radio-buttons-and-listboxes/
But it was bit complex and i was unable to correct my HTML however i want.
Then i had a look of simple and easy implementation from stackoverflow, pass enum to html.radiobuttonfor MVC3
and used a HtmlHelper in cshtml like below
#Html.RadioButtonForEnum(m => m.Gender)
HTML Produced
<label for="_Gender_Male">
<input type="radio" value="Male" name="Gender" id="_Gender_Male"
data-val-required="Gender is required" data-val="true" checked="checked">
<span class="radiotext">Male</span>
</label>
<label for="_Gender_Female">
<input type="radio" value="Female" name="Gender" id="_Gender_Female">
<span class="radiotext">Female</span></label>
It really simple and works well for me. But i am not getting values
from Resource files. My application is multilingual and I use a Global
Resource file for different language support.
Issue:
Male displayed should be Man and Female displayed should be Kvinna should be from Resource file, as my current culture is sv-se
Could any one help/ provide a simple solution which has a good control over HTML?
All you have to do is adapt my original helper so that it takes into account the DisplayAttribute:
public static class HtmlExtensions
{
public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(
this HtmlHelper<TModel> htmlHelper,
Expression<Func<TModel, TProperty>> expression
)
{
var metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
if (!metaData.ModelType.IsEnum)
{
throw new ArgumentException("This helper is intended to be used with enum types");
}
var names = Enum.GetNames(metaData.ModelType);
var sb = new StringBuilder();
var fields = metaData.ModelType.GetFields(
BindingFlags.Static | BindingFlags.GetField | BindingFlags.Public
);
foreach (var name in names)
{
var id = string.Format(
"{0}_{1}_{2}",
htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
metaData.PropertyName,
name
);
var radio = htmlHelper.RadioButtonFor(expression, name, new { id = id }).ToHtmlString();
var field = fields.Single(f => f.Name == name);
var label = name;
var display = field
.GetCustomAttributes(typeof(DisplayAttribute), false)
.OfType<DisplayAttribute>()
.FirstOrDefault();
if (display != null)
{
label = display.GetName();
}
sb.AppendFormat(
"<label for=\"{0}\">{1}</label> {2}",
id,
HttpUtility.HtmlEncode(label),
radio
);
}
return MvcHtmlString.Create(sb.ToString());
}
}
Now if you have decorated some of the enum values with the DisplayAttribute, the values will come from the resource file.
You should replace in the extension method were it uses name for the <label> to use the resource you would like.
You should use a code kind of this one I adapted from here:
var type = typeof(metaData.ModelType);
var memInfo = type.GetMember(name);
var attributes = memInfo[0].GetCustomAttributes(typeof(DisplayAttribute), false);
var description = ((DisplayAttribute)attributes[0]).GetDescription();
And then put description into the <label>.
I've not tested it!

MVC CheckBoxListFor not posting as expected

I have build a CheckBoxListFor extension on HtmlHelper thanks to this wonderful answer http://bit.ly/Aevcea (code below) but am finding it doesn't post as expected.
My form is based on a model Group which has (amongst other properties) string Name and int[] PersonIDs.
The CheckBoxListFor renders something like this:
<ul>
<li><input type="checkbox" name="PersonIDs" value="1" id="PersonIDs_1" /></li>
<li><input type="checkbox" name="PersonIDs" value="2" id="PersonIDs_2" /></li>
</ul>
My controller has an Edit(Group group) method to handle submission of this form. However, upon submit I'm finding group.PersonIDs is null. There is though a Request.Form["PersonIDs"] set to the selected values (e.g. "1,2" if both items above are checked). Also, if I add another parameter to my Edit method (int[] PersonIDs) then that arrives with the expected contents (the selected IDs).
Can anyone explain what I'm doing wrong? The relevant bit of my view looks like this (extra bits stripped out):
#Html.TextBoxFor(m => m.Group.Name)
#Html.CheckBoxListFor(m => m.Group.PersonIDs, Model.MultiSelectListOfAllPeople)
Note that the group parameter in my Edit method does come back with Name set according to the form.
Just for completeness, here is the full body of my CheckBoxListFor extension:
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList multiSelectList, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder wrapper = new TagBuilder("ul");
wrapper.AddCssClass("clearfix");
wrapper.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
wrapper.InnerHtml += String.Format("<li><input type=\"checkbox\" name=\"{0}\" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></li>",
propertyName,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(wrapper.ToString());
}
OK, the problem was that the CheckBoxListFor extension needed to render the control name as Group.PersonIDs and not simply PersonIDs. The form was bound to an object that itself was a sub-property of the view model. I've quickly adapted my CheckBoxListFor method as follows, but would gratefully accept a more elegant solution! I'm passing an additional boolean parameter includeDeclaringType to tell it whether to include the name of the declaring type in the ID. Not sure if this can be inferred any other way..?
public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, IEnumerable<TProperty>>> expression, MultiSelectList multiSelectList, bool includeDeclaringType, object htmlAttributes = null)
{
//Derive property name for checkbox name
MemberExpression body = expression.Body as MemberExpression;
string declaringTypeName = body.Member.DeclaringType.Name;
string propertyName = body.Member.Name;
//Get currently select values from the ViewData model
IEnumerable<TProperty> list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
//Convert selected value list to a List<string> for easy manipulation
List<string> selectedValues = new List<string>();
if (list != null)
{
selectedValues = new List<TProperty>(list).ConvertAll<string>(delegate(TProperty i) { return i.ToString(); });
}
//Create div
TagBuilder wrapper = new TagBuilder("ul");
wrapper.AddCssClass("clearfix");
wrapper.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);
//Add checkboxes
foreach (SelectListItem item in multiSelectList)
{
var name = string.Concat(
includeDeclaringType ? string.Format("{0}.", declaringTypeName) : "",
propertyName
);
var id = string.Concat(
includeDeclaringType ? string.Format("{0}_", declaringTypeName) : "",
propertyName,
"_",
item.Value
);
wrapper.InnerHtml += String.Format("<li><input type=\"checkbox\" name=\"{0}\" id=\"{1}\" " +
"value=\"{2}\" {3} /><label for=\"{1}\">{4}</label></li>",
name,
id,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);
}
return MvcHtmlString.Create(wrapper.ToString());
}
You can also use ExpressionHelper.GetExpressionText method, it will generate the correct input name for you:
var expressionText = ExpressionHelper.GetExpressionText(expression);
wrapper.InnerHtml +=
string.Format("<li><input type=\"checkbox\" name=\"{0}" id=\"{0}_{1}\" " +
"value=\"{1}\" {2} /><label for=\"{0}_{1}\">{3}</label></li>",
expressionText,
item.Value,
selectedValues.Contains(item.Value) ? "checked=\"checked\"" : "",
item.Text);

Resources