I have a project to make an online shop between users (post a product, buy, etc.) using a database. In this project I have a view called "ShoppingCart":
#model IEnumerable<MyFirstProject.Models.Product>
#{
ViewBag.Title = "ShoppingCart";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Your Shopping Cart</h2>
#if (Model == null)
{
<div style="float:left">Your cart is empty.</div>
<div>
Total payment: 0
</div>
}
else
{
decimal tPrice = 0;
<div>
<table style="float:left">
#foreach (var product in Model)
{
tPrice = tPrice + product.Price;
{ Html.RenderPartial("ProductLine", product);}
}
</table>
</div>
<div>
Total payment: #tPrice
</div>
}
It receives a list of products which the user decided to buy and displays them (not the important part). I need to add a button which will send the list to an action result in the "ShoppingController":
[HttpPost]
public ActionResult ShoppingCart(List<Product> bought)
{
if (ModelState.IsValid)
{
foreach (var listP in bought.ToList())
{
foreach (var databaseP in db.Products.ToList())
{
if (listP.ProductID == databaseP.ProductID)
{
databaseP.State = 1;
db.SaveChanges();
break;
}
}
}
return RedirectToAction("Index");
}
else
{
return View(bought);
}
}
"State" indicates if the product was bought or not (0=not bought, 1=bought), db is the database
If you wan't to post any data from a view to an action method, you should keep that data in form elements and keep that in a form. Since you want to post a collection of items, You may use Editor Templates.
Let's start by creating a view model.
public class ShoppingCartViewModel
{
public decimal TotalPrice { set; get; }
public List<Product> CartItems { set; get; }
}
public class Product
{
public int Id { set; get; }
public string Name { set; get; }
}
Now in your GET action, you will create an object of the ShoppingCartViewModel, load the CartItems property and send to the view.
public ActionResult Index()
{
var cart = new ShoppingCartViewModel
{
CartItems = new List<Product>
{
new Product { Id = 1, Name = "Iphone" },
new Product { Id = 3, Name = "MacBookPro" }
},
TotalPrice = 3234.95
};
return View(cart);
}
Now i will create an EditorTemplate. To do that, Go to your ~/Views/YourControllerName folder, and Create a directory called EditorTemplates and add a view with name Product.cshtml
The name of the file should match with the name of the type.
Open this new view and add the below code.
#model YourNamespace.Product
<div>
<h4>#Model.Name</h4>
#Html.HiddenFor(s=>s.Id)
</div>
You can keep the display however you want. But the important thing is, We need to keep a form field for the productId. We are keeping that in a hidden field here.
Now let's go back to our main view. We need to make this view strongly typed to our ShoppingCartViewModel. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceYourNamespaceHere.ShoppingCartViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.CartItems)
<p>Total : #Model.TotalPrice</p>
<input type="submit" />
}
And in your HttpPost action method, We will have a paramer of type ShoppingCartViewModel. When the form is submitted, MVC Model binder will map the posted form values to an object of ShoppingCartViewModel.
[HttpPost]
public ActionResult Index(ShoppingCartViewModel model)
{
foreach (var item in model.CartItems)
{
var productId = item.Id;
// to do : Use productId and do something
}
return RedirectToAction("OrderSucessful");
}
You can iterate through the CartItems collection and get the Id of the Products and do whatever you want.
If you wan't to allow the user to edit the items (using a check box) in this page, Take a look at this answer. It is basically same, but you add a boolean property to Product class and use that for rendering a checkbox.
Related
I am passing a list from my controller to my view, and then inside a form I loop through each list item and display it with some textboxes that filled with value for each item. And then I want to submit every texboxes to controller, but it return null on it.
here is my model:
public class DataRedpack
{
public string IdRedpack { get; set; }
//and other properties...
}
and here is my view:
#model IEnumerable<SisFoPengelolaanPengirimanBarang.Models.DataRedpack>
#using (Html.BeginForm("InsertRedpack", "Admin", FormMethod.Post))
{
foreach (var item in Model)
{
#Html.Hidden("IdRedpack", item.IdRedpack)
#Html.TextBox("AmountPack", null, new { type = "number", #class = "form-control", #name = "AmountPack", #min=1, #max=50 })
}
}
it pass null to my controller:
public ActionResult InsertRedpack(List<string> IdRedpack, List<int> AmountPack) //it returing null on this
{
if (IdRedpack.Any() && AmountPack.Any()) //and got exception on this
{
//updating redpack with id and so on...
}
}
I need help how to figure this
Add "name" for each element in the loop like "IdRedpack[0]" . replace an index variable with each loop. Also change your Controller Action method like.
public ActionResult InsertRedpack(IEnumerable<SisFoPengelolaanPengirimanBarang.Models.DataRedpack> model)
{
//Validate your model here
}
I'm looking to have something like the following:
#foreach(var option in ViewBag.OptionsAvailable)
{
//the 'option' variable will be used in these fields, of course
<input type='checkbox' name='thingToUse' />
<input type='text' name='textToSaveForThing' />
}
ViewBag.OptionsAvailable can be of variable length. I would like to take one of these options and add it to Model.Options with the saved value IF its checkbox is selected. The idea here is that, on the HttpPost controller method, I want to only acknowledge/save the value in the textbox if its corresponding checkbox is selected. What's the best way to approach this?
I've come across this explanation of binding to a list, but I'm not sure how to evolve upon that to create what I want.
Start by creating a view model to represent what you want to display/edit
public class OptionVM
{
public string Text { get; set; }
public bool IsSelected { get; set; }
public string Answer { get; set; }
.... // other properties
}
Then in the controller, initialize a collection of your view model and pass it to the view
public ActionResult Edit()
{
List<OptionVM> model = new List<OptionVM>();
.... // populate it
return View(model);
}
and the view
#model List<yourAssembly.OptionVM>
#using (Html.BeginForm())
{
for (int i = 0; i < Model.Count; i++)
{
<label>
#Html.CheckBoxFor(m => m[i].IsSelected)
<span>#Model[i].Text</span>
</label>
#Html.TextBoxFor(m => m[i].Answer)
#Html.ValidationMessageFor(m => m[i].Answer)
}
<input type="submit" ... />
}
and submit to
public ActionResult Edit(List<OptionVM> model)
{
// for example, get the answers where the checkbox has been selected
var selectedAnswers = model.Where(m => m.IsSelected).Select(m => m.Answer);
}
and you could enhance this by using a foolproof [RequiredIfTrue] or similar validation attribute applied to the Answer property to ensure the textbox has a value if the corresponding checkbox is checked
There is no need to have two inputs. You can use the "value" attribute of the checkboxes, and store that information. Also, in your controller, you can simply add a parameter with the same name as your input.
View
#using (Html.BeginForm())
{
foreach (string option in ViewBag.OptionsAvailable)
{
<input type='checkbox' name='checkboxes' value="#option" />
#option <!-- text to display -->
}
<button type="submit">Submit</button>
}
Controller
[HttpGet]
[ActionName("Index")]
public ActionResult GetCar()
{
ViewBag.OptionsAvailable = new List<string>
{
"Red",
"Yellow",
"Blue"
};
return View(new Car());
}
[HttpPost]
[ActionName("Index")]
public ActionResult PostCar(string[] checkboxes)
{
Car car = new Car
{
ColorsSelected = new List<string>()
};
foreach (string value in checkboxes)
{
car.ColorsSelected.Add(value);
}
return RedirectToAction("Index");
}
I have a model in my ASP.NET MVC application:
public class SearchArrayModel
{
public long ID { get; set; }
public string Name { get; set; }
public struct AttribStruct
{
public string AttribName { get; set; }
public string[] AttribValues { get; set; }
}
public AttribStruct[] AttribStructTable { get; set; }
}
In controller I'm filling it by some data from WebAPI (filling process is okay), I created an array of SearchArrayModel because I have a lot of items to fill from webAPI (It's a webAPI from site similar to ebay), for example some phones with its names, and attributes which u normally see on the auction site).
SearchArrayModel[] src = new SearchArrayModel[x];
{
//filling the fields
}
And on the end of the ActionResult I have:
return View(src);
//Visual Studio tells me that it is a "SearchArrayModel[] src"
I have also View, where I want to get and display the data:
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
<h2>#Model.AttribStructTable[1].AttribName</h2>
<h3>#Model.AttribStructTable[1].AttribValues[1]</h3>
//indexes of arrays are just for testing, if all will be good I will write a loop
}
But when I'm starting the app I have an error:
The model item passed into the dictionary is of type
'allegrotest.Models.SearchArrayModel[]', but this dictionary requires
a model item of type 'allegrotest.Models.SearchArrayModel
I know that this is a complex array and I don't know how to pass this array from Controller to View.
I tried to write in View:
#model allegrotest.Models.SearchArrayModel[]
but then I can't get into the fields inside the #Model
Starting from assumption that "filling process is okay" is wrong.
Note: I made this assumption because I see that you are not interested in Model[index] and I noticed in SearchArrayModel[x]; { } the ;.
SearchArrayModel src = new SearchArrayModel
{
AttribStructTable = new SearchArrayModel.AttribStruct[]
{
new SearchArrayModel.AttribStruct{AttribName = "0Nume", AttribValues = new []{"0one", "0two"}},
new SearchArrayModel.AttribStruct{AttribName = "1Nume", AttribValues = new []{"1one", "1two"}},
},
Name = "SearchArrayName"
};
Your View is okay and is working
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
Another solution will be to make the modelof View to be an IEnumerable and in the Action you can make return View(src.ToList());
Also I noticed, when I tested your code, that you have Model.AttribStructTable which is wrong because your Model is a collection and you have to specify which element from model you want to display Model[index] (not posible with IEnumerable), Model.First() or you can iterate through collection.
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.First().AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
If you iterate through all items from Model will look like this
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var searchArrayModel in Model)
{
#foreach(var attribStruct in #searchArrayModel)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
}
#model allegrotest.Models.SearchArrayModel[]
which is an array. So you could try
#foreach (SearchArrayModel item in Model)
{
<h2>#item.AttribStructTable[1].AttribName</h2>
<h3>#item.AttribStructTable[1].AttribValues[1]</h3>
..
}
or
#for (int i = 0; i < Model.Length; ++i)
{
<h2>#Model[i].AttribStructTable[1].AttribName</h2>
<h3>#Model[i].AttribStructTable[1].AttribValues[1]</h3>
..
}
I have populated a dropdown list with values from Database Table. The list gets populated with correct table data but all values have ZERO index in the list. Here is the code to fill dropdown list:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
IEnumerable<SelectListItem> items = db.Attractions
.ToList()
.Select(c => new SelectListItem
{
Value = c.A_ID.ToString(),
Text = c.Name
});
ViewBag.Attractions = items;
return View();
}
And on Dropdown View Page:
<div class="editor-label">
#Html.LabelFor(model => model.Attraction)
</div>
<div class="editor-field">
#Html.DropDownList("Attractions")
</div>
For example if table have 3 values A,B, and C. These values are appearing in dropdown list but when I get its selected index in POST request function, it always returns ZERO. Here is the POST submit function:
//Post
[HttpPost]
public ActionResult NewBooking(BookingView booking)
{
try
{
BookingManager bookingManagerObj = new BookingManager();
bookingManagerObj.Add(booking);
ViewBag.BookingSavedSucess = "Booking saved!";
return View("WelcomeConsumer","Home");
}
catch
{
return View(booking);
}
}
booking.Attraction is always ZERO even user selected greater than ZERO index item.
Any suggestions?
I would guess that it is because you are getting a collection of SelectListItems back and not an actual SelectList. Try something like:
<div class="editor-field">
#Html.DropDownListFor(model => model.Attraction, new SelectList(ViewBag.Attractions, "Value", "Text");
It's best not to use ViewBag, you should always use a ViewModel.
Say you have a ViewModel like this:
public class AttractionViewModel
{
public int AttractionId { get; set; }
public SelectList Attractions { get; set; }
}
and modify your view like this - I presume you already have a form in there, the relevant bit is the #Html.DropDownListFor(...) and making sure you have the full namespace to the ViewModel if you haven't already included it in the Views web.config file:
#model AttractionViewModel
#using(Html.BeginForm("NewBooking", "ControllerName"))
{
<div class="editor-label">
#Html.LabelFor(model => model.AttractionId)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.AttractionId, Model.Attractions)
</div>
<input type="submit" value="Submit">
}
and modify your HttpGet like this:
//Get
public ActionResult NewBooking()
{
var db = new VirtualTicketsDBEntities2();
var items = db.Attractions.ToList();
var attractionIdDefault = 0;// default value if you have one
var vm = new AttractionViewModel {
AttractionId = attractionIdDefault,// set this if you have a default value
Attractions = new SelectList(items, "A_ID", "Name", attractionIdDefault)
}
return View(vm);
}
and create an HttpPost ActionResult like this:
// Post
public ActionResult NewBooking(AttractionViewModel vm)
{
var attractionId = vm.AttractionId; // You have passed back your selected attraction Id.
return View();
}
Then it should work.
I know that you have already selected your answer but here is an alternative way of doing what you did. When I started off with ASP.NET MVC I struggled with SelectListItem and found another way of populating my drop down list. I have stuck to this way ever since.
I always have a view model that I bind to my view. I never send through a domain model, always a view model. A view model is just a scaled down version of your domain model and can contain data from multiple domain models.
I have made some modifications to your code and tips, but like I mentioned, it's just an alternative to what you already have.
Your domain model could look like this. Try and give your property names some meaningful descriptions:
public class Attraction
{
public int Id { get; set; }
public string Name { get; set; }
}
You view model could look something like this:
public class BookingViewModel
{
public int AttractionId { get; set; }
public IEnumerable<Attraction> Attractions { get; set; }
// Add your other properties here
}
Do not have your data access methods in your controllers, rather have a service layer or repository expose this functionality:
public class BookingController : Controller
{
private readonly IAttractionRepository attractionRepository;
public BookingController(IAttractionRepository attractionRepository)
{
this.attractionRepository = attractionRepository;
}
public ActionResult NewBooking()
{
BookingViewModel viewModel = new BookingViewModel
{
Attractions = attractionRepository.GetAll()
};
return View(viewModel);
}
[HttpPost]
public ActionResult NewBooking(BookingViewModel viewModel)
{
// Check for null viewModel
if (!ModelState.IsValid)
{
viewModel.Attractions = attractionRepository.GetAll();
return View(viewModel);
}
// Do whatever else you need to do here
}
}
And then your view will populate your drop down like this:
#model YourProject.ViewModels.Attractionss.BookingViewModel
#Html.DropDownListFor(
x => x.AttractionId,
new SelectList(Model.Attractions, "Id", "Name", Model.AttractionId),
"-- Select --"
)
#Html.ValidationMessageFor(x => x.AttractionId)
I hope this helps.
I have the following action method, when I press the update button on my cart and post to this method I need it bind all productId and partquantity values into the respective parameters/arrays (int[] ProductId, int[] partquantity) and it does this. I am presuming when form data, that is keys and values are posted they arrive in some sort of order, likely as elements are laid out on the HTML page (top to bottom)? So I wish for the operation on each cart item to be performed using the correct partquantity entered, that is for the correct productId. I am guessing if they post and bind in strict order then partquantity[2] should be the correct quantity for ProductId[2] etc. ?
The below logic in trying to increment f by 1 for each operation on each productId in the ProductId[] array does not work. I need to get this to work because say I have 5 items added to the cart and change the quantity for 4 of them I wish to just press the one update button and it will update for all these items\lines in the cart. So method needs to catch all the posted productId and quantities and use in the correct order, so the right quantity is assigned to the right cart item which is looked up by ProductId.
public RedirectToRouteResult UpdateCart(Cart cart, int[] ProductId, int[] partquantity, string returnUrl)
{
int f = 0;
int x = partquantity.Length;
while (f <= x)
{
foreach (var pid in ProductId)
{
f++;
var cartItem = cart.Lines.FirstOrDefault(c => c.Product.ProductID == pid);
cartItem.Quantity = partquantity[f];
}
}
return RedirectToAction("Index", new { returnUrl });
}
This is the View:
<% foreach (var line in Model.Cart.Lines)
{ %>
<tr>
<td align="center"><%: Html.TextBox("partquantity", line.Quantity)%></td>
<td align="left"><%: line.Product.Name%></td>
<td align="right"><%: line.Product.ListPrice.ToString("c")%></td>
<td align="right">
<%: (line.Quantity * line.Product.ListPrice).ToString("c")%>
</td>
</tr>
<%: Html.Hidden("ProductId", line.Product.ProductID)%>
<% } %>
Custom Binder
public class CartModelBinder : IModelBinder
{
private const string cartSessionKey = "_cart";
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if (bindingContext.Model != null)
throw new InvalidOperationException("Cannot update instances");
Cart cart = (Cart)controllerContext.HttpContext.Session[cartSessionKey];
if (cart == null)
{
cart = new Cart();
controllerContext.HttpContext.Session[cartSessionKey] = cart;
}
return cart;
}
}
}
Your Cart object should be enough as parameter for this scenario, you can have something like this.
The general idea would be to use an index so you get your Cart object with your Lines as you passed them to the view originally but with updated Quantity values.
Your model as I understand it:
public class Cart
{
...
public List<CartItem> Lines {get; set; }
}
public class CartItem
{
public Product Product {get; set;}
public int Quantity {get; set;}
...
}
In your view:
#model Cart
...
#using(Html.BeginForm())
{
#{ int index = 0; }
#foreach(var l in Model.Lines)
{
#Html.Hidden("cart.Lines.Index", index);
#Html.Hidden("cart.Lines[" + index + "].Product.ProductID", l.Product.ProductID)
#Html.TextBox("cart.Lines[" + index + "].Quantity")
#{ index++; }
}
<input type="submit" value="Update Quantity" />
}
Your controller:
public ActionResult UpdateCart(Cart cart)
{
// you should have new values on Quantity properties of the cart.Lines items.
}
Wouldn't it be easier to have an Object - say BasketItem that has the productId and the quantity as Properties? so you would have one array/list/ienumerable to pass on to update and to bind.
Your problem would be obsolete since the connection between quantity and productid is done via the object.