© Lee Naylor 2016

Lee Naylor, ASP.NET MVC with Entity Framework and CSS , 10.1007/978-1-4842-2137-2_9

9. Checkout: Creating and Viewing Orders

Lee Naylor

(1)Newton-le-Willows, Merseyside, UK

This chapter covers working with orders, including creating them via a checkout process, viewing the details of orders as either an administrator or a user, and searching or sorting them. We'll use the display and editor templates for the Address type that we created earlier in the book to simplify some of the view code in this chapter. We don’t cover any payment provider integration during this book, but in a real-world system, you would need to add a step to the checkout process to obtain and verify payments prior to confirming an order.

Note

If you want to follow along with the code in this chapter, you must either have completed Chapter 8 or download Chapter 8’s source code from www.apress.com as a starting point.

Modeling Orders

We are going to model orders in a similar way to the basket, with an order consisting of a list of order lines; however when we modeled a basket, we did not store the basket in the database, whereas we are going to store both order and order lines in the database to model orders. We are going to add a one-to-many relationship between users and orders, where a user can have many orders but an order is placed only by a single user. Add two new classes to the Models folder, in the Order.cs and OrderLine.cs files, as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;


namespace BabyStore.Models
{
    public class Order
    {
        [Display(Name = "Order ID")]
        public int OrderID { get; set; }
        [Display(Name = "User")]
        public string UserID { get; set; }
        [Display(Name = "Deliver to")]
        public string DeliveryName { get; set; }
        [Display(Name = "Delivery Address")]
        public Address DeliveryAddress { get; set; }
        [Display(Name = "Total Price")]
        [DataType(DataType.Currency)]
        [DisplayFormat(DataFormatString = "{0:c}")]
        public decimal TotalPrice { get; set; }
        [Display(Name = "Time of Order")]
        public DateTime DateCreated { get; set; }
        public List<OrderLine> OrderLines { get; set; }
    }
}


using System.ComponentModel.DataAnnotations;

namespace BabyStore.Models
{
    public class OrderLine
    {
        public int ID { get; set; }
        public int OrderID { get; set; }
        public int? ProductID { get; set; }
        public int Quantity { get; set; }
        public string ProductName { get; set; }
        [Display(Name = "Unit Price")]
        [DataType(DataType.Currency)]
        [DisplayFormat(DataFormatString = "{0:c}")]
        public decimal UnitPrice { get; set; }
        public virtual Product Product { get; set; }
        public virtual Order Order { get; set; }
    }
}

These two new classes will represent an order and the lines from the basket for the order. We store the ProductName property in each OrderLines to maintain a record of the product bought even if the actual product entity is deleted from the database. We also store the unit price in the OrderLines because this will be the price at the time of the order; in the future, this could change so we’ll record this as the order is placed. The OrderLines will contain two foreign keys via the properties ProductID and OrderID to relate it to a products and an order. ProductID is nullable because the web site allows products to be deleted, although in a real system, products would probably be archived rather than deleted.

Note

Entity Framework 6 does not support cross-database querying, therefore we have not included a navigational property to the ApplicationUser class in the Order class. Instead, we'll use Entity Framework to manually relate users to orders. Another possible alternative solution is to create a view in one of the databases relating one to another, but I have not taken this approach in this book.

Next, update the DAL/StoreContext.cs file as follows to ensure the database context contains a reference to the new Order and OrderLines classes.

using BabyStore.Models;
using System.Data.Entity;


namespace BabyStore.DAL
{
    public class StoreContext:DbContext
    {
        public DbSet<Product> Products { get; set; }
        public DbSet<Category> Categories { get; set; }
        public DbSet<ProductImage> ProductImages { get; set; }
        public DbSet<ProductImageMapping> ProductImageMappings { get; set; }
        public DbSet<BasketLine> BasketLines { get; set; }
        public DbSet<Order> Orders { get; set; }
        public DbSet<OrderLine> OrderLines { get; set; }
    }
}

Creating Sample Order Data and Updating the Database

Before we proceed with updating the database for the new Order and OrderLines entities, we’re going to add some extra code to the Seed method of the MigrationsStoreConfiguration.cs file to create some sample Order and OrderLines data, as follows:

