How to pass Quantity from View to Controller without using Ajax/JQuery - asp.net-mvc

I have been trying to pass the quantity to my controller but cannot figure out how. I am new to this and need some help! I know that I need to make an ActionResult UpdateCart(int BookID, int quantity) but do not know what needs to go in it.
Here is my view code:
#{
ViewBag.Title = "Cart";
}
#using FinalProject_Lio_Lopez_Jafri_Wood.Controllers;
#model ShoppingCartItem
<h2>Cart</h2>
<table class="table table-hover">
<tr>
<th>Book Title</th>
<th>Book Unique Nnmber</th>
<th>Book Price</th>
<th>Quantity</th>
<th>Option</th>
<th>Sub Total</th>
</tr>
#{decimal s = 0;}
#foreach (ShoppingCartItem item in (List<ShoppingCartItem>)Session["cart"])
{
s += item.Books1.BookPrice * item.Quantity;
<tr>
<td>#item.Books1.BookTitle</td>
<td>#item.Books1.BookUniqueNumber</td>
<td>$ #item.Books1.BookPrice</td>
<td>#Html.TextBoxFor(m => m.Quantity, new { #Value = "1", #class = "form-control" , style="width:50px;" })</td>
<td>
#Html.ActionLink("Refresh Quantity", "Update", "ShoppingCart", new{id = item.Books1.BookID, quantity = item.Quantity}) | #Html.ActionLink("Remove Item", "Delete", "ShoppingCart",
new { id = item.Books1.BookID }, null)
</td>
<td>$ #(item.Books1.BookPrice * item.Quantity)</td>
</tr>
}
<tr>
<td align="right" colspan="5">TOTAL</td>
<td>$ #s</td>
</tr>
</table>
<br />
#Html.ActionLink("Continue Shopping", "Search", "Books")
<input type="button" value="Check Out" class="btn-info btn-active" style="float: right" />
Here is my controller code so far:
public class ShoppingCartController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
public ActionResult Index()
{
return View();
}
private int isExisting(int id)
{
List<ShoppingCartItem> cart = (List<ShoppingCartItem>) Session["cart"];
for (int i = 0; i < cart.Count; i++ )
if(cart[i].Books1.BookID==id)
return i;
return -1;
}
public ActionResult Delete(int id)
{
int index = isExisting(id);
List<ShoppingCartItem> cart = (List<ShoppingCartItem>)Session["cart"];
cart.RemoveAt(index);
Session["cart"] = cart;
return View("Cart");
}
public ActionResult UpdateCart(int BookID, int quantity)
{
return View("Cart");
}
public ActionResult AddToCart(int id)
{
if (Session["cart"] == null)
{
List<ShoppingCartItem> cart = new List<ShoppingCartItem>();
cart.Add(new ShoppingCartItem(db.Books.Find(id), 1));
Session["cart"] = cart;
}
else
{
List<ShoppingCartItem> cart = (List<ShoppingCartItem>) Session["cart"];
int index = isExisting(id);
if (index == -1)
cart.Add(new ShoppingCartItem(db.Books.Find(id), 1));
else
cart[index].Quantity++;
Session["cart"] = cart;
}
return View("Cart");
}
}
}

You seem to have some syntax issues. I would recommend you use resharper, it should underscodre at least such syntax errors.
You have different action names in your controller and view. MVC is not (yet) smart enough to figure out that "Update" and "UpdateCard" is the same thing.
You have another naming issue. Default MVC routing convention is to use id if you want the parameter to be part of URL and did not change routing. That's configurable, but the default is that.
Check parameters of the ActionLink. You should have specified routing parameters, but it seems you specify html attributes. Check the declaration of the Html.ActionLink. Note that to be sure you can always use named parameters.
Updates must never be doen with GET (like you do), as this provokes unwanted or random updates. Imagine for example a search engine (google) indexing your site - it will "click" your link (navigate to that) and this will add goods to the cart. Not good. I suggest you check some starter course on asp.net mvc...
Anyways, to fix the things, try:
Controller:
public ActionResult UpdateCart(int id, int quantity)
{
return View("Cart");
}
View:
#Html.ActionLink("Refresh Quantity", "UpdateCart", "ShoppingCart",
new { id = item.Books1.BookID, quantity = item.Quantity}, null)

Related

MVC DropDownList lagging

