I have a pricing page which display a list of products along with their prices. User can choose to add multiple products to shopping cart (active shopping cart is being shown on right hand side of page). Currently this is how I have implemented it using Ajax/Jquery...
Code snippet from my view (ASPX): Looping thro available products in ViewModel and displaying details:
<% foreach (var _product in _supplier.HotelProducts)
{ %>
<tr>
<td colspan="2" align="left" valign="top"><% = _product.Description %></td>
<td align="left">
<% using (Html.BeginForm("AddToCart", "ShoppingCart", FormMethod.Post, new { #class = "addProductToCartForm" }))
{ %>
<input type="hidden" name="hSupplierID" id="hSupplierID" value="<% = _supplier.ID %>" />
<input type="hidden" name="hProductCode" id="hProductCode" value="<% = _product.Code %>" />
<input type="hidden" name="hProductDescription" id="hProductDescription" value="<% = _product.Description %>" />
<input type="hidden" name="hProductPrice" id="hProductPrice" value="<% = _product.TotalPrice %>" />
<input type="submit" value="+ Add to cart" />
<% } %>
</td>
<td valign="top" align="center">
<span id="spanProductPrice" class="_price">$<% = _product.TotalPrice %></span>
</td>
</tr>
<% } %>
As you can see from above code snippet, I have "+ Add to cart" button againts each product and my requirement is to pass SupplierID and Product details (Code, Desc & Price) to my Controller and Cart. Please note that I get list of Products & their pricing from an external webservice and there is no way for me to just pass the Product Code and retrieve corresponding description & price on server side, that's why I need to capture required product details when user adds it to cart.
$(function () {
$(".addProductToCartForm").submit(function (e) {
e.preventDefault();
var HiddenCartForm = {
SupplierID: $(this.hSupplierID).val(),
Code: $(this.hProductCode).val(),
Description: $(this.hProductDescription).val(),
TotalPrice: $(this.hProductPrice).val()
};
$.post($(this).attr("action"), HiddenCartForm, function (data) {
//alert("Success");
renderCart(data);
});
return false; // form already submitted using ajax, don't submit it again the regular way
});
});
function renderCart(data) {
$("#rightColumn").html(data);
}
Here is my custom HiddenCartForm object which I use to pass information from View to Controller via JQuery
public class HiddenCartForm
{
public string SupplierID { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public decimal? TotalPrice { get; set; }
//public ProductView Product { get; set; }
}
I have TWO questions:
[1] Is there a better way to handle this scenario? I am little uncomfortable with so many forms & hidden fields (for holding SupplierID & Product Details) on the View. These forms & hidden fields will be visible when someone does view source.
[2] I need pretty much all of the information from "_product" when user adds a particular product to shopping cart. Is there a better way to pass this information via JQuery instead of using hidden fields as I am looping thro the products foreach (var _product in _supplier.HotelProducts) in my view?
I am on MVC 2 currently.
Why do you need to pass the product's description and price in your form post? Couldn't the controller action that handles the AJAX post look those values up from the database? Depending on how model is defined, supplier ID might also be removed for similar reasons.
Related
I am looking for a way to pass currently selected model from view to controller.
My select code:
<select asp-for="Product">
#foreach (var p in Model.Products)
{
<option value="#p">#p.ProductName</option>
}
</select>
I'm trying to bind option value #p to bind with my property below:
[BindProperty]
public Product Product { get; set;}
But whenever after submitting my form the Product property has "null" values.
Any suggestions?
I think you can use a hidden type for this. No need to query to find ProductName after submit in list.
<input type="hidden" asp-for="ProductName" />
<select asp-for="ProductId">
#foreach (var p in Model.Products)
{
<option value="#p.Id">#p.ProductName</option>
}
</select>
<script>
$(function () {
$('form').submit(function () {
var productName = $('#ProductId option:selected').text();
$('#ProductName').val(productName);
});
});
</script>
Usually you would pass the Id of the selected item when the form is posted. That is typically enough data to work with. However, if you really want the ModelBinder to create a Product instance from the posted values, your form fields should be named Product.ProductId and Product.ProductName. The other answer shows how to obtain the ProductName value and then assign it to a hidden field.
<select asp-for-"Product.ProductId" asp-items="Model.Products">
...
</select>
<input type="hidden" asp-for="Product.ProductName" />
I've been struggling with this for a while now. I'm constructing this view:
But when I hit the 'Update' button after do some changes the web refreshes to show the original values.
About the view: I get this view using an IEnumerable and looping thru each item in the model inside a form. Then, inside the form, there is a table that contains only 1 row. I do this in order to wrap all the items of the record in one form. This is part of code:
#foreach (var item in Model)
{
<form asp-action="Test" asp-route-id="#item.Id">
<table class="table">
<tbody>
<tr>
<td>
<input type="hidden" asp-for="#item.Id" />
<div class="form-group">
<div class="col-md-10">
<input asp-for="#item.MchName" readonly class="form-control" />
<span asp-validation-for="#item.MchName" class="text-danger"></span>
</div>
</div>
</td>
//more fields
<td>
<input type="submit" value="Update" class="btn btn-default" />
</td>
</tr>
</tbody>
</table>
</form>}
I declare an asp-action and a asp-route-id:
<form asp-action="Test" asp-route-id="#item.Id">
Question: Is this good enough? Is there something missing?
This is the Get Method:
public async Task<IActionResult> Test()
{
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I'm not passing any argument to the controller, yet the view will list the items following the given structure using an IEnumerable. Should I pass anything to the Get Method or is it fine as it is?
This is the Post Method:
#model IEnumerable<Application.Models.Machine>
[HttpPost, ActionName("Test")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> TestPost(int? id)
{
if (id == null)
{
return NotFound();
}
var machinetoUpdate = await _context.Machines
.SingleOrDefaultAsync(s => s.Id == id);
if (await TryUpdateModelAsync(
machinetoUpdate,
"",
s => s.MchName, s => s.StoreID, s => s.PUnit, s => s.Status))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction("Test");
}
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I don't know if because the entity I retrieve the id from (and that I use to update the model thru TryUpdateModelAsync()) is also being used to compare to the model that thru the view this might not been working properly.
Thanks in advance for any help.
I have project on ASP MVC 5. I have a model "Article". This model have HashSet and ICollection of Author. Author - second model:
public partial class Article
{
public Article()
{
Authors = new HashSet<Author>();
}
[DisplayName("Авторы")]
public virtual ICollection<Author> Authors { get; set; }
I need to add page of creating Article, on which you can increase the number of authors(using AJAX), and each author to register the fields. I decided to use partial view of Author's model, without "Create" button(Create button used only view of creating Article). I need in unlimited adding new partial views, and after fill them - get all data from them. How make it? I newbie in MVC, and can't imagine how it will works.
http://i.stack.imgur.com/0RHD0.png - an illustration of how it should look
Is there a need to use partials? wouldnt it be easier to write a small script that would instead clone the first author enclosing element and just change the names of the elements involved to create a new author?
<div id="enclosingDiv" data-count="x">
<div class="someClass" data-index='x1' >
Author1 name Aurthor1 Textboxname="CollectionList[Index].Property"...
</div>
Now when creating a new Authouther, you can just create:
<script>
function createNewAuthor()
{
//clone first author
var count = $('encolsingDiv').attr('data-count');
//var count = $('encolsingDiv').children().length;
var author = $('enclosingDiv').first().clone();
//change name and id,etc using data-count
author.find('*[name$='value'])attr('name','ListCollection[count + 1]");
author.find('*[name$='value'])attr('id',....);
author.attr('data-index',count +1)
$('enclosingDiv').append(author);
$('enclosingDiv').attr('data-count',count + 1 to it);//makes life easier
}
function deleteAuthor(authourIndex)
{
//assumes that an author can be removed
$('div[data-index="'+authorIndex+'"]").remove();
$('enclosingDiv').children.each(function()
{
//if delete functionality exists, change the names of the element indices using the count variable
change all indices of element properties concerned
$(this).find('*[name$='value']).attr('name','ListCollection['+count+'].sumproperty");
count++;
});
}
</script>
So you can use that for create and delete methods, you don't need partials for that.
The code might need some work as what I show is the concept
It is not that hard. Your partial views will be posted as a collection.
Suppose that your partial view has 2 values, FirstName and LastName. It should be something like this:
#{
Guid index = Guid.NewGuid();
}
<input type="hidden" name="People.index" value="#index" />
<input type="text" name="People[#index].FirstName" value="" />
<input type="text" name="People[#index].LastName" value="" />
The final output would be:
<input type="hidden" name="People.index" value="B756DAD8-5D5D-449E-A4B4-E61F75C1562C" />
<input type="text" name="People[B756DAD8-5D5D-449E-A4B4-E61F75C1562C].FirstName" value="" />
<input type="text" name="People[B756DAD8-5D5D-449E-A4B4-E61F75C1562C].LastName" value="" />
<input type="hidden" name="People.index" value="B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32" />
<input type="text" name="People[B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32].FirstName" value="" />
<input type="text" name="People[B78B7BBC-EB0E-41CB-BE18-C1E3F7526F32].LastName" value="" />
Your model must have a collection People object.
public class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Article
{
//other properties...
public ICollection<Person> People { get; set; }
}
Your Controller:
public ActionResult YourAction (Article model)
{
//...
}
Untested code, but it should work fine.
I've a MVC application, whose SharedLayout view(Master Page) gives user capability to search. They could search their order by Order No or By Bill no. So there are two option buttons the Shared View along with the textbox. Code is somewhat like this
#using (Html.BeginForm("Track", "Tracking", FormMethod.Post))
{
<div style="text-align: center">
<textarea cols="20" id="txtNo" name="txtOrderNo" rows="2" ></textarea>
</div>
<div style="text-align: center">
<input type="radio" name="optOrderNo" checked="checked" value="tracking" />Order No <input type="radio" name="optRefNo" value="tracking" />Ref No
</div>
<div style="text-align: center">
<input type="submit" value="Track" />
</div>
}
So it'll go to TrackingController and Track Method in it and return the view. It works fine for a single search as a View is associated with a controller's methods. It works fine but how could i conditionally return the other view based on the radio button selection.
What i come up with is this
[HttpPost]
public ActionResult Track(FormCollection form)
{
string refNo = null;
if (form["optRefNo"] == null)
{
string OrderNo = form["txtOrderNo"];
var manager = new TrackingManager();
var a = manager.ConsignmentTracking(OrderNo);
var model = new TrackingModel();
if (OrderNo != null)
model.SetModelForConsNo(a, consNo);
return View(model);
}
refNo = form["txtConsNo"];
return TrackByRef(refNo);
}
public ActionResult TrackByRef(string refNo)
{
//what ever i want to do with reference no
return View();
}
Kindly guide.
Thanks
View has an overload where the first parameter is a string. This is the name (or path) to the view you want to use, rather than the default (which is a view that matches the action's name).
public ActionResult TrackByRef(string refNo)
{
//what ever i want to do with reference no
return View("Track");
// or, if you want to supply a model to Track:
// return View("Track", myModel);
}
I am having a trouble while trying to create an entity with a custom view modeled create form. Below is my custom view model for Category Creation form.
public class CategoryFormViewModel
{
public CategoryFormViewModel(Category category, string actionTitle)
{
Category = category;
ActionTitle = actionTitle;
}
public Category Category { get; private set; }
public string ActionTitle { get; private set; }
}
and this is my user control where the UI is
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CategoryFormViewModel>" %>
<h2>
<span><%= Html.Encode(Model.ActionTitle) %></span>
</h2>
<%=Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>
<p>
<span class="bold block">Başlık:</span>
<%=Html.TextBoxFor(model => Model.Category.Title, new { #class = "width80 txt-base" })%>
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<%=Html.TextBoxFor(model => Model.Category.OrderNo, new { #class = "width10 txt-base" })%>
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Save" />
</p>
<% } %>
When i click on save button, it doesnt bind the category for me because of i am using custom view model and strongly typed html helpers like that
<%=Html.TextBoxFor(model => Model.Category.OrderNo) %>
My html source looks like this
<form action="/Admin/Categories/Create" method="post">
<p>
<span class="bold block">Başlık:</span>
<input class="width80 txt-base" id="Category_Title" name="Category.Title" type="text" value="" />
</p>
<p>
<span class="bold block">Sıra Numarası:</span>
<input class="width10 txt-base" id="Category_OrderNo" name="Category.OrderNo" type="text" value="" />
</p>
<p>
<input type="submit" class="btn-admin cursorPointer" value="Kaydet" />
</p>
</form>
How can i fix this?
Your View Model needs a default constructor without parameters and you need public set methods for each of the properties. The default model binder uses the public setters to populate the object.
The default model binder has some rules it follows. It chooses what data to bind to in the following order:
Form parameters from a post
Url route data defined by your route definitions in global.asax.cs
Query string parameters
The default model binder then uses several strategies to bind to models/parameters in your action methods:
Exact name matches
Matches with prefix.name where prefix is the parent class and name is the subclass/property
Name without prefix (as long as there are no collisions you don't have to worry about providing the prefix)
You can override the behavior with several options from the Bind attribute. These include:
[Bind(Prefix = "someprefix")] -- Forces a map to a specific parent class identified by the prefix.
[Bind(Include = "val1, val2")] -- Whitelist of names to bind to
[Bind(Exclude = "val1, val2")] -- Names to exclude from default behavior
You could use editor templates. Put your ascx control in ~/Views/Shared/EditorTemplates/SomeControl.ascx. Then inside your main View (aspx page) include the template like so (assuming your view is strongly typed to CategoryFormViewModel):
<%= Html.EditorForModel("SomeControl") %>
instead of
<% Html.RenderPartial("SomeControl", Model) %>
Make a default constructor for your viewmodel and initialize the Category there
public CategoryFormViewModel()
{
Category = new Category()
}
And at your controller action receive the viewmodel
public ActionResult ActionName(CategoryFormViewModel model)
{
//here you can access model.Category.Title
}