Model:
public virtual ICollection<Product> OriginalProducts { get; set; }
public virtual ICollection<Product> SimilarProducts { get; set; }
View (Create and Edit are equal):
<div id="divSimilar" class="form-group">
#Html.Label("Similar Products", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.ListBox("Products", null, htmlAttributes: new { #class = "form-control" })
</div>
</div>
Controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "MyAttributes")] Product product)
{
if (ModelState.IsValid)
{
db.Products.Add(product);
List<string> selectedSimilars = Request.Form.GetValues("Products").ToList();
foreach (string Id in selectedSimilars)
{
Product similarProd = db.Products.Find(System.Convert.ToInt32(Id));
if (similarProd != null)
product.SimilarProducts.Add(similarProd);
}
db.SaveChanges();
return RedirectToAction("Index").Success("Successfully created");
}
ViewBag.Products = new SelectList(db.Products, "Id", "Name", product.SimilarProducts);
return View(product);
}
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.Products = new SelectList(db.Products.Where(p => p.Id != product.Id), "Id", "Name", product.SimilarProducts);
return View(product);
}
So, since "Create" part is working fine, I want to know how can I make to get all selected SimilarProducts (that I added in "Create") in my "Edit" view. What changes are necessary in controller to make it work?
Btw, since I'm using the ListBox, I think there's a different way of the DropDownList, because I used this way for all my DropDownLists and are working fine.
EDIT
I want to display in "Edit" view, all products (that were selected when I created that product) in blue. In other words, the ActionResult "Edit" should get all selected products from the SimilarProducts list, as in the DropDownList.
ListBox Control may have multiple selected Items (thus, multiple different values) as demonstrated in the following code snippet (re: https://msdn.microsoft.com/en-us/library/system.windows.controls.listbox.selecteditems%28v=vs.110%29.aspx)
private void SelectedItems(object sender, RoutedEventArgs e)
{
if (lb.SelectedItem != null)
{
label1.Content = "Has " + (lb.SelectedItems.Count.ToString()) + " item(s) selected.";
}
}
You should specify the business logic for finding the Item in the selection (e.g., first in the selection). But, if selection mode is set to Single you may use the property SelectedItem.
Hope this may help.
Finally I managed to make it work, I'll post the solution, in case of anyone needs in the future:
ViewBag.Products = new MultiSelectList(db.Products.Where(p => p.Id != product.Id), "Id", "Name", product.SimilarProducts.Select(p => p.Id));
Little explanation:
Since I'm using ListBox, so I must use MultiSelectList, because SelectList recognizes just one item selected, its parameter is: (object selectedValue), already on MultiSelectList is: (IEnumerable selectedValues), so I changed SelectList to MultiSelectList and added it: .Select(p => p.Id)) into my ActionResult "Edit".
Related
when i run the code it gives InvalidOperationException: The ViewData item that has the key 'Id' is of type 'System.Int32' but must be of type 'IEnumerable'. error what should i do
//Controller
public IActionResult Create()
{
//ViewBag.Dersler = new SelectList(_db.Dersler.ToList(), "PKfromDersler", "DersName");
List<SelectListItem>values=(from Ders in _db.Dersler.ToList()
select new SelectListItem
{
Text=Ders.DersName,
Value=Ders.Id.ToString()
}).ToList();
ViewBag.v1 = values;
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Create(Ogretmen obj)
{
if (ModelState.IsValid)
{
_db.Ogretmenler.Add(obj);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View(obj);
}
//view
<div class="mb-3">
<h6 >Ders Seçiniz</h6>
#Html.DropDownListFor(Ders=>Ders.Id,(IEnumerable<SelectListItem>)ViewBag.v1)
</div>
You may try
#Html.DropDownListFor(Ders=>Ders.Id,new SelectList(ViewBag.v1,"Value","Text"))
This is my test result.
public IActionResult Index()
{
var value = new List<UserModel>
{
new UserModel{ id = 1, name = "user1" },
new UserModel{ id = 3, name = "user3" }
};
ViewBag.v1 = value;
return View();
}
As I noticed you didn't pass the model to View
Bellow are the following steps
First get all the list from Dersler table
Initialize ViewBag by creating selectlist and pass the list you generate in first step.
Pass model to view.
Use Html.DropDownListFor to create dropdown using ViewBag as selectlist
public IActionResult Create()
{
//ViewBag.Dersler = new SelectList(_db.Dersler.ToList(), "PKfromDersler", "DersName");
var values = (from Ders in _db.Dersler
select Ders).ToList();
ViewBag.v1 = new SelectList(values, "Id", "DersName");
return View(new Ogretmen()); //Here pass the model object
}
View
#model Ogretmen
<!--
Your view code...
...
#*
model.yourId is the id of Ogretmen class for which you bind the value from dropdownlist
*#
#Html.DropDownListFor(model => model.yourId, (IEnumerable<SelectListItem>)ViewBag.v1, "-- Option label --", new { #class = "form-control" })
...
-->
TL;DR: How do I handle form data that is being submitted with nonstandard names for the data?
The stats:
MVC 5
ASP.NET 4.5.2
I am bringing in two different models:
public async Task<ActionResult> Index() {
var prospectingId = new Guid(User.GetClaimValue("CWD-Prospect"));
var cycleId = new Guid(User.GetClaimValue("CWD-Cycle"));
var viewModel = new OnboardingViewModel();
viewModel.Prospecting = await db.Prospecting.FindAsync(prospectingId);
viewModel.Cycle = await db.Cycle.FindAsync(cycleId);
return View(viewModel);
}
One called Prospecting, the other called Cycle. The Prospecting one is working just fine, as nothing else on the page needs it except one small item.
The Cycle has a mess of separate forms on the page, each needing to be separately submittable, and editing just one small part of the Cycle table. My problem is, I don't know how to submit the correct data to the backend. I am also not entirely sure how to "catch" that data.
The bright spot is that apparently the front end is properly reflective of what is in the db. As in, if I manually change the db field to a true value, the checkbox ends up being selected on refresh.
My current form is such:
#using(Html.BeginForm("UpdatePDFResourceRequest", "Onboarding", FormMethod.Post, new { enctype = "multipart/form-data" })) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<fieldset>
#Html.LabelFor(Model => Model.Cycle.PDFResourceLibrary, htmlAttributes: new { #class = "control-label" })
#Html.CheckBoxFor(Model => Model.Cycle.PDFResourceLibrary, new { #class = "form-control" })
#Html.ValidationMessageFor(Model => Model.Cycle.PdfResourceLibrary, "", new { #class = "text-danger" })
<label class="control-label"> </label><button type="submit" value="Save" title="Save" class="btn btn-primary glyphicon glyphicon-floppy-disk"></button>
</fieldset>
}
But the resulting HTML is such:
<input id="Cycle_PDFResourceLibrary" class="form-control" type="checkbox" value="true" name="Cycle.PDFResourceLibrary" data-val-required="'P D F Resource Library' must not be empty." data-val="true">
As you can see, the name= is Cycle.PDFResourceLibrary and I don't know how to catch this on the backend.
My model for that specific form is:
public class PDFResourceRequestViewModel {
[DisplayName("PDF Resource Library Request")]
public bool PDFResourceLibrary { get; set; }
[DisplayName("Date Requested")]
[DataType(DataType.Date)]
public DateTime PDFResourceLibraryDate { get; set; }
[DisplayName("Notes")]
public string PDFResourceLibraryNotes { get; set; }
}
(not the overall model for that table, though)
And the method used to handle the form submission is:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> UpdatePDFResourceRequest(PDFResourceRequestViewModel model) {
var id = new Guid(User.GetClaimValue("CWD-Cycle"));
Cycle cycle = await db.Cycle.FindAsync(id);
if(cycle == null) {
return HttpNotFound();
}
try {
cycle.CycleId = id;
cycle.PDFResourceLibrary = model.PDFResourceLibrary;
cycle.PDFResourceLibraryDate = DateTime.Now;
cycle.PDFResourceLibraryNotes = model.PDFResourceLibraryNotes;
db.Cycle.Add(cycle);
await db.SaveChangesAsync();
return RedirectToAction("Index");
} catch { }
return View(model);
}
Now, I know that the method is wrong, for one I am editing just three values out of dozens in that table, so I need to be using something like this method. Problem is, the form is getting submitted with the name= of Cycle.PDFResourceLibrary and it is not being matched up on the back end.
Help?
You can use the [Bind(Prefix="Cycle")] attribute to 'strip' the prefix so that name="Cycle.PDFResourceLibrary" effectively becomes name="PDFResourceLibrary" and will bind to your PDFResourceRequestViewModel
public async Task<ActionResult> UpdatePDFResourceRequest([Bind(Prefix="Cycle")]PDFResourceRequestViewModel model)
#model IEnumerable<Evidencija.Models.Vozilo>
#{
ViewBag.Title = "PokreniIzvjestaj";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>PokreniIzvjestaj</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Vozilo</legend>
<p>
#Html.DropDownList("Vozila", Model.Select(p => new SelectListItem { Text = p.VoziloID.ToString(), Value = p.VoziloID.ToString() }), "Izaberi vozilo")
</p>
<input type="submit" value="Dodaj stavku" />
</fieldset>
}
I want to send id of table vozilo to controler with dropdownlist.
Controler accepts vozilo as a parameter but it is ollways zero.
How can I solve this without using viewmodel.
[HttpPost]
public ActionResult PokreniIzvjestaj(Vozilo v)
{
ReportClass rpt = new ReportClass();
rpt.FileName = Server.MapPath("~/Reports/Vozilo.rpt");
rpt.Load();
//ReportMethods.SetDBLogonForReport(rpt);
//ReportMethods.SetDBLogonForSubreports(rpt);
// rpt.VerifyDatabase();
rpt.SetParameterValue("#VoziloId",v.VoziloID);
Stream stream = null;
stream = rpt.ExportToStream(CrystalDecisions.Shared.ExportFormatType.PortableDocFormat);
return File(stream, "application/pdf", "Vozilo.pdf");
//PortableDocFormat--pdf format
//application/pdf -- vezan za pdf format, ako je drugi tip mjenja se u zavisnosti od izabranog
//naziv.pdf -- naziv dokumenta i izabrana ekstenzija
}
[HttpGet]
public ActionResult PokreniIzvjestaj()
{
var vozila = db.Voziloes.ToList();
return View(vozila);
}
There are two method from controler.
You currently binding your drop down to a property named Vozilo. A <select> post back single value (in your case the VoziloID or the selected option. Your POST method then tries to bind a complex object Vozilo to an int (assuming VoziloID is typeofint) which of course fails and the model isnull`. You could solve this changing the method to
[HttpPost]
public ActionResult PokreniIzvjestaj(int Vozilo)
The parameter Vozilo will now contain the value of the selected VoziloID.
However it not clear why you want to "solve this without using viewmodel" when using a view model is the correct approach
View model
public class VoziloVM
{
[Display(Name = "Vozilo")]
[Required(ErrorMessage = "Please select a Vozilo")]
public int? SelectedVozilo { get; set; }
public SelectList VoziloList { get; set; }
}
Controller
public ActionResult PokreniIzvjestaj()
{
var viziloList = db.Voziloes.Select(v => v.VoziloID);
VoziloVM model = new VoziloVM();
model.VoziloList = new SelectList(viziloList)
model.SelectedVozilo = // set a value here if you want a specific option selected
return View(model);
}
[HttpPost]
public ActionResult PokreniIzvjestaj(VoziloVM model)
{
// model.SelectedVozilo contains the value of the selected option
....
}
View
#model YourAssembly.VoziloVM>
....
#Html.LabelFor(m => m.SelectedVozilo)
#Html.DropDownListFor(m => m.SelectedVozilo, Model.VoziloList, "-Please select-")
#Html.ValidationMessageFor(m => m.SelectedVozilo)
....
My dropdown is pulling and displaying the correct list, however once selected, I click save and the selected option is disregarded and once again the value is empty.
//get
public ActionResult Edit(int id)
{
Prospect prospect = db.Prospects.Find(id);
if (prospect == null)
{
return HttpNotFound();
}
ViewBag.ProductID = new SelectList(db.Products, "ProductID", "Name", prospect.Product);
return View(prospect);
}
//post
[HttpPost]
public ActionResult Edit(Prospect prospect)
{
if (ModelState.IsValid)
{
db.Entry(prospect).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ProductID = new SelectList(db.Products, "ProductID", "Name", prospect.Product);
return View(prospect);
}
//view
<div class="editor-label">
#Html.LabelFor(model => model.Product)
</div>
<div class="editor-field">
#Html.DropDownList("ProductId", String.Empty)
#Html.ValidationMessageFor(model => model.Product)
</div>
Any help will be greatly appreciated
only for helpers (except display) are tied to the model. change your drop down list to
#Html.DropDownListFor(x => x.ProductID, (SelectList)ViewBag.ProductID)
where ProductID is whatever value in your model you want the selected item tied to. You also set the drop down this way by setting that value before passing it to the view
Update:
I agree with Muffin Mans answer. Using ViewBag to send drop down lists to the view can be unreliable. A different way to put the answer the muffin man provided
Add an list to your model
public List<SelectListItem> Products { get; set; }
then on your controller populate that list from the database. Muffin Man provided one way to do it. We access our data differently so I populate my list with a foreach
var products = //populate the list from your database
List<SelectListItem> ls = new List<SelectListItem>();
foreach(var temp in products){
ls.Add(new SelectListItem() { Text = temp.ProductName, Value = temp.ProductID });
}
Model.Products = ls; // set the list in your model to the select list you just built
then on your view instead of casting a view bag list to a select list you can just reference the list from the model
#Html.DropDownListFor(x => x.ProductID, Model.Products)
You shouldn't be tying your view directly to your database table type. Use a view model. Additionally this type of data belongs in your view model, not the viewbag. The view bag is great for sharing things like page title between your view and the layout page.
public class ProspectViewModel
{
public IEnumerable<SelectListItem> ProspectList { get; set; }
[DisplayName("Product")] //This is for our label
public int SelectedProspectId { get; set; }
}
Get
public ActionResult Edit(int id)
{
var prospect = db.Prospects.Find(id);
if (prospect == null)
{
return HttpNotFound();
}
var model = new ProspectViewModel
{
ProductList = db.Products.Select(x=> new SelectListItem { ... })
};
return View(model);
}
Post
[HttpPost]
public ActionResult Edit(ProspectViewModel model)
{
if (ModelState.IsValid)
{
var prospect = new Prospect { /* populate with values from model */ };
db.Prospects.Attach(prospect);
db.Entry(prospect).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
//Need to repopulate drop down list
//And we don't need to set SelectedProductId because it's already been posted back
model.ProductList = db.Products.Select(x=> new SelectListItem { ... });
return View(model);
}
View
<div class="editor-label">
#Html.LabelFor(model => model.SelectedProductId)
</div>
<div class="editor-field">
#Html.DropDownListFor(x=> x.SelectedProductId, Model.ProductList)
#Html.ValidationMessageFor(x=> x.SelectedProductId)
</div>
This is outside the scope of this answer, but you shouldn't be doing data access within your controller. Microsoft's examples show this because they are meant to be "Hello world" examples, not necessarily to be taken literally.
I'm trying to pass the selected value of a DropDownList to a new controller method. However, in the controller, leagueKey is always coming back null. The drop down list is populating with values.
If I change new { leagueKey = Model.SelectedLeagueKey} to new {leagueKey = "test"} the controller correct receives the "test" value. It appears that the DropDownList isn't binding the selected value to Model.SelectedLeagueKey.
Model
public Dictionary<string, string> Leagues { get; set; }
public string SelectedLeagueKey { get; set; }
View
<div class="edit-field">
#Html.DropDownListFor(model => model.SelectedLeagueKey, new SelectList(Model.Leagues, "Key", "Value", Model.SelectedLeagueKey),"Select League")
</div>
#Html.ActionLink("Select League", "AddTeam", "Team", new { leagueKey = Model.SelectedLeagueKey}, null)
Controller
public ActionResult AddTeam(LTEDContext context, string leagueKey)
{
//Do something with leagueKey here
return View();
}
Your view send you the SelectedLeagueKey parameter according to
<div class="edit-field">
#Html.DropDownListFor(model => model.SelectedLeagueKey, new SelectList(Model.Leagues, "Key", "Value", Model.SelectedLeagueKey),"Select League")
</div>
Try to use the next code in a view:
#using (Html.BeginForm("AddTeam", "Team")) {
<div class="edit-field">
#Html.DropDownListFor(model => model.SelectedLeagueKey, new SelectList(Model.Leagues, "Key", "Value", Model.SelectedLeagueKey),"Select League")
</div>
<submit type="submit"/>
}
and the next one in controller:
public ActionResult AddTeam(LTEDContext context, string SelectedLeagueKey)
{
//Do something with leagueKey here
return View();
}