protected override void Seed(BabyStore.DAL.StoreContext context)
{
...previous code omitted for brevity...
    var orders = new List<Order>
    {
        new Order { DeliveryAddress = new Address { AddressLine1="1 Some Street",
            Town="Town1", County="County", Postcode="PostCode" }, TotalPrice=4.99M,
            UserID="[email protected]", DateCreated=new DateTime(2014, 1, 1) ,
            DeliveryName="Admin" },
        new Order { DeliveryAddress = new Address { AddressLine1="1 Some Street",
            Town="Town1", County="County", Postcode="PostCode" }, TotalPrice=2.99M,
            UserID="[email protected]", DateCreated=new DateTime(2014, 1, 2) ,
            DeliveryName="Admin" },
        new Order { DeliveryAddress = new Address { AddressLine1="1 Some Street",
            Town="Town1", County="County", Postcode="PostCode" }, TotalPrice=1.99M,
            UserID="[email protected]", DateCreated=new DateTime(2014, 1, 3) ,
            DeliveryName="Admin" },
        new Order { DeliveryAddress = new Address { AddressLine1="1 Some Street",
            Town="Town1", County="County", Postcode="PostCode" }, TotalPrice=24.99M,
            UserID="[email protected]", DateCreated=new DateTime(2014, 1, 4) ,
            DeliveryName="Admin" },
        new Order { DeliveryAddress = new Address { AddressLine1="1 Some Street",
            Town="Town1", County="County", Postcode="PostCode" }, TotalPrice=8.99M,
            UserID="[email protected]", DateCreated=new DateTime(2014, 1, 5) ,
            DeliveryName="Admin" }
    };


    orders.ForEach(c => context.Orders.AddOrUpdate(o => o.DateCreated, c));
    context.SaveChanges();


    var orderLines = new List<OrderLine>
    {
        new OrderLine { OrderID = 1, ProductID = products.Single( c=> c.Name == "Sleep
            Suit").ID, ProductName="Sleep Suit", Quantity=1, UnitPrice=products.Single( c=>
            c.Name == "Sleep Suit").Price },
        new OrderLine { OrderID = 2, ProductID = products.Single( c=> c.Name == "Vest").ID,
            ProductName="Vest", Quantity=1, UnitPrice=products.Single( c=> c.Name ==
           "Vest").Price },
        new OrderLine { OrderID = 3, ProductID = products.Single( c=> c.Name == "Orange and
            Yellow Lion").ID, ProductName="Orange and Yellow Lion", Quantity=1,
            UnitPrice=products.Single( c=> c.Name == "Orange and Yellow Lion").Price },
        new OrderLine { OrderID = 4, ProductID = products.Single( c=> c.Name == "3 Pack of
            Bottles").ID, ProductName="3 Pack of Bottles", Quantity=1,
            UnitPrice=products.Single( c=> c.Name == "3 Pack of Bottles").Price },
        new OrderLine { OrderID = 5, ProductID = products.Single( c=> c.Name == "3 Pack of
            Bibs").ID, ProductName="3 Pack of Bibs", Quantity=1, UnitPrice=products.Single(  
            c=> c.Name == "3 Pack of Bibs").Price }
    };


    orderLines.ForEach(c => context.OrderLines.AddOrUpdate(ol => ol.OrderID, c));
    context.SaveChanges();
}

Ensure that you add the statement using System; to the top of the file. Here the DateCreated property is used to distinguish which orders to update and the OrderID property is used to decide which OrderLines to update. These properties are used because the dates are unique and in the past; therefore, the Seed method won't get any conflicts with new orders and we know that the OrderID starts from 1 in sequence and is the easiest way to ensure we don't get any conflicts when updating the OrderLines. Using other properties such as ProductID or UnitPrice will cause an error informing you that more than one item exists in the sequence because the Seed method will find duplicate records if additional orders have been placed by web site users.

Next, open the Package Manager Console and run the following commands to create a new migration named addorders. Update the database to add new tables for Orders and OrderLines:

add-migration addorders -Configuration StoreConfiguration
update-database -Configuration StoreConfiguration

Open Server Explorer and expand the StoreContext connection. You should be able to see new tables for Orders and OrderLines, as shown in Figure 9-1.

A419071_1_En_9_Fig1_HTML.jpg
Figure 9-1. The new Orders and OrderLines tables expanded in Server Explorer

The Orders table contains fields prefixed with DeliveryAddress to represent the delivery address fields, because the Order class contains a DeliveryAddress property composed of the Address type.

Next, view the data of both the tables. You should see that the sample data entered by the Seed method of the MigrationsStoreConfiguration.cs file is now stored in the OrderLines and Orders tables, as shown in Figure 9-2 and 9-3, respectively.

A419071_1_En_9_Fig2_HTML.jpg
Figure 9-2. The sample data in the OrderLines table
A419071_1_En_9_Fig3_HTML.jpg
Figure 9-3. The sample data in the Orders table

Displaying Order Data

Adding an OrdersController Class

In order to start managing the order data, you need to add a new controller called OrdersController to the Controllers folder. You do this using the scaffolding option MVC5 Controller with Views, Using Entity Framework, based on the Order model class and using the StoreContext as the data context class and generate views. Figure 9-4 shows the options to choose.

A419071_1_En_9_Fig4_HTML.jpg
Figure 9-4. Adding a new OrderController class with views

In the new controller, delete the Edit and Delete methods, as they won’t be used in this project.

Displaying a List of Orders

To make an order or view orders, it is necessary to be logged in. First, annotate the OrdersController class with [Authorize] as shown:

[Authorize]                
public class OrdersController : Controller
{

This will ensure that a user must be logged in to use the methods of the OrdersController class. Next, to display a list of orders, update the Index method as follows in order to control what it shown to the user. When an admin user is logged in, they can see every order, but when a normal user is logged in, then they can only see their own orders:

// GET: Orders
public ActionResult Index()
{
    if (User.IsInRole("Admin"))
    {
        return View(db.Orders.ToList());
    }
    else
    {
        return View(db.Orders.Where(o => o.UserID == User.Identity.Name));
    }
}

Now modify the auto-generated Views/Orders.Index.cshtml file so that the heading is updated and the OrderID is shown. Ensure that the links to create, edit, and delete orders are removed:

@model IEnumerable<BabyStore.Models.Order>

@{
    ViewBag.Title = "Orders";
}


<h2>@ViewBag.Title</h2>

<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.OrderID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UserID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TotalPrice)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DateCreated)
        </th>
        <th></th>
    </tr>


@foreach (var item in Model) {
    <tr>
        <td>
            @Html.DisplayFor(modelItem => item.OrderID)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.UserID)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.DeliveryName)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.DeliveryAddress)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.TotalPrice)
        </td>
        <td>
            @Html.DisplayFor(modelItem => item.DateCreated)
        </td>
        <td>
            @Html.ActionLink("Details", "Details", new { id=item.OrderID })
        </td>
    </tr>
}
</table>
Note

This file uses @Html.DisplayFor(modelItem => item.DeliveryAddress) to display the details of the delivery address for the order. This in turn uses the file ViewsSharedDisplayTemplatesAddress.cshtml created earlier in the project.