I am posting the id of a dropdownlist back to the index (index2 view). but is lagging behind. After a second time pressing Select it shows me the correct list.
http://www.jeroenchristens.be/CountriesWorld
(the first page is only for showing the complete list, after selecting from the dropdownlist,, it gets to index2, a shorter list) And then after choosing another Selection from the dropdownlist, you have to try this twice each time.
I successfully copied this from the id the value and pass this on, why is it lagging behind.
Index2 Viewpage
#using System.Collections
#using System.Web.UI.WebControls
#model IEnumerable<CVtje.Models.Countries>
<h2>Index</h2>
#using (Html.BeginForm("Index2", "CountriesWorld", new { #id = Request.Form["SelectedContinent"] }, FormMethod.Post))
{
<div class="form-group">
#Html.DropDownList("SelectedContinent",
new SelectList((IEnumerable) ViewData["continentsList"], "Continent", "Continentomschrijving"))
<button type="submit" class="btn btn-primary">Select</button>
</div>
}
<table id="countriesworld" class="table table-active table-hover">
<thead>
<tr>
<th>Vlag</th>
<th>
Code
</th>
<th>
Land
</th>
<th>Continent</th>
</tr>
</thead>
#foreach (var item in Model)
{
<tr>
<td>
<img src="#string.Format("../../images/countries/{0}.png", item.Code)" width="25" HEIGHT="15" />
</td>
<td>
#item.Code
</td>
<td>
#item.Country
#*#Html.ActionLink("Details", "Index", "ReizensDetails", new { id = item.ReizenId }, null)*#
#*|
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
<button data-myprofile-id="#item.Id" class="btn-link js-delete">Delete</button>*#
</td>
<td>#item.Continents.Continentomschrijving</td>
</tr>
}
</table>
my controller:
public ActionResult Index(int? id)
{
List<Continents> continentsList = new List<Continents>();
continentsList = _context.Continents.ToList();
ViewData["continentsList"] = continentsList;
var countriesWorld = _context.Countries.OrderBy(e => e.Country).ToList();
return View(countriesWorld);
}
[HttpPost]
public ActionResult Index2(int id)
{
//return View(db.MyProfiles.ToList());
List<Continents> continentsList = new List<Continents>();
continentsList = _context.Continents.ToList();
ViewData["SelectedContinent"] = id.ToString();
ViewData["continentsList"] = continentsList;
var countriesWorld = _context.Countries.Where(e => e.Continent == id).OrderBy(e => e.Country).ToList();
return View(countriesWorld);
You have added a route value using new { #id = Request.Form["SelectedContinent"] } in your BeginForm() method.
Assuming the initial value is 0, then it generates action = "/CountriesWorld/Index2/0". Lets assume you select the option with value="1" and you now post the form. The id attribute is bound to 0 and you filter the Countries based on .Where(e => e.Continent == 0) - no where have you ever used the value of the selected option which is bound to a non-existent property named SelectedContinent.
Now you return the view and the forms action attribute is now action = "/CountriesWorld/Index2/1" (because Request.Form["SelectedContinent"] is 1). If you select the option with value="2", the same thing occurs - you ignore the value of the selected option and the filter the Countries based on .Where(e => e.Continent == 1) because the id parameter is 1.
Always bind to a model, which in your case will be
public class CountriesVM
{
public int? SelectedContinent { get; set }
public IEnumerable<SelectListItem> ContinentsList { get; set; }
public IEnumerable<Country> Countries { get; set; }
}
and in the view, strongly bind to your model (note the FormMethod.Get and the 3rd parameter in DropDownListFor())
#model CountriesVM
#using (Html.BeginForm("Index", "CountriesWorld", FormMethod.Get))
{
#Html.DropDownListFor(m => m.SelectedContinent, Model.ContinentsList, "All")
<button type="submit" class="btn btn-primary">Select</button>
}
<table ... >
....
#foreach(var country in Model.Countries)
{
....
}
</table>
and you need only one method
public ActionResult Index(int? selectedContinent)
{
var countries = _context.Countries.OrderBy(e => e.Country);
if (selectedContinent.HasValue)
{
countries = countries.Where(e => e.Continent == selectedContinent.Value);
}
continentsList = _context.Continents.Select(x => new SelectListItem
{
Value = x.Continent.ToString(),
Text = x.Continentomschrijving
});
var model = new CountriesVM
{
SelectedContinent = selectedContinent,
ContinentsList = continentsList,
Countries = countries
};
return View(model);
}
Note you might also want to consider caching the Continents to avoid repeated database calls assuming they do not change often (and invalidate the cache if their values are updated)

Searching always returns empty list in MVC?

I'm trying to implement filtering in a page that contains some data, the page shows three different entities: Branches, Items and Categories, so I used a view model:
public class WarehouseData
{
public IEnumerable<Item> Items { get; set; }
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Branch> Branches { get; set; }
}
in the controller:
public ActionResult Index(string sort, string search)
{
var warhouse = new WarehouseData();
warhouse.Items = db.Items.Include(c => c.Categories).ToList();
warhouse.Branches = db.Branches;
ViewBag.Search = search;
warhouse.Branches = db.Branches.ToList();
switch (sort)
{
case "q_asc":
warhouse.Items = warhouse.Items.OrderBy(c => c.Quantity).ToList();
ViewBag.Sort = "q_desc";
break;
case "q_desc":
warhouse.Items = warhouse.Items.OrderByDescending(c => c.Quantity).ToList();
ViewBag.Sort = "q_asc";
break;
default:
warhouse.Items = warhouse.Items.OrderBy(c => c.Name).ToList();
ViewBag.Sort = "q_asc";
break;
}
if (!string.IsNullOrEmpty(search))
{
warhouse.Items = warhouse.Items.Where(i => i.Name.Contains(search)).ToList();
ViewBag.Search = search;
}
return View(warhouse);
}
this is the Index view:
#model WarehouseManagementMVC.ViewModels.WarehouseData
#{
ViewBag.Title = "Index";
}
<h2 style="display:inline-block">Registered Branches | </h2> #Html.ActionLink("Add New Branch", "Create", controllerName: "Branch")
#foreach (var branch in Model.Branches)
{
<ul>
<li>#Html.ActionLink(branch.Location, "Details", "Branch", routeValues: new { id = branch.Id }, htmlAttributes: null)</li>
</ul>
}
<hr />
<h2>All Items Available</h2>
<div>
#using (#Html.BeginForm("Index", "Warehouse", FormMethod.Get))
{
<input type="text" name="search" value="#ViewBag.Search"/>
<input type="submit" value="Filter" />
}
</div>
<table class="table table-bordered">
<tr>
<th>Product</th>
<th>#Html.ActionLink("Quantity", "Index", new { sort = ViewBag.Sort, search = ViewBag.Search })</th>
<th>Categories</th>
</tr>
#foreach (var item in Model.Items)
{
<tr>
<td>
#Html.ActionLink(item.Name, "Details", "Item", routeValues: new { id = item.Id }, htmlAttributes: null)
</td>
<td>
<span>#item.Quantity</span>
</td>
<td>
#{foreach (var cat in item.Categories)
{
#cat.Name <br />
}
}
</td>
</tr>
}
</table>
But when I search, the result is always empty list, why?
There's only two things that would cause Items to be empty here:
There's no items in the DB.
None of those items have Name values that contain the search term. Be advised in particular here that Contains is case-sensitive. So if the name is Foo and you searched for foo it will not match. If you want to do case-insensitive search, then you should cast both Name and search to either lowercase or uppercase before comparing:
Where(m => m.Name.ToLower().Contains(search.ToLower()))
While we're here. A search should ideally filter at the database level. Currently, you're selecting all items in the database and then filtering them in-memory, which is highly-inefficient. As soon as you you call ToList() in the second line of the action, the query has been sent, but you have to do so in order to set warhouse.Items. Instead, you should store your items in a temporary variable, i.e. IQueryable items = db.Items.Include(c => c.Categories); and do all your conditional ordering and filtering on that. Then, finally, set warhouse.Items = items.ToList();.
I found that the search is case sensitive, this solved it:
if (!string.IsNullOrEmpty(search))
{
warhouse.Items = warhouse.Items.Where(i => i.Name.ToLower().Contains(search.ToLower())).ToList();
ViewBag.Search = search;
}
If you have another suggestion for the code , I appreciate sharing it.

