MVC Checkbox grouping - c#

I've got a view that needs some checkboxes describing days of the week. I want to return back to the controller which boxes are selected. I found some help from various Google searches, etc, and here's what I have.
Section 1:
In the Controller:
vm.AllDays = Enum.GetValues(typeof(DayOfWeek)).Cast<DayOfWeek>().ToList();
In the View:
#foreach (var day in Model.AllDays) {
<input type="checkbox" name="days" value="#day"/> <label>#day</label><br />
}
And then in my post handler:
[HttpPost]
public ActionResult _AddEdit(SubscriptionDetailsModel vm, IEnumerable<string> days) {
//etc, etc, etc
}
This works, I get the selected days coming in on the days parameter. However, I got curious and changed the 'name' attribute on the checkboxes to the field in my model that I ultimately want these values to go to. In summary, my model for the view contains an object that has a List in it. That List is the collection I would like the selected values to end up in. But here's the relevant code.
Section 2
Model snippets:
public class SubscriptionDetailsModel {
public CronTabModel CrontabModel { get; set; }
//...
}
public class CronTabModel {
public List<DayOfWeek> On { get; set; }
}
Controller:
[HttpPost]
public ActionResult _AddEdit(SubscriptionDetailsModel vm) {
//etc, etc, etc
}
And, of course, view:
#foreach (var day in Model.AllDays) {
<input type="checkbox" name="On" value="#day"/> <label>#day</label><br />
}
Edit: The reason I was seeing confusing results is due to a typo elsewhere in my View.
Technically, the code in Section 1 is usable for my situation, I can do some string parsing, etc. Is it possible to make the Checkbox group populate from one collection (Model.AllDays) and return in a second collection (Model.CronTabModel.On)?

Model binding to a list is a bit awkward, but possible. MVC has an issue with gaps in indices, they did build in a helper to fix it. The hidden field lets MVC know about the gaps so it can bind effectively.
#for (var index =; index < Model.AllDays.Count(); index++) {
#Html.Hidden("AllDays.Index", index)
#Html.CheckBoxFor(m => Model.AllDays[Index], new { Value= Model.AllDays[index] }
<label>#Model.AllDays[Index]</label><br />
}
That should let you bind to a list of checkboxes that post their values back and are bound to a property called AllDays on your model.
If you want to change the name, you will need to change the name on your Model that you pass out. Doing it this way allows MVC to add the indexers onto the name, so you end up with
AllDays[0] = "Sunday"
AllDays[1] = "Monday"
Etc etc.
Some more reading about array binding http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx

Related

How to return to controller a IEnumerable of the values selected in the view from a list

I have a view, which simply does a foreach loop on a IEnumerable passed in the viewmodel, I have a way for people to select these entities on the list (apply a class to them to highlight), and this currently works for some clientside printing through jquery. Is there a way I can get these entities (staff) which have the highlight class and push them back as a model to the controller?
I have tried to use html.hiddenfor and just putting all the staff in there and making a form, with asp-action of my function on the controller and just a submit button, this did not work
the controller contains
public IActionResult StaffSelectionAction(StaffIndexModel model)
{
//things to do to process the selected staff
foreach (Staff staff in model.staff){
//Do stuff
}
return RedirectToAction("Index");
}
the view contains
<form asp-action="StaffSelectionAction">
#Html.HiddenFor(b => b.staff)
<input type="submit" value="Process Staff" class="btn btn-default" asp-action="StaffSelectionAction"/>
</form>
model contains
public IEnumerable<Staff> staff { get; set; }
Edit: spelling mistake in code
There are a few ways to handle this.
One way is to create an helper class, extending the Staff model and adding a 'selectable' attribute for it. Something like:
public class SelectableStaff : Staff
{
public bool Selected {get; set;}
}
Then in your view:
#Html.CheckBoxFor(m => m.Selected)
Using model binding, binding back to the controller with type SelectableStaff should then give you the selected values where you can do something like:
foreach (SelectableStaff staff in model.staff.Where(x => x.Selected)){
//Do stuff
}
You can get it back to Staff easily enough using linq and contains. Alternatively, you can also use #Html.Checkbox("myfriendlyname"), then include a FormCollection in the controller and pull the variable out of it. Personally, I think the model binding is less error prone.

using DropdownlistFor helper for a list of names

