Magicshui's Blog

Magicshui on Programming && Life

MVC步步深入:10

当用户购物完成以后就需要进行付款了,但是我们前面说到了,我们允许没有登陆的用户购买,所以在付款的时候需要验证是否已经登陆。

如果登陆了就好,但是没有登陆就需要进行注册了。当用户注册以后,因为开始是匿名购买,所以需要合并订单到现在的用户中来:

private void MigrateShoppingCart(string UserName)
 {
    // Associate shopping cart items with logged-in user
    var cart = ShoppingCart.GetCart(this.HttpContext);

    cart.MigrateCart(UserName);
    Session[ShoppingCart.CartSessionKey] = UserName;
 }

下面是登录函数:

[HttpPost]
 public ActionResult LogOn(LogOnModel model, string returnUrl)
 {
    if (ModelState.IsValid)
    {
        if (MembershipService.ValidateUser(model.UserName, model.Password))
        {
            MigrateShoppingCart(model.UserName);

            FormsService.SignIn(model.UserName, model.RememberMe);
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
            else
            {
                return RedirectToAction("Index", "Home");
            }
        }
        else
        {
            ModelState.AddModelError("", "The user name or
password provided is incorrect.");
        }
    }
    // If we got this far, something failed, redisplay form
    return View(model);
 }

以及注册函数:

[HttpPost]
 public ActionResult Register(RegisterModel model)
 {
    if (ModelState.IsValid)
    {
        // Attempt to register the user
        MembershipCreateStatus createStatus =
MembershipService.CreateUser(model.UserName, model.Password, model.Email);

        if (createStatus == MembershipCreateStatus.Success)
        {
            MigrateShoppingCart(model.UserName);

            FormsService.SignIn(model.UserName, false /* createPersistentCookie
*/);
            return RedirectToAction("Index", "Home");
        }
        else
        {
            ModelState.AddModelError("", AccountValidation.ErrorCodeToString(createStatus));
        }
    }
    // If we got this far, something failed, redisplay form
    ViewBag.PasswordLength = MembershipService.MinPasswordLength;
    return View(model);
 }

紧接着的付款需要使用付款控制器,首先自然是创建一个啦:

using System;
 using System.Linq;
 using System.Web.Mvc;
 using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
    [Authorize]
    public class CheckoutController : Controller
    {
        MusicStoreEntities storeDB = new MusicStoreEntities();
        const string PromoCode = "FREE";

因为我们其实不是要实现真的付款过程,所以使用一个促销码来代替。

//
// GET: /Checkout/AddressAndPayment
public ActionResult AddressAndPayment()
{
    return View();
}
//
// POST: /Checkout/AddressAndPayment
[HttpPost]
 public ActionResult AddressAndPayment(FormCollection values)
 {
    var order = new Order();
    TryUpdateModel(order);

    try
    {
        if (string.Equals(values["PromoCode"], PromoCode,
            StringComparison.OrdinalIgnoreCase) == false)
        {
            return View(order);
        }
        else
        {
            order.Username = User.Identity.Name;
            order.OrderDate = DateTime.Now;

            //Save Order
            storeDB.AddToOrders(order);
            storeDB.SaveChanges();
            //Process the order
            var cart = ShoppingCart.GetCart(this.HttpContext);
            cart.CreateOrder(order);

            return RedirectToAction("Complete",
                new { id = order.OrderId });
        }
    }
    catch
    {
        //Invalid - redisplay with errors
        return View(order);
    }
}

当完成以后,我们就给个提示:

//
// GET: /Checkout/Complete
public ActionResult Complete(int id)
 {
    // Validate customer owns this order
    bool isValid = storeDB.Orders.Any(
        o => o.OrderId == id &&
        o.Username == User.Identity.Name);

    if (isValid)
    {
        return View(id);
    }
    else
    {
        return View("Error");
    }
}

最后就是添加视图,这个很简单,大家都会,不过因为需要验证,所以,我们复习下验证的特性:

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

namespace MvcMusicStore.Models
{
    [Bind(Exclude = "OrderId")]
    public partial class Order
    {
        [ScaffoldColumn(false)]
        public int OrderId { get; set; }
        [ScaffoldColumn(false)]
        public System.DateTime OrderDate { get; set; }
        [ScaffoldColumn(false)]
        public string Username { get; set; }
        [Required(ErrorMessage = "First Name is required")]
        [DisplayName("First Name")]
        [StringLength(160)]
        public string FirstName { get; set; }
        [Required(ErrorMessage = "Last Name is required")]
        [DisplayName("Last Name")]
        [StringLength(160)]
        public string LastName { get; set; }
        [Required(ErrorMessage = "Address is required")]
        [StringLength(70)]
        public string Address { get; set; }
        [Required(ErrorMessage = "City is required")]
        [StringLength(40)]
        public string City { get; set; }
        [Required(ErrorMessage = "State is required")]
        [StringLength(40)]
        public string State { get; set; }
        [Required(ErrorMessage = "Postal Code is required")]
        [DisplayName("Postal Code")]
        [StringLength(10)]
        public string PostalCode { get; set; }
        [Required(ErrorMessage = "Country is required")]
        [StringLength(40)]
        public string Country { get; set; }
        [Required(ErrorMessage = "Phone is required")]
        [StringLength(24)]
        public string Phone { get; set; }
        [Required(ErrorMessage = "Email Address is required")]
        [DisplayName("Email Address")]
        [RegularExpression(@"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}",
            ErrorMessage = "Email is is not valid.")]
        [DataType(DataType.EmailAddress)]
        public string Email { get; set; }
        [ScaffoldColumn(false)]
        public decimal Total { get; set; }
        public List OrderDetails { get; set; }
    }
}

最后就是更新一下错误提示页面:

@{
    ViewBag.Title = "Error";
}

Error

We're sorry, we've hit an unexpected error. Click here if you'd like to go back and try that again.

现在我们的音乐商店基本上已经完工了,最后还一步骤就是添加导航。

接下来,添加信息提示的局部页面:

12

//
// GET: /ShoppingCart/CartSummary
[ChildActionOnly]
 public ActionResult CartSummary()
{
    var cart = ShoppingCart.GetCart(this.HttpContext);

    ViewData["CartCount"] = cart.GetCount();
    return PartialView("CartSummary");
}

注意到这个ChildActionOnly没?他是必须的,这样页面才知道这一款相当于一个局部的模板。

修改前台:

@Html.ActionLink("Cart
(" + ViewData["CartCount"] + ")",
    "Index",
    "ShoppingCart",
    new { id = "cart-status" })

同时我们还可以通过

@Html.RenderAction("CartSummary", "ShoppingCart")在页面的其他地方来显示这个小局部页面。
接下来再创建菜单栏,如图:
1
/
// GET: /Store/GenreMenu
[ChildActionOnly]
 public ActionResult GenreMenu()
{
    var genres = storeDB.Genres.ToList();
    return PartialView(genres);
 }
然后更新总的模板:



    


    @{Html.RenderAction("GenreMenu",
"Store");}
@RenderBody()
更新浏览页面的模板:
@model MvcMusicStore.Models.Genre

@{
    ViewBag.Title = "Browse Albums";
}

@Model.Name Albums

21

下面我们再提出一个小小的要求,那就是置顶畅销的专辑。

更新一下Album类:

public virtual Genre  Genre                     { get; set;
}
        public virtual Artist Artist                    { get; set; }
        public virtual List
OrderDetails   { get; set; }
    }
}