lambda expression Problems

My question is when I click actionlink,the view send specific ID to controller(ex. ProductID = 6), but my controller grab all data to me not specific ID data.
I think the problem is the lambda expression at controller, it will grab all data to me.
These are my Models:
public class ShoppingCart
{
public List<ShoppingCartItemModel> items = new List<ShoppingCartItemModel>();
public IEnumerable<ShoppingCartItemModel> Items
{
get { return items; }
}
}
public class ShoppingCartItemModel
{
public Product Product
{
get;
set;
}
public int Quantity { get; set; }
}
Controller :
[HttpGet]
public ActionResult EditFromCart(int ProductID)
{
ShoppingCart cart = GetCart();
cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
});
return View(cart);
//return RedirectToAction("Index", "ShoppingCart");
}
private ShoppingCart GetCart()
{
ShoppingCart cart = (ShoppingCart)Session["Cart"];
//如果現有購物車中已經沒有任何內容
if (cart == null)
{
//產生新購物車物件
cart = new ShoppingCart();
//用session保存此購物車物件
Session["Cart"] = cart;
}
//如果現有購物車中已經有內容,就傳回 view 顯示
return cart;
}
View
#model ShoppingCart
#{
ViewBag.Title = "購物車內容";
}
<h2>Index</h2>
<table class="table">
<thead>
<tr>
<th>
Quantity
</th>
<th>
Item
</th>
<th class="text-right">
Price
</th>
<th class="text-right">
Subtotal
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.items)
{
<tr>
<td class="text-center">
#item.Quantity
</td>
<td class="text-center">
#item.Product.ProductName
</td>
<td class="text-center">
#item.Product.Price.ToString("c")
</td>
<td class="text-center">
#( (item.Quantity * item.Product.Price).ToString("c"))
</td>
<td>
#using (Html.BeginForm("RemoveFromCart", "ShoppingCart"))
{
#Html.Hidden("ProductId", item.Product.ProductID)
#*#Html.HiddenFor(x => x.ReturnUrl)*#
<input class="btn btn-warning" type="submit" value="Remove">
}
</td>
<td>
#using (Html.BeginForm("EditFromCart", "ShoppingCart", FormMethod.Get))
{
#Html.Hidden("ProductId", item.Product.ProductID)
<input class="btn btn-warning" type="submit" value="Edit">
}
</td>
</tr>
}
</tbody>
</table>
The key issue is that this code doesn't return the result of the LINQ queries, because you have not assigned a variable to the result:
cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
});
I strongly suggest you create a viewmodel specifically to display the cart items.
public class CartItemsViewModel
{
public List<ShoppingCartItemModel> Items { get; set; }
}
[HttpGet]
public ActionResult EditFromCart(int ProductID)
{
ShoppingCart cart = GetCart();
var viewModel = new CartItemsViewModel();
viewModel.Items.AddRange(cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
}));
return View(viewModel);
}
In my example I use the .AddRange() method to take the results of the LINQ calls against the cart items and store them in the viewmodel's Items property.
You must have to assign filtered value to cart like this.
cart.item = cart.items.Where(r => r.Product.ProductID == ProductID)
.Select(r => new ShoppingCartItemModel
{
Product = r.Product,
Quantity = r.Quantity
}).ToList();
use ToList(); or FirstOrDefault() as per your condition
You need to hold the return value from the linq query on cart.Items in a variable and pass that to the View method.
At the moment, the result of your query is being lost and the whole cart passed to the View method.