To see the new Index method of the OrdersController class and the updated Orders index view in action, start the web site without debugging and enter the URL /Orders (e.g., http://localhost:58735/Orders) to access the new Orders index page. You will be prompted to log in because of the [Authorize] attribute added to the OrdersController class, so log in as the user [email protected] using the password [email protected] .

You should now see the new Orders index HTML pages showing all the orders in the system (i.e., the ones that we added using the Seed method when updating the database), as shown in Figure 9-5.

A419071_1_En_9_Fig5_HTML.jpg
Figure 9-5. The list of orders in the system as viewed by an admin user

Now log in as the user [email protected] using the password P@ssw0rd and access the Orders index page again. This time you will not see any orders because this user has not yet placed any and this user cannot see any of the other orders in the system.

Displaying Order Details

To display the order details, first update the Details method of the OrdersController class. This modification is necessary to ensure that a user cannot view another user’s orders. The code will only allow users to view the detail of an order if either they are an admin user or they are the user who the order is for. It also includes the OrderLines property in the order prior to passing it to the view using eager loading:

// GET: Orders/Details/5
public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Order order = db.Orders.Include(o => o.OrderLines).Where(o => o.OrderID ==
        id).SingleOrDefault();


    if (order == null)
    {
        return HttpNotFound();
    }


    if (order.UserID == User.Identity.Name || User.IsInRole("Admin"))
    {
        return View(order);
    }
    else
    {
        return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
    }
}

Next, update the ViewsOrdersDetails.cshtml file to display the OrderLines related to each order, display the order's ID and the total price at the top of the order's details, and then remove the link to edit the order as follows:

@model BabyStore.Models.Order

@{
    ViewBag.Title = "Order Details";
}


<h2>@ViewBag.Title</h2>

<hr />
<div class="row">
    <div class="col-md-8">
        <h4>Items(s)</h4>
    </div>
    <div class="col-md-2">
        <h4>Quantity</h4>
    </div>
    <div class="col-md-2">
        <h4>Unit Price</h4>
    </div>
</div>
@foreach (var item in Model.OrderLines)
{
    <div class="row">
        <div class="col-md-8">@Html.DisplayFor(pn => item.ProductName)</div>
        <div class="col-md-2">@Html.DisplayFor(q => item.Quantity)</div>
        <div class="col-md-2">@Html.DisplayFor(up => item.UnitPrice)</div>
    </div>
}
<hr />
<div>
    <dl class="dl-horizontal">
        <dt>
            @Html.DisplayNameFor(model => model.TotalPrice)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.TotalPrice)
        </dd>


        <dt>
            @Html.DisplayNameFor(model => model.OrderID)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.OrderID)
        </dd>


        <dt>
            @Html.DisplayNameFor(model => model.UserID)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.UserID)
        </dd>


        <dt>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.DeliveryName)
        </dd>


        <dt>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.DeliveryAddress)
        </dd>


        <dt>
            @Html.DisplayNameFor(model => model.DateCreated)
        </dt>


        <dd>
            @Html.DisplayFor(model => model.DateCreated)
        </dd>


    </dl>
</div>
<p>
    @Html.ActionLink("Back to List", "Index")
</p>

Start the web site without debugging, then enter the URL /Orders and log in as the user [email protected] using the password [email protected]. Click on the Details link for the order with the order ID of 1. You should now see the details of the order, as shown in Figure 9-6.

A419071_1_En_9_Fig6_HTML.jpg
Figure 9-6. The updated Order Details page showing the order with the ID of 1

If you now log in as one of the users who is not in the admin role and enter the URL /Orders/Details/1, then you will be prompted to log in. Unless you then log in as an admin user, you will be continuously returned to the login page and will not be able to see the order details.

Placing an Order

We are going to create a simple ordering process that allows users to review their orders prior to submitting them. During this process, users can edit the delivery address details.

Creating an Order for Review

First obtain a user manager in the OrdersController class using the same code you used in previous chapters as shown in bold. We are using this code to access user information when creating first creating an order to be reviewed:

using System;                  
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using BabyStore.DAL;
using BabyStore.Models;
using Microsoft.AspNet.Identity.Owin;


namespace BabyStore.Controllers
{


    [Authorize]
    public class OrdersController : Controller
    {
        private StoreContext db = new StoreContext();


        private ApplicationUserManager _userManager;

