I have the following code in my view:
#foreach (var x in model)
{
<input type="checkbox" name="#x.name" /> #x.name
}
That loop will create about 10 check boxes with each of them having a unique name generated during the runtime. The whole point of this is to make this dynamic without me having to type the values of each name. So when I am trying to check in my controller if each of these check boxes are checked or not, how do I do it? Normally, I would pass a parameter to my controller
public ActionResult MyController(string/bool checkboxName)
and this would work fine if I had one checkbox. However, passing 10 parameters for each check box in the controller sounds insane! What if I add more to x in model later so that I have 20 check boxes?
In the loop you are creating the checkboxes, add them to an ICollection and then iterate over that collection looking for CheckBox.Checked.
I'm certain there is a helper function for that in MVC but I'm traveling and don't quite remember it. Something like this should work, adapt as needed.
Note: This assumes they are all on the same page.
#foreach (var control in this.Controls)
{
if (control is CheckBox)
{
if (((CheckBox)control).Checked)
{
//record as you need
}
}
}
I think one of the ways to do this, which might not be ideal but can still work is to use the FormCollection in the MVC framework since you are using a loop, you can loop the elements in your action
/// your view side
#foreach (var x in model)
{
<input type="checkbox" name="#x.name" /> #x.name
}
///The action side
public ActionResult MyController(FormCollection collection, YourModel model)
{
foreach (var x in model)
{
bool IsThisCheked= Convert.ToBoolean(collection[x.name].ToString());
}
}
I think you can pass the whole list of checkboxes to the post controller and then iterate through them like
[HttpPost]
public ActionResult About(List<Requirements> requirements)
{
foreach (var item in requirements)
{
if (item.IsSelected == true)
{
//perform some operation
}
}
List is the list that I passed from view to controller on submitting the form
Now coming to view it can be something like this
#for (int i = 0; i < Model.Requirements.Count; i++)
{
#Html.HiddenFor(m => m.Requirements[i].RId)
#Html.CheckBoxFor(m => m.Requirements[i].IsSelected, new { id = "requirements_" + i, #class = "divider" })
#Html.DisplayFor(m => m.Requirements[i].RName)
}
RId,RName,IsSelected(someproperty to store boolean) are my model properties related to checkboxlist
Related
I have a form where the user can add as many rows as needed. Each time they are expected to select a different option from the dropdown list provided. At the moment there is no validation to stop them selecting the same value multiple times.
Each row is a "ResourceCount".
The ViewModel has an IList of ResourceCountViewModel, so each row is being added as an item to the list.
The ResourceCount view model consists of an "id" to store the dropdown value selected and a "quantity" for the number field.
I can't think of a way to use the Compare annotation in this scenario.
How can I implement appropriate validation for this?
EDIT:
There is lots of good advice in the accepted answer. I went with a simple check in the controller post action:
if (resourceCounts.Count != resourceCounts.Where(d => d.Id!= 0)
.Select(x => x.Id)
.Distinct()
.Count())
{
ModelState.AddModelError(string.Empty, "You can't select the same option more than once");
//etc.
}
This is simply not possible using a ValidationAttribute (either in-built or custom ones) and MVC's unobtrusive client side validation.
Validation attributes are applied to properties of a model (your class), and the context in which validation is checked is for that instance only - it has no knowledge of other instances of the model within the collection, therefore it cannot compare other instances.
Conversely if you apply a ValidationAttribute to a collection (e.g. List<T> property), unobtrusive validation will not work because the data-val-* attributes necessary to add rules to the $.validator could only be generated if you include an input for the collection property (as opposed to each property of each object in the collection) which means ModelBinding would fail.
You will need to write your own controller code and scripts to achieve your custom validation.
On the client side, you could handle the .change() event of the <select> and check if any previous selections match, and if so display an error message. You have not shown your model, or the view, but based on the following html (repeated for each object in the collection
<select class="select" .....>
<option value="">Please select</option>
<option value="1">On-call</option>
....
<select>
<div class="error"></div> // see notes below if you using ValidationMessageFor()
then the script would be
var errors = $('.error');
var selects = $('.select');
$('.select').change(function() {
var selected = $.map(selects, function(s, i) {
return $(s).val();
})
$.each(selects, function(i, s) {
var error = $(this).next('.error');
var val = $(this).val();
var index = i;
if ($(this).val()) {
var others = selected.filter(function(s, i) {
if (i != index) {
return s;
}
});
if (others.indexOf(val) > -1) {
error.text('Please make a unique selection');
} else {
error.text('');
}
} else {
error.text('');
}
})
})
Refer this fiddle for a working example.
Alternatively you could hide/show (or disable) options in each <select> to prevent the user making invalid selections in the first place, but that becomes more complex if your dynamically adding/deleting items, and/or when your view is editing existing data where the property already has a selected value (I'll leave that to you to ask a new question showing your attempt if you want to implement that).
On the server side, you can check for duplicate values, and if so, add a ModelState error and return the view, for example
var selected = new List<int>();
for (int i = 0 i < model.Count; i++)
{
if (selected.Contains(model[i].YourProperty))
{
ModelState.AddModelError("", "Please make a unique selection");
break;
}
else
{
selected.Add(model[i].YourProperty);
}
}
if (!ModelState.IsValid)
{
return View(model);
}
....
or using linq
if (model.Select(x => x.YourProperty).GroupBy(x => x).Any(g => g.Count() > 1))
{
ModelState.AddModelError("", "Please make a unique selection");
}
which would then be displayed in the views #Html.ValidationSummary().
If your using #Html.ValidationMessageFor(m => m[i].YourProperty) in your view for each dropdownlist, then the above loop can be modified to
if (selected.Contains(model[i].YourProperty))
{
var propertyName = string.Format("[{0}].yourPropertyName", i);
ModelState.AddModelError(propertyName, "Please make a unique selection");
break;
}
and modify the script to add/remove the message for the <span> element generated by ValidationMessageFor() (i.e instead of the <div class="error"> element as shown above)
As a side note, if you want to learn more about how validation attributes in conjunction with client side validation work, I recommend reading The Complete Guide To Validation In ASP.NET MVC 3 - Part 2.
I have installed Key Value Pair package in Umbraco back office and created a some key value pairs and I am trying to get those within the View to populate the dropdownlist.
#{
if (CurrentPage.HasValue("superHeros"))
{
<ul>
#foreach (var item in CurrentPage.superHeros.Split(','))
{
<li>#item</li>
}
</ul>
}
}
Second options
#if (Model.Content.HasValue("superHeros"))
{
var preValue = Umbraco.GetPreValueAsString(Model.Content.GetPropertyValue<int>("superHeros"));
<p>#preValue</p>
}
Non of them working though. How do I need to call it in the View?
I have a Model that produces an IENumerable, so I already have a foreach loop to iterate and organize each of my model's contents. However, I have one piece of the model that I only want to print once. Here is what I have now:
#foreach (var item in Model)
{
if (item.Site == "Source Search")
{
More Results
}
}
The problem is that this causes it to print the link 50 times (how many items I have in each Model), but I only want it once.
Any ideas?
Use a flag.
bool found = false;
#foreach (var item in Model)
{
if (item.Site == "Source Search" && !found)
{
found = true;
More Results
}
}
The simplest way would be to simply add a break to exit the loop after it's first rendered:
#foreach (var item in Model)
{
if (item.Site == "Source Search")
{
More Results
break;
}
}
This should be an easy one, but without ViewState, I'm clueless here (I've been babied with WebForms for too long, I know!).
My scenario:
View
#foreach (var product in Model.Products)
{
<tr>
<td>#Html.ActionLink("Compare", "Compare", new { id = product.ProductId })</td>
</tr>
}
Controller
public ActionResult Compare(int id = 0)
{
var product = SelectProduct(id); // selects the product from a list of cached products.
if (product != null)
{
// _productDetails is a Model specifically for my View.
_productDetails.ComparedProducts.Add(product);
}
return View("Index", _productDetails);
}
Obviously, when you click on "Compare" for each item, it'll add to the the ComparedProducts list. But, with there being no ViewState, this will get cleared on every page refresh and lose the last product. I want products to be kept in this ComparedProducts list, but how?
I'm guessing they need to be appended to the querystring, so /Carousel/Compare/?id=2122,1221,1331,1333 etc. If so, how is this possible?
Thanks in advance.
Updated
If I did want to go the query string route, how do I do this?
I've tried:
<td>#Html.ActionLink("Compare", "Compare", new { id = product.ProductId, compared = Model.ComparedProducts.Select(a => a.ProductId) })</td>
But that brings out:
compared=System.Linq.Enumerable%2BWhereSelectListIterator`2[Product%2CSystem.Int32]
Which I'd expect really. I guess I'd make yet a further ViewModel property and simply store the Compared Id's in there to not have much business logic within my View?
+1 for your relationship with webforms :)
I think from now on, you can start to keep state in the other ways you already know from webforms like Session State: http://msdn.microsoft.com/en-us/library/ms178581(v=vs.100).aspx
You are also right on the querystring, after all, if you want to keep things simple, is better to use the simplest methods , for instance:
<url>?reference=123&compare=456
EXAMPLE
you need the first action as HttpGet and now this one as httpPOST
[HttpPost]
public ActionResult Compare(myModel model)
{
var product = SelectProduct(model.reference); // selects the product from a list of cached products.
if (product != null)
{
// _productDetails is a Model specifically for my View.
// you can always update the model you got in the first place and send it back
model.ComparedProducts.Add(product); //
}
return View("Index", model);
Your view should react according to empty properties to display
If i got a list of checkbox in a View, and this list came from Enum (flags). If my checkbox as all the same name, did my controller will update automaticly my Enum (flags) values in my ViewModel with multiple selection ?
Suppose i get in my View
<% foreach (var t in Enum.GetValues(typeof(FoodType)))
{
Response.Write(t.ToString() + " ");
%>
<input type="checkbox" name="TypeOfFood" value="<%:(int)t %>" />
<% }%>
My Controller working like this
public ActionResult Manage(FoodEntity food)
{
}
If i check many check box when i look then FoodType property in my foodEntity, only the value of the first checkbox is selected, but my enum is a flag... what i need, if i want support flag ?
thanks.
Unfortunately no.
It will just grab the first checked value and assign that to your value field.
That would be a pretty cool feature though.
Heres a quick way to get the value you're looking for back into your model:
int newEnumValue = Request.Form["CheckBoxField"].Split(',').Aggregate(0, (acc, v) => acc |= Convert.ToInt32(v), acc => acc);