I have this piece of code in my View which belongs to a form
<div class="col-md-10">
#foreach (var l in leads)
{
#: #Html.CheckBox("cbLead", false, new { #value = #l.Id }) #Html.TextBox("worth", "") - #Html.Label(l.Name)
}
</div>
And this is the form with I handle the post:
[HttpPost]
public ActionResult Update(string[] cbLead, double[] worth)
{
// code
}
I have 24 checkboxes, but for each checkbox selected I receive 2 values in the Update method. So for example if I select 3 out of that 24 checkboxes, I receive 27 values in the string[] cblead.
Example with 24 checkboxes:
And this is what I get in the method:
So I receive the value checked and an added false after. Any tips?
That's because the Html.CheckBox helper generates an additional hidden field with the same name and the value false. The reason for that is because if the checkbox is not checked, then no value will be sent to the server and thus the model binder will fail to properly bind to a boolean property on your model. Also notice that the Html.CheckBox helper expects that you are working with boolean values on your models. Your syntax here is incorrect:
#Html.CheckBox("cbLead", false, new { #value = #l.Id })
You seem to be trying to manually set the value attribute of the checkbox (which should not be done when using helpers) to the Id property of your model which I suppose is not boolean but rather a Guid as can be seen from the screenshot.
This is by design and is expected behavior. If you do not want this behavior that you could write your own custom helper or use plain HTML instead.
I suspect that what you need to receive on the server is the list of IDs along with a boolean value corresponding to whether the element was checked or not. For this purpose I suggest you writing the following view model:
public class MyViewModel
{
public IList<LeadViewModel> Leads { get; set; }
}
public class LeadViewModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Worth { get; set; }
public bool IsChecked { get; set; }
}
and then:
#for (var i = 0; i < Model.Leads.Count; i++)
{
Html.CheckBoxFor(x => x.Leads[i].IsChecked)
Html.HiddenFor(x => x.Leads[i].Id)
Html.TextBoxFor(x => x.Leads[i].Worth) -
Html.LabelFor(x => x.Leads[i].Name)
}
Related
Summary:
I'm trying to use two DropDownList controls to filter the data that is currently being sorted and displayed in a view.
What we are going to learn
Creating the ViewController for One to Many and Many-to-Many relationships that could Filter the data using DropDownList
Possible Causes
If my DropdownList code is not terrible wrong, The ViewModel I'm using to display the data has no proper support for the DropDownList items.
In other words, the RazorView and my ViewModels are not compatible for what I'm trying to achieve. If I try to change my ViewModel or RazorView, I get an eldless loop of errors for my existing code.
OR The Linq Query needs an expert attention
Here is FilterViewModel.cs
public IEnumerable <App> Apps { get; set; }
public IEnumerable <Language> Languages { get; set; }
public IEnumerable <Platform> Platforms { get; set; }
public IEnumerable <AgeGroup> AgeGroups { get; set; }
public IEnumerable <ProductCode> ProductCodes { get; set; }
Here is AppsController.cs
public ActionResult FilterApps(App app)
{
var apps = _context.Apps.ToList();
var languages = _context.Languages.ToList();
var productCodes = _context.ProductCodes.ToList();
var platforms = _context.Platforms.ToList();
var ageGroups = _context.AgeGroups.ToList();
var viewModel = new FilterViewModel
{
AgeGroups = ageGroups,
Languages = languages,
Platforms = platforms,
ProductCodes = productCodes,
Apps = apps
.Where(a => a.LanguageId == app.LanguageId && a.PlatformId == app.PlatformId)
// I also tried all possible combinations :(a.Lanage.Id etc)
};
return View("FilterApps", viewModel);
}
Here is the FilterApps.cshtml
#model Marketing.ViewModels.FilterViewModel
<h2>FilterApps</h2>
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div class="form-group">
#Html.DropDownListFor( m => m.Languages,
new SelectList(Model.Languages, "Id", "Name"),"Select Language",
new { #class = "form-control", #id = "dropDown" })
#Html.DropDownListFor(m => m.Platforms,
new SelectList(Model.Platforms, "Id", "Name"), "Select Platform",
new { #onchange = "this.form.submit();",
#class = "form-control", #id = "dropDown" })
</div>
}
//The existing code below is working fine so far.
#foreach (var group in Model.AgeGroups)
{
<h4>#group.Name</h4>
#foreach (var app in Model.Apps.OrderBy(a => a.AppOrder))
{
if (app.AgeGroupId == group.Id)
{
#app.ProductCode.Name
#app.Name
#app.Platform.Name
}
}
}
Probably unnecessary but I hope the additional information will help.
Additional Information
The App.cs is referencing all other tables e.g.
public Language Language { get; set; }
public int LanguageId { get; set; }
public Platform Platform { get; set; }
public int PlatformId { get; set; }
and so on...
What I have already tried
Several breakpoints and Logs to track the data, I also tried to use the following but it ruins my existing sorting and grouping.
public App App { get; set; } //Instead of the IEnumerable<App>
There are multiple issues with your code.
First you cannot bind a <select> element to a collection of complex objects. A <select> posts back the value of its selected option (which will be an int assuming the Id property of Language is int).
Next the view in the model is FilterViewModel (and your generating form controls with name attributes based on those properties), but your posting back to a different model (App) which does not contain those properties so nothing would bind anyway.
Your adding a null label option ("Select Language") and if that were selected, it would post a null value which would cause your query to fail.
There are also some bad practices which I have noted below.
Your view model should be
public class AppsFilterVM
{
public int? Language { get; set; }
public int? Platform { get; set; }
public IEnumerable<SelectListItem> LanguageOptions { get; set; }
public IEnumerable<SelectListItem> PlatformOptions { get; set; }
...
public IEnumerable <App> Apps { get; set; }
}
Its not clear what AgeGroups and ProductCodes are for so I have omitted them in the code above, and from your comments, I have assumed that the user can filter by either Language or Platform or both
The controller code would be
public ActionResult FilterApps(AppsFilterVM model)
{
var apps = _context.Apps;
if (model.Language.HasValue)
{
apps = apps.Where(x => x.LanguageId == model.Language.Value);
}
if (model.Platform.HasValue)
{
apps = apps.Where(x => x.PlatformId == model.Platform.Value);
}
model.Apps = apps;
ConfigureViewModel(model);
return View(model);
}
private void ConfigureViewModel(AppsFilterVM model)
{
// populate the selectlists
var languages = _context.Languages;
var platforms = _context.Platforms
model.LanguageOptions = new SelectList(languages, "Id", "Name");
model.PlatformOptions = new SelectList(platforms , "Id", "Name");
}
Then in the view (note its making a GET, not a POST)
#model.AppsFilterVM
....
#using (Html.BeginForm("FilterApps", "Apps", FormMethod.Get))
{
#Html.LabelFor(m => m.Language)
#Html.DropdownListFor(m => m.Language, Model.LanguageOptions, "No filter")
#Html.ValidationMessageFor(m => m.Language)
#Html.LabelFor(m => m.Platform)
#Html.DropdownListFor(m => m.Platform, Model.PlatformOptions, "No filter")
#Html.ValidationMessageFor(m => m.Platform)
<input type="submit" value="Filter" />
}
#foreach (var group in Model.AgeGroups)
{
....
There a a few other thing you should not be doing. Your giving both <select> elements the same id attribute which is invalid html (the DropDownListFor() method already generates a unique id based on the property name).
You should not submit a form based on the change event of a <select> Not only is it unexpected behavior, if a user uses the keyboard to navigate through options (e.g. using the arrow keys, or typing a character to go to the first option starting with that letter, then the form will be immediately submitted. In addition, the user might select an option from the 2nd dropdownlist first, which would immediately post before they have a chance to select the option in the first one. Allow the user to make their selections, check them, and then submit the form when they choose to.
Your view should not contain linq queries, and your grouping and ordering should be done in the controller before you pass the model to the view. Your Apps property should in fact be a view model containing a property for the group name, and a collection property for the Apps, (similar to the view models in your previous question) so that the view is simply
#foreach(var group in Model.AgeGroups)
{
#group.Name
foreach (var app in group.Apps)
{
#app.ProductCode
#app.Name
#app.Platform
}
}
You should also consider using ajax to submit your form, which would call separate server method that returns a partial view of just the Apps, and update the DOM in the success callback, which would improve performance. For an example, refer Rendering partial view on button click in ASP.NET MVC.
My query is getting all the correct values and the selectlist is populated, but my dropdownlist only shows the first value in the selectlist. how can i get it to show the returned value of Captain?
Viewmodel:
public class EditTeamsView : BaseCommunityView
{
...
public IList<Domain.Team> ExistingTeams { get; set; }
...
public EditTeamsView()
{
ExistingTeams = new List<Domain.Team>();
}
}
public class Team
{
...
public string Captain { get; set; }
public IEnumerable<SelectListItem> MemberSelectList { get; set; }
}
View:
#for (var c = 0; c < Model.ExistingTeams.Count; c++)
{
#Html.DropDownListFor(x => x.ExistingTeams[c].Captain, Model.ExistingTeams[c].MemberSelectList,
new { #class = "form-control "})
}
Each option in the dropdown will need a value property and a display property so you may need to change up your Team class's MemberSelectList object to IEnumerable that contains value and text properties.
To set the default value of a drop down list based on a string value like what you have, new up a SelectList that requires an IEnumerable list of objects like what you have, a value property, a text property, and then the default value object.
See here: https://msdn.microsoft.com/en-us/library/system.web.mvc.selectlist.selectlist(v=vs.118).aspx#M:System.Web.Mvc.SelectList.
#Html.DropDownListFor(x => x.ExistingTeams[c].Captain,
new SelectList(Model.MemberSelectList, "Value", "Text", Model.Captain)
)
I am displaying radio buttons using enum class.
public enum RegisteredBy
{
[Display(Name = "Customer", Order = 0)]
H,
[Display(Name = "Dealer/Contractor", Order = 1)]
S,
}
When i am rendering this on my view and on submit I am not selected any radio button. Even though it is taking "H" as default value. So that it is not showing any validation message.
#using ConsumerProductRegistration.Models;
#using ProductRegistration.Models.Enums;
#model ProductRegistration.Models.Registration
#Html.RadioButtonFor(m => m.RegisteredBy, RegisteredBy.H, new { id = "RegisteredByCustomer" })
#Html.Label("Customer")<br />
#Html.RadioButtonFor(m => m.RegisteredBy, RegisteredBy.S, new { id = "RegisteredByDealer" })
#Html.Label("Dealer/Contractor")
#Html.ValidationMessageFor(m => m.RegisteredBy)
In Model:
public class Registration
{
[Required(ErrorMessage = "Select at least one option")]
[Display(Name = "Registered by*")]
public RegisteredBy RegisteredBy { get; set; }
}
In view:
public ActionResult CustomerInfo(Registration registration)
{
return View(registration);
}
please suggest me.If user does not select we should show the error message.
The default underlying type of the enumeration elements is int. By default, the first enumerator has the value 0, and the value of each successive enumerator is increased by 1.
When you are not selecting anything and posting the form, the default value 0 is automatically getting set (default value of integer).
In this case, you can make your property nullable with [Required] attribute which sends null as value when nothing is selected. And as it is decorated with [Required] attribute, it will give you required field validation error.
[Required]
public RegisteredBy? RegisteredBy { get; set; }
I have a MultiSelectList whose dataValueField is a number in a code-behind model and dataTextField field is string.
When I select multiple values in the resulting html select element I get validation error saying that the field must be a number. This makes sense because the backing field is an integer and when you select several entries the id values for buildings are concatenated using commas. What would be the work around for this? Thanks.
The model is as follows.
// Selected buildings are stored in this table.
public class ClientSelectedBuildings
{
public int ClientSelectedBuildingsId { get; set; }
// ...
[Display(Name = "Please select the buildings under consideration.")]
public int? BuildingId { get; set; }
}
// Building list is retrieved from this table.
public class Buildings
{
public int BuildingsId { get; set; }
// ...
[StringLength(255)]
public string BuildingName { get; set; }
}
My view looks as follows.
#model TheApplication.Models.ClientSelectedBuildings
<div class="outer">
<div class="inner">
#Html.LabelFor(t => t.BuildingId)
#Html.ListBoxFor(t => t.BuildingId, (MultiSelectList)ViewBag.Buildings, new { size = "4" })
#Html.ValidationMessageFor(t => t.BuildingId)
</div>
</div>
The issue is that your domain model is only allowing one BuildingId, yet the form will attempt to send multiple via the list box.
This is a perfect example where your domain model does not perfectly match up with the view model. Domain and View each have different concerns and outside of very very basic CRUD situations, you'll find that form views will always require a separate model.
You won't be able to bind directly to ClientSelectedBuildings (without a custom modelbinder). Instead, bind to an intermediate model that then can be mapped into multiple ClientSelectedBuildings.
// Here's the model submitted from the view. This will have to be mapped to domain
// entities.
public class FormModel
{
// ... Other form fields ...
public int[] BuildingIds { get; set;
// ... Other form fields ...
}
// Example controller action that processes the form input.
[HttpPost]
public ActionResult MyPostAction(FormModel input)
{
if (ModelState.IsValid)
{
// Loop all submitted building ids, map the data into domain entities.
foreach(var buildingId in input.BuildingIds)
{
// Create the domain entity.
var selected = new ClientSelectedBuildings
{
ClientSelectedBuildingsId = ... Wherever this comes from ...
BuildingId = buildingId;
};
// Add to the data repository.
this.MyDbContext.ClientSelectedBuildings.Add(selected);
}
// Submit all changes to the data context.
this.MyDbContext.ClientSelectedBuildings.SaveChanges();
// ... Return redirect to success view ...
}
// ... Return error view ...
}
I have a DropDropListFor that looks like this:
using (Ajax.BeginForm("FilterListingsWorkflow", "Listing",
new {
categoryguid = Model.SelectedCategoryGuid,
workflowstatus = Model.SelectedWorkflowStatus,
entityName = Model.EntityName,
},
new AjaxOptions {
HttpMethod = "POST",
UpdateTargetId = "listingTable",
InsertionMode = InsertionMode.Replace
}))
{
<p>Filter listings by Workflow Status:</p>
#Html.DropDownListFor(m =>
Model.SelectedWorkflowStatus,
Enum.GetValues(typeof (WorkflowStatus))
.Cast<WorkflowStatus>()
.Select(v =>
new SelectListItem {
Text = v.ToString(),
Value = ((int) v).ToString()
}),
new { onchange = "$(this.form).submit()" })
}
As you can see, there are three objects that are being sent to the controller:
Model.SelectedCategoryGuid, Model.SelectedWorkFlowStatus and EntityName.
WorkFlowStatus is an enum, and for some reason, whenever I click on any of the enums in the DropDownList, it always comes out to be the FIRST one.
I have three Enum values which get inserted in to the DropDownList which are:
Draft, Published and Archived.
So, whenever I click on any one of them and when I hover over the variable in the Controller, I see that the parameter variable: workflowstatus is always Draft (which is the first one).
Does anyone know why the CORRECT enum isn't being passed over?
It's really bugging me...
The name of your DropDownList is SelectedWorkflowStatus. So make sure your controller action takes such parameter as argument:
[HttpPost]
public ActionResult FilterListingsWorkflow(WorkflowStatus selectedWorkflowStatus)
{
...
}
That's where you will get the correct value. I can see that you are setting some route values for your form:
new {
categoryguid = Model.SelectedCategoryGuid,
workflowstatus = Model.SelectedWorkflowStatus,
entityName = Model.EntityName,
}
But don't look the workflowStatus parameter in your action. This guy will contain the old value, the one that was hardcoded in the form when the view first rendered. The fresh value is stored in the selectedWorkflowStatus parameter coming from your DropDown.
All this being said the proper way to handle this situation is (as always in ASP.NET MVC) to use a view model:
public class MyViewModel
{
public Guid SelectedCategoryGuid { get; set; }
public WorkflowStatus WorkflowStatus { get; set; }
public string EntityName { get; set; }
public WorkflowStatus SelectedWorkflowStatus { get; set; }
}
that your controller action will take:
[HttpPost]
public ActionResult FilterListingsWorkflow(MyViewModel model)
{
// model.SelectedWorkflowStatus will contain the value from the DropDown
// model.WorkflowStatus will contain the initial value
...
}
Also please replace:
m => Model.SelectedWorkflowStatus
with:
m => m.SelectedWorkflowStatus
in your DropDown. You don't need to capture the model in a closure.