        public ApplicationUserManager UserManager
        {
            get
            {
                return _userManager ??
                   HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
            }
            private set
            {
                _userManager = value;
            }
        }
...following code omitted for brevity...

Next, rename the GET version of the Create method in the OrdersController class to Review and update it as shown to obtain the current basket details and transfer them to a new Order object.

// GET: Orders/Review
public async Task<ActionResult> Review()
{
    Basket basket = Basket.GetBasket();
    Order order = new Order();


    order.UserID = User.Identity.Name;
    ApplicationUser user = await UserManager.FindByNameAsync(order.UserID);
    order.DeliveryName = user.FirstName + " " + user.LastName;
    order.DeliveryAddress = user.Address;
    order.OrderLines = new List<OrderLine>();
    foreach (var basketLine in basket.GetBasketLines())
    {
        OrderLine line = new OrderLine { Product = basketLine.Product, ProductID =
            basketLine.ProductID, ProductName = basketLine.Product.Name, Quantity =
            basketLine.Quantity, UnitPrice = basketLine.Product.Price };
        order.OrderLines.Add(line);
    }
    order.TotalPrice = basket.GetTotalCost();
    return View(order);
}

Ensure you add the using statement using System.Threading.Tasks; to the top of the file. We changed the method to be asynchronous in order to use the UserManager's FindByNameAsync method. The method then gets the current basket and creates a new order, then finds the current user and assigns their details to the order, and finally adds all the basketItems to the order. The order is then passed to the view so that that the user can review it and edit it or confirm and submit it.

Displaying an Order for Review

First rename the Views/Orders/Create.cshtml file to Review.cshtml. Next, update the file with the changes shown in bold in the following code:

@model BabyStore.Models.Order

@{
    ViewBag.Title = "Review Your Order";
}


<h2>@ViewBag.Title</h2>

@using (Html.BeginForm("Create", "Orders"))
{
    @Html.AntiForgeryToken()


    <div class="form-horizontal">
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        <div class="row">
            <div class="col-md-2"><label>Item</label></div>
            <div class="col-md-2"><label>Quantity</label></div>
            <div class="col-md-2"><label>Unit Price</label></div>
        </div>


        @foreach (var item in Model.OrderLines)
        {
            <div class="row">
                <div class="col-md-2">@Html.DisplayFor(modelItem => item.Product.Name)</div>
                <div class="col-md-2">@Html.DisplayFor(modelItem => item.Quantity)</div>
                <div class="col-md-2">@Html.DisplayFor(modelItem => item.UnitPrice)</div>
            </div>
        }


        <div class="form-group">
            @Html.LabelFor(model => model.TotalPrice, htmlAttributes: new { @class = "control-
                label col-md-2" })
            <div class="col-md-10 form-control-static">
                @Html.DisplayFor(model => Model.TotalPrice)
                @Html.HiddenFor(model => Model.TotalPrice)
            </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => model.UserID, htmlAttributes: new { @class = "control-
                labelcol-md-2" })
            <div class="col-md-10 form-control-static">
                @Html.DisplayFor(model => Model.UserID)
                @Html.HiddenFor(model => Model.UserID)
            </div>
        </div>


        <div class="form-group">
            @Html.LabelFor(model => model.DeliveryName, htmlAttributes: new { @class =
                "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.DeliveryName, new { htmlAttributes = new {
                    @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.DeliveryName, "", new { @class =
                    "text-danger" })
            </div>
        </div>


        @Html.EditorFor(model => model.DeliveryAddress)

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}


<div>
    @Html.ActionLink("Edit Basket", "Index", "Basket")
</div>


@section Scripts {
    @Scripts.Render("∼/bundles/jqueryval")
}

The view has been updated so that it now displays details of each order line and the details of the user, including allowing the delivery name and address to be edited if required. The form has been updated so that it targets the Create action method in the OrdersController class rather than the Review method.

To see how the new code works, we first need a way to allow users to move from the basket to the Orders Review view so add a new button to the ViewsBasketIndex.cshtml file to target the Review method of the OrdersController, as follows:

@model BabyStore.ViewModels.BasketViewModel

@{
    ViewBag.Title = "Your Basket";
}


<h2>@ViewBag.Title</h2>
@if (Model.BasketLines.Count() > 0)
{
    <div>
        @using (Html.BeginForm("UpdateBasket", "Basket"))
        {
            @Html.AntiForgeryToken();
            <input class="btn btn-sm btn-success" type="submit" value="Update Basket" />
            <hr />
            <div class="row">
                <div class="col-md-4"><label>Item</label></div>
                <div class="col-md-3"><label>Quantity</label></div>
                <div class="col-md-1"><label>Price</label></div>
                <div class="col-md-1"><label>Subtotal</label></div>
            </div>
            <hr />
            for (int i = 0; i < Model.BasketLines.Count; i++)
            {
            <div class="row">
                <div class="col-md-4">
                    @Html.ActionLink(Model.BasketLines[i].Product.Name, "Details", "Products",
                        new { id = Model.BasketLines[i].ProductID }, null)<br />
                    @if (Model.BasketLines[i].Product.ProductImageMappings != null &&
                        Model.BasketLines[i].Product.ProductImageMappings.Any())
                    {
                        <a href="@Url.Action("Details", "Products", new { id =
                            Model.BasketLines[i].ProductID })">
                            <img src="@(Url.Content(Constants.ProductThumbnailPath) +
                                Model.BasketLines[i].Product.ProductImageMappings.OrderBy(pim
                                => pim.ImageNumber).ElementAt(0).ProductImage.FileName)">
                        </a>
                    }
                </div>
                <div class="col-md-3">
                    @Html.HiddenFor(productID => Model.BasketLines[i].ProductID)
                    @Html.TextBoxFor(quantity => Model.BasketLines[i].Quantity)
                    <p>
                        @Html.ValidationMessageFor(quantity =>
                            Model.BasketLines[i].Quantity,"", new { @class = "text-danger" })
                    </p>
                </div>
                <div class="col-md-1">
                    @Html.DisplayFor(price => Model.BasketLines[i].Product.Price)
                </div>
                <div class="col-md-1">
                    @((Model.BasketLines[i].Quantity *
                        Model.BasketLines[i].Product.Price).ToString("c"))
                </div>
                <div class="col-md-1">
                    @Html.ActionLink("Remove", "RemoveLine", "Basket", new { id =
                        Model.BasketLines[i].Product.ID }, null)
                </div>
            </div>
            <hr />
            }
        }
        <div class="row">
            <div class="col-md-8">
                @Html.DisplayNameFor(model => model.TotalCost)
            </div>
            <div class="col-md-1">
                @Html.DisplayFor(model => model.TotalCost)
            </div>
            <div class="col-md-1">
                @Html.ActionLink("Order Now", "Review", "Orders", null, new { @class = "btn
                    btn-sm btn-success" })
            </div>
        </div>
    </div>
}
else
{
    <p>Your Basket is empty</p>
}
<div>
    @Html.ActionLink("Continue Shopping", "Index", "Products")
</div>


@section Scripts {
    @Scripts.Render("∼/bundles/jqueryval")
}

Now start the web site without debugging and add some items to the basket. Then in the basket, click the new Order Now button, as shown in Figure 9-7.

A419071_1_En_9_Fig7_HTML.jpg
Figure 9-7. The basket updated with the Order Now button

After clicking the Order Now button and after logging in, you will be directed to review the order via the Orders Review page generated by the updated ViewsOrdersReview.cshtml file, as shown in Figure 9-8. This allows you to edit the delivery details of the order, including the delivery name and the delivery address, plus you can return to the basket to edit its content.

A419071_1_En_9_Fig8_HTML.jpg
Figure 9-8. Reviewing an Order

Saving an Order to the Database

We are going to treat saving an order to the database as a two-step process, allowing the OrdersController class to save the order and then we are going to create the related OrderLines using the Basket class. We have taken this approach because we cannot simply add the Orders and OrderLines to the database using the OrdersController. We use the Basket class to obtain the data for OrderLines via the GetBasketLines method. Attempting to then save OrderLines to the database using Entity Framework in the BasketController results in an error because the BasketLines entity will then be tracked by two contexts.

Add a new method to the ModelsBasket.cs file to create a list of OrderLines and then save them to the database as follows:

public decimal CreateOrderLines(int orderID)
{
    decimal orderTotal = 0;


    var basketLines = GetBasketLines();

    foreach (var item in basketLines)
    {
        OrderLine orderLine = new OrderLine
        {
            Product = item.Product,
            ProductID = item.ProductID,
            ProductName = item.Product.Name,
            Quantity = item.Quantity,
            UnitPrice = item.Product.Price,
            OrderID = orderID
        };


        orderTotal += (item.Quantity * item.Product.Price);
        db.OrderLines.Add(orderLine);
    }


    db.SaveChanges();
    EmptyBasket();
    return orderTotal;
}

The method takes an integer as an input parameter and this will be the ID of the order associated with the OrderLines to be saved.

Next update the HttpPost version of the Create method in the ControllersOrdersController.cs file, as shown in the following code to save the order passed from the Review page. The DateCreated property of the order is updated to the current time and the UserID, DeliveryName, and DeliveryAddress are all taken from the Review page. The order is then saved and the CreateOrderLines method of the Basket class is called to save the related OrderLines and set the total price of the order. Once this is saved, the user is redirected to the Details action method, which will lead to them seeing the details of the placed order.

// POST: Orders/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "UserID,DeliveryName,DeliveryAddress")] Order order)
{
    if (ModelState.IsValid)
    {
        order.DateCreated = DateTime.Now;
        db.Orders.Add(order);
        db.SaveChanges();


        //add the orderlines to the database after creating the order
        Basket basket = Basket.GetBasket();
        order.TotalPrice = basket.CreateOrderLines(order.OrderID);
        db.SaveChanges();
        return RedirectToAction("Details", new { id = order.OrderID });
    }
    return RedirectToAction("Review");
}

To see this code in action, start the web site without debugging and log in as [email protected] with the password P@ssw0rd1. Add items to the shopping basket as shown in Figure 9-7 if they are not already in the basket. Click on the Order Now button and change the Deliver to Name in the review screen to test user. Click on the Create button. You should now see a new order created and the order details screen returned, as shown in Figure 9-9.

A419071_1_En_9_Fig9_HTML.jpg
Figure 9-9. A completed order displayed in the Order Details page

If you now open the Orders database table via Server Explorer, you will see the new order created, as shown in Figure 9-10.

A419071_1_En_9_Fig10_HTML.jpg
Figure 9-10. The orders database table with the new order highlighted

The OrderLines table also now contains the lines associated with the order, as shown in Figure 9-11. Note that the OrderID has the same value as the OrderID of the new order; in this example, this is 22.

A419071_1_En_9_Fig11_HTML.jpg
Figure 9-11. The OrderLines entries related to the new order

Note that we didn’t attempt to perform any complex model binding from the review screen to the Create method of the OrdersController because of the complexity of the model involved. As well as attempting to bind to a set of OrderLines, we would have also had to ensure that the product data was correctly modeled in the Review view and this would have been very complex to undertake. In this scenario, it is much simpler to revisit the database and retrieve the relevant information again.

Updating Product Deletion to Avoid Foreign Key Conflicts

We have a foreign key ProductID in the OrderLines entity referencing the Product entity. When a user deletes a product, we do not want the OrderLines to be deleted, so we created this as a nullable foreign key. To prevent a foreign key violation when a product is deleted, we need to set this foreign key to be null. As an alternative, we could have recorded the product ID but not as a foreign key; however, we’ve chosen to record the product’s name instead in the ProductName property of the OrderLines entity. In a production system, you probably would not allow deletion of products and may simply mark them as no longer available.

To prevent the foreign key violation, update the ControllersProductController.cs DeleteConfirmed method as follows:

// POST: Products/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
    Product product = db.Products.Find(id);
    db.Products.Remove(product);


    var orderLines = db.OrderLines.Where(ol => ol.ProductID == id);
    foreach (var ol in orderLines)
    {
        ol.ProductID = null;
    }


    db.SaveChanges();
    return RedirectToAction("Index");
}

Adding Links to the Orders Index View

At the moment, there is no link to the Order index page. Update the ViewsManageIndex.cshtml file to add a link to the bottom of the page so a user can view their orders as shown in bold:

@model BabyStore.Models.ApplicationUser                

@{
    ViewBag.Title = "My Details";
}


<h2>@ViewBag.Title</h2>

<div>
    <hr />
    <dl class="dl-horizontal">
        @Html.Partial("_UserDetailsPartial", Model)
        <dt>
            Password
        </dt>
        <dd>
            @Html.ActionLink("Change your password", "ChangePassword")
        </dd>
    </dl>
</div>


<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.Id })
</p>
<h2>@Html.ActionLink("View my orders", "Index", "Orders")</h2>

Next, add a link to the Admin index view file called ViewsAdminIndex so that the admin user can view all orders in the system from this view:

@{
    ViewBag.Title = "Admin";
}


<h2>@ViewBag.Title</h2>
<div class="container">
    <div class="row">
        @Html.ActionLink("Manage Images", "Index", "ProductImages")
    </div>
    <div class="row">
        @Html.ActionLink("Manage Roles", "Index", "RolesAdmin")
    </div>
    <div class="row">
        @Html.ActionLink("Manage Users", "Index", "UsersAdmin")
    </div>
    <div class="row">
        @Html.ActionLink("View all Orders", "Index", "Orders")
    </div>
</div>

Searching and Sorting Orders

So far, we have an orders page where users or admins can view orders, which is a good start. However, we may end up with a site with thousands of orders, so we need to filter and sort the orders. First, we will add a text search feature that will search all the text fields and the ID of an order. We will also include the products of the order in the search so that an order can be located when searching for a product.

Orders Text Searching

To add text search to orders, start by modifying the Index method of the ControllersOrdersController.cs file as follows:

// GET: Orders
public ActionResult Index(string orderSearch)
{
    var orders = db.Orders.OrderBy(o => o.DateCreated).Include(o => o.OrderLines);


    if (!User.IsInRole("Admin"))
    {
        orders = orders.Where(o => o.UserID == User.Identity.Name);
    }


    if (!String.IsNullOrEmpty(orderSearch))
    {
        orders = orders.Where(o => o.OrderID.ToString().Equals(orderSearch) ||
            o.UserID.Contains(orderSearch) || o.DeliveryName.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine1.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine2.Contains(orderSearch) ||
            o.DeliveryAddress.Town.Contains(orderSearch) ||
            o.DeliveryAddress.County.Contains(orderSearch) ||
            o.DeliveryAddress.Postcode.Contains(orderSearch) ||
            o.TotalPrice.ToString().Equals(orderSearch) ||
            o.OrderLines.Any(ol => ol.ProductName.Contains(orderSearch)));
    }


    return View(orders);
}

We have replaced the entire contents of the method to retrieve all the orders from the database and filter them to the current user if required. We then perform a search over many fields within the order, including the OrderID, DeliveryName, all the DeliveryAddress fields, and the TotalPrice field. Finally we have allowed the user to search within the name of each product within each OrderLines by using the Any operator. We used eager loading at the beginning of the method when obtaining the orders by using the code Include(o => o.OrderLines) in order to allow searching against the OrderLines table.

Now add an HTML form to target this index method by modifying the ViewsOrdersIndex.cshtml page as follows:

@model IEnumerable<BabyStore.Models.Order>

@{
    ViewBag.Title = "Orders";
}


<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Index", "Orders", FormMethod.Get))
{
    <div class="row form-group">
        <div class="col-md-2">
            <label>Search Orders by ID or Text:</label>
        </div>
        <div class="col-md-3">
            @Html.TextBox("OrderSearch", null, new  {
                @class = "form-control",@placeholder = "Search Orders" })
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </div>
}
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.OrderID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UserID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TotalPrice)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DateCreated)
        </th>
        <th></th>
    </tr>


    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.OrderID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UserID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryAddress)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.TotalPrice)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DateCreated)
            </td>
            <td>
                @Html.ActionLink("Details", "Details", new { id = item.OrderID })
            </td>
        </tr>
    }