I know it was silly to ask this question but I'm not able to figure it out and I need your help guys.
First of all I am new to MVC. In my Project I am using a dropdownlistFor helper for displaying a list of names available for a particular Id. I did that and it is displaying names for an id.
Now while posting the form I am getting a Null Reference Exception for property used in the dropdownlist.
Here is my property in model which is a list of names.
In my controller in the [HttpGet] I did this which calls a function and returns a list of names for that Id.
Now the list of names is being displyed while form loading. And my view is as
When I am submitting the form I am getting a Null Reference Exception because in new SelectList(Model.InterviewerName) the Model is NULL.
Is there anyway to get me out of this issue.
I think you should update your viewmodel like this:
public class InterviewViewModel
{
public List<SelectListItem> Interviewers { set;get;}
public int SelectedInterviewerID { set;get;}
//Other properties relevant to the view as needed
}
And in your GET action set the Interviewers collection property:
public ActionResult Interview()
{
var vm=new InterviewViewModel();
vm.Interviewers =GetInterViewrsFromSomewhere();
return View(vm);
}
public List<SelectListItem> GetInterViewrsFromSomewhere()
{
var list=new List<SelectListItem>();
//Items hard coded for demo. you can read from your db and fill here
list.Add(new SelectListItem { Value="1", Text="AA"});
list.Add(new SelectListItem { Value="2", Text="BB"});
return list;
}
And in your view which is strongly typed to InterviewViewModel
#model InterviewViewModel
#using(Html.Beginform())
{
<p>Select interviewer :
#Html.DropdownlistFor(x=>x.SelectedInterviewerID,Model.Interviewers,"Select")
<input type="submit" />
}
So when the form is posted, The selected interviewers id will be available in the SelectedInterviewerID property:
[HttpPost]
public ActionResult Interview(InterviewViewModel model)
{
if(ModelState.IsValid)
{
//check for model.SelectedIntervieweID value
//to do :Save and redirect
}
//Reload the dropdown data again before returning to view
vm.Interviewers=GetInterViewrsFromSomewhere();
return View(vm);
}
In the HttpPost action method, if you are returning the viewmodel back to the view, you need to refill the dropdown content because HTTP is stateless and it won't keep the dropdown content between the requests (Webforms does this using Viewstate and here in MVC we don't have that).
I think i spotted the problem, you need to do the following two things to resolve,
1) change the model property public IList<string> InterviewerName to public string InterviewerName
2) use ViewBag to take the selectlist values to the View.
Let me know if it helps.

Persist Data through multiple form POSTs

I was wondering what the best way to approach this problem in ASP.NET MVC would be. The following is a trivial example of what I'd like to be able to do:
I have a webpage with textbox and a submit button. When the submit button is pressed the I would like the contents to be displayed on the same webpage. When it is pressed again I would like what was already displayed from the first submission to be displayed as well as the new data that was just submitted.
I have tried saving this data to a model, but the model is wiped clean every time the form posts. How could I do this and keep the data from the post before the last one (and the post before that)?
If you want data to persist between requests, as a starting point I would use 'TempData'. The TempData property value is stored in session state and exists until it is read or until the Session expires.
Example ViewModel:
public class SomeClass
{
public string Something { get; set; }
public List<string> RetainedValues { get; set; }
}
Example Controller:
[HttpGet]
public ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult Index(SomeClass postedValues)
{
// retrieve retained values
var retained = (List<string>) TempData["RetainedValues"] ?? new List<string>();
retained.Add(postedValues.Something);
// save for next post
TempData["RetainedValues"] = retained;
// setup viewmodel
var model = new SomeClass
{
RetainedValues = retained
};
return View("Index", model);
}
Example View (strongly typed):
<div>
#foreach(var item in Model.RetainedValues)
{
<div>#item</div>
}
</div>
#using(Html.BeginForm())
{
#Html.EditorFor(m=>m.Something)
<input type="submit"/>
}
Just put an hidden field for your model property then your previews value will be loaded on it and passed it back to the next post.
Ex.: #Html.HiddenFor(model => model.YourProperty)
So knowing that you could have two properties ex.: one named newValue and other called allValues.
the allValues you use it with an hidden field and your newValue you use to insert the new ones. So on post you just add the newValue to the allValues.
Something like that:
model.allValues += newValue;
--UPDATE
Or you can use session or tempdata as mentioned by #Jesse
For this case I would prefer to use hidden fields as it has a lower complexity and its data didnt need be secure as it will be shown to the user anyway.