然后对于HomeController做出如下的修改:

using System.Collections.Generic;
 using System.Linq;
 using System.Web.Mvc;
 using MvcMusicStore.Models;

namespace MvcMusicStore.Controllers
{
    public class HomeController : Controller
    {
        //
        // GET: /Home/
        MusicStoreEntities storeDB = new MusicStoreEntities();
        public ActionResult Index()
        {
            // Get most popular albums
            var albums = GetTopSellingAlbums(5);

            return View(albums);
        }
        private List GetTopSellingAlbums(int count)
        {
            // Group the order details by album and return
            // the albums with the highest count
            return storeDB.Albums
                .OrderByDescending(a => a.OrderDetails.Count())
                .Take(count)
                .ToList();
        }
    }
}

最后就是更新视图:让他能够正确显示这些专辑

@model List
@{
    ViewBag.Title = "ASP.NET MVC Music Store";
}

Fresh off the grill

11

这就是最后的成果,帅吧~呜哈哈,其实MVC的功能比这个更加强大~我们以后会慢慢说来,这算是第二个阶段结束了,新的“冒险”开始了~开船吧~

MVC步步深入:9

我们在购物的时候都是希望添加商品以后能够继续进行购物,完成以后一次付款,所以购物车的功能对于一个消费类网站是必要的。

