I'm working on an application to track orders that are coming through a web application. The application won't sell products, and it's designed to track orders as they come in via phone.
I'm still new to ASP.NET MVC, so I'm still learning the ropes. I'm trying to use model binding to get this to work.
Here's my view model:
public class OrderDetailViewModel
{
public Order Order { get; set; }
public List<OrderLine> OrderLinesList { get; set; }
public OrderDetailViewModel()
{
this.Order = new Order();
List<OrderLine> OrderLines = new List<OrderLine>();
OrderLines = new List<OrderLine>();
OrderLines.Add(new OrderLine());
this.OrderLinesList = OrderLines;
}
public OrderDetailViewModel(Order order)
{
this.Order = order;
}
public string SalesRepName
{
get
{
ApplicationDbContext db = new ApplicationDbContext();
ApplicationUser Rep = db.Users.First(u => u.UserName == Order.SalesRep);
return Rep.FirstName + " " + Rep.LastName;
}
}
public IEnumerable<SelectListItem> CustomerList
{
get
{
var Db = new OrderContext();
var Customers = Db.Customers;
var AllCustomers = Customers.Select(c => new SelectListItem
{
Value = c.Id.ToString(),
Text = c.FirstName + " " + c.LastName
});
return AllCustomers;
}
}
public IEnumerable<SelectListItem> ProductList
{
get
{
var Db = new OrderContext();
var Products = Db.Products;
var AllProducts = Products.Select(p => new SelectListItem
{
Value = p.Id.ToString(),
Text = p.Name + ", " + p.Airshow + " - " + p.AirshowDate
});
return AllProducts;
}
}
}
Here's the data model for OrderLine:
public class OrderLine
{
[Key]
public int Id { get; set; }
[Required, Display(Name="Order Number")]
public int OrderId { get; set; }
[Required]
public int ProductId { get; set; }
[Required]
public int Quantity { get; set; }
[Required]
[DisplayFormat(ApplyFormatInEditMode=true, DataFormatString="{0:C}")]
public float Price { get; set; }
[Required, Display(Name="Tax Rate"), DisplayFormat(DataFormatString="{0:P}")]
public float TaxRate { get; set; }
public Nullable<float> Discount { get; set; }
public virtual Product Product { get; set; }
public virtual Order Order { get; set; }
}
And here's the View:
@model AirshowCouponsMvc.Models.OrderDetailViewModel
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>OrderDetailViewModel</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<table class="table table-striped table-responsive table-hover">
<tr>
<th colspan="2">Order Date: @Html.Label(String.Format("{0:MMMM d, yyyy}", DateTime.Now))</th>
<th colspan="2">Customer: @Html.DropDownListFor(model => model.Order.CustomerId, Model.CustomerList)</th>
</tr>
<tr>
<th>
@Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().Product)
</th>
<th>
@Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().Quantity)
</th>
<th>
@Html.LabelFor(model => model.Order.OrderLines.FirstOrDefault().TaxRate)
</th>
<th>
@Html.HiddenFor(model => model.OrderLinesList)
</th>
</tr>
@foreach (AirshowCouponsMvc.Models.OrderLine item in Model.OrderLinesList)
{
<tr>
<td>
@Html.DropDownListFor(model => item.ProductId, Model.ProductList)
</td>
<td>
@Html.EditorFor(model => item.Quantity)
</td>
<td>
@Html.EditorFor(model => item.TaxRate)
</td>
<td>
@Html.HiddenFor(model => item.Price)
@Html.HiddenFor(model => item.OrderId)
@Html.HiddenFor(model => item.Id)
@Html.HiddenFor(model => item.Discount)
@Html.HiddenFor(model => item.Order)
@Html.HiddenFor(model => item.Product)
</td>
</tr>
}
<tr>
<td></td>
<td>@Html.ActionLink("Add line", "AddLine")</td>
<td>@if (Model.OrderLinesList.Count > 1) { @Html.ActionLink("Remove line", "RemoveLine") }</td>
</tr>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10 pull-right">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Finally, here's the code for the controller:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(OrderDetailViewModel model)
{
if (ModelState.IsValid)
{
OrderContext db = new OrderContext();
model.Order.OrderDate = DateTime.Now;
model.Order.SalesRep = User.Identity.Name;
db.Orders.Add(model.Order);
foreach (var line in model.Order.OrderLines)
{
line.Price = line.Product.Price;
line.Discount = OrderDetailViewModel.DiscountRate(line.Quantity);
line.OrderId = model.Order.Id;
db.OrderLines.Add(line);
}
return RedirectToAction("Index");
}
return View(model);
}
Yes, I know that the controller doesn't process the list. I had used the property from the data model, then I read that you need to define a List as a property in the view model. I was in the middle of changing it to this, but when I hit a break at the beginning of the Create action, I noticed that the OrderLinesList is null.
Also, I read that in older version of ASP.NET MVC (at least I haven't seen it in reference to more recent versions of ASP.NET MVC) that you have to use a for loop instead of a foreach loop. I tried going that route, but I still had the problem of a null List.
Here's the code that I tried as a for loop in the Create view. This was inserted in place of the foreach loop currently shown:
@for (int i = 0; i < Model.OrderLines.Count(); i++)
{
<tr>
<td>
@Html.DropDownListFor(model => model.OrderLines[i].ProductId, Model.ProductList)
</td>
<td>
@Html.EditorFor(model => model.OrderLines[i].Quantity)
</td>
<td>
@Html.EditorFor(model => model.OrderLines[i].TaxRate)
</td>
<td>
@Html.HiddenFor(model => model.OrderLines[i].Id)
@Html.HiddenFor(model => model.OrderLines[i].Price)
@Html.HiddenFor(model => model.OrderLines[i].Discount)
@Html.HiddenFor(model => model.OrderLines[i].OrderId)
@Html.HiddenFor(model => model.OrderLines[i].Order)
@Html.HiddenFor(model => model.OrderLines[i].Product)
</td>
</tr>
}
My end goal is to be able to track multiple line items for each order, preferably with model binding.
I've read through quite a few references to try to get this to work, including:
http://seesharpdeveloper.blogspot.com/2012/05/mvc-model-binding-to-list-of-complex.html
Model binding generic list is null in asp.net mvc
MVC3 Non-Sequential Indices and DefaultModelBinder
ASP.NET MVC 4 - for loop posts model collection properties but foreach does not
I've followed the advice in these, and it's not working for me.
I would appreciate any help on getting this fixed.