FormCollection modify keys

How can I override keys in FormCollection (I need this because I have a property bound to multiple CheckBoxes in a View)? I did try this when a post back is in Action:
formCollection["DecisionReasons"] = formCollection["DecisionReasons"].Replace(",false", "").Replace("false,", "").Replace(",","|");
...but when I UpdateModel only the first value is updated in the model (in my model I have a DecisionReason string).
Do I need a ModelBinder (how can I do that?) or is there another way to do this?
Part of View
<div style="width:300px;height:250px;overflow:auto;">
#foreach (var a in (IEnumerable<SelectListItem>)ViewBag.AllDecisionReasons)
{
#Html.CheckBox("DecisionReasons", a.Selected, new { value = a.Value })
<label>#a.Text</label><br />
}
#Html.ValidationMessage("DecisionReasons", (string)Model.DecisionReasons)
if i check more than one checkbox in my View my Model property wich is string is updated with only one value example (if in View i check 2 checkboxes my Model will be updated with first value, so i need that Model property have value "valu1,valu2" and so on.)
sorry for my bad english.
I commented, above, asking for more details, but I get the impression that you're using checkboxes to represent the individual values in a Flags enumeration.
The default model binder doesn't handle mapping flags in this way, but I found a custom model binder that does in this article.
Edit:
OK, I see from your update (which would be better if you added it to your question, rather than posting it as an answer), your model has a comma-delimited string property containing each of the selected DecisionReasons.
I suggest that you consider the use of a ViewModel. The ViewModel is an abstraction of your model that is tailored for the way in which you present it in your view.
You can derive your ViewModel from your Model class, to reduce the amount of work. Consider this (untested) code:
public class MyModel
{
public virtual string DecisionReasons { get; set; }
}
public class MyViewModel : MyModel
{
public string[] DecisionReasonValues { get; set; }
public override string DecisionReasons
{
get
{
return string.Join(",", DecisionReasonValues);
}
set
{
DecisionReasonValues = value.Split(',');
}
}
}
Use MyViewModel as the model for your View and use DecisionReasonValues to render the checkboxes, not DecisionReasons. ASP.NET MVC will populate DecisionReasonValues from your checkboxes but you can access them as a comma-delimeted string through the overridden DecisionReasons property.

How to use multiple form elements in ASP.NET MVC

So I am new to ASP.NET MVC and I would like to create a view with a text box for each item in a collection. How do I do this, and how do I capture the information when it POSTs back? I have used forms and form elements to build static forms for a model, but never dynamically generated form elements based on a variable size collection.
I want to do something like this in mvc 3:
#foreach (Guest guest in Model.Guests)
{
<div>
First Name:<br />
#Html.TextBoxFor(???) #* I can't do x => x.FirstName here because
the model is of custom type Invite, and the
lambda wants to expose properties for that
type, and not the Guest in the foreach loop. *#
</div>
}
How do I do a text box for each guest? And how do I capture them in the action method that it posts back to?
Thanks for any help.
Definitely a job for an editor template. So in your view you put this single line:
#Html.EditorFor(x => x.Guests)
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/Guest.cshtml)
#model AppName.Models.Guest
<div>
First Name:<br />
#Html.TextBoxFor(x => x.FirstName)
</div>
And that's about all.
Now the following actions will work out of the box:
public ActionResult Index(int id)
{
SomeViewModel model = ...
return View(model);
}
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: do something with the model your got from the view
return RedirectToAction("Success");
}
Note that the name of the editor template is important. If the property in your view model is:
public IEnumerable<Guest> Guests { get; set; }
the editor template should be called Guest.cshtml. It will automatically be invoked for each element of the Guests collection and it will take care of properly generating ids and names of your inputs so that when you POST back everything works automatically.
Conclusion: everytime you write a loop (for or foreach) inside an ASP.NET MVC view you should know that you are doing it wrong and that there is a better way.
You can do this:
#for (int i = 0; i < Model.Guests.Count; i++) {
#Html.TextBoxFor(m => m.Guests.ToList()[i].FirstName)
}
There are more examples and details on this post by Haacked.
UPDATE: The controller post action should look like this:
[HttpPost]
public ActionResult Index(Room room)
{
return View();
}
In this example I'm considering that you have a Room class like this:
public class Room
{
public List<Guest> Guests { get; set; }
}
That's all, on the post action, you should have the Guests list correctly populated.

Categories