Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
oleh Jon Galloway
MVC Music Store adalah aplikasi tutorial yang memperkenalkan dan menjelaskan langkah demi langkah cara menggunakan ASP.NET MVC dan Visual Studio untuk pengembangan web.
MVC Music Store adalah implementasi penyimpanan sampel ringan yang menjual album musik secara online, dan mengimplementasikan administrasi situs dasar, rincian masuk pengguna, dan fungsionalitas ke cart belanja.
Seri tutorial ini merinci semua langkah yang diambil untuk membangun aplikasi sampel MVC Music Store ASP.NET. Bagian 8 mencakup Keranjang Belanja dengan Ajax Updates.
Kami akan mengizinkan pengguna untuk menempatkan album di ke cart mereka tanpa mendaftar, tetapi mereka harus mendaftar sebagai tamu untuk menyelesaikan checkout. Proses belanja dan checkout akan dipisahkan menjadi dua pengontrol: Pengontrol ShoppingCart yang memungkinkan penambahan item secara anonim ke keranjang, dan Pengontrol Checkout yang menangani proses checkout. Kita akan mulai dengan Shopping Cart di bagian ini, lalu membangun proses Checkout di bagian berikut.
Menambahkan kelas model Cart, Order, dan OrderDetail
Proses Shopping Cart and Checkout kami akan menggunakan beberapa kelas baru. Klik kanan folder Model dan tambahkan kelas Cart (Cart.cs) dengan kode berikut.
using System.ComponentModel.DataAnnotations;
namespace MvcMusicStore.Models
{
public class Cart
{
[Key]
public int RecordId { get; set; }
public string CartId { get; set; }
public int AlbumId { get; set; }
public int Count { get; set; }
public System.DateTime DateCreated { get; set; }
public virtual Album Album { get; set; }
}
}
Kelas ini sangat mirip dengan yang lain yang telah kami gunakan sejauh ini, dengan pengecualian atribut [Kunci] untuk properti RecordId. Item Kelir kami akan memiliki pengidentifikasi string bernama CartID untuk memungkinkan belanja anonim, tetapi tabel menyertakan kunci primer bilangan bulat bernama RecordId. Berdasarkan konvensi, Entity Framework Code-First mengharapkan bahwa kunci primer untuk tabel bernama Cart akan berupa CartId atau ID, tetapi kita dapat dengan mudah mengambil alihnya melalui anotasi atau kode jika kita mau. Ini adalah contoh bagaimana kita dapat menggunakan konvensi sederhana dalam Kerangka Kerja Entitas Code-First ketika sesuai dengan kita, tetapi kita tidak dibatasi oleh mereka ketika tidak.
Selanjutnya, tambahkan kelas Pesanan (Order.cs) dengan kode berikut.
using System.Collections.Generic;
namespace MvcMusicStore.Models
{
public partial class Order
{
public int OrderId { get; set; }
public string Username { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string State { get; set; }
public string PostalCode { get; set; }
public string Country { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public decimal Total { get; set; }
public System.DateTime OrderDate { get; set; }
public List<OrderDetail> OrderDetails { get; set; }
}
}
Kelas ini melacak ringkasan dan informasi pengiriman untuk pesanan. Ini belum akan dikompilasi, karena memiliki properti navigasi OrderDetails yang tergantung pada kelas yang belum kita buat. Mari kita perbaiki sekarang dengan menambahkan kelas bernama OrderDetail.cs, menambahkan kode berikut.
namespace MvcMusicStore.Models
{
public class OrderDetail
{
public int OrderDetailId { get; set; }
public int OrderId { get; set; }
public int AlbumId { get; set; }
public int Quantity { get; set; }
public decimal UnitPrice { get; set; }
public virtual Album Album { get; set; }
public virtual Order Order { get; set; }
}
}
Kami akan membuat satu pembaruan terakhir ke kelas MusicStoreEntities kami untuk menyertakan DbSets yang mengekspos kelas Model baru tersebut, juga termasuk Artis> DbSet<. Kelas MusicStoreEntities yang diperbarui muncul seperti di bawah ini.
using System.Data.Entity;
namespace MvcMusicStore.Models
{
public class MusicStoreEntities : DbContext
{
public DbSet<Album> Albums { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Artist> Artists {
get; set; }
public DbSet<Cart>
Carts { get; set; }
public DbSet<Order> Orders
{ get; set; }
public DbSet<OrderDetail>
OrderDetails { get; set; }
}
}
Mengelola logika bisnis Kedai Belanja
Selanjutnya, kita akan membuat kelas ShoppingCart di folder Model. Model ShoppingCart menangani akses data ke tabel Keranjang. Selain itu, ini akan menangani logika bisnis untuk menambahkan dan menghapus item dari ke cart belanja.
Karena kami tidak ingin mengharuskan pengguna untuk mendaftar ke akun hanya untuk menambahkan item ke ke cart belanja mereka, kami akan menetapkan pengidentifikasi unik sementara kepada pengguna (menggunakan GUID, atau pengidentifikasi unik global) saat mereka mengakses ke cart belanja. Kami akan menyimpan ID ini menggunakan kelas Sesi ASP.NET.
Catatan: Sesi ASP.NET adalah tempat yang nyaman untuk menyimpan informasi spesifik pengguna yang akan kedaluwarsa setelah mereka meninggalkan situs. Meskipun penyalahgunaan status sesi dapat memiliki implikasi performa pada situs yang lebih besar, penggunaan cahaya kami akan bekerja dengan baik untuk tujuan demonstrasi.
Kelas ShoppingCart mengekspos metode berikut:
AddToCart mengambil Album sebagai parameter dan menambahkannya ke keranjang pengguna. Karena tabel Keranjang melacak kuantitas untuk setiap album, itu termasuk logika untuk membuat baris baru jika diperlukan atau hanya menambah kuantitas jika pengguna telah memesan satu salinan album.
RemoveFromCart mengambil ID Album dan menghapusnya dari keranjang pengguna. Jika pengguna hanya memiliki satu salinan album di ke cart mereka, baris akan dihapus.
EmptyCart menghapus semua item dari keranjang belanja pengguna.
GetCartItems mengambil daftar CartItems untuk ditampilkan atau diproses.
GetCount mengambil jumlah total album yang dimiliki pengguna di keranjang belanja mereka.
GetTotal menghitung total biaya semua item dalam ke cart.
CreateOrder mengonversi ke cart belanja menjadi pesanan selama fase checkout.
GetCart adalah metode statis yang memungkinkan pengontrol kami untuk mendapatkan objek keranjang. Ini menggunakan metode GetCartId untuk menangani pembacaan CartId dari sesi pengguna. Metode GetCartId memerlukan HttpContextBase sehingga dapat membaca CartId pengguna dari sesi pengguna.
Berikut kelas ShoppingCart lengkapnya:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcMusicStore.Models
{
public partial class ShoppingCart
{
MusicStoreEntities storeDB = new MusicStoreEntities();
string ShoppingCartId { get; set; }
public const string CartSessionKey = "CartId";
public static ShoppingCart GetCart(HttpContextBase context)
{
var cart = new ShoppingCart();
cart.ShoppingCartId = cart.GetCartId(context);
return cart;
}
// Helper method to simplify shopping cart calls
public static ShoppingCart GetCart(Controller controller)
{
return GetCart(controller.HttpContext);
}
public void AddToCart(Album album)
{
// Get the matching cart and album instances
var cartItem = storeDB.Carts.SingleOrDefault(
c => c.CartId == ShoppingCartId
&& c.AlbumId == album.AlbumId);
if (cartItem == null)
{
// Create a new cart item if no cart item exists
cartItem = new Cart
{
AlbumId = album.AlbumId,
CartId = ShoppingCartId,
Count = 1,
DateCreated = DateTime.Now
};
storeDB.Carts.Add(cartItem);
}
else
{
// If the item does exist in the cart,
// then add one to the quantity
cartItem.Count++;
}
// Save changes
storeDB.SaveChanges();
}
public int RemoveFromCart(int id)
{
// Get the cart
var cartItem = storeDB.Carts.Single(
cart => cart.CartId == ShoppingCartId
&& cart.RecordId == id);
int itemCount = 0;
if (cartItem != null)
{
if (cartItem.Count > 1)
{
cartItem.Count--;
itemCount = cartItem.Count;
}
else
{
storeDB.Carts.Remove(cartItem);
}
// Save changes
storeDB.SaveChanges();
}
return itemCount;
}
public void EmptyCart()
{
var cartItems = storeDB.Carts.Where(
cart => cart.CartId == ShoppingCartId);
foreach (var cartItem in cartItems)
{
storeDB.Carts.Remove(cartItem);
}
// Save changes
storeDB.SaveChanges();
}
public List<Cart> GetCartItems()
{
return storeDB.Carts.Where(
cart => cart.CartId == ShoppingCartId).ToList();
}
public int GetCount()
{
// Get the count of each item in the cart and sum them up
int? count = (from cartItems in storeDB.Carts
where cartItems.CartId == ShoppingCartId
select (int?)cartItems.Count).Sum();
// Return 0 if all entries are null
return count ?? 0;
}
public decimal GetTotal()
{
// Multiply album price by count of that album to get
// the current price for each of those albums in the cart
// sum all album price totals to get the cart total
decimal? total = (from cartItems in storeDB.Carts
where cartItems.CartId == ShoppingCartId
select (int?)cartItems.Count *
cartItems.Album.Price).Sum();
return total ?? decimal.Zero;
}
public int CreateOrder(Order order)
{
decimal orderTotal = 0;
var cartItems = GetCartItems();
// Iterate over the items in the cart,
// adding the order details for each
foreach (var item in cartItems)
{
var orderDetail = new OrderDetail
{
AlbumId = item.AlbumId,
OrderId = order.OrderId,
UnitPrice = item.Album.Price,
Quantity = item.Count
};
// Set the order total of the shopping cart
orderTotal += (item.Count * item.Album.Price);
storeDB.OrderDetails.Add(orderDetail);
}
// Set the order's total to the orderTotal count
order.Total = orderTotal;
// Save the order
storeDB.SaveChanges();
// Empty the shopping cart
EmptyCart();
// Return the OrderId as the confirmation number
return order.OrderId;
}
// We're using HttpContextBase to allow access to cookies.
public string GetCartId(HttpContextBase context)
{
if (context.Session[CartSessionKey] == null)
{
if (!string.IsNullOrWhiteSpace(context.User.Identity.Name))
{
context.Session[CartSessionKey] =
context.User.Identity.Name;
}
else
{
// Generate a new random GUID using System.Guid class
Guid tempCartId = Guid.NewGuid();
// Send tempCartId back to client as a cookie
context.Session[CartSessionKey] = tempCartId.ToString();
}
}
return context.Session[CartSessionKey].ToString();
}
// When a user has logged in, migrate their shopping cart to
// be associated with their username
public void MigrateCart(string userName)
{
var shoppingCart = storeDB.Carts.Where(
c => c.CartId == ShoppingCartId);
foreach (Cart item in shoppingCart)
{
item.CartId = userName;
}
storeDB.SaveChanges();
}
}
}
ViewModels
Pengontrol Kelir Belanja kami perlu mengomunikasikan beberapa informasi kompleks ke tampilannya yang tidak dipetakan dengan bersih ke objek Model kami. Kami tidak ingin memodifikasi Model agar sesuai dengan pandangan kami; Kelas model harus mewakili domain kami, bukan antarmuka pengguna. Salah satu solusinya adalah meneruskan informasi ke Tampilan kami menggunakan kelas ViewBag, seperti yang kami lakukan dengan informasi dropdown Store Manager, tetapi meneruskan banyak informasi melalui ViewBag menjadi sulit dikelola.
Solusi untuk ini adalah menggunakan pola ViewModel . Saat menggunakan pola ini, kami membuat kelas dengan jenis kuat yang dioptimalkan untuk skenario tampilan spesifik kami, dan yang mengekspos properti untuk nilai/konten dinamis yang diperlukan oleh templat tampilan kami. Kelas pengontrol kami kemudian dapat mengisi dan meneruskan kelas yang dioptimalkan tampilan ini ke templat tampilan kami untuk digunakan. Ini memungkinkan keamanan jenis, pemeriksaan waktu kompilasi, dan editor IntelliSense dalam templat tampilan.
Kami akan membuat dua Model Tampilan untuk digunakan di pengontrol Keranjang Belanja kami: ShoppingCartViewModel akan menyimpan konten keranjang belanja pengguna, dan ShoppingCartRemoveViewModel akan digunakan untuk menampilkan informasi konfirmasi saat pengguna menghapus sesuatu dari keranjang mereka.
Mari kita buat folder ViewModels baru di akar proyek kita untuk menjaga semuanya tetap terorganisir. Klik kanan proyek, pilih Tambahkan/ Folder Baru.
Beri nama folder ViewModels.
Selanjutnya, tambahkan kelas ShoppingCartViewModel di folder ViewModels. Ini memiliki dua properti: daftar item Ke cart, dan nilai desimal untuk menahan harga total untuk semua item di keliru.
using System.Collections.Generic;
using MvcMusicStore.Models;
namespace MvcMusicStore.ViewModels
{
public class ShoppingCartViewModel
{
public List<Cart> CartItems { get; set; }
public decimal CartTotal { get; set; }
}
}
Sekarang tambahkan ShoppingCartRemoveViewModel ke folder ViewModels, dengan empat properti berikut.
namespace MvcMusicStore.ViewModels
{
public class ShoppingCartRemoveViewModel
{
public string Message { get; set; }
public decimal CartTotal { get; set; }
public int CartCount { get; set; }
public int ItemCount { get; set; }
public int DeleteId { get; set; }
}
}
Pengontrol Kelir Belanja
Pengontrol Ke cart Belanja memiliki tiga tujuan utama: menambahkan item ke keramaian, menghapus item dari ke cart, dan melihat item di ke cart. Ini akan menggunakan tiga kelas yang baru saja kita buat: ShoppingCartViewModel, ShoppingCartRemoveViewModel, dan ShoppingCart. Seperti di StoreController dan StoreManagerController, kita akan menambahkan bidang untuk menyimpan instans MusicStoreEntities.
Tambahkan pengontrol Shopping Cart baru ke proyek menggunakan templat Pengontrol kosong.
Berikut adalah Pengontrol ShoppingCart lengkap. Tindakan Indeks dan Tambahkan Pengontrol akan terlihat sangat akrab. Tindakan Pengontrol Remove dan CartSummary menangani dua kasus khusus, yang akan kita bahas di bagian berikut.
using System.Linq;
using System.Web.Mvc;
using MvcMusicStore.Models;
using MvcMusicStore.ViewModels;
namespace MvcMusicStore.Controllers
{
public class ShoppingCartController : Controller
{
MusicStoreEntities storeDB = new MusicStoreEntities();
//
// GET: /ShoppingCart/
public ActionResult Index()
{
var cart = ShoppingCart.GetCart(this.HttpContext);
// Set up our ViewModel
var viewModel = new ShoppingCartViewModel
{
CartItems = cart.GetCartItems(),
CartTotal = cart.GetTotal()
};
// Return the view
return View(viewModel);
}
//
// GET: /Store/AddToCart/5
public ActionResult AddToCart(int id)
{
// Retrieve the album from the database
var addedAlbum = storeDB.Albums
.Single(album => album.AlbumId == id);
// Add it to the shopping cart
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.AddToCart(addedAlbum);
// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
//
// AJAX: /ShoppingCart/RemoveFromCart/5
[HttpPost]
public ActionResult RemoveFromCart(int id)
{
// Remove the item from the cart
var cart = ShoppingCart.GetCart(this.HttpContext);
// Get the name of the album to display confirmation
string albumName = storeDB.Carts
.Single(item => item.RecordId == id).Album.Title;
// Remove from cart
int itemCount = cart.RemoveFromCart(id);
// Display the confirmation message
var results = new ShoppingCartRemoveViewModel
{
Message = Server.HtmlEncode(albumName) +
" has been removed from your shopping cart.",
CartTotal = cart.GetTotal(),
CartCount = cart.GetCount(),
ItemCount = itemCount,
DeleteId = id
};
return Json(results);
}
//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
public ActionResult CartSummary()
{
var cart = ShoppingCart.GetCart(this.HttpContext);
ViewData["CartCount"] = cart.GetCount();
return PartialView("CartSummary");
}
}
}
Ajax Updates dengan jQuery
Selanjutnya kita akan membuat halaman Indeks Keranjang Belanja yang sangat ditik ke ShoppingCartViewModel dan menggunakan templat Tampilan Daftar menggunakan metode yang sama seperti sebelumnya.
Namun, alih-alih menggunakan Html.ActionLink untuk menghapus item dari kemudi, kita akan menggunakan jQuery untuk "menyambungkan" peristiwa klik untuk semua tautan dalam tampilan ini yang memiliki kelas HTML RemoveLink. Daripada memposting formulir, penanganan aktivitas klik ini hanya akan membuat panggilan balik AJAX ke tindakan pengontrol RemoveFromCart kami. RemoveFromCart mengembalikan hasil serial JSON, yang kemudian diurai oleh panggilan balik jQuery kami dan melakukan empat pembaruan cepat ke halaman menggunakan jQuery:
-
- Menghapus album yang dihapus dari daftar
-
- Updates jumlah kelir di header
-
- Menampilkan pesan pembaruan kepada pengguna
-
- Updates harga total ke cart
Karena skenario hapus sedang ditangani oleh panggilan balik Ajax dalam tampilan Indeks, kami tidak memerlukan tampilan tambahan untuk tindakan RemoveFromCart. Berikut adalah kode lengkap untuk tampilan /ShoppingCart/Index:
@model MvcMusicStore.ViewModels.ShoppingCartViewModel
@{
ViewBag.Title = "Shopping Cart";
}
<script src="/Scripts/jquery-1.4.4.min.js"
type="text/javascript"></script>
<script type="text/javascript">
$(function () {
// Document.ready -> link up remove event handler
$(".RemoveLink").click(function () {
// Get the id from the link
var recordToDelete = $(this).attr("data-id");
if (recordToDelete != '') {
// Perform the ajax post
$.post("/ShoppingCart/RemoveFromCart", {"id": recordToDelete },
function (data) {
// Successful requests get here
// Update the page elements
if (data.ItemCount == 0) {
$('#row-' + data.DeleteId).fadeOut('slow');
} else {
$('#item-count-' + data.DeleteId).text(data.ItemCount);
}
$('#cart-total').text(data.CartTotal);
$('#update-message').text(data.Message);
$('#cart-status').text('Cart (' + data.CartCount + ')');
});
}
});
});
</script>
<h3>
<em>Review</em> your cart:
</h3>
<p class="button">
@Html.ActionLink("Checkout
>>", "AddressAndPayment", "Checkout")
</p>
<div id="update-message">
</div>
<table>
<tr>
<th>
Album Name
</th>
<th>
Price (each)
</th>
<th>
Quantity
</th>
<th></th>
</tr>
@foreach (var item in
Model.CartItems)
{
<tr id="row-@item.RecordId">
<td>
@Html.ActionLink(item.Album.Title,
"Details", "Store", new { id = item.AlbumId }, null)
</td>
<td>
@item.Album.Price
</td>
<td id="item-count-@item.RecordId">
@item.Count
</td>
<td>
<a href="#" class="RemoveLink"
data-id="@item.RecordId">Remove
from cart</a>
</td>
</tr>
}
<tr>
<td>
Total
</td>
<td>
</td>
<td>
</td>
<td id="cart-total">
@Model.CartTotal
</td>
</tr>
</table>
Untuk menguji hal ini, kita harus dapat menambahkan item ke keramaian belanja kita. Kami akan memperbarui tampilan Detail Toko kami untuk menyertakan tombol "Tambahkan ke ke cart". Saat berada di dalamnya, kami dapat menyertakan beberapa informasi tambahan Album yang telah kami tambahkan sejak terakhir kali memperbarui tampilan ini: Genre, Artis, Harga, dan Album Art. Kode tampilan Detail Penyimpanan yang diperbarui muncul seperti yang ditunjukkan di bawah ini.
@model MvcMusicStore.Models.Album
@{
ViewBag.Title = "Album - " + Model.Title;
}
<h2>@Model.Title</h2>
<p>
<img alt="@Model.Title"
src="@Model.AlbumArtUrl" />
</p>
<div id="album-details">
<p>
<em>Genre:</em>
@Model.Genre.Name
</p>
<p>
<em>Artist:</em>
@Model.Artist.Name
</p>
<p>
<em>Price:</em>
@String.Format("{0:F}",
Model.Price)
</p>
<p class="button">
@Html.ActionLink("Add to
cart", "AddToCart",
"ShoppingCart", new { id = Model.AlbumId }, "")
</p>
</div>
Sekarang kita dapat mengeklik toko dan menguji penambahan dan penghapusan Album ke dan dari ke cart belanja kami. Jalankan aplikasi dan telusuri ke Indeks Penyimpanan.
Selanjutnya, klik Genre untuk melihat daftar album.
Mengklik judul Album sekarang menampilkan tampilan Detail Album kami yang diperbarui, termasuk tombol "Tambahkan ke ke cart".
Mengeklik tombol "Tambahkan ke ke cart" menunjukkan tampilan Indeks Ke cart Belanja kami dengan daftar ringkasan ke cart belanja.
Setelah memuat ke cart belanja, Anda dapat mengeklik tautan Hapus dari ke cart untuk melihat pembaruan Ajax ke kedai belanja Anda.
Kami telah membangun keliling belanja yang berfungsi yang memungkinkan pengguna yang tidak terdaftar untuk menambahkan item ke keliling mereka. Di bagian berikut, kami akan mengizinkan mereka untuk mendaftar dan menyelesaikan proses checkout.