I am trying to use Jquery Multiselect plugin from Beautiful site and MVC3 together to send values to server. As shown in example from Darin the key is to create MultiSelectModelBinder class that will, I guess, recognize values send from client, because the multiselect plugin uses the [] notation to send the selected values to the server. My aproach is a little diferent, i fill dropDownList from my controller and not the model, keeping the model clean, and also been able to fill the list from Database. I used Darins example to create MultiSelectModelBinder and register it,in the model binder in Application_Start(). My problem is that I always keep getting empty Model back to my controller, here is the code:
MODEL:
public class PersonsSearchModel
{
public string Person { get; set; }
public string Company { get; set; }
//here is my Cities collection
public IEnumerable<string> Cities { get; set; }
}
VIEW:
#model MyNamespace.Model.PersonsSearchModel
#using (Ajax.BeginForm("Search", "Persons", new AjaxOptions
{
HttpMethod = "GET",
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "results",
LoadingElementId = "progress"
},
new { #id = "searchFormPerson" }
))
{
<span>
#Html.TextBoxFor(x => Model.Person, new { #class = "halfWidth"})
</span>
<span>
#Html.TextBoxFor(x => Model.Company, new { #class = "halfWidth"})
</span>
<span>
#Html.ListBoxFor(x => x.Cities, Model.Items, new { #id="combobox1"})
</span>
<input name="Search" type="submit" class="searchSubmit" value="submit" />
}
CONTROLLER:
public ActionResult Index()
{
var listCities = new List<SelectListItem>();
listCities.Add(new SelectListItem() { Text = "Select one...", Value = "" });
listCities.Add(new SelectListItem() { Text = "New York", Value = "New York" });
listCities.Add(new SelectListItem() { Text = "Boston", Value = "Boston" });
listCities.Add(new SelectListItem() { Text = "Miami", Value = "Miami" });
listCities.Add(new SelectListItem() { Text = "London", Value = "London" });
ViewBag.Cities = listCities;
return View();
}
public class MultiSelectModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var model = (PersonsSearchModel)base.BindModel(controllerContext, bindingContext);
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName + "[]");
if (value != null)
{
return value.RawValue;
}
return model;
}
}
Here the data from client sholud arive, butt is always null?
public PartialViewResult Search(PersonsSearchModel psm)
{
var person = psm.Person;
var company = psm.Company;
var city = psm.Cities.ElementAt(0);
return GetResultPartialView(city);
}
GLOBAL.asax.cs
protected void Application_Start()
{
//...
//model binder
ModelBinders.Binders.Add(typeof(IEnumerable<string>), new
FinessenceWeb.Controllers.PersonsController.MultiSelectModelBinder());
}
JQUERY
$("#combobox1").multiSelect();
I had the same issue, although your provided solution still works. There is another way of doing it with less effort.
Actually defaultModelbinder does bind to multiple selected values if you can change your input parameter to List<inputparameter> and change the line #Html.DropDownListFor to #Html.ListBoxFor.
The key difference between these 2 controls is, First one being a single selection box and second one being a multiple selector.
Hope this helps some one having the same issue.
Well... After looking into DOM, and Jquery plugin, turns out the plugin gives the select element, atribute name, current id, so they are the same, and the form, well.. look's at the name attr. So solution wolud be:
$("#Cities").multiSelect();
Cheers!
Related
I have dropdownlist and want to pass value in Controller. View
#using (Html.BeginForm())
{
#Html.DropDownList("dropOrg", ViewBag.dropOrg as SelectList)
<input type="submit" value="save" />
}
Controller
foreach (int tmp in org)
{
string s = tmp + " - " + orgNames[tmp];
SelectListItem item1 = new SelectListItem() { Text = s, Value = tmp.ToString() };
items.Add(item1);
}
ViewBag.dropOrg = items;
What should i do?
It will be better if you create ViewModel for your View:
public class SampleViewModel
{
public string DropDownListValue { get; set; }
}
then in your controller's get method:
public ActionResult SomeAction()
{
var org = GetOrg(); //your org
var orgNames = GetOrgNames(); //your orgNames
// . . .
ViewBag.DropDownListValue = new SelectList(org.Select(s =>
new SampleViewModel
{
DropDownListValue = $"{s} - {orgNames[s]}"
}, "DropDownListValue", "DropDownListValue");
return View(new SampleViewModel())
}
your SomeAction View:
#model YourAppNamespace.SampleViewModel
<h1>Hello Stranger</h1>
#using (Html.BeginForm())
{
#Html.DropDownList("DropDownListValue")
<input type="submit" value="Submit"/>
}
Note that:
The DropDownList helper used to create an HTML select list
requires a IEnumerable<SelectListItem>, either explicitly or
implicitly. That is, you can pass the IEnumerable<SelectListItem>
explicitly to the DropDownList helper or you can add the
IEnumerable<SelectListItem> to the ViewBag using the same name for
the SelectListItem as the model property.
We have used here implicit passing, that is we have used same name for SelectListItem and ViewBag (which is DropDownListValue).
Then when you hit Submit, you need HttpPost method for SomeAction:
[HttpPost]
public ActionResult SomeAction(SampleViewModel model)
{
var org = GetOrg(); //your org
var orgNames = GetOrgNames(); //your orgNames
//. . . Validation etc..
ViewBag.DropDownListValue = new SelectList(org.Select(s =>
new SampleViewModel
{
DropDownListValue = $"{s} - {orgNames[s]}"
}, "DropDownListValue", "DropDownListValue", model.DropDownListValue);
var doSomething = model.DropDownListValue; //Your selected value from DropDownList
return View(model)
}
References: DotNetFiddle Example,
Using the DropDownList Helper with ASP.NET MVC
I have an ASP.NET MVC project with entities based on EF6 (model first). So my entities are all auto-generated for me. I have an entity, Site and I just want the user to select a Site before proceeding. I have tried a couple of ways, all of them work, but they seem very messy and unnecessary.
I was curious about the cleanest way to create a DropdownList of Sites, then get the selected site when the form is submitted (by Id or whatever other mechanism is better).
Currently I have:
The index where the user is asked to select a site:
public ActionResult Index()
{
ViewBag.Sites = new SelectList(db.Sites.ToList(), "Id", "Name");
return View();
}
The view:
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("sites", "Site:");
#Html.DropDownList("Sites", null, new { #class = "selectpicker" });
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
And the SetSite action, where the form is submitted
[HttpPost]
public ActionResult SetSite()
{
if (Request.Form["Sites"] != null)
{
Session["Site"] = db.Sites.Find(Request.Form["Sites"]);
return RedirectToAction("Home");
}
else
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
}
A few problems arise from this method. First, I really wanted to take advantage of the #model functionality in razor and point it towards my Site class, but since it's auto-generated, I don't want to go poking around and adding a whole bunch of view properties. (beggars can't be choosers?)
Second, the Request.Form['Sites'] returns a string, and converting it to and int is ugly as hell.
As I mentioned, I'd like to use the #model functionality with Html.DropDownListFor. Is that possible when working with a list of Sites from the DB?
How can I get this done cleanly?
Solution 1:-
Controller:-
private List<Country> GetCountries()
{
var list = new Entity.Result<List<Entity.Country>>();
list = _context.Countries.Select(tc => new Entity.Country
{
Id = tc.Id,
Name = tc.Name,
IsdCode = tc.Code,
}).ToList();
return list.Data.Select(x => new Country
{
id = x.Id,
Name = x.Name,
}).ToList();
}
HttpGet Method:-
public ActionResult Add(int id)
{
try
{
}
catch (Exception ex)
{
}
finally
{
ViewBag.countryList = GetCountries();
}
return View()
}
View Method:-
#Html.DropDownListFor(x => x.countryId, new SelectList(ViewBag.countryList, "id", "Name"), KahandasDhanji.Common.Localization.Application.Resources.ddlCountry,
new { #id = "ddlCountry", #rows = 1 })
In Http Post Form Submitimg u handle that model value in HTTPPOST Method.
Solution 2:-
FormCollection class we can capture the form's values within the controller.
[HttpPost]
public ActionResult Add(FormCollection form)
{
string strDDLValue = form["Sites"].ToString();
return View(MV);
}
Hope Its Work !!!
You can use a ViewModel to avoid converting the string value from Request.Form. Below is how your ViewModel class should look like
public class MyViewModel
{
public MyViewModel()
{
this.DropdownItems = new List<SelectListItem>();
}
public int SelectedSiteId { get; set; }
public List<SelectListItem> DropdownItems { get; set; }
}
Change the get action method in your controller as below
public ActionResult Index()
{
List<Site> sites = db.Sites.ToList();
MyViewModel model = new MyViewModel();
foreach(var site in sites)
{
model.DropdownItems.Add(new SelectListItem() { Text = site.Name, Value = site.ID.ToString() });
}
return View(model);
}
Add #model MyViewModel at the first line in your view code and use Html.DropDownListFor method to generate the dropdownlist
#model MyViewModel
#using (Html.BeginForm("SetSite", "Home"))
{
#Html.Label("SelectedSiteId", "Site:");
#Html.DropDownListFor(m => m.SelectedSiteId, Model.DropdownItems, new { #class = "selectpicker" })
<div style="width:100%;height:25px"></div>
<button class="btn btn-default" style="width:100%">Go</button>
}
The post action method in your controller should look like below. model.SelectedSiteId will be the selected value of the dropdownlist and the type is int so you won't have to do any conversion such as Convert.ToInt32(Request.Form['Sites']).
[HttpPost]
public ActionResult SetSite(MyViewModel model)
{
Session["Site"] = model.SelectedSiteId;
return RedirectToAction("Home");
}
I am trying to pass categorized value (string) into model that requires int value.
I have model AccountViewModel below:
[Required]
[Display(Name = "Fleet Type")]
public int FleetType { get; set; }
When a user registers for the first time, they must choose the fleet type
I have an AccountController:
public async Task<ActionResult> Register(RegisterViewModel model)
{
if (ModelState.IsValid)
{
var fleetType = 0;
string fleetTypeStr = ViewBag.FleetType;
switch (fleetTypeStr)
{
case "17' Truck":
fleetType = 1;
break;
case "20' Truck":
fleetType = 2;
break;
default:
fleetType = 0;
break;
}
var user = new ApplicationUser
{
UserName = model.Email,
Email = model.Email,
LoginId = model.LoginId,
FirstName = model.FirstName,
LastName = model.LastName,
//FleetType = model.FleetType,
FleetType = fleetType,
};
In Account/Register view:
ViewBag.FleetTypeList = new SelectList(new List<string>
{
"Pickup Truck", "Cargo Vans", "10' Truck", "15' Truck", "17' Truck",
"20' Truck", "26' Truck"
}, "fleetTypeList");
#using (Html.BeginForm("Register", "Account", FormMethod.Post, new { #class = "form-horizontal", role = "form" }))
{
// ........
#Html.DropDownListFor(m => m.FleetType, ViewBag.FleetTypeList as SelectList, new { #class = "btn btn-primary btn-lg dropdown-toggle" })
But I get a error message because the ViewBag.FleetTypeList is list of strings but the datatype FleetType requires int inside the model.
Please help me!
You should not be reading the ViewBag value in your HttpPost action method.
ViewBag is usually used to transfer some data from your GET action method to the view. You can use it to pass the list of items you want to build your dropdown.
With your current code, the HTML helper will render a SELECT element with name attribute value "FleetType" and when you submit the form, the selected option's value attribute value (in your case the same as the text) will be posted as the value of this SELECT element. But since your FleetType property is of int type, model binder will not be able to bind that string value to the int property !
A better solution is added after this. Read further.
You should add a string type property to your view model and use that
public class RegisterViewModel
{
public string SelectedFleet { set;get;}
// Your other properties
}
And in the view, use this property as the first param if the DropDownListFor helper method.
#Html.DropDownListFor(m => m.SelectedFleet ,
ViewBag.FleetTypeList as SelectList,
new { #class = "btn btn-primary btn-lg dropdown-toggle" })
Now in your httppost action method, read the SelectedFleet value and use it
var fleetType = 0;
var fleetTypeStr = model.SelectedFleet ;
switch (fleetTypeStr)
{
}
A Better Solution
But a better solution is to completely avoid the switch block. What if you set the option's value attribute to the corresponding int value ?
So update your GET action method to have a list of SelectListItem in ViewBag.FleetTypeList
ViewBag.FleetTypeList = new List<SelectListItem>{
new SelectListItem { Text="17 Truck", Value="1"},
new SelectListItem { Text="20 Truck", Value="2"},
new SelectListItem { Text="Something else", Value="0"}
};
and in the view
#Html.DropDownListFor(m => m.FleetType, ViewBag.FleetTypeList as List<SelectListItem>
, new { #class = "btn btn-primary btn-lg dropdown-toggle" })
Now the SELECT element will be rendered with name attribute "FleetType" and when you submit the form, the selected option's value (int) will be submitted. You can avoid the switch block and simply use model.FleetType wherever needed.
my UI has a text box and a button, everytime I add a new element I need to show the list in the same view. I'm using partial view so I need to keep loading this partial view everytime I add a new element to my list. how can I modify my code to achieve that?
View
#Html.TextBoxFor(m => m.emailsAdded, new { #class = "form-control wide", placeholder = "Email ID", type = "email", Name = "txtEmail" }
<button id="thisButton">Add</button>
<div id="content"></div>
<script>
$(document).ready(function() {
$("#thisButton").on("click", function () {
var val = $('#emailsAdded').val();
$.ajax({
url: "/Admin/UpdateEmailList?email="+val,
type: "GET"
})
.done(function(partialViewResult) {
$("#content").html(partialViewResult);
});
});
});
</script>
Model
public class ABC
{
public IEnumerable<string> emailsAdded { get; set; }
}
Controller
[HttpGet]
public ActionResult UpdateEmailList(string email)
{
if (Session["emails"] == null)
{
List<string> aux1 = new List<string>();
aux1.Add(email);
Session["emails"] = aux1;
}
else
{
List<string> aux2 = new List<string>();
aux2 = (List<string>)Session["emails"];
aux2.Add(email);
Session["emails"] = aux2;
}
var abc = new ABC
{
emailsAdded = (List<string>)Session["emails"]
};
return PartialView("_EmailsListPartialView", abc);
}
Partial view
#using project.Models
#model project.Models.ABC
<table class="tblEmails">
#foreach (var emails in Model.emailsAdded)
{
<tr><td>#emails.ToString()</td></tr>
}
</table>
With my code I'm able to reload my div and add the new element, when doesn't work for the second time....how can I modify my code so I can keep adding stuff?
SOLUTION:
I updated my controller to show how I resolved this issue. Not really sure if it is the best way to do it, but at least helped me to resolve.
I'm storing the list of emails in Session["emails"] and every time I add a new email to the list, I just update it a pass it to a new list with all the records and at the end return the partial view.
I have a controller with 2 Index methods:
public ActionResult Index()
{
viewModel.PipelineIndex pivm = new viewModel.PipelineIndex(null, User.Identity.Name);
return View(pivm);
}
[HttpPost]
public ActionResult Index(viewModel.PipelineIndex model, FormCollection collection)
{
viewModel.PipelineIndex pivm = null;
if (ModelState.IsValid)
{
string key = collection.AllKeys[0];
string ID = collection.Get(key).ToString();
pivm = new viewModel.PipelineIndex(ID, User.Identity.Name);
}
else
pivm = new viewModel.PipelineIndex(null, User.Identity.Name);
return View(pivm);
}
The ViewModel I am using is a well defined class:
public class PipelineIndex
{
private Models.Context _db = new Models.Context();
public List<SelectListItem> GroupList { get; set; }
public List<string> ButtonCaptions { get; set; }
public List<ContactDetail> ContactList { get; set; }
public string PageTitle { get; set; }
...
The View consumes the ViewModel setting up a Grid and a Drop Down control:
#model BlueSkies.Pipeline.ViewModels.PipelineIndex
#{ ViewBag.Title = "Index"; }
#using (Html.BeginForm())
{
<h2>#Model.PageTitle</h2>
<div style="clear:both">
#if (Model != null)
{
var grid = new WebGrid(canPage: true, rowsPerPage: 15, canSort: true, ajaxUpdateContainerId: "grid");
grid.Bind(Model.ContactList, rowCount: Model.ContactList.Count, autoSortAndPage: true);
grid.Pager(WebGridPagerModes.All);
#grid.GetHtml(htmlAttributes: new { id = "grid" },
columns: grid.Columns(
grid.Column(format: (item) => Html.ActionLink("View", "Details", "Contacts", new { ID = item.Name }, null)),
grid.Column(format: (item) => Html.ActionLink("Edit", "Edit", "Contacts", new { ID = item.Name }, null)),
grid.Column("Name"),
grid.Column(columnName: "Phone1", header: "Phone")
));
}
</div>
<hr />
<div>
#*foreach (string caption in ViewBag.ButtonCaptions)
{
#Html.ActionLink(caption, "Index", "Pipeline", new { ID = caption }, new { #class = "menuSubButton" })
}*#
#Html.DropDownList("GroupDropDown", Model.GroupList, new { #onchange = "this.form.submit()" }) Select a pipe section...
</div>
}
Where I am having challenges is when the Drop Down fires the Form.Submit (on the onChange event). No model is being returned to my Controller. I do have the FormCollection but I would rather have the updated model including the new selectedItem in the drop down. What am I missing? And yes, I am looking for a non-JS based solution at this point - or as close as I can. I don't want to AJAX this page.
TIA
NOTE: There is a similar question here. It is AJAX based but getting the same null model on call into the controller. Why is it so hard to find the right answer? :)
I think the rendered HTML form will have a select with the name "GroupDropDown", is that right? If so, the selected value will be posted back on submit with that name and would be bound to either a parameter called groupDropDown or to a string property GroupDropDown on your model class. Do you have such a property on your model?
I am mentally exhausted, so I add this disclaimer. Thought Answering a question would get me away from my day.
The business problem appears to be "select one item from drop down and have contents rendered accordingly". If that is correct, there is no need to pass back the entire contents of the View Model; you only need to pass back the id, which is demoed numerous times on many sites.
Am I missing something?