View is returning FormCollection in Chrome but not IE

I have a View that has a DDL. Once a value is selected in the DDL the form can be submitted and the items for the vendor will be retrieved from a Web Api service and the page is reloaded. This form works as designed in Chrome but it fails to find the DDL value in the FormCollection object in IE. What did I miss?
View Code
#using AMCWeb.Models
#model AppointsViewModel
#{
ViewBag.Title = "Index";
}
<h2>Appraisal Appointment</h2>
#using (Html.BeginForm("GetAppointmentsByVendor", "AppraisalAppointment", FormMethod.Post, new {#id = "validationlist"}))
{
<br/>
<br/>
#Html.DropDownListFor(model => model.SelectedCompany, new SelectList(Model.Vendors.OrderBy(s => s.Company), "Id", "Company", Model.SelectedCompany), "- Select Vendor -")
<br/>
<input type="submit" value="Get Appointments" onclick="location.href='#Url.Action("GetAppointmentsByVendor", "AppraisalAppointment")'" />
<br/>
<table>
<tr>
<th>Id</th>
<th>Loan Number</th>
<th>State</th>
<th>Order Date</th>
<th>Apt Date</th>
<th>Est Due Date</th>
<th>Fees</th>
<th>MN Status</th>
</tr>
#foreach (AMCWeb.Models.AppraisalAppointment appraisalAppointment in Model.Appraisals)
{
//...load the table with data
}
</table>
}
Controller Code
public ActionResult GetAppointmentsByVendor(FormCollection formValues)
{
List<string> messages = new List<string>();
if (formValues["selectedCompany"].Trim() == string.Empty)
{
messages.Add("You must select a Vendor to begin.");
ViewBag.Messages = messages;
appointments.Vendors = _vendorRepository.Get();
return View("Index", appointments);
}
var vendorId = Convert.ToInt32(formValues["selectedCompany"]);
appointments.Appraisals = _appraisalAppointmentRepository.GetByVendor(vendorId);
appointments.Vendors = _vendorRepository.Get();
appointments.SelectedCompany = vendorId;
return View("Index", appointments);
}
Value from IE:
Value from Chrome:
UPDATE:
It appears that the vendorId was being passed. What I found was happening as I stepped through the code was it was indeed not in the FormCollection object, the code then broke on the first 'if statement' because it was not in the collection but if I continued to step through the code it jumped right back up the the first line var vendorId = Convert.ToInt32(formValues["selectedCompany"]); and the value was there. So I modified the code as follows
public ActionResult GetAppointmentsByVendor(FormCollection formValues)
{
// to set up the model for reload
IEnumerable<AppraisalAppointment> appointment = new[] { new AppraisalAppointment() };
appointments.Appraisals = appointment;
appointments.Vendors = _vendorRepository.Get();
// for use in the tryparse
var selectedCompany = formValues["selectedCompany"];
int vendorId;
// for message deliver to the user
List<string> messages = new List<string>();
// if the vendorId is not an int then they didn't select one return
if (!Int32.TryParse(selectedCompany, out vendorId))
{
appointments.SelectedCompany = 0;
messages.Add("You must select a Vendor to begin.");
ViewBag.Messages = messages;
return View("Index", appointments);
}
// get the data for the user
appointments.Appraisals = _appraisalAppointmentRepository.GetByVendor(vendorId);
appointments.SelectedCompany = vendorId;
return View("Index", appointments);
}