</table>

This code generates an HTML form that uses GET to call the Index method of the OrdersController class. The new search form should appear as shown in Figure 9-12.

A419071_1_En_9_Fig12_HTML.jpg
Figure 9-12. The orders text search form added above the list of orders

Searching Orders by Date

To add date searching to the Orders page, first modify the Index method of the OrdersController class as follows to add a start and end date between which to search:

// GET: Orders
public ActionResult Index(string orderSearch, string startDate, string endDate)
{
    var orders = db.Orders.OrderBy(o => o.DateCreated).Include(o => o.OrderLines);


    if (!User.IsInRole("Admin"))
    {
        orders = orders.Where(o => o.UserID == User.Identity.Name);
    }


    if (!String.IsNullOrEmpty(orderSearch))
    {
        orders = orders.Where(o => o.OrderID.ToString().Equals(orderSearch) ||
            o.UserID.Contains(orderSearch) || o.DeliveryName.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine1.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine2.Contains(orderSearch) ||
            o.DeliveryAddress.Town.Contains(orderSearch) ||
            o.DeliveryAddress.County.Contains(orderSearch) ||
            o.DeliveryAddress.Postcode.Contains(orderSearch) ||
            o.TotalPrice.ToString().Equals(orderSearch) ||
            o.OrderLines.Any(ol => ol.ProductName.Contains(orderSearch)));
    }


    DateTime parsedStartDate;
    if (DateTime.TryParse(startDate, out parsedStartDate))
    {
        orders = orders.Where(o => o.DateCreated >= parsedStartDate);
    }


    DateTime parsedEndDate;
    if (DateTime.TryParse(endDate, out parsedEndDate))
    {
        orders = orders.Where(o => o.DateCreated <= parsedEndDate);
    }


    return View(orders);
}

