I'm pretty new at web development. So, sorry if this I'm asking dumb questions. I'm building a list of checkboxes based on available filter criteria pulled from a database. The model i pass to my view has a property
public List<string> ServicesList{ get; set; }
My view code contains a loop to create a checkbox for each item in the list.
#foreach (string svc in Model.ServicesList)
{
if (svc != "")
{
<input type="checkbox" name="SelectedServices" title="#svc" value="#svc" checked="checked" /> #svc
<br />
}
}
My Controller Action looks like this:
public ActionResult ServiceListExplorer(string[] SelectedServices, FormCollection fc)
{
}
This works as far as displaying the list of services as checkboxes and having access inside the controller action to which ones are checked by the user once the httppost occurs. But, I have 2 questions:
How can I pass which items should be checked initially via the Model? This would allow me to save off their filter criteria and pre-select the services. I was thinking maybe I need to use a SelectList instead of List but not really sure how.
How can I make the "checked" attribute dynamic by binding it to a value in the model?
Instead of creating a list of string objects, you should define a class for e.g.
public class ServiceObject
{
public string Name{get;set;}
public bool IsChecked{get;set;}
}
then your code will become-
public List<ServiceObject> ServicesList{ get; set; }
what you have to fill is the IsChecked property to true if your checkbox is to be selected:
then this line will become-
<input type="checkbox" name="SelectedServices" title="#svc" value="#svc" checked=Model.IsChecked />
First of all instead of ServicesList should be list of objects not list of string :
public class Service
{
public string ServiceName {get; set;}
public bool isChecked {get;set;}
}
....
public List<Service> ServicesList{ get; set; }
....
//that controller will collect data posted from view
//(your form will post only checked checkboxes)
[HttpPost]
public JsonResult Update(FormCollection services)
{
foreach (string item in services)
{
System.Diagnostics.Debug.WriteLine(item);
}
return Json(services);
}
not in your view :
#using (Html.BeginForm("Update", "Home", FormMethod.Post)) {
foreach (MvcMusicStore.Models.Service svc in Model)
{
#svc.ServiceName #Html.CheckBox(svc.ServiceName, svc.isChecked);
<br/>
}
<input type="submit" value="submit"/>
}
Related
I have a basic model
public class SpeakerConsent
{
public string FieldLabel { get; set; }
public List<SelectListItem> OptionValues { get; set; }
}
My razor page currently looks like the following
#for (var i = 0; i < Model.OptionValues.Count(); i++)
{
#Html.RadioButtonFor(x => Model.OptionValues[i].Selected, Model.OptionValues[i].Value )
}
I might have 4 or 5 items in my OptionValues.
Question : How can i bind to the Selected property of the SelectListItem so when the model is posted i can identify which on of the radio buttons has been selected?
With your current code, the HTML markup generated for the input element will be like below
<input data-val="true" name="OptionValues[0].Selected" type="radio" value="201">
the name is Selected and the value is a number. The Selected property of the SelectListItem is bool type. Model binding will not work as expected when you post this form data.
IMHO, The easiest way to handle a scenario like this is to use editor templates.
I will first create a class which represents the data I want to use with the radio buttons.
public class MyOption
{
public bool IsSelected { set; get; }
public int Id { set; get; }
public string Text { set; get; }
}
Now in my main view model, I will add a collection type property
public class SpeakerConsent
{
public List<MyOption> Options { set; get; }
}
Now in your ~/Views/Shared/ directory, create a folder called EditorTemplates and then create a view called MyOption.cshtml in that. You can have the HTML markup you want to use to render the radio button in that. Here I am keeping the Id property in a hidden field along with the radio button and label.
#model YourNamespace.MyOption
<div>
<span>#Model.Text</span>
#Html.HiddenFor(g=>g.Id)
#Html.RadioButtonFor(b => b.IsSelected, true)
</div>
Now in your GET action, you can populate this Options property of yout SpeakerConsent view model object and send to the view.
public ActionResult Create()
{
var vm = new SpeakerConsent();
vm.Options = new List<MyOption>
{
new MyOption { Id=1, Text="Seattle"},
new MyOption { Id=2, Text="Detroit"},
new MyOption { Id=31, Text="Kerala"},
};
return View(vm);
}
Now in your main view, you can simply call the EditorFor helper method.
#model YourNamespace.SpeakerConsent
#using (Html.BeginForm("Index", "Home"))
{
#Html.EditorFor(a=>a.Options)
<button type="submit">Save</button>
}
Make sure to check the HTML markup generated by this code. notice the
name attribute value of the inputs.
When the form is submitted, you can read the Options property value, loop through them and read the Id and IsSelected property value and use them as needed
[HttpPost]
public ActionResult Create(SpeakerConsent model)
{
// check model.Options
// to do : return something
}
I'm working on a search criteria building page. In addition to several string and numerical type fields, there are several "multiple choice" options.
I'm using the [Get] signature without parameters(pass the CriteriaModel to the view) >> [Post] signature with CriteriaModel parameter (redirect to searching controller)
I've built lightweight option classes (just value, name pairs) and am populating several List<> with the primitive options.
Using Html.DropDownListFor, I'm able to get them to display.
...but...
When I enter the [Post] version, the List<>s are all set to null and empty. Further, the other criteria fields supposed to be populated afterwards are also default and empty.
Technically, I don't need a whole list of values back - if I could even just have the index of the selected value - but I'm up against a wall here.
Pertinent model data:
public class CriteriaModel
{
[DisplayName("Owner Name")]
public string OwnerName { get; set; }
[DisplayName("Subdivision")]
public List<Subdivision> Subdivision { get; set; }
[DisplayName("PIN")]
public string PIN { get; set; }
}
public class Subdivision
{
public int ID { get; set; }
public string Name { get; set; }
}
Pertinent controller code:
[HttpGet]
public ActionResult Index()
{
CriteriaModel criteria = new CriteriaModel();
...fill in the Subdivisions...
View(criteria);
}
[HttpPost]
public ActionResult Index(CriteriaModel search_criteria)
{
return View("Search obtained" + search_criteria.Subdivision.First().Name);
}
And pertinent View markup:
#model REOModern.Models.CriteriaModel
...bunch of HTML...
#Html.LabelFor(model => model.Subdivision)
#Html.DropDownListFor(x => x.Subdivision, new SelectList(Model.Subdivision, "ID", "Name", Model.Subdivision.First().ID))
...other HTML...
<button type="submit" class="btn btn-primary" value="Index">Search</button>
I should clarify: I know that my 'return View("Search obtained" + ...' will fail, but it should show the piece of data that I need. The problem is it's a null reference exception. Until I can fix that, there's no point in building a user-friendly View for submitted search criteria.
MVC does not repopulate the List<> elements.
You would split the selected value out into another property of the model.
So in your model, include something like this
public int SelectedValue { get; set; }
Then for your Html.DropDownListFor helper you would use
Html.DropDownListFor(model => model.SelectedValue, Model.DropDownList, new { /* htmlAttributes */ });
Of course they're empty. The only data that exists in your post action is that which was posted via the form. Since the entire dropdown list, itself, was not posted, merely a selected item(s), the lists are empty. For anything like this, you need to rerun the same logic in your post action to populate them as you did in your get action. It's usually better to factor out this logic into a private method on your controller that both actions can use:
private void PopulateSomeDropDownList(SomeModel model)
{
// logic here to construct dropdown list
model.SomeDropDownList = dropdownlist;
}
Then in your actions:
PopulateSomeDropDownList(model);
return View(model);
My Problem is like this ,Im trying to get a model object from a view after seinding it with a form,the model Looks like this:
public class PackageModel
{
public PackageDTO Package { get; set; }
public IEnumerable<SelectListItem> Allcategories { get; set; }
}
while PackageDTO is just an DTO object conatining many attributes.
Now the view for this model,ist just showing the attributes and this model will be sent within a httppost request to the index page as normal(there it will be processed ans saved ),
the index method in the Controller Looks like this:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
}
Now i dont know what im doing wrong,but the Object packageModel is not totally null,just the list Allcategories and another string Attribute in the PackageDTO object,the rest seems to be working.
The view contains this code
<fieldset>
<legend>#Resources.AppvManagementService_EditPackage_Title</legend>
#using (Html.BeginForm("Index","WantedController",FormMethod.Post,new {enctype="multipart/form-data"}))
{
#Html.ValidationSummary()
<labelName </label>#Html.TextBoxFor(model=>model.Package.Name) <br/>
<label>Sid </label>#Html.TextBoxFor(model=>model.Package.Sid,new {#disabled="disabled"}) <br/>
<label>Category </label>#Html.DropDownList("CategoryName",Model.Allcategories,Model.Package.Category)<br/>
<label>Description: </label>#Html.TextBoxFor(model=>model.Package.Description) <br/>
<label>Type: </label>#Html.TextBoxFor(model=>model.Package.Type) <br/>
<button type="submit">submit</button>
}
Doest anyone have any idea why ist like this?? am i doing something wrong(im sure i am :))
thx for every one
How do you expect Allcategories to be populated? Your view contains a field, which posts a value under the name "CategoryName" - there's nothing in your view that populates a list of categories. More importantly; to you really need it to be populated? It seems to me that Allcategories is only really needed for populating the dropdown in the view. On the post, you shouldn't need it. If you DO still need it, you're going to have to either:
Repopulate it in the controller on the HttpPost method:
[HttpPost]
public ActionResult Index(PackagemODEL packageModel, FormCollection form)
{
packageModel.Allcategories = new IEnumerable<SelectListItem>();
}
Clutter up your view with pointless hidden fields to pass the values back in (I wouldn't recommend this for a list of items unless you really need to):
#for (int i = 0; i < Model.Allcategories.Count; i++)
{
#Html.HiddenFor( m => m.Allcategories[i])
}
Populate it in the model constructor:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public PackageModel()
{
Allcategories = new IEnumerable<SelectListItem>();
/* Add values to Allcategories here */
}
}
If the values of Allcategories doesn't change, you could also consider making it a static readonly property of your model and hardcoding the values (or pulling them from a config file or similar).
As for getting back the selected CategoryName, you need a field in your model in which to store it, otherwise the only way to access it at the moment is via Request.Form:
public class PackageModel
{
public IEnumerable<SelectListItem> Allcategories { get; set; }
public string CategoryName { get; set; }
}
#Html.DropDownListFor(m => m.CategoryName, Model.Allcategories, Model.Package.Category)
An aside: Please, please, please take your DTO out of your model and set appropriate properties in your model itself. Your DTO does not belong in your view model, which is a model for your view and nothing more.
I'm populated a partial view with a strongly-typed model. In this partial view is a form. When I submit the form it tells me that objects inside of my model are null, even though they are not because the partial view rendered all elements based on that same model.
More specifically, I'm having trouble passing back all of my checkboxes. If you look at my controller you can see that I check to see if CompanyOptions is null, and every time I run the program it prints STUFF IS NULL, meaning that it's null.
Model:
public class Company
{
public string Name { get; set; }
public string DatabaseName { get; set; }
public CompanyOptions CompanyOptions;
}
public class CompanyOptions
{
public CompanyLicenseOptions CompanyLicenseOptions { get; set; }
}
public class CompanyLicenseOptions
{
public List<CompanyLicenseOption> CompanyLicenseOptionsList;
}
View:
#using (Html.BeginForm("Action", FormMethod.Post))
{
for (int i = 0; i < Model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList.Count; i++)
{
#Html.CheckBoxFor(model => model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[i].IsLicensed, checkboxHtmlAttributes);
<label for="#Model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[i].LicenseName">#Model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[i].LicenseName</label>
<br/>
}
#Html.HiddenFor(model => model.DatabaseName)
<input id="submit_licenses" type="submit" style="display:none;" />
}
Controller:
[HttpPost]
public void Action(Company model)
{
System.Diagnostics.Debug.WriteLine("STUFF:" + model.DatabaseName);
if(model.CompanyOptions!=null)foreach (var item in model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList) System.Diagnostics.Debug.WriteLine("STUFF:" + item);
else System.Diagnostics.Debug.WriteLine("STUFF IS NULL");
}
Generated HTML:
<input class="licenses" data-val="true" disabled="" id="CompanyOptions_CompanyLicenseOptions_CompanyLicenseOptionsList_0__IsLicensed" name="CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[0].IsLicensed" type="checkbox" value="true" /><input name="CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[0].IsLicensed" type="hidden" value="false" />
The Irrelevant JS
$('#save_licenses').click(function () {
swap_licenses(true);
$('#submit_licenses').click();
});
POST:
Request URL:http://localhost:3080/Controller/Action
Request Method:POST
Status Code:200 OK
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[0].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[1].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[2].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[3].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[4].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[5].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[6].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[7].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[8].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[9].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[10].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[11].IsLicensed:false
CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[12].IsLicensed:false
DatabaseName:myDb
<input class="licenses" data-val="true" disabled="" id="CompanyOptions_CompanyLicenseOptions_CompanyLicenseOptionsList_0__IsLicensed" name="CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[0].IsLicensed" type="checkbox" value="true" /><input name="CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList[0].IsLicensed" type="hidden" value="false" />
Here's your problem:
disabled=""
Your checkbox is disabled, so nothing will ever get sent to the server. That's how HTML works. Disabled elements are never sent. So get rid of this attribute.
If you want to prevent the user from modifying the value, and yet the initial value get sent to the server use the readonly attribute, not disabled.
Also another problem I see with your code is with the CompanyLicenseOptionsList collection field. It should be a property with public getter and setter:
public class CompanyLicenseOptions
{
public List<CompanyLicenseOption> CompanyLicenseOptionsList { get; set; }
}
Same stands true for your CompanyOptions field (you have defined it as a field, whereas it should be a property):
public class Company
{
public string Name { get; set; }
public string DatabaseName { get; set; }
public CompanyOptions CompanyOptions { get; set; }
}
UPDATE:
Now that you have fixed the problem with your missing getters and setters, all that's left is make sure that all the models intervening in this object graph have default (parameterless) constructors. That's a requirement if you want they to appear as action argument because otherwise the default model binder wouldn't know how to instantiate them. If for some reason you cannot add a default constructor to all your objects, I would very strongly recommend you revise your object hierarchy and start using view models right away.
You should use a foreach loop instead of a simple for, this way:
#using (Html.BeginForm("Action", FormMethod.Post))
{
foreach (var option in Model.CompanyOptions.CompanyLicenseOptions.CompanyLicenseOptionsList)
{
#Html.CheckBoxFor(o => o.IsLicensed, checkboxHtmlAttributes);
<label for="#option.LicenseName">#option.LicenseName</label>
<br/>
}
#Html.HiddenFor(model => model.DatabaseName)
<input id="submit_licenses" type="submit" style="display:none;" />
}
Since the endpoint of all these checkboxes is a List<T>, you'll need to make sure that it is instantiated before use:
public class CompanyOptions
{
public CompanyLicenseOptions CompanyLicenseOptions { get; set; }
}
public class CompanyLicenseOptions
{
public List<CompanyLicenseOption> CompanyLicenseOptionsList;
public CompanyLicenseOptions()
{
CompanyLicenseOptionsList = new List<CompanyLicenseOption>();
}
}
EDIT: To ensure that readers get proper context for this answer and avoid confusion, I've reproduced the OP code above mine.
I have a List of about 20 items I want to display to the user with a checkbox beside each one (a Available property on my ViewModel).
When the form is submitted, I want to be able to pass the value of each checkbox that is checked back to my controller method via the Selections property on my ViewModel.
How would I go about doing this using the Form Helper class in MVC? Is this even possible?
PS: I don't want a listbox where the user can just highlight multiple items.
Model:
public class MyViewModel
{
public int Id { get; set; }
public bool Available { get; set; }
}
Controller:
public class HomeController: Controller
{
public ActionResult Index()
{
var model = Enumerable.Range(1, 20).Select(x => new MyViewModel
{
Id = x
});
return View(model);
}
[HttpPost]
public ActionResult Index(IEnumerable<MyViewModel> model)
{
...
}
}
View ~/Views/Home/Index.cshtml:
#model IEnumerable<AppName.Models.MyViewModel>
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
Editor template ~/Views/Home/EditorTemplates/MyViewModel.cshtml:
#model AppName.Models.MyViewModel
#Html.HiddenFor(x => x.Id)
#Html.CheckBoxFor(x => x.Available)
The best thing to do would be to create a template that can be reused. I have some code at home that I can post later tonight.
Maybe check SO for similar posts in the mean time.
Dynamic list of checkboxes and model binding
This blog post also could help;
http://tugberkugurlu.com/archive/how-to-handle-multiple-checkboxes-from-controller-in-asp-net-mvc-sample-app-with-new-stuff-after-mvc-3-tools-update