I am currently working on an Edit function for my project and I cannot seem to get the View Model right to allow it to be passed back into the Controller from the View. The structure of the View Model is as such:
public class CreateUserViewModel : ICreateUserViewModel
{
#region Properties
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string SelectedUserType { get; set; }
public List<ICreateUserItemViewModel> UserTypes { get; set; }
public List<ICreateUserItemViewModel> Products { get; set; }
public List<ICreateUserItemViewModel> Languages { get; set; }
#endregion
#region Constructor
public CreateUserViewModel()
{
}
public CreateUserViewModel(List<Product> products, List<Language> languages)
{
Products = new List<ICreateUserItemViewModel>();
foreach (var prod in products)
{
var prodVM = new CreateUserItemViewModel
{
Name = prod.Name,
IsSelected = false,
ID = (int)prod.ID
};
Products.Add(prodVM);
}
Languages = new List<ICreateUserItemViewModel>();
foreach (var lang in languages)
{
var langVM = new CreateUserItemViewModel
{
Name = lang.Name,
IsSelected = false,
ID = (int)lang.ID
};
Languages.Add(langVM);
}
}
#endregion
}
The subclass ViewModel CreateUserItemViewModel:
public class CreateUserItemViewModel : ICreateUserItemViewModel
{
public string Name { get; set; }
public int ID { get; set; }
public bool IsSelected { get; set; }
}
I want this subclass to be represented in the view as a checkbox, so the user can choose to include it or not.
User Controller:
[HttpPost]
public ActionResult Create(CreateUserViewModel model)
{
if (ModelState.IsValid)
{
User newUser = new User();
newUser.UserName = model.UserName;
newUser.Password = model.Password;
newUser.Email = model.Email;
newUser.Products = model.Products; //Always NULL
When I put a break point on this part of the application the properties for username password and email are populated but Products and Languages are empty. I used Fiddler2 to watch what was being passed into the controller and this was the output:
UserName=asdasdsdasd&Password=asd&Email=asD%40asd.com&type.IsSelected=Admin
&prod.ID=1&prod.IsSelected=true&prod.IsSelected=false
&prod.ID=2&prod.IsSelected=false
&prod.ID=3&prod.IsSelected=false
&prod.ID=4&prod.IsSelected=false
&lang.ID=1&lang.IsSelected=true&lang.IsSelected=false
&lang.ID=2&lang.IsSelected=true&lang.IsSelected=false
&lang.ID=3&lang.IsSelected=false
&lang.ID=4&lang.IsSelected=false
&lang.ID=5&lang.IsSelected=false
&lang.ID=6&lang.IsSelected=false
&lang.ID=7&lang.IsSelected=false
&lang.ID=8&lang.IsSelected=false
&lang.ID=9&lang.IsSelected=false
&lang.ID=10&lang.IsSelected=false
&lang.ID=11&lang.IsSelected=false
&lang.ID=12&lang.IsSelected=false
&lang.ID=13&lang.IsSelected=false
&lang.ID=14&lang.IsSelected=false
&lang.ID=15&lang.IsSelected=false
&lang.ID=16&lang.IsSelected=false
&lang.ID=17&lang.IsSelected=false
&lang.ID=18&lang.IsSelected=false
&lang.ID=19&lang.IsSelected=false
A nugget of the code for the Create View is:
<div class="editor-label">
#Html.Label("User Products:")
</div>
<div class="editor-field">
#foreach (var prod in Model.Products)
{
#Html.Label(prod.Name)
#Html.HiddenFor(x => prod.ID)
#Html.CheckBoxFor(x => prod.IsSelected, new { name = prod.Name })
}
</div>
<div class="editor-label">
#Html.Label("User Languages:")
</div>
<div class="editor-field">
#foreach (var lang in Model.Languages)
{
#Html.Label(lang.Name)
#Html.HiddenFor(x => lang.ID)
#Html.CheckBoxFor(x => lang.IsSelected, new { name = lang.Name })
}
</div>
<p>
<input type="submit" value="Create" />
</p>
I have been working on this for quite a while and I am beginning to think what I am trying to do is not possible. I want it to return the CreateUserViewModel fully populated with all the values that the user has selected, but I just do not know how to achieve this.
Any ideas?
Use a for loop instead of a foreach.
#for (int i=0; i < Model.Products.Count; i++)
{
#Html.Label(prod.Name)
#Html.HiddenFor(model => model.Products[i].ID)
#Html.CheckBoxFor(model => model.Products[i].IsSelected, new { name = prod.Name })
}
Your property expression needs to contain enough information for the model binder to figure out how to bind the POST values to the model.
You could also try creating an Editor Template for ICreateUserItemViewModel and then changing your markup to the following.
#Html.EditorFor(model => model.Products)
As already mentioned in posted answer, it can be done by applying indexes but if the elements are non-sequential refer below article
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
Also refer below one for introduction to "Model Binding"
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
Adding to the suggestion list, consider removing the CreateUserViewModel(List products, List languages) from the viewmodel. ViewModel should contain only properties and place additional logic (i.e, attaching UserTypes, etc., in your case) inside the controller.
Related
I have a form that needs to capture values from checkboxes in a form. Each checkbox should have an integer value and when the form is submitted, the view model should validate the values and at least one should be selected.
I need to also build a two way binding so that the framework will auto select the options that are selected when the page is loaded.
Here is what my model looks like
public class SomeViewModel
{
[Required(ErrorMessage = "You must select at least one site.")]
[Display(Name = "All site to have access too")]
public int[] Sites { get; set; }
}
The I encapsulate my ViewModel in a Presentation class called Presenter like so
public class Presenter
{
public SomeViewModel Access { get; set; }
public IEnumerable<Site> AvailableSites { get; set; }
}
Now, I pass Presenter to my view and want to render the
And here is how my view looks like
<div class="form-group">
#Html.LabelFor(m => m.Access.Sites, new { #class = "col-sm-2 control-label" })
<div class="col-sm-10">
#for (int i = 0; i < Model.AvailableSites.Count(); i++ )
{
<label class="radio-inline">
#Html.CheckBoxFor(m => m.Access.Sites[i])
</label>
}
</div>
#Html.ValidationMessageFor(m => m.Access.Sites)
</div>
since #Html.CheckBoxFor accepts bool value, and I am passing an integer I am getting an error on the #Html.CheckBoxFor(m => m.Access.Sites[i]) line inside the view.
How can I correct this issue? and how can I correctly render checkboxes in this view?
As you have discovered, you can only use CheckBoxFor() to bind to a bool property. Its a bit unclear why you have 2 view models for this when you could simplify it by just using
public class Presenter
{
[Required(ErrorMessage = "You must select at least one site.")]
[Display(Name = "All site to have access too")]
public int[] Sites { get; set;
public IEnumerable<Site> AvailableSites { get; set; }
}
One option you can consider (based on the above model and assuming type of Site contains properties int ID and string Name)
Change the view to to manually generate <input type="checkbox" /> elements
#foreach(var site in Model.AvailableSites)
{
// assumes your using Razor v2 or higher
bool isSelected = Model.Sites.Contains(s.ID);
<label>
<input type="checkbox" name="Sites" value="#site.ID" checked = #isSelected />
<span>#s.Name</span>
</label>
}
#Html.ValidationMessageFor(m => m.Sites)
Note the #Html.LabelFor(m => m.Sites) is not appropriate. A <label> is an accessibility element for setting focus to its associated form control and you do not have a form control for Sites. You can use <div>#Html.DisplayNameFor(m => m.Sites)</div>
A better option (and the MVC way) is to create a view model representing what you want in the view
public class SiteVM
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
and in your GET method, return a List<SiteVM> based on all available site (for example
List<SiteVM> model = db.Sites.Select(x => new SiteVM() { ID = x.ID, Name = x.Name };
return View(model);
and in the view use a for loop of EditorTemplate (refer this answer for more detail) to generate the view
#model List<SiteVM>
....
#using (Html.BeginForm())
{
for(int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(m => m[i].ID)
#Html.HiddenFor(m => m[i].Name)
<label>
#Html.CheckBoxFor(m => m[i].IsSelected)
<span>#Model[i].Name</span>
</label>
#Html.ValidationMessage("Sites")
}
....
}
and then in the POST method
[HttpPost]
public ActionResult Edit(List<SiteVM> model)
{
if (!model.Any(x => x.IsSelected))
{
ModelState.AddModelError("Sites", "Please select at least one site");
}
if (!ModelState.IsValid)
{
return View(model);
}
...
}
In either case you cannot get client side validation using jquery.validate.unobtrusive because you do not (and cannot) create a form control for a property which is a collection. You can however write your own script to display the validation message and cancel the default submit, for example (assumes you use #Html.ValidationMessageFor(m => m.Sites, "", new { id = "sitevalidation" }))
var sitevalidation = $('#sitevalidation');
$('form').submit(function() {
var isValid = $('input[type="checkbox"]:checked').length > 0;
if (!isValid) {
sitevalidation.text('Please select at least one site');
return false; // cancel the submit
}
}
$('input[type="checkbox"]').click(function() {
var isValid = $('input[type="checkbox"]:checked').length > 0;
if (isValid) {
sitevalidation.text(''); // remove validation message
}
}
After checking boxes and submitting the form, the ShouldSend field is the default value (false) in the Controller post method, even if I checked the appropriate box to make it true. I've looked up similar issues, and the two most common recommendations are to check that:
the complex objects are indexed so the model binder can put them back together in the list
I use CheckBoxFor in order to bind the result of the checkbox to my model
I'm currently doing both of those things, but it's still not binding. Everything else is binding just fine (the list of MessageTypeViewModels in SidebarViewModel, for example).
Any idea why my checkboxes aren't binding?
ViewModels
public class WrapperViewModel
{
public WrapperViewModel()
{
Sidebar = new SidebarViewModel();
Content = new ContentViewModel();
}
public SidebarViewModel Sidebar { get; set; }
public ContentViewModel Content { get; set; }
}
public class SidebarViewModel
{
public SidebarViewModel()
{
MessageTypeViewModels = new List<MessageTypeViewModel>
{
new TypeViewModel {Type = "Type 1", Label = "Label 1"},
new TypeViewModel {Type = "Type 2", Label = "Label 2"},
// etc.
};
}
public IEnumerable<MessageTypeViewModel> MessageTypeViewModels { get; set; }
public int Field2 { get; set; }
public int? Field3 { get; set; }
}
public class MessageTypeViewModel
{
public string Type { get; set; }
public string Label { get; set; }
public bool ShouldSend { get; set; }
}
Views
// Index.cshtml
#model MessageGeneratorViewModel
#using (Ajax.BeginForm("SendMessages", new AjaxOptions
{
HttpMethod = "POST",
UpdateTargetId = "content",
}))
{
<div id="sidebar">
<h3>Message Types</h3>
<ul>
#Html.EditorFor(m => m.MessageTypeViewModels, "MessageTypeEditorTemplate")
</ul>
<h3>Target</h3>
<ul>
<li>
#Html.LabelFor(m => m.Field2)
#Html.TextBoxFor(m => m.Field2)
</li>
<li>
#Html.LabelFor(m => m.Field3)
#Html.TextBoxFor(m => m.Field3)
</li>
</ul>
<button type="submit">Send Message</button>
</div>
}
// MessageTypeEditorTemplate.cshtml
#model List<MessageTypeViewModel>
#for (int i = 0; i < Model.Count; i++)
{
<li>
<label>
#Html.CheckBoxFor(m => m[i].ShouldSend)
#Model[i].Label
</label>
</li>
}
Controller
public ActionResult SendMessages(WrapperViewModel model)
{
// model.Sidebar.MessageTypeViewModels[<any-index>].ShouldSend is false, even if I checked the appropriate box.
}
Ah, I should have been posting the SidebarViewModel, not the WrapperViewModel. Now everything binds correctly.
public ActionResult SendMessages(SidebarViewModel sidebar)
{
// do stuff
}
Using MVC4 am wanting to implement functionality which will allow a user to add new items to the database.
I've managed to achieve this adding items to a single table, but now I need to display data from multiple tables, then populate the added / selected data to those tables.
I have these 3 tables
Threats
ID
Description
ThreatHasSecurityEvent
ThreatID
SecurityEventID
SecrutiyEvents
ID
Description
And here's my code so far:
ViewModel
public class ThreatWithSecurityEvents
{
public Threat Threat { get; set; }
public SecurityEvent SecurityEvent { get; set; }
public List<int> SecurityEventIds { get; set; }
public ThreatWithSecurityEvents()
{
SecurityEventIds = new List<int>();
}
}
Get Controller
[HttpGet]
public ActionResult AddNewThreat()
{
ThreatWithSecurityEvents ViewModel = new ThreatWithSecurityEvents();
var SecurityEvents = _DBContext.SecurityEvents.Select(x => new SelectListItem()
{
Text = x.Description,
Value = x.ID.ToString()
});
ViewBag.SecurityEventDropdown = SecurityEvents;
return View(ViewModel);
}
View
#model RiskAssesmentApplication.Models.ThreatWithSecurityEvents
#{
ViewBag.Title = "AddNewThreat";
//Layout = "~/Views/Shared/MasterLayout.cshtml";
}
<div style="font-family: Calibri">
<h2>AddNewThreat</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Threat</legend>
#using (Html.BeginForm("Add New Threat", "Threats"))
{
Html.HiddenFor(model => model.SecurityEventIds);
<div class="editor-label">
#Html.LabelFor(model => #Model.Threat.Description, "Threat Description")
</div>
<div class="editor-field">
#Html.EditorFor(model => #Model.Threat.Description)
#Html.ValidationMessageFor(model => #Model.Threat.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => #Model.SecurityEvent.Description, "Associated Security Event")
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SecurityEventIds, ViewBag.SecurityEventDropdown as IEnumerable<SelectListItem>)
</div>
<p>
<input type="submit" value="Add New" />
</p>
}
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
</div>
Am unsure how to implement the Post Action Method and a Save Method in the repository.
Previously I could inject a new Threat Object and send it to the edit view doing something like:
Previous Get Method - AddNewThreat
[HttpGet]
public ActionResult AddNewThreat()
{
return View("EditThreat", new Threat());
}
and I would then use the EditThreat Action Method to post back
Previous Post Action - AddNewThreat
[HttpPost]
public ActionResult EditThreat(Threat Threat)
{
if (ModelState.IsValid)
{
repository.SaveThreat(Threat);
TempData["message"] = string.Format("{0} new description has been saved", Threat.Description);
return RedirectToAction("GetThreat", new { ThreatID = Threat.ID });
}
else
{
// something is incorrect!
return View(Threat);
}
}
Previous Save Method - SaveThreat From Repository
public void SaveThreat(Threat Threat)
{
if (Threat.ID == 0)
{
_context.Threats.Add(Threat);
}
else
{
Threat dbEntry = _context.Threats.Find(Threat.ID);
if (dbEntry != null)
{
dbEntry.Description = Threat.Description;
}
}
_context.SaveChanges();
}
That's as far as I have got so far.
I want the user to be able to enter a new threat description and then select a security event or multiple events from a drop down list which will be associated with the new threat.
I realize am going to have to change the post back action method in the controller and the Save method in my repository, but I cant work out how to get both the new Threat description and the existing security events saved back to the database. I've had a search but as of yet haven't found / understood anything.
Any advice/help would be great.
Thanks
You view model should be
public class NewThreatVM
{
public string Description { get; set; } // add validation attributes as required
public List<int> SelectedSecurityEvents { get; set; }
public SelectList SecurityEventList { get; set; } // or IEnumerable<SelectListItem>
}
Side note: The Threat.ID property is not required in a create view, however if your want to use this for editing an existing Threat as well, add property int? ID and use if (model.ID.HasValue) in the POST method to determine if its a new or existing Threat
and the simplified view
#model yourAssembly.NewThreatVM
#Html.BeginForm())
{
#Html.TextBoxFor(m => m.Description)
#Html.ListBoxFor(m => m.SelectedSecurityEvents, Model.SecurityEventList)
<input type="Submit" value="Create" />
}
Side notes: Your view should not include a hidden input for the Security Event ID's (you cannot bind an input to a complex object or collection)
then the controller
public ActionResult Create()
{
NewThreatVM model = new NewThreatVM model();
ConfigureViewModel(model);
return View(model);
}
[HttpPost]
public ActionResult Create(NewThreatVM model)
{
if (!ModelState.IsValid)
{
ConfigureViewModel(model);
return View(model);
}
// Initialize new data model and map properties from view model
Threat threat = new Threat() { Description = model.Description };
// Save it (which will set its ID property)
_context.Threats.Add(Threat);
_context.SaveChanges();
// Save each selected security event
foreach (int selectedEvent in model.SelectedSecurityEvents)
{
ThreatHasSecurityEvent securityEvent = new ThreatHasSecurityEvent()
{
ThreatID = threat.ID,
SecurityEventID = selectedEvent
};
_context.ThreatHasSecurityEvents.Add(securityEvent);
}
_context.SaveChanges();
return RedirectToAction("GetThreat", new { ThreatID = threat.ID });
}
private void ConfigureViewModel(NewThreatVM model)
{
var securityEvents = _context.SecurityEvents;
model.SecurityEventList = new SelectList(securityEvents, "ID", "Description");
}
I believe the easiest way to achieve this, is "dividing" your form into separated steps.
You have2 entities: Threats, SecurityEventID
Threat has a collection of SecurityEvents
Create a form to add/edit Threats (url: Threats/Add | Threats/Edit/ThreatId)
Create a form to add/delete Events of an existing Threat (url: Threats/AddEvent/ThreatIdHere
Use custom ViewModels instead of the original class to send data to controller. Examples:
public class AddThreatViewModel
{
public string Description { get; set; }
//since it's a add view model, we dont need a ThreatId here
}
[HttpPost]
public ActionResult AddThreat(AddThreatViewModel model)
{
//convert the view model to Threat, add to database
}
public class AddThreatEvent
{
public int ThreatId { get; set; }
public int SecrutiyEventId { get; set; }
}
[HttpPost]
public ActionResult AddThreatEvent(AddThreatEventmodel)
{
//add threat event into existing threat
}
I have been trying to get two models to appear in a single view using ViewModels but am failing.
I have simple view with 3 fields right now (Comment, Name, Department) and and a model to match it. I have a controller that returns an empty view and when you submit the form, the empty model is filled in and passed back to the controller. I now want to turn the department field into a drop down and have deduced (maybe incorrectly?) that I should create another model with the static values in it and then pass the ViewModel to the view via the controller but when I attempt to do this, it fails:
View:
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="fieldset">
<fieldset>
<legend>CommentDb</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Comment)
</div>
<div>
#Html.TextAreaFor(model => model.Comment, new {style = "width: 900px; height:200px;", autocomplete = "off" })
#Html.ValidationMessageFor(model => model.Comment)
</div>
<div class="editor-label">
#Html.Label("Your Name")
#*#Html.LabelFor(model => model.CommentByName)*#
#Html.TextBoxFor(model => model.CommentByName, new { autocomplete = "off", placeholder = "Optional" } )
#Html.ValidationMessageFor(model => model.CommentByName)
</div>
<div class="editor-label">
#Html.Label("Your Department", new { #class = "display-label" })
#*#Html.LabelFor(model => model.Department)*#
#Html.TextBoxFor(model => model.Department, new { autocomplete = "off", placeholder = "Optional" })
#Html.ValidationMessageFor(model => model.Department)
</div>
</fieldset>
<br />
<br />
</div>
<div>
<input type="submit" value="Create" id="submit"/>
</div>
}
Department drop down model:
namespace SuggestionBox.Models
{
public class DropDownModel
{
public String Departments { get; set; }
public String SetDropDownList()
{
Departments = "Engineering";
return Departments;
}
}
}
Database model:
namespace SuggestionBox.Models
{
public class CommentModel
{
[Key]
public int CommentiD { get; set; }
public string CommentByName { get; set; }
public string Department { get; set; }
[Required]
public string Comment { get; set; }
public DateTime InsertDate { get; set; }
}
}
namespace SuggestionBox.Models
{
public class CommentDbContext : DbContext
{
public CommentDbContext() : base()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<CommentDbContext, SuggestionBox.Migrations.Configuration>());
}
public DbSet<CommentModel> Comments { get; set; }
}
}
My attempted ViewModel:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
}
}
The Controller:
public ViewResult Index()
{
SuggestionBoxViewModel vm = new SuggestionBoxViewModel();
return View(vm);
}
In return View(vm);, the IDE says: Arguement type 'SuggestionBox.ViewModels.SuggestionBoxViewModel' is not assignable to the model type 'SuggestionBox.Models.CommentModel'
Any idea what I am doing wrong here?
Cheers.
You are making a simple task into something overly complex. Your view model should contain only the properties used in the view, and should not contain methods. Populating your view model properties is the responsibility of the controller.
View model
public class CommentModelVM
{
[Required]
public string Comment { get; set; }
public string CommentByName { get; set; }
[Display(Name="Your Department")] // add attributes associated with the view
public string Department { get; set; }
public SelectList DepartmentList { get; set } // to populate the dropdown options
}
Controller
public ActionResult Create()
{
CommentModelVM model = new CommentModelVM();
ConfigureViewModel(model);
return View(model);
}
public ActionResult Create(CommentModelVM model)
{
if (!ModelState.IsValid())
{
// Repopulate options and return view
ConfigureViewModel(model);
return View(model);
}
// Save and redirect
}
private void ConfigureViewModel(CommentModelVM model)
{
List<string> departments = // create your list of departments here (from database or static list)
model.DepartmentList = new SelectList(departments);
}
View
....
#Html.LabelFor(m => m.Department)
#Html.DropDownListFor(m => m.Department, Model.DepartmentList)
....
I just want to start by saying that generally it is a bad idea to name properties or classes for our ViewModels out the view controls that they are bound to. For example: DropDownModel. Doing this creates confusion since Models and ViewModels don't represent a UI component they represent entities and data that the view uses to render its UI controls.
With that being said to answer you question I don't see the need for a ViewModel class to represent the static list of departments for your dropdown. I think you should just add a new list of departments to your SuggestionBoxViewModel class like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public IEnumerable<string> Departments { get; set; }
public string SelectedDepartment { get; set; }
public SuggestionBoxViewModel()
{
Departments = new [] {"Engineering","Sales"};
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
}
}
Then in your view all you have to do is bind the dropdown to the list of departments. Like this:
At the top of your view:
#model SuggestionBox.ViewModels.SuggestionBoxViewModel
Then where you want the dropdown to display:
#Html.DropDownListFor(m => m.SelectedDepartment, new SelectList(Model.Departments))
And that's it! I hope this helps.
Your View seems to expect a Comment Model.
If you wanted to bind to a ViewModel, then you would have to implement all the properties that Comment exposes.
So your ViewModel might look like this:
namespace SuggestionBox.ViewModels
{
public class SuggestionBoxViewModel
{
public CommentModel Comments { get; set; }
public DropDownModel Departments { get; set; }
public SuggestionBoxViewModel()
{
Departments = new DropDownModel();
Departments.SetDropDownList();
Comments = new CommentModel();
}
public int CommentiD
{
get { return Comments.CommentiD; }
}
public string CommentByName
{
get { return Comments.CommentByName; }
}
...etc.
}
}
I have the following cshtml form
#using (Html.BeginForm(Html.BeginForm("Create", "UserRole", Model, FormMethod.Post)))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Role</legend>
<div class="editor-label">
#Html.Label(Model.User.UserName)
</div>
<div class="editor-field">
#Html.CheckBoxList(Model.CheckboxList)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
And I wish to get the Model.CheckboxList selected Items in my action.
I have the following Create Action in my Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(UserRoleViewModel userRoleViewModel)
{
if (ModelState.IsValid)
{
//_context.Role.Add(role);
//_context.SaveChanges();
//return RedirectToAction("Index");
}
return View(viewModel);
}
However the viewModel.CheckboxList is 0.
How can I pass the selected values of the checkboxlist, and also the Model.User to the Controller Action?
My ViewModel looks like this :-
public User User { get; set; }
public IEnumerable<Role> RoleList { get; set; }
public List<UserRoleViewModel> UserList { get; set; }
public IEnumerable<SelectListItem> CheckboxList { get; set; }
public UserRoleViewModel()
{
}
public UserRoleViewModel(User user, IEnumerable<Role> roleList )
{
User = user;
RoleList = roleList;
}
Thanks for your help and time!
UPDATE ----------- After reading this post enter link description here, I tried to adapt my code to follow the example, but I am still finding problems with this updated code.
Now I have the following :-
cshtml :-
#model IEnumerable<MvcMembership.ViewModels.RoleCheckboxListViewModel>
#using (Html.BeginForm())
{
#Html.EditorForModel()
<input type="submit" value="OK" />
}
Views/Role/EditorTemplates/RoleCheckboxListViewModel.cshtml
#model MvcMembership.ViewModels.RoleCheckboxListViewModel
#Html.HiddenFor(x => x.RoleId)
#Html.HiddenFor(x => x.RoleName)
<div>
#Html.CheckBoxFor(x => x.Checked)
#Html.LabelFor(x => x.Checked, Model.RoleName)
</div>
ViewModels :-
public class RoleCheckboxListViewModel
{
public string RoleId { get; set; }
public string RoleName { get; set; }
public bool Checked { get; set; }
}
and the controller action is as follows :-
public ActionResult Create(int? uid)
{
var checkBoxList = new[]
{
new RoleCheckboxListViewModel() {
RoleId = "1", Checked = true, RoleName = "item 1" },
new RoleCheckboxListViewModel() {
RoleId = "2", Checked = true, RoleName = "item 2" },
new RoleCheckboxListViewModel() {
RoleId = "3", Checked = true, RoleName = "item 3" },
};
return View(checkBoxList);
}
The problem I have now is that on the Create.cshtml. I cannot see the checkboxlist, but only 123 displayed as well as the OK button.
Any help would be very much appreciated cause I am at a dead end at the moment.
I've accomplished this with the following parts:
1) A view model for the child element that adds the bool property that will represent whether or not the checkbox is checked in the View later... ie:
public class CategoryViewModel
{
public int ID { get; set; }
public string Name { get; set; }
public bool Assigned { get; set; }
}
2) A view model for the parent element that adds a collection property for this new child element view model, ie:
public class ManufacturerViewModel
{
public Manufacturer Manufacturer { get; set; }
public IList<CategoryViewModel> Categories { get; set; }
public ManufacturerViewModel()
{
Categories = new List<CategoryViewModel>();
}
}
3) A service layer method for getting a list of all child elements, while also setting the bool property for each ("Assigned" in my example). To be used by your controller.
public IList<CategoryViewModel> GetCategoryAssignments(Manufacturer mfr)
{
var categories = new List<CategoryViewModel>();
foreach (var category in GetCategories())
{
categories.Add(new CategoryViewModel
{
ID = category.ID,
Name = category.Name,
Assigned = mfr.Categories.Select(c => c.ID).Contains(category.ID)
});
}
return categories;
}
4) A method for updating the parent item's collection based on your checkboxlist selections. To be used by your controller.
public void UpdateCategories(string[] selectedCategories, ManufacturerViewModel form)
{
if (selectedCategories == null)
selectedCategories = new string[] { };
var selectedIds = selectedCategories.Select(c => int.Parse(c)).ToList();
var assignedIds = form.Manufacturer.Categories.Select(c => c.ID).ToList();
foreach (var category in GetCategories())
{
if (selectedIds.Contains(category.ID))
{
if (!assignedIds.Contains(category.ID))
form.Manufacturer.Categories.Add(category);
}
else
{
if (assignedIds.Contains(category.ID))
form.Manufacturer.Categories.Remove(category);
}
}
}
5) Modifications to your Create/Edit view. ie:
#Html.EditorFor(model => model.Categories)
You must also add this so that the original assigned values are included in post data. You'll have to add a HiddenFor for each property that you have set as Required through validation.
for (int i = 0; i < Model.Manufacturer.Categories.Count; i++)
{
#Html.HiddenFor(model => model.Manufacturer.Categories[i].ID);
#Html.HiddenFor(model => model.Manufacturer.Categories[i].Name);
}
6) And finally, a new EditorTemplate for your child view model element. ie:
#model YourProject.ViewModels.CategoryViewModel
<li>
<input type="checkbox"
id="#string.Format("cb{0}{1}", #Model.Name, #Model.ID)"
name="selectedCategories" //Notice this name corresponds to string[] selectedCategories so that it can be extracted from the post data
value="#Model.ID"
#(Html.Raw(Model.Assigned ? "checked=\"checked\"" : "")) />
<label for="#string.Format("cb{0}{1}", #Model.Name, #Model.ID)">#Model.Name</label>
#Html.HiddenFor(model => model.ID)
</li>
Hopefully my own application gives you a better idea of how to solve this issue.
Store your selected value into the variable as follows, and pass it to an hidden field, then you can access it easily
var modelSelected = document.getElementById("modelName");
document.getElementById('selectedModel').value =
modelSelected.options[modelSelected.selectedIndex].text;
<input id="selectedModel" name="selectedModel" type="hidden" runat="server" />