View not sending ViewModel to Controller

I am attempting to send a ViewModel with a IList<Hole> from the view to the controller after data is gathered in a for loop to pass into a method, however, the ViewModel being passed continues to be null. What am I missing that is not passing the ViewModel from the View to the Controller?
My ViewModel is:
public class HoleViewModel : IEnumerable
{
public int FacilityId { get; set; }
public int CourseId { get; set; }
//public Hole Hole { get; set; }
public IList<Hole> Holes { get; set; }
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}
My View is:
#using GT_App.Models
#model GT_App.ViewModel.HoleViewModel
....
<form method="post" action="/Hole/Create">
<fieldset>
<div>
#{
var holeCount = 4;
}
<table style="display: inline">
<thead>
<th>Number</th>
<th>Yardage</th>
<th>Par</th>
<th>Hdcp</th>
</thead>
#for (int i = 0; i < holeCount; i++)
{
<tr>
<td>
#Html.TextBoxFor(m => m.Holes[i].Number)
</td>
<td>
#Html.TextBoxFor(m => m.Holes[i].Yardage)
</td>
<td>
#Html.TextBoxFor(model => model.Holes[i].Par)
</td>
<td>
#Html.TextBoxFor(model => model.Holes[i].Handicap)
</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
</form>
My Controller is:
public ActionResult Create()
{
ViewBag.FacilityId = new SelectList(db.Facilities, "FacilityId", "Name");
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name");
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(HoleViewModel holes)
{
if (ModelState.IsValid)
{
for (int i = 0; i < holes.Holes.Count; i++)
{
var item = new Hole();
if (Session["FacilityId"] != null || Convert.ToInt32(Session["FacilityId"]) != 0)
{
item.FacilityId = Convert.ToInt32(Session["FacilityId"]);
}
if (Session["CourseId"] != null || Convert.ToInt32(Session["CourseId"]) != 0)
{
item.CourseId = Convert.ToInt32(Session["CourseId"]);
}
item.Number = Convert.ToInt32(Request.Form["Number" + i]);
item.Yardage = Convert.ToInt32(Request.Form["Yardage" + i]);
item.Par = Convert.ToInt32(Request.Form["Par" + i]);
item.Handicap = Convert.ToInt32(Request.Form["Handicap" + i]);
holes.Holes.Add(item);
}
// itterate thru collection to add individual holes to Entity
foreach (Hole hole in holes)
{
db.Holes.Add(hole);
db.SaveChanges();
}
//return RedirectToAction("Index");
}
ViewBag.CourseId = new SelectList(db.Courses, "CourseId", "Name", Session["CourseId"]);
//return View(Session["CourseId"]);
return RedirectToAction("Index");
}
If there's a model validation error in your second Create action, then typically you'd return the view again using the submitted model, which would then show the validation errors on the webpage.
You're not doing that - you're redirecting to the Index action regardless of whether the model was valid. I'd put the RedirectToAction call just after the call to SaveChanges, and then at the end of the method return View(holes);.
Oh, and I wouldn't put the SaveChanges call inside the loop. Do it after the loop. There are other issues with that code, but I'm going to stop there... :-)

Resources