zoukankan      html  css  js  c++  java
  • MVC编程实例----简易电子商务网站(一)

    一.总体概览、规划

      本文将会创建一个基本的电子商务网站。由于电子商务网站的基本功能都是差不多的,此处省去了需求分析等工作,直接总结出结论。分为4个基本功能:

    • 商品浏览
    • 会员功能
    • 购物车
    • 订单结账

      其中每项还可以细分,由于比较简单不再赘述。

    二.实现思路和步骤

      由于项目是非常简单的,这里并没有采用分层结构。MVC分别代表了三个部分,Model、View和Control。开发过程中创立对象时谁先谁后?在《ASP.NET MVC4开发指南》中(本文就是采用了该书中的实例),作者黄保翕这样阐述“以笔者的实务开发经验来说,觉得M(Model)是MVC架构的中心,有了Model之后就可以让Controller与View参考这些Model(模型),先定义出计划开发的Controller与Action,然后再创建所有Action对应的View(无属性的View),之后就可以将不同单元的Controller与View分工开发,最后再进行集成即可”,本项目的实现步骤就基本是按照这样的思想。首先按照功能分为4个部分(这样进一步简化了问题),每个功能的实现通过三步来完成,最后把他们集成到一起。

      另外,虽然原来的范例中使用了code first技术,我等菜鸟还是习惯从db first,对localdb不甚了解,这个范例还是先建了数据库MVCShopping,并录入了一些测试数据。

    三.按功能具体实现

      依次实现各个部分:

    1.商品浏览

      这是最简单通用的一部分,与经典的MVC Movie Store中所呈现的业务逻辑一样。包括:商品类别列表、某类别下的商品列表和某一商品的详细信息。按照实现步骤,分三步实现:

    1)数据模型规划

      数据模型涉及到了两个数据实体,商品类别(ProductCategory)和商品信息(Product)。在Models文件夹下新建两个类:ProductCategory和Product

     [DisplayName("商品类别")]
        [DisplayColumn("Name")]
        public class ProductCategory
        {
            [Key]
            public int Id { get; set; }
    
            [DisplayName("商品类别名称")]
            [Required(ErrorMessage="请输入商品类别名称")]
            [MaxLength(20,ErrorMessage="类别名称不可超过20个字")]
            public string Name { get; set; }
    
            public virtual ICollection<Product> Products { get; set; }
        }
     [DisplayName("商品信息")]
        [DisplayColumn("Name")]
        public class Product
        {
            [Key]
            public int Id { get; set; }
    
            [DisplayName("商品类别")]
            [Required]
            public virtual ProductCategory ProductCategory { get; set; }
    
            [DisplayName("商品名称")]
            [Required(ErrorMessage = "请输入商品名称")]
            [MaxLength(60, ErrorMessage = "不得超过60个字")]
            public string Name { get; set; }
    
            [DisplayName("商品简介")]
            [Required(ErrorMessage = "请输入商品简介")]
            [MaxLength(250, ErrorMessage = "商品简介不超过250字")]
            public string Description { get; set; }
    
            [DisplayName("商品颜色")]
            [Required(ErrorMessage = "请选择颜色")]
            public Color Color { get; set; }
    
            [DisplayName("商品售价")]
            [Required(ErrorMessage = "请输入商品售价")]
            [Range(1,10000,ErrorMessage="商品售价必须介于1到10000之间")]
            public int Price { get; set; }
    
            [DisplayName("上架时间")]
            [Description("如果不设置上架时间,代表不发售")]
            public DateTime? PublishOn{get;set;}
        }

      在这里可能新手会比较困惑的是每个字段上都有形如[DisplayName("商品名称")] [Required]等内容。这些统称为注解,可分为验证注解和显示注解两类。后面会见到很多诸如此类的注解,便于方便和验证。

    2)控制器架构规划

      浏览商品主要会涉及到三个页面:“首页”显示商品类别列表,“列表页”显示商品列表,“详细页”显示商品明细。把这三个也的功能放在一个Controller控制器中。在Controllers文件夹中添加控制器,HomeController。

    public class HomeController : Controller
        {
            MvcShoppingContext db = new MvcShoppingContext();
            // 首页
            public ActionResult Index()
            {
                var data = db.ProductCategories.ToList();
                data = db.ProductCategories.ToList();
                return View(data);
            }
            //商品列表
            public ActionResult ProductList(int id)
            {
                var productCategory = db.ProductCategories.Find(id);
                var data = productCategory.Products.ToList();
                return View(data);
            }
            //商品明细
            public ActionResult ProductDetail(int id)
            {
                var data = db.Products.Find(id);
                return View(data);
    
            }
        }
      很简单,根据类别id查找某一类别商品,根据商品id查找商品详细信息。
      唯一可能产生疑问的在于 return View(data)这句代码。在vs中查看View方法的重载,发现它既可以传string类型的ViewName值来指定具体视图页面,也可以传object类型的model值来传递数据。实际上,通过View(data)把数据传给视图对象时,就等价于是:ViewData.Model=data;而我们知道视图通过强类型的参数可以方便很多操作,所以此处实际上是进行了简化,直接把data通过View()方法传过来然后 在视图中用@model 指定类型进行强类型的转换。操作更加简洁。
    3)创建视图页面

      最后创建展示页面。在HomeController控制器中添加相应的视图。

    Index.cshtml

    @model IEnumerable<MVCShopping.Models.ProductCategory>
    
    <h2>@Html.DisplayNameFor(t=>t.Name)</h2>
    <ul>
        @foreach (var item in Model)
        { 
          <li>@Html.ActionLink(item.Name, "ProductList", new { id=item.Id})</li>
        }
    </ul>

    ProductList.cshtml

    @model IEnumerable<MVCShopping.Models.Product>
    @{
        var ajaxOption = new AjaxOptions() {
            HttpMethod="Post",
            OnSuccess = "AddToCartSuccess",
            OnFailure = "AddToCartFailure",
        };
    }
    @section scripts{
    @Scripts.Render("~/bundles/jqueryval")
        <script>
            function AddToCartSuccess() {
                alert('添加购物从成功');
            }
            function AddToCartFailure(xhr) {
                alert('添加购物车失败(HTTP状态代码:'+xhr.status+')');
            }
        </script>
    
    }
    <h2>@Html.DisplayNameFor(t => t.ToList()[0])</h2>
    <h2>@Html.DisplayNameFor(t => t.Name)</h2>
    <h3>您正在浏览[@Model.First().ProductCategory.Name]分类的信息</h3>
    
    <table>
        <tr>
            <th>@Html.DisplayNameFor(model=>model.Name)</th>
            <th>@Html.DisplayNameFor(model => model.Description)</th>
            <th>@Html.DisplayNameFor(model => model.Price)</th>
            <th>添加购物车</th>
        </tr>
        @foreach (var item in Model)
        { 
            <tr>
                <td>@Html.ActionLink(item.Name, "ProductDetail", new { id=item.Id})</td>
                <td>@Html.DisplayFor(w=>item.Description)</td>
                <td>@Html.DisplayFor(w=>item.Price)</td>
                <td>@Ajax.ActionLink("添加购物车", "AddToCart", "Cart", new { ProductId=item.Id},ajaxOption)</td>
            </tr>
        }
    </table>

    ProductDetail.cshtml

    @model MVCShopping.Models.Product
    
    @{
        var ajaxOption = new AjaxOptions()
        {
            OnSuccess = "AddToCartSuccess",
            OnFailure = "AddToCartFailure",
        };
    }
    @section scripts{
        @Scripts.Render("~/bundles/jqueryval")
        <script>
            function AddToCartSuccess() {
                alert('添加成功');
            }
            function AddToCartFailure(xhr) {
                alert('添加购物车失败(HTTP状态代码:' + xhr.status + ')');
            }
        </script>
        }
    
    <h2>您正在察看"@Model.Name"商品</h2>
    <fieldset>
        <legend>@Html.DisplayNameFor(m=>m)</legend>
        <div class="display-label">
            @Html.DisplayNameFor(model=>model.Description)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.Description)
        </div>
    
        <div class="display-label">
            @Html.DisplayNameFor(model => model.Price)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.Price)
        </div>
    
        <div class="display-label">
            @Html.DisplayNameFor(model => model.PublishOn)
        </div>
        <div class="display-field">
            @Html.DisplayFor(model => model.PublishOn)
        </div>
    </fieldset>
    <p>
        @Ajax.ActionLink("添加购物车","AddToCart","Cart",ajaxOption)
    </p>

      可以看出来,不论是html辅助方法还是ajax辅助方法的使用都是很固定很简洁的,也比较容易掌握,要不然也不会mvc能根据模板自动生成许多html控件。这里的页面使用了许多强类型的辅助方法,多写几个页面就觉着都是套路。比我菜和跟我一样菜的可以看我总结的这些内容,基本都涵盖了。

    2.会员功能

      这里的会员功能主要用到了常用的Forms认证。登录和注销的方法分别是:FormsAuthentication.SetAuthCookie()和FormsAuthentication.SignOut()。详见Fish Li大神的这篇文章。还是分三步走。

    1)数据模型规划

    Member.cs

    [DisplayName("会员信息")]
        [DisplayColumn("Name")]
        public class Member
        {
            [Key]
            public int id { get; set; }
    
    
            [DisplayName("会员帐号")]
            [Description("以Email为会员的登陆帐号")]
            [Required(ErrorMessage = "请输入Email地址")]
            [MaxLength(250, ErrorMessage = "不得超过250个字")]
            [DataType(DataType.EmailAddress)]
            public string Email { get; set; }
    
            [DisplayName("会员密码")]
            [Description("密码将以SHA1进行哈西运算,通过运算后的结果转为HEX表示法的字符串长度都为40")]
            [Required(ErrorMessage = "请输入密码")]
            [MaxLength(40, ErrorMessage = "不得超过40个字")]
            [DataType(DataType.Password)]
            public string Password { get; set; }
    
            [DisplayName("中文姓名")]
            [Description("忽略外国人")]
            [Required(ErrorMessage = "请输入中文姓名")]
            [MaxLength(5, ErrorMessage = "不得超过5个字")]
            public string Name { get; set; }
    
            [DisplayName("网络昵称")]
            [Required(ErrorMessage = "请输入网络昵称")]
            [MaxLength(15, ErrorMessage = "不得超过15个字")]
            public string Nickname { get; set; }
    
            [DisplayName("会员注册时间")]
            public DateTime RegisterOn { get; set; }
    
            //AuthCode会保存一个GUID值
            [DisplayName("会员启用认证码")]
            [MaxLength(36)]
            [Description("当AuthCode等于null代表会员已通过Email认证")]
            public string AuthCode { get; set; }
    
            public virtual ICollection<OrderHeader> orders { get; set; }
        }

    其中的字段AuthCode是为了可以进行会员验证功能的。当你注册一个账号时常常能收到一封确认邮件,这个字段就是为了实现此功能的。在后面的功能扩展中会进行展示。

    MemberLoginViewModel.cs

    public class MemberLoginViewModel
        {
            /// <summary>
            /// 在帐户这显示指定了DataType(DataType.EmailAddress,ErrorMessage="请输入您的Email地址")
            /// 但是并不现实错误信息,这是因为MVC4并没有针对DataType属性支持客户端的js验证功能
            /// </summary>
            [DisplayName("会员帐号")]
            [Required(ErrorMessage = "请输入{0}")]
            [DataType(DataType.EmailAddress,ErrorMessage="请输入您的Email地址")]
            public string Email { get; set; }
    
            [DisplayName("会员密码")]
            [Required(ErrorMessage = "请输入{0}")]
            [DataType(DataType.Password)]
            public string Password { get; set; }
        }

    这个Model是为登录界面提供的。由于登录时只有两个可以用到的字段,为了能够使用强类型方式使用Model,所有新建了一个ViewModel。

    2)控制器架构规划

    public class MemberController : Controller
        {
            MvcShoppingContext db = new MvcShoppingContext();
            private string pwSalt = "AlrySq1oPe2Mh784QQwG6jRAfkdPpDa90J0i";
            // 会员注册页面
    
            public ActionResult Register()
            {
                return View();
            }
    
            //写入会员信息
            [HttpPost]
            public ActionResult Register([Bind(Exclude="RegisterOn,AuthCode")]Member member)
            {
                //检查会员是否存在
                var chk_member = db.Members.Where(p => p.Email == member.Email).FirstOrDefault();
                if (chk_member != null)
                {
                    ModelState.AddModelError("Email","您输入的Email已经有人注册过了!");
                }
                if (ModelState.IsValid)
                { 
                    //将密码加盐在之后进行哈希运算
                    member.Password = FormsAuthentication.HashPasswordForStoringInConfigFile(pwSalt + member.Password, "SHA1");
                    member.RegisterOn = DateTime.Now;
                    //会员验证码,采用Guid当成验证码属性,避免有会员使用到重复的验证码
                    member.AuthCode = Guid.NewGuid().ToString();
                    db.Members.Add(member);
                    db.SaveChanges();
    
                    return RedirectToAction("Index","Home");
                }
                else
                { return View(); }
            }
    
            //显示会员登陆页面
            public ActionResult Login(string returnUrl)
            {
                ViewBag.ReturnUrl = returnUrl;
                return View();
            }
            //会员登陆
            [HttpPost]
            public ActionResult Login(string email, string password, string returnUrl)
            {
                if (ValidateUser(email, password))
                {
                    FormsAuthentication.SetAuthCookie(email,false);
                    if (string.IsNullOrEmpty(returnUrl))
                    {
                        return RedirectToAction("Index", "Home");
                    }
                    else
                        return Redirect(returnUrl);
                }
                ModelState.AddModelError("", "输入的帐号或者密码错误");
                return View();
            }
    
            private bool ValidateUser(string email, string password)
            {
                var hash_pw = FormsAuthentication.HashPasswordForStoringInConfigFile(pwSalt + password, "SHA1");
                var member = db.Members.Where(p => p.Email == email && p.Password == hash_pw).FirstOrDefault();
                return(member!=null);
            }
    
            //会员注销
            public ActionResult Logout()
            {
                FormsAuthentication.SignOut();
                Session.Clear();
                return RedirectToAction("Index", "Home");
            }
    
    
            public ActionResult ValidateRegister()
            {
    
                return View();
            }
        }

      带有[HttpPost]标记的表明是Post请求时执行的部分。默认是[HttpGet]。会员功能主要就是包含了注册和登录两部分。

    3)创建视图页面

    Login.cshtml

    @model MVCShopping.Models.MemberLoginViewModel
    
    @{
        ViewBag.Title = "Login";
    }
    
    <h2>Login</h2>
    
    @using (Html.BeginForm()) {
        @Html.ValidationSummary(true)
    
        <fieldset>
            <legend>MemberLoginViewModel</legend>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Email)
            </div>
            <div class="editor-field">
                @Html.TextBoxFor(model => model.Email, new { data_val_Email="请输入Email地址"})
                @Html.ValidationMessageFor(model => model.Email)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Password)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Password)
                @Html.ValidationMessageFor(model => model.Password)
            </div>
    
            <p>
                <input type="submit" value="登陆" />
            </p>
        </fieldset>
    }
    <div>
        @Html.ActionLink("Back to List", "Index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }

    Register.cshtml

    @model MVCShopping.Models.Member
    
    @{
        ViewBag.Title = "注册";
    }
    
    <h2>会员注册</h2>
    
    @using (Html.BeginForm()) {
        @Html.ValidationSummary(true)
    
        <fieldset>
            <legend>请输入会员注册信息</legend>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Email)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Password)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Password)
                @Html.ValidationMessageFor(model => model.Password)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Name)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Name)
                @Html.ValidationMessageFor(model => model.Name)
            </div>
    
            <div class="editor-label">
                @Html.LabelFor(model => model.Nickname)
            </div>
            <div class="editor-field">
                @Html.EditorFor(model => model.Nickname)
                @Html.ValidationMessageFor(model => model.Nickname)
            </div>
    
            <p>
                <input type="submit" value="注册" />
            </p>
        </fieldset>
    }
    
    <div>
        @Html.ActionLink("Back to List", "index")
    </div>
    
    @section Scripts {
        @Scripts.Render("~/bundles/jqueryval")
    }

    3.购物车功能

      这个功能主要是用到了Session(我见过的购物车都是以session实现的),在Session中存储Cart信息。基本上就是围绕以下这一段关键代码进行的:

    List<Cart> Carts
            {
                get {
                    if (Session["Carts"] == null)
                    {
                        Session["Carts"] = new List<Cart>();
                    }
                    return Session["Carts"] as List<Cart>;
                }
                set { Session["Carts"] = value; }
            }

    1)数据模型规划

    public class Cart
        {
            [DisplayName("选购商品")]
            [Required]
            public Product Product { get; set; }
    
            [DisplayName("选购数量~")]
            [Required]
            public int Amount { get; set; }
        }

    2)控制器架构规划

    public class CartController : Controller
        {
            //非会员也可使用所以购物车保存在Session中
            // 显示当前购物从项目
            MvcShoppingContext db = new MvcShoppingContext();
            List<Cart> Carts
            {
                get {
                    if (Session["Carts"] == null)
                    {
                        Session["Carts"] = new List<Cart>();
                    }
                    return Session["Carts"] as List<Cart>;
                }
                set { Session["Carts"] = value; }
            }
            public ActionResult Index()
            {
                return View(this.Carts);
            }
            //添加产品项目到购物车,如果没有传入Amount参数则默认购买数量为1
            //因为要通过Ajax调用这个Action,所以可以先标示Post属性
            [HttpPost]
            public ActionResult AddToCart(int ProductId, int Amount = 1)
            {
                var product = db.Products.Find(ProductId);
                //验证产品是否存在
                if (product == null)
                    return HttpNotFound();
                var existiongCart = this.Carts.FirstOrDefault(p => p.Product.Id == ProductId);
                if (existiongCart != null)
                {
                    existiongCart.Amount += 1;
                }
                else
                {
                    this.Carts.Add(new Cart() { Product=product,Amount=Amount});
                }
                return new HttpStatusCodeResult(HttpStatusCode.Created);
            }
            //移出购物从项目
            [HttpPost]
            public ActionResult Remove(int ProductId)
            {
                var existingCart = this.Carts.FirstOrDefault(p => p.Product.Id == ProductId);
                if (existingCart != null)
                {
                    this.Carts.Remove(existingCart);
                }
                return new HttpStatusCodeResult(System.Net.HttpStatusCode.OK);
            }
            //更新数量
            [HttpPost]
            public ActionResult UpdateAmount(List<Cart> Carts)
            {
                foreach (var item in Carts)
                {
                    var existingCart = this.Carts.FirstOrDefault(p => p.Product.Id == item.Product.Id);
                    if (existingCart != null)
                    {
                        existingCart.Amount = item.Amount;
                    }
                }
                return RedirectToAction("Index","Cart");
            }
        }

    3)创建视图页面

    index.cshtml

    @model IEnumerable<MVCShopping.Models.Cart>
    
    @{
        var ajaxOption = new AjaxOptions()
        {
            OnSuccess = "RemoveCartSuccess",
            OnFailure = "RemoveCartFailure",
            Confirm = "您确定要从购物车删除吗?",
            HttpMethod = "Post",
        };
    }
    
    @section scripts{
        @Scripts.Render("~/bundles/jqueryval")
        
        <script>
            function RemoveCartSuccess() {
                alert('移出购物从成功');
                location.reload();
            }
            function RemoveCartFailure(xhr)
            {
                alert('移出购物车失败(Http状态代吗:' + xhr.status);
            }
        </script>
        }
    
    <h2>购物车列表</h2>
    @using (Html.BeginForm("UpdateAmount", "Cart"))
    { 
        <table>
            <tr>
                <th>产品名称</th>
                <th>单价</th>
                <th>数量</th>
                <th>小计</th>
                <th></th>
            </tr>
            @{int subTotal = 0;}
    
            @foreach (var item in Model)
            {
                subTotal += item.Product.Price * item.Amount;
                var ddlAmountList = new SelectList(Enumerable.Range(1,10),item.Amount);
                @Html.Hidden(item.Product.Id.ToString())
                <tr>
                    <td>@Html.DisplayFor(t=>item.Product.Name)</td>
                    <td>NT¥@(item.Product.Price)</td>
                    <td>@Html.DropDownListFor(t=>item.Amount,ddlAmountList)</td>
                    <td>NT¥@(item.Product.Price*item.Amount)</td>
                    <td>
                        @Ajax.ActionLink("删除","Remove",new{ProductId=item.Product.Id},ajaxOption)
                    </td>
                </tr>
            }
            <tr>
                <th></th>
                <th></th>
                <th>总价</th>
                <th id="subtotal">NT¥ @subTotal</th>
                <th></th>
            </tr>
        </table>
        <p>
            <input type="submit" value="更新数量" />
            <input type="button" value="完成订单" onclick="location.href='@Url.Action("Complete","Order")';" />
        </p>
    }

     

  • 相关阅读:
    【Henu ACM Round#15 F】Arthur and Questions
    【Henu ACM Round#16 F】Om Nom and Necklace
    【Henu ACM Round#16 E】Paths and Trees
    JS制作的简单的三级及联
    .Net实现的批量删除(使用了repeater控件)
    setInterval和setTimeout调用方法小知识科普
    AJAX制作JSON格式的实时更新数据的方法
    关于获取网站域名的写法杂谈
    JS初识(着重讲解Date函数)
    Guid函数
  • 原文地址:https://www.cnblogs.com/hunji-fight/p/3819298.html
Copyright © 2011-2022 走看看