下面我们添加这样的几个类:

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; }
    }
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 OrderDetails { get; set; }
    }
 public class MusicStoreEntities : DbContext
    {
        public DbSet     Albums  { get; set; }
        public DbSet     Genres  { get; set; }
        public DbSet    Artists { get; set; }
        public DbSet      Carts { get; set; }
        public DbSet     Orders { get; set; }
        public DbSet OrderDetails { get; set; }
    }
}

我们希望用户可以在不登陆的情况下依然可以选择购物车添加喜欢的东西,但是付款的时候就必须是注册用户了。对于购物车,我们将会露出以下的几个函数:AddToCart、RemoveFromCart、EmptyCart、GetCartItems、GetCount、GetTotal、CreateOrder、GetCart。具体代码如下:

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 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 orderDetails = 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);
            }
            // 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();
        }
    }
}

然后我们创建一些视图模型:

using System.Collections.Generic;
 using MvcMusicStore.Models;

namespace MvcMusicStore.ViewModels
{
    public class ShoppingCartViewModel
    {
        public List CartItems { get; set; }
        public decimal CartTotal { get; set; }
    }
}
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; }
    }
}

接下来就是创建控制器:

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.ActionLink("Remove from cart",
"RemoveFromCart",
    new { id = item.RecordId }, new AjaxOptions { OnSuccess = "handleUpdate"
})

然后完整的页面代码就是:

@model MvcMusicStore.ViewModels.ShoppingCartViewModel
@{
    ViewBag.Title = "Shopping Cart";
}


Review your cart:

@Html.ActionLink("Checkout >>", "AddressAndPayment", "Checkout")

@foreach (var item in Model.CartItems) { }
Album Name Price (each) Quantity
@Html.ActionLink(item.Album.Title, "Details", "Store", new { id = item.AlbumId }, null) @item.Album.Price @item.Count Remove from cart
Total     @Model.CartTotal

效果图:

12

MVC步步深入:8

求个解释,给个原型是本文的目的,其实就是说如何进行数据验证。因为我们使用的是EF来作为ORM在保存数据库之前需要进行一次数据验证保证合法性(在编辑器传数据的时候就貌似别验证了)。

在前边的文章中,我们看到标签什么的都是自动生成,他怎么生成的呢?

namespace MvcMusicStore.Models
{
    [Bind(Exclude = "AlbumId")]
    public class Album
    {
        [ScaffoldColumn(false)]
        public int      AlbumId    { get; set; }
        [DisplayName("Genre")]
        public int      GenreId    { get; set; }
        [DisplayName("Artist")]
        public int      ArtistId   { get; set; }
        [Required(ErrorMessage = "An Album Title is required")]
        [StringLength(160)]
        public string   Title      { get; set; }
        [Required(ErrorMessage = "Price is required")]
        [Range(0.01, 100.00,
            ErrorMessage = "Price must be between 0.01 and 100.00")]
        public decimal Price       { get; set; }
        [DisplayName("Album Art URL")]
        [StringLength(1024)]
        public string AlbumArtUrl { get; set; }
        public virtual Genre  Genre    { get; set; }
        public virtual Artist Artist   { get; set; }
    }
}

上边的代码就是对于属性的注释,这个是可以运行且具有真正约束力的东西。

12

是不是觉得有些东西似曾相识?原来是DisplayName这里边的东东现在显示在了页面上。

当然这些是服务器端验证,我们希望在客户端验证来减少时间、提高用户体验。这些都不需要修改代码,自动就会完成,天生的~

其实关于这些有更多的内容,将会在本系列的后期说道,对于EFCTP5的介绍~

因为篇幅太短了,就继续说下去,关于认证和用户类型的东西吧~

我们可定时希望自己的站点能够有用户注册,并且具有权限体系,而微软也想到了这一点,于是提供了一套预设方案。但是MVC3生成的时候没有选择,所以只能将2中的一些文件拷过来,如下:

4

通过下面的设置进行开启服务项:

4

进入了这个后台的页面,看图说话吧:

678910

然后我们进行基于角色的认证体系:

[Authorize(Roles = "Administrator")]
public class StoreManagerController : Controller
{
    // Controller code here
}

Authorize标签可以被使用到任意的controller级别类上。因此,当你访问的时候系统发现你没有登陆或是角色不对就会自动导向到注册页面上去。