The DateTime.TryParseDate method has been used to parse the string passed from the form into a date and then the code searches the CreatedDate property of an order to determine if it is between the two date parameters. Either parameter can be left blank so, for example, you can search for all orders after a particular date. If the string entered was not a date, the search will not be performed.

To add some HTML controls to the form to search by date, add the following code to the ViewsOrdersIndex.cshtml file in order to add two date HTML5 date picker controls. The value of each control is also set to the value used in the search by using the code value="@Request.QueryString["StartDate"]" and value="@Request.QueryString["EndDate"]".

@model IEnumerable<BabyStore.Models.Order>

@{
    ViewBag.Title = "Orders";
}


<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Index", "Orders", FormMethod.Get))
{
    <div class="row form-group">
        <div class="col-md-2">
            <label>Search Orders by ID or Text:</label>
        </div>
        <div class="col-md-3">
            @Html.TextBox("OrderSearch", null, new {@class = "form-control",
                @placeholder = "Search Orders" })
        </div>
        <div class="col-md-2">
            <label>Search between dates:</label>
        </div>
        <div class="col-md-2">
            <input type="date" id="StartDate" name="StartDate" class="form-control"
                   value="@Request.QueryString["StartDate"]" />
        </div>
        <div class="col-md-2">
            <input type="date" id="EndDate" name="EndDate" class="form-control"
                   value="@Request.QueryString["EndDate"]" />
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </div>


}
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.OrderID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.UserID)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.TotalPrice)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DateCreated)
        </th>
        <th></th>
    </tr>


    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.OrderID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UserID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryAddress)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.TotalPrice)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DateCreated)
            </td>
            <td>
                @Html.ActionLink("Details", "Details", new { id = item.OrderID })
            </td>
        </tr>
    }
