In my VIew ive got 3 checkboxes, i want to validate them that way :
if one checkbox is checked other 2 is disabled, if checked checkbox is unchecked, other 2 checkboes is enabled again.
Controller
public ActionResult SolarPart()
{
var model = new SolarParentViewModel();
var list = new List<URLTimeLimitViewModel>();
list.Add(new URLTimeLimitViewModel { Name = "14 dage", IsChecked = false, Id = 1 });
list.Add(new URLTimeLimitViewModel { Name = "1 Måned", IsChecked = false, Id = 2 });
list.Add(new URLTimeLimitViewModel { Name = "2 Måneder", IsChecked = false, Id = 3 });
model.TimeLimit = list;
return View(model);
}
View
#for (var i = 0; i < Model.TimeLimit.Count; i++)
{
<div class="editor-label">
#Html.LabelFor(c=>Model.TimeLimit[i].Name, Model.TimeLimit[i].Name)
#Html.HiddenFor(c=>Model.TimeLimit[i].Id)
#Html.CheckBoxFor(c=>Model.TimeLimit[i].IsChecked)
</div>
}
If the values are mutually exclusive you might consider using radio buttons instead of checkboxes. They seem more adapted to your scenario. If for some very weird reason you still want to use checkboxes which are mutually exclusives you could use javascript and subscribe to the change event of each of them and based on the value toggle the other 2 checkboxes. And for validating this model on the server you could write a custom validation attribute which could either be applied to the TimeLimit property on your view model:
public class MaximumOneTimeLimitCanBeCheckedAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
var list = value as IEnumerable<URLTimeLimitViewModel>;
if (list == null)
{
return true;
}
return list.Where(x => x.IsChecked).Count() < 2;
}
}
and then:
public class SolarParentViewModel
{
[MaximumOneTimeLimitCanBeChecked]
public IList<URLTimeLimitViewModel> TimeLimit { get; set; }
}
Related
I have a razor page which contains a for loop which iterates over a list of User objects.
Each repetion of the loop contains a dropdownlist:-
<button type="submit" asp-page-handler="SetUserStatuses">Update Statuses</button>
#for (int i = 0; i < Model.UserList.Count; i++)
{
#Model.UserList[i].UserAccountStatusId
#Html.DropDownList("UserAccountStatusId", new SelectList(Enum.GetValues(typeof(UserAccountStatus)).Cast<UserAccountStatus>().Select
(v => new SelectListItem
{
Text = v.GetDescription(),
Value = (Convert.ToInt32(v)).ToString()
}), "Value", "Text", Model.UserList[i].UserAccountStatusId))
#Html.Hidden("UserId", Model.UserList[i].UserId)
The pagehandler is just:-
public void OnPostSetUserStatuses(int[] UserId, int[] UserAccountStatusId)
{
for (var i = 0; i < UserId.Length; i++)
{
userService.SetUserStatus(UserId[i], UserAccountStatusId[i]);
}
var userList = userService.GetUsers();
UserList = userList;
return Page();
}
The model is defined as such:-
public List<User> UserList { get; set; }
[BindProperty]
public int[] UserAccountStatusId { get; set; }
[BindProperty]
public int[] UserId { get; set; }
So when I post the submit, the page handler is fired, and the db is updated and the new values are returned to the model within UserList.
Additionally, the line which says:-
#Model.UserList[i].UserAccountStatusId
is shown with the correct value.
However, the dropdownlist is not refreshed with the correct selected value. I think the problem is that I have multiple dropdowns and there seems to be some binding issue going on which I cannot for the life of me fathom out.
Can someone help please.
try to use DropDownListFor
#Html.DropDownListFor(model=> model.UserAccountStatusId[i],
...., #Model.UserAccountStatusId[i])
````
This is something that has always puzzled me as to the best way round, while keeping maintainable code. The below code sets up a list of months and years for a payment gateway form, before assigning these to a variable of type List<SelectListItem>.
Intial Action
PayNowViewModel paymentGateway = new PayNowViewModel();
List<SelectListItem> paymentGatewayMonthsList = new List<SelectListItem>();
List<SelectListItem> paymentGatewayYearsList = new List<SelectListItem>();
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
paymentGateway.ExpiryMonth = paymentGatewayMonthsList;
paymentGateway.ExpiryYear = paymentGatewayYearsList;
return View(paymentGateway);
It's a fair bit of code, and I find myself repeating this code, in similar formats to re-setup the dropdown lists options should the ModelState.IsValid be false and I want to return back to the view for the user to correct there mistakes.
HttpPost Action - Code
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel paymentGatewayForm, FormCollection form)
{
if (ModelState.IsValid)
{
// Post processing actions...
return View();
}
else
{
for (int i = 1; i <= 12; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayMonthsList.Add(selectListItem);
}
int year = DateTime.Now.Year;
for (int i = year; i <= year + 10; i++)
{
SelectListItem selectListItem = new SelectListItem();
selectListItem.Value = i.ToString();
selectListItem.Text = i.ToString("00");
paymentGatewayYearsList.Add(selectListItem);
}
form.ExpiryMonth = paymentGatewayMonthsList;
form.ExpiryYear = paymentGatewayYearsList;
return View("MakePayment", form);
}
}
What's the best way to centralise this dropdown setup code so its only in one place? At present you'll see a large proportion (the for loops), is exactly repeated twice. A base controller with function? Or is it better to re-setup like the above?
Any advice appreciated!
Mike.
Add a private method to your controller (the following code assumes your ExpiryMonth and ExpiryYear properties are IEnumerable<SelectListItem> which is all that the DropDownListFor() method requires)
private void ConfigureViewModel(PayNowViewModel model)
{
model.ExpiryMonth = Enumerable.Range(1, 12).Select(m => new SelectListItem
{
Value = m.ToString(),
Text = m.ToString("00")
});
model.ExpiryYear = Enumerable.Range(DateTime.Today.Year, 10).Select(y => new SelectListItem
{
Value = y.ToString(),
Text = y.ToString("00")
});
}
and then in the GET method
public ActionResult ConfirmPayment()
{
PayNowViewModel model = new PayNowViewModel();
ConfigureViewModel(model);
return View(model);
}
and in the POST method
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult ConfirmPayment(PayNowViewModel model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
.... // save and redirect (should not be returning the view here)
}
If the set of your dropdown options is fixed (or recompilation is OK after the potential options change), you can use an enum to store your options.
public enum Month {
// if the dropdown is not required, add default value 0
Optional = 0,
[Display(Name = #"Month_January")]
January = 1,
[Display(Name = #"Month_February")]
February = 2,
// etc ..
}
To render this as a dropdown use an EditorTemplate Enum.cshtml:
#model Enum
#{
var enumType = ViewData.ModelMetadata.ModelType;
var allValues = Enum.GetValues(enumType).Cast<object>().ToSelectList(Model);
// read any attributes like [Required] from ViewData and ModelMetadata ...
var attributes = new Dictionary<string, object>();
}
#Html.DropDownListFor(m => m, allValues, attributes)
The ToSelectList extension method loops over all enum values and converts them to SelectListItems:
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list) {
return ToSelectList<T>(list, list.FirstOrDefault());
}
public static IList<SelectListItem> ToSelectList<T>(this IEnumerable<T> list, T selectedItem) {
var items = new List<SelectListItem>();
var displayAttributeType = typeof(DisplayAttribute);
foreach (var item in list) {
string displayName;
// multi-language:
// assume item is an enum value
var field = item.GetType().GetField(item.ToString());
try {
// read [Display(Name = #"someKey")] attribute
var attrs = (DisplayAttribute)field.GetCustomAttributes(displayAttributeType, false).First();
// lookup translation for someKey in the Resource file
displayName = Resources.ResourceManager.GetString(attrs.Name);
} catch {
// no attribute -> display enum value name
displayName = item.ToString();
}
// keep selected value after postback:
// assume selectedItem is the Model passed from MVC
var isSelected = false;
if (selectedItem != null) {
isSelected = (selectedItem.ToString() == item.ToString());
}
items.Add(new SelectListItem {
Selected = isSelected,
Text = displayName,
Value = item.ToString()
});
}
return items;
}
To support multiple languages, add translations for the display name keys, e.g. "Month_January", to the Resource file.
Now that the setup code has been abstracted away using some reflection magic, creating a new viewmodel is a breeze :>
public class PayNowViewModel {
// SelectListItems are only generated if this gets rendered
public Month ExpiryMonth { get; set; }
}
// Intial Action
var paymentGateway = new PayNowViewModel();
return View(paymentGateway);
// Razor View: call the EditorTemplate
#Html.EditorFor(m => m.ExpiryMonth)
Note that in the EditorTemplate, Model is passed as the selected item to ToSelectList. After postback, Model will hold the currently selected value. Therefore it stays selected, even if you just return the model after an error in the controller:
// HttpPost Action
if (!ModelState.IsValid) {
return View("MakePayment", paymentGatewayForm);
}
Took us some time to come up with this solution, credits go to the Saratiba team.
I know there are many custom implementation of CheckBoxListFor helper method to fill up the missing feature in the MVC framework. But I am not ready to use them just yet. I am interested in creating a checkbox list using MVC 4 or 5 provided features only. So, I created this model class:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace MvcApplication1.Models
{
public class FruitViewModel
{
public int[] SelectedFruits { get; set; }
public IList Fruits
{
get
{
return new List{
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
};
}
}
}
public class Fruit
{
public int Id { get; set; }
public string Name { get; set; }
public bool Selected { get; set; }
}
}
And here is the controller class:
using MvcApplication1.Models;
using System.Web.Mvc;
namespace MvcApplication1.Controllers
{
public class FruitController : Controller
{
public ActionResult Index()
{
FruitViewModel model = new FruitViewModel();
return View(model);
}
[HttpPost]
public ActionResult Index(FruitViewModel model)
{
if (ModelState.IsValid)
{
}
return View(model);
}
}
}
And here is the razor view:
#model MvcApplication1.Models.FruitViewModel
#{
ViewBag.Title = "Index";
}
<h2>Select your favorite fruits</h2>
#using (Html.BeginForm("Index", "Fruit"))
{
<p>Using Html helper method:</p>
for (int i = 0; i < Model.Fruits.Count; i++)
{
#Html.CheckBoxFor(m => m.Fruits[i].Selected) #Model.Fruits[i].Name<br />
}
<p>Plain html without html helper</p>
for (int i = 0; i < Model.Fruits.Count; i++)
{
<input type="checkbox" name="SelectedFruits" value="#Model.Fruits[i].Id" checked="#Model.Fruits[i].Selected" />
#Model.Fruits[i].Name<br />
}
<input type="submit" name="submit" value="Submit" />
}
Here are the problems I am having:
The plain HTML version will populate the SelectedFruits collection with my selected fruits' Ids properly, as you can see from the screen shot. But when the page refreshes after post back, the selected checkboxes status is reset to not checked.
The version using the Html helper CheckBoxFor will not populate my SelectedFruits collection with my selected fruits' IDs, although it does seem to maintain the check box status as checked after post back completes.
So, in either case, there is a big problem.
What is the correct way to set up a checkbox list such that I can get the SelectedFruits collection populated correctly and the status of the checkboxes maintained after form post completes (important when I add other stuff on the page and if validation fails).
I would suggest that you can use the following solution,
Controller Change [Only the Get Method to set the data for the view to display]
public ActionResult Index()
{
FruitViewModel model = new FruitViewModel();
model.Fruits = new List<Fruit>
{
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
};
return View(model);
}
FruitModel Change, we are setting the property to be populated dynamically based on the fruits that the user has selected.
public int[] SelectedFruits
{
get
{
return Fruits != null && Fruits.Count > 0
? Fruits.Where(f => f.Selected == true).Select(f => f.Id).ToArray()
: new int[0];
}
set { }
}
View Page Change [Index.cshtml]
for (int i = 0; i < Model.Fruits.Count; i++)
{
#Html.CheckBoxFor(m => m.Fruits[i].Selected) #Model.Fruits[i].Name<br />
#Html.HiddenFor(m => m.Fruits[i].Id)
#Html.HiddenFor(m => m.Fruits[i].Name)
}
The problem that I found in your code was that you have used the checkbox name property as "SelectedFruits" in the html for the checkbox that you have manually rendered. Whereas the markup rendered by the Html Helper is having the name of "Fruits[0].Selected" etc...
Hence, the selected fruits was not properly modelbound.
Kindly verify the currently generated markup and post your feedback in case of any queries.
Your problem is that the Fruits property in your viewmodel always creates a new array where the Selected property for each fruit is always false. You can fix it by just initializing the array once in your viewmodel instead:
public IList<Fruit> Fruits
{
get
{
if (_fruits == null)
_fruits = new List<Fruit> {
new Fruit{Id=1, Name="Apple", Selected = false},
new Fruit{Id=2, Name="Banana", Selected = false},
new Fruit{Id=3, Name="Cherry", Selected = false},
new Fruit{Id=4, Name="Durian", Selected = false},
new Fruit{Id=5, Name="Elderweiss Grape", Selected = false}
}
return _fruits;
};
}
private List<Fruit> _fruits;
Once that's fixed, you'll have to update your controller to fix the Selected properties:
[HttpPost]
public ActionResult Index(FruitViewModel model)
{
foreach (int fruitId in model.SelectedFruits)
model.Fruits[fruitId].Selected = true;
return View(model);
}
I have one Model Having two virtual Properties i.e
public virtual IEnumerable MediumIds { get; set; }
public virtual IEnumerable AnsLanguageIds { get; set; }
I have Used ViewBag To Populate Them i.e
ViewBag.MediumIds = db.ExamMediums.Where(x => x.ExamId == _ExamId).Select(x => x.Medium);
ViewBag.AnsLanguageIds = new SelectList(db.AnswerLanguages.ToList(), "AnswerLanguageId", "AnsLanguage");
And My View Is
#foreach (var item in
ViewBag.MediumIds)
{
<input id="MediumIds" name="MediumIds" value="#item.MediumId" type="checkbox" /><strong>
#item.Medium1 </strong>
#Html.DropDownList("AnsLanguageIds")
<br />
}
I want The functionality like when the checkbox is selected than only the the dropdown should be enabled else it should be disabled and also i want that for which medium which anslanguage is selected
Your answer will be appreciated.
You can make the drop down lists disabled by default by passing new { disabled = "disabled" } as the DropDownList() method's htmlAttributes argument. This JQuery should toggle the select's disabled state when each checkbox is checked:
$(function() {
$("input#MediumIds").click(function() {
var checkbox = $(this);
var dropDownlist = checkbox.sibling("select:first");
dropDownlist.attr("disabled", checkbox.is(":checked") ? "" : "disabled");
});
});
...I'm not sure what you mean by "i want that for which medium which anslanguage is selected"?
For a project at work I'm trying to create a process that lets a user dynamically create a form that other users could then fill out the values for. I'm having trouble figuring out how to go about getting this to play nice with the built in model binding and validation with ASP MVC 3, though.
Our view model is set up something like this. Please note that I've over simplified the example code:
public class Form
{
public FieldValue[] FieldValues { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
public string Value { get; set; }
}
And our view looks something like:
#model Form
#using (Html.BeginForm("Create", "Form", FormMethod.Post))
{
#for(var i = 0; i < Model.Fields.Count(); i++)
{
#Html.TextBoxFor(_ => #Model.Fields[i].Value)
}
<input type="submit" value="Save" name="Submit" />
}
I was hoping that we'd be able to create a custom ModelValidatorProvider or ModelMetadataProvider class that would be able to analyze a FieldValue instance, determine if its Field.IsRequired property is true, and then add a RequiredFieldValidator to that specific instance's validators. I'm having no luck with this, though. It seems that with ModelValidatorProvider(and ModelMetadataProvider) you can't access the parent container's value(ie: GetValidators() will be called for FieldValue.Value, but there's no way from there to get the FieldValue object).
Things I've tried:
In the ModelValidatorProvider, I've tried using
ControllerContext.Controller.ViewData.Model, but that doesn't work if
you have nested types. If I'm trying to figure out the validators
Form.FieldValues[3], I have no idea which FieldValue to use.
I tried using a custom ModelMetadata that tries to use the internal
modelAccessor's Target property to get the parent, but this also
doesn't work if you have a nested type. Somewhere internal to MVC, an
expression like the one in my example will result in the Target being
the Model's type(Form), not FieldValue. So I get the same problem as
above where I have no idea what instance of FieldValue to compare
against.
A class-level validation attribute that I could put on the FieldValue
class itself, but this only gets called during server validation. I
need client-side validation, too.
Is what I'm trying to do even possible in MVC? Or is there something I'm missing entirely?
One possibility is to use a custom validation attribute.
But before getting into the implementation I would like to point out a potential flaw in your scenario. The IsRequired property is part of your model. This means that when the form is submitted its value must be known so that we conditionally apply the required rule to the corresponding property. But for this value to be known when the form is submitted this means that it must be either part of the form (as a hidden or standard input field) or must be retrieved from somewhere (datastore, ...). The problem with the first approach is obvious => hidden field means that the user can set whatever value he likes, so it's no longer a real validation because it is the user that decides which field is required.
This warning being said, let's suppose that you trust your users and decide to take the hidden field approach for storing the IsRequired value. Let's see how a sample implementation:
Model:
public class Form
{
public FieldValue[] Fields { get; set; }
}
public class FieldValue
{
public Field Field { get; set; }
[ConditionalRequired("Field")]
public string Value { get; set; }
}
public class Field
{
public bool IsRequired { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new Form
{
Fields = new[]
{
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = true }, Value = "" },
new FieldValue { Field = new Field { IsRequired = false }, Value = "value 3" },
}
};
return View(model);
}
[HttpPost]
public ActionResult Index(Form model)
{
return View(model);
}
}
View:
#model Form
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.Fields)
<input type="submit" value="Save" name="Submit" />
}
ConditionalRequiredAttribute:
public class ConditionalRequiredAttribute : ValidationAttribute, IClientValidatable
{
private RequiredAttribute _innerAttribute = new RequiredAttribute();
private readonly string _fieldProperty;
public ConditionalRequiredAttribute(string fieldProperty)
{
_fieldProperty = fieldProperty;
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var containerType = validationContext.ObjectInstance.GetType();
var field = containerType.GetProperty(_fieldProperty);
if (field == null)
{
return new ValidationResult(string.Format("Unknown property {0}", _fieldProperty));
}
var fieldValue = (Field)field.GetValue(validationContext.ObjectInstance, null);
if (fieldValue == null)
{
return new ValidationResult(string.Format("The property {0} was null", _fieldProperty));
}
if (fieldValue.IsRequired && !_innerAttribute.IsValid(value))
{
return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName });
}
return ValidationResult.Success;
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
ValidationType = "conditionalrequired",
};
rule.ValidationParameters.Add("iserquiredproperty", _fieldProperty + ".IsRequired");
yield return rule;
}
}
Associated unobtrusive adapter:
(function ($) {
$.validator.unobtrusive.adapters.add('conditionalrequired', ['iserquiredproperty'], function (options) {
options.rules['conditionalrequired'] = options.params;
if (options.message) {
options.messages['conditionalrequired'] = options.message;
}
});
$.validator.addMethod('conditionalrequired', function (value, element, parameters) {
var name = $(element).attr('name'),
prefix = name.substr(0, name.lastIndexOf('.') + 1),
isRequiredFiledName = prefix + parameters.iserquiredproperty,
requiredElement = $(':hidden[name="' + isRequiredFiledName + '"]'),
isRequired = requiredElement.val().toLowerCase() === 'true';
if (!isRequired) {
return true;
}
return value && value !== '';
});
})(jQuery);