</table>

If you view the Orders page using Google Chrome (my browser of choice throughout the book), then you should see the new date inputs, as shown in Figure 9-13, with the ability to pick a date or enter one by hand.

A419071_1_En_9_Fig13_HTML.jpg
Figure 9-13. The new date search viewed in Google Chrome
Note

Google Chrome offers HTML5 support for date picker style controls, but other browser implementations differ. Firefox does not include any kind of date pickers and instead just displays two text boxes, whereas Microsoft Edge (the latest Microsoft browser) offers a scroll style mobile list to allow you to choose a date.

To see the new search in action, perform a search between the dates 02/01/2014 and 03/01/2014. You should see the search results showing to two orders between these dates that we previously entered in the Seed method, as shown in Figure 9-14.

A419071_1_En_9_Fig14_HTML.jpg
Figure 9-14. Searching for orders between 02/01/2014 and 03/01/2014

Sorting Orders

Previously, when sorting on the products index page, we used a view model. This is best practice when modeling additional data in the view that does not belong in the business model for the entity. There is an alternative approach to use the ViewBag object for small amounts of data. We will use this approach in the following code to demonstrate how it can be used. In the orders page we will allow sorting by the user field, total price, and date created (time of order). It doesn’t make sense to allow sorting on the other properties since they can be searched for already. Modify the Index method of the OrdersController class as follows to allow sorting to take place:

// GET: Orders
public ActionResult Index(string orderSearch, string startDate, string endDate, string
    orderSortOrder)
{
    var orders = db.Orders.OrderBy(o => o.DateCreated).Include(o => o.OrderLines);


    if (!User.IsInRole("Admin"))
    {
        orders = orders.Where(o => o.UserID == User.Identity.Name);
    }


    if (!String.IsNullOrEmpty(orderSearch))
    {
        orders = orders.Where(o => o.OrderID.ToString().Equals(orderSearch) ||
            o.UserID.Contains(orderSearch) || o.DeliveryName.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine1.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine2.Contains(orderSearch) ||
            o.DeliveryAddress.Town.Contains(orderSearch) ||
            o.DeliveryAddress.County.Contains(orderSearch) ||
            o.DeliveryAddress.Postcode.Contains(orderSearch) ||  
            o.TotalPrice.ToString().Equals(orderSearch) ||
            o.OrderLines.Any(ol => ol.ProductName.Contains(orderSearch)));
    }


    DateTime parsedStartDate;
    if (DateTime.TryParse(startDate, out parsedStartDate))
    {
        orders = orders.Where(o => o.DateCreated >= parsedStartDate);
    }


    DateTime parsedEndDate;
    if (DateTime.TryParse(endDate, out parsedEndDate))
    {
        orders = orders.Where(o => o.DateCreated <= parsedEndDate);
    }


    ViewBag.DateSort = String.IsNullOrEmpty(orderSortOrder) ? "date" : "";
    ViewBag.UserSort = orderSortOrder == "user" ? "user_desc" : "user";
    ViewBag.PriceSort = orderSortOrder == "price" ? "price_desc" : "price";


    switch (orderSortOrder)
    {
        case "user":
            orders = orders.OrderBy(o => o.UserID);
            break;
        case "user_desc":
            orders = orders.OrderByDescending(o => o.UserID);
            break;
        case "price":
            orders = orders.OrderBy(o => o.TotalPrice);
            break;
        case "price_desc":
            orders = orders.OrderByDescending(o => o.TotalPrice);
            break;
        case "date":
            orders = orders.OrderBy(o => o.DateCreated);
            break;
        default:
            orders = orders.OrderByDescending(o => o.DateCreated);
            break;
    }


    return View(orders);
}

In this new code, the ViewBag object is used to store values for the current date sort order, user sort order, and price sort order. By default the normal ascending sort order is used and if this has been chosen by the user then the corresponding descending sort order is used.

The switch statement uses the orderSortOrder parameter to decide which sort order to use. The default sort order when no sorting is chosen by the user is to sort by showing the most recent orders first (date created descending). To add some sorting links, modify the ViewsOrdersIndex.cshtml file as follows:

@model IEnumerable<BabyStore.Models.Order>

@{
    ViewBag.Title = "Orders";
}


<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Index", "Orders", FormMethod.Get))
{
    <div class="row form-group">
        <div class="col-md-2">
            <label>Search Orders by ID or Text:</label>
        </div>
        <div class="col-md-3">
            @Html.TextBox("OrderSearch", null, new {@class = "form-control",
                 @placeholder = "Search Orders" })
        </div>
        <div class="col-md-2">
            <label>Search between dates:</label>
        </div>
        <div class="col-md-2">
            <input type="date" id="StartDate" name="StartDate" class="form-control"
                   value="@Request.QueryString["StartDate"]" />
        </div>
        <div class="col-md-2">
            <input type="date" id="EndDate" name="EndDate" class="form-control"
                   value="@Request.QueryString["EndDate"]" />
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </div>


}
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.OrderID)
        </th>
        <th>
            @Html.ActionLink("User", "Index", new { orderSortOrder = ViewBag.UserSort })
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </th>
        <th>
            @Html.ActionLink("Total Price", "Index", new { orderSortOrder = ViewBag.PriceSort
                })
        </th>
        <th>
            @Html.ActionLink("Time of Order", "Index", new { orderSortOrder = ViewBag.DateSort
                })
        </th>
        <th></th>
    </tr>


    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.OrderID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UserID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryAddress)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.TotalPrice)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DateCreated)
            </td>
            <td>
                @Html.ActionLink("Details", "Details", new { id = item.OrderID })
            </td>
        </tr>
    }
</table>

If you now start the web site and perform sorting on the Orders index page, the sorting will work as expected; however, if you run a search and then try sorting, the search term will be lost and all the orders will be returned. To fix this, we need to add some code to the Index method of the OrdersController class as follows. This will force the program to remember the search term and any date searches.

// GET: Orders
public ActionResult Index(string orderSearch, string startDate, string endDate, string
    orderSortOrder)
{
    var orders = db.Orders.OrderBy(o => o.DateCreated).Include(o => o.OrderLines);


    if (!User.IsInRole("Admin"))
    {
        orders = orders.Where(o => o.UserID == User.Identity.Name);
    }


    if (!String.IsNullOrEmpty(orderSearch))
    {
        orders = orders.Where(o => o.OrderID.ToString().Equals(orderSearch) ||
            o.UserID.Contains(orderSearch) || o.DeliveryName.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine1.Contains(orderSearch) ||
            o.DeliveryAddress.AddressLine2.Contains(orderSearch) ||
            o.DeliveryAddress.Town.Contains(orderSearch) ||
            o.DeliveryAddress.County.Contains(orderSearch) ||
            o.DeliveryAddress.Postcode.Contains(orderSearch) ||
            o.TotalPrice.ToString().Equals(orderSearch) ||
            o.OrderLines.Any(ol => ol.ProductName.Contains(orderSearch)));
    }


    DateTime parsedStartDate;
    if (DateTime.TryParse(startDate, out parsedStartDate))
    {
        orders = orders.Where(o => o.DateCreated >= parsedStartDate);
    }


    DateTime parsedEndDate;
    if (DateTime.TryParse(endDate, out parsedEndDate))
    {
        orders = orders.Where(o => o.DateCreated <= parsedEndDate);
    }


    ViewBag.DateSort = String.IsNullOrEmpty(orderSortOrder) ? "date" : "";
    ViewBag.UserSort = orderSortOrder == "user" ? "user_desc" : "user";
    ViewBag.PriceSort = orderSortOrder == "price" ? "price_desc" : "price";
    ViewBag.CurrentOrderSearch = orderSearch;
    ViewBag.StartDate = startDate;
    ViewBag.EndDate = endDate;


    switch (orderSortOrder)
    {
        case "user":
            orders = orders.OrderBy(o => o.UserID);
            break;
        case "user_desc":
            orders = orders.OrderByDescending(o => o.UserID);
            break;
        case "price":
            orders = orders.OrderBy(o => o.TotalPrice);
            break;
        case "price_desc":
            orders = orders.OrderByDescending(o => o.TotalPrice);
            break;
        case "date":
            orders = orders.OrderBy(o => o.DateCreated);
            break;
        default:
            orders = orders.OrderByDescending(o => o.DateCreated);
            break;
    }


    return View(orders);
}

Next modify the ViewsOrdersIndex.cshtml file to use these additional ViewBag entries and add them to each of the sorting links as follows:

@model IEnumerable<BabyStore.Models.Order>

@{
    ViewBag.Title = "Orders";
}


<h2>@ViewBag.Title</h2>
@using (Html.BeginForm("Index", "Orders", FormMethod.Get))
{
    <div class="row form-group">
        <div class="col-md-2">
            <label>Search Orders by ID or Text:</label>
        </div>
        <div class="col-md-3">
            @Html.TextBox("OrderSearch", null, new
       {
           @class = "form-control",
           @placeholder =
               "Search Orders"
       })
        </div>
        <div class="col-md-2">
            <label>Search between dates:</label>
        </div>
        <div class="col-md-2">
            <input type="date" id="StartDate" name="StartDate" class="form-control"
                   value="@Request.QueryString["StartDate"]" />
        </div>
        <div class="col-md-2">
            <input type="date" id="EndDate" name="EndDate" class="form-control"
                   value="@Request.QueryString["EndDate"]" />
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
    </div>


}
<table class="table">
    <tr>
        <th>
            @Html.DisplayNameFor(model => model.OrderID)
        </th>
        <th>
            @Html.ActionLink("User", "Index", new  {orderSortOrder = ViewBag.UserSort,
                orderSearch = ViewBag.CurrentOrderSearch,startdate = ViewBag.StartDate,
                endDate = ViewBag.EndDate })
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryName)
        </th>
        <th>
            @Html.DisplayNameFor(model => model.DeliveryAddress)
        </th>
        <th>
            @Html.ActionLink("Total Price", "Index", new { orderSortOrder = ViewBag.PriceSort, orderSearch = ViewBag.CurrentOrderSearch, startdate = ViewBag.StartDate, endDate = ViewBag.EndDate })
        </th>
        <th>
            @Html.ActionLink("Time of Order", "Index", new  {orderSortOrder =
                ViewBag.DateSort,orderSearch = ViewBag.CurrentOrderSearch,
                startdate = ViewBag.StartDate, endDate = ViewBag.EndDate })
        </th>
        <th></th>
    </tr>


    @foreach (var item in Model)
    {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.OrderID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.UserID)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DeliveryAddress)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.TotalPrice)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.DateCreated)
            </td>
            <td>
                @Html.ActionLink("Details", "Details", new { id = item.OrderID })
            </td>
        </tr>
    }
</table>

The search term and any date searches carried out will now be remembered when a user performs a sort of the orders.

Summary

This chapter covered how to model a checkout process by creating an order from a basket. You also saw how to display a list of orders, view order details, and search and sort through orders, including searching by date. We'll build on the code in this chapter in the next chapter when we look at paging through orders.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset