自强不息,厚德载物!身心自在,道法自然!


第九话 Asp.Net MVC3.0【MVC项目实战の五】

随着我们购物车的不断完善,我们简单的完成到最后的订单模块。我们需要一个扩展的模型,在我们的域模型类库里,添加一个类(ShippingDetail)类,它的具体代码如下:

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

namespace SportsStore.Domain.Entities
{
    public class ShippingDetails
    {
        [Required(ErrorMessage="Please enter a name")]
        public string Name { get; set; }

        [Required(ErrorMessage="Please enter the first Address")]
        public string Address1 { get; set; }
        public string Address2 { get; set; }
        public string Address3 { get; set; }

        [Required(ErrorMessage="Please enter a city name")]
        public string City { get; set; }

        [Required(ErrorMessage="Please enter a state name")]
        public string State { get; set; }

        public string Zip { get; set; }

        [Required(ErrorMessage="Please enter a country name")]
        public string Country { get; set; }

        public bool GiftWrap { get; set; }
    }
}

接下来需要完善我们结算功能,我们需要添加一个结算按钮,在我们Web项目的Views/Cart/Index.cshtml修改如下(实际就添加了一个按钮):

@model SportsStore.WebUI.Models.CartIndexViewModel

@{
    ViewBag.Title = "Cart Index";
}

<h2>Your Cart</h2>
<table width="90%" align="center">
    <thead>
    <tr>
    <th align="center">Qunantity</th>
    <th align="left">Item</th>
    <th align="right">Price</th>
    <th align="right">Subtotal</th>
    </tr>
    </thead>
    <tbody>
    @foreach (var line in Model.Cart.Lines)
    {
        <tr>
        <td align="center">@line.Quantity</td>
        <td align="left">@line.Product.Name</td>
        <td align="right">@line.Product.Price.ToString("c")</td>
        <td align="right">@((line.Quantity*line.Product.Price).ToString("c"))</td>
        <td>
        @using (Html.BeginForm("RemoveFromCart","Cart"))
        {
            @Html.Hidden("ProductId",line.Product.ProductID)
            @Html.HiddenFor(h=>h.ReturnUrl)
            <input class="actionButtons" type="submit" value="Remove" />
        }
        </td>
        </tr>
    }
    </tbody>
    <tfoot>
    <tr>
    <td colspan="3">Total:</td>
    <td align="right">@Model.Cart.ComputeTotalValue().ToString("C")</td>
    </tr>
    </tfoot>
</table>
<p align="center" class="actionButtons"><a href="@Model.ReturnUrl">Continue shopping</a>
@Html.ActionLink("Checkout now", "Checkout")
</p>

上面红色部分的代码会呈现出一个带有支付连接的按钮,运行我们的项目如下图1.

图1.

接着我们需要建立一个视图(Checkout),在Cartcontroller控制器里添加一个返回视图(Checkout)的Action(方法),具体代码如下:

 public ViewResult Checkout() 
        {
            return this.View(new ShippingDetails());
        }

添加完Action(方法)后,然后就需要添加视图(Checkout),右键选择添加视图,如下图2.

图2.这里还是选择强类型视图,因我我们需要使用之前我们定义的ShippingDeatails类。创建好视图(Checkout)后,修改他的内容如下:

@model SportsStore.Domain.Entities.ShippingDetails

@{
    ViewBag.Title = "SportStore:Checkout";
}

<h2>Check out now</h2>
Please enter your details,and we'll ship your goods right away!
@using (Html.BeginForm())
{
    @Html.ValidationSummary()
    <h3>Ship to</h3>
    <div>Name: @Html.EditorFor(h=>h.Name)</div>
    
    <h3>Address</h3>
    <div>Address1:@Html.EditorFor(h=>h.Address1)</div>
    <div>Address2:@Html.EditorFor(h=>h.Address2)</div>
    <div>Address3:@Html.EditorFor(h=>h.Address3)</div>
    <div>City:@Html.EditorFor(h=>h.City)</div>
    <div>State:@Html.EditorFor(h=>h.State)</div>
    <div>Zip:@Html.EditorFor(h=>h.Zip)</div>
    <div>Country:@Html.EditorFor(h=>h.Country)</div>
    
    <h3>Options</h3>
    <label>
    @Html.EditorFor(h=>h.GiftWrap) GIft wrap these items
    </label>
    
    <p align="center">
    <input class="actionButtons" type="submit" value="Complete order" />
    </p>
}

搞完视图运行我们的Web项目,结果如下图3

图3.使用Html.EditorFor辅助方法为每一个表单字段呈现input元素,我们让MVC框架能够算出view model属性需要哪一种input元素,而不是显示的指定。

接下我们实现订单处理器我们需要一个组件来处理订单的详情,为了保持MVC模型的严则,首先定义一个接口,并实现该接口。然后使用我们的DI容器--Ninject,在我们的Domain模型域类库项目里的Abstract文件夹里面定义一个接口(IOrderProcessor),具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Abstract
{
    public  interface IOrderProcessor
    {
        void ProcessOrder(Cart cart, ShippingDetails shippingDetails);
    }
}

 然后我们需要实现IOrderProcessor接口去处理客户提交的订单,如果一切OK的话,我们需要给用户发一份Email邮件告知用户。当然这里已经把购物流程简化的不像样子了,真正的流程这里应该是和银行(第三方)交互,等待支付成功后需要发邮件给用户,这里就简单的实现下。在我们的SportsStore.Domain类库下的Concrete文件夹下创建EmailOrderProcessor类,在这个类里使用.Net内置的SMTP实现发送电子邮件,具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Mail;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;

namespace SportsStore.Domain.Concrete
{
    public class EmailSettings
    {
        public string MailToAddress = "**************";            //自己试着配置
        public string MailFromAddress = "*********@qq.com";   //自己试着配置
            public bool UseSsl = false;
            public string Username = "********@qq.com";   //自己试着配置
            public string Password = "*************";   //自己试着配置   用QQ的话这里要配置密码
            public string ServerName = "Smtp.qq.com";   //pop.qq.com   Smtp.qq.com  mail.qq.com
            public int ServerPort = 25;                
            public bool WriteAsFile = false;
            public string FileLocation = @"E:\work\SportsStore\sports_store_emails";
    }
    public class EmailOrderProcessor : IOrderProcessor 
    {
        private EmailSettings emailSetings;
        public EmailOrderProcessor(EmailSettings settings) 
        {
            this.emailSetings = settings;
        }
        public void ProcessOrder(Cart cart, ShippingDetails shippingInfo) 
        {
            using (var smtpClient = new SmtpClient())
            {
                smtpClient.EnableSsl = this.emailSetings.UseSsl;
                smtpClient.Host = this.emailSetings.ServerName;
                smtpClient.Port = this.emailSetings.ServerPort;
                smtpClient.UseDefaultCredentials = false;
                smtpClient.Credentials = new NetworkCredential(this.emailSetings.Username, this.emailSetings.Password);

                if (this.emailSetings.WriteAsFile)
                {
                    smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
                    smtpClient.PickupDirectoryLocation = this.emailSetings.FileLocation;
                    smtpClient.EnableSsl = false;
                }
                StringBuilder body = new StringBuilder().AppendLine("A New Order Has Been Submitted")
                                                        .AppendLine("---")
                                                        .AppendLine("Items:");

                foreach (var item in cart.Lines)
                {
                    var subtotal = item.Product.Price * item.Quantity;
                    body.AppendFormat("{0} × {1}(Subtotal:{2:c})", item.Quantity, item.Product.Name, subtotal);
                }

                body.AppendFormat("Total order Value:{0:c}", cart.ComputeTotalValue())
                    .AppendLine("---")
                    .AppendLine("Ship to:")
                    .AppendLine(shippingInfo.Name)
                    .AppendLine(shippingInfo.Address1)
                    .AppendLine(shippingInfo.Address2 ?? "")
                    .AppendLine(shippingInfo.Address3 ?? "")
                    .AppendLine(shippingInfo.City)
                    .AppendLine(shippingInfo.State ?? "")
                    .AppendLine(shippingInfo.Country)
                    .AppendLine(shippingInfo.Zip)
                    .AppendLine("---")
                    .AppendFormat("GIft wrap:{0}", shippingInfo.GiftWrap ? "Yes" : "No");
                MailMessage mailMessage = new MailMessage(
                    this.emailSetings.MailFromAddress,            //From
                    this.emailSetings.MailToAddress,              //To
                    "New Order Submitted!",                       //Subject
                    body.ToString());
                if (this.emailSetings.WriteAsFile) 
                {
                    mailMessage.BodyEncoding = Encoding.ASCII;
                }
                smtpClient.Send(mailMessage);
            }
        }
    }
}

现在,我们已经实现IOrderProcessor接口,我们可以使用Ninject创建实例方法配置它。在我们之前Web项目的Infrastructure文件夹下的NinjectControllerFactory类,具体代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Ninject;
using System.Web.Routing;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.Domain.Concrete;
using System.Configuration;

namespace SportsStore.WebUI.Infrastructure
{
    public class NinjectControllerFactory : DefaultControllerFactory
    {
        private IKernel ninjectKernel;

        public NinjectControllerFactory() 
        {
            this.ninjectKernel = new StandardKernel();
            AddBindings();
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType);
        }

        private void AddBindings()
        {
            //绑定额外数据
            this.ninjectKernel.Bind<IProductRepository>().To<EFProductRepository>();

            EmailSettings emailSettings = new EmailSettings
            {
                WriteAsFile = bool.Parse(ConfigurationManager.AppSettings["Email.WriteAsFile"] ?? "false")
            };
            this.ninjectKernel.Bind<IOrderProcessor>().To<EmailOrderProcessor>()
                                                      .WithConstructorArgument("settings", emailSettings);  
        }
    }
}

这里的Email.WriteAsFile在配置文件里面配置的,主要是考虑是在没有smtp服务器的情况下,将邮件复制到指定目录。其实一般的邮箱都开通了smtp服务的,所以我们将这里的默认值设为false。在Web.config里面配置<add key="Email.WriteAsFile" value="false"/>,具体如下:Web项目的Views文件下Web.config如下:

  <appSettings>
    <add key="webpages:Enabled" value="false" />
    <add key="Email.WriteAsFile" value="true" />
  </appSettings>

我们现在需要完善我们Cartcontroller,我们需要一个构造函数在用户单击支付按钮的时候,他需要实现IOrderProcessor接口,然后发送邮件,具体修改CartController控制器代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;

namespace SportsStore.WebUI.Controllers
{
    public class CartController : Controller
    {
        private IProductRepository repository;
        private IOrderProcessor orderProcessor;

        public CartController(IProductRepository repo,IOrderProcessor proc) 
        {
            this.repository = repo;
            this.orderProcessor = proc;
        }
        //添加购物车
        public RedirectToRouteResult AddToCart(Cart cart, int productId, string returnUrl)
        {
            Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId);
            if (product != null)
            {
                cart.AddItem(product, 1);
            }
            return this.RedirectToAction("Index", new { returnUrl });
        }
        //移除商品
        public RedirectToRouteResult RemoveFromCart(Cart cart, int productId, string returnUrl) 
        {
            Product product = this.repository.Products.FirstOrDefault(h => h.ProductID == productId);
            if (product != null)
            {
                cart.RemoveLine(product);
            }
            return this.RedirectToAction("Index", new { cart = cart, returnUrl = returnUrl });
        }

        private Cart GetCart() 
        {
            Cart cart = (Cart)Session["Cart"];
            if (cart == null)
            {
                cart = new Cart();
                this.Session["Cart"] = cart;
            }
            return cart;
        }

        public ViewResult Index(Cart cart, string returnUrl)
        {
            return this.View(new CartIndexViewModel
            {
                Cart = cart,
                ReturnUrl = returnUrl
            });
        }
        //简易的购物车总结
        public ViewResult Summary(Cart cart) 
        {
            return this.View(cart);
        }
        //结算的方法
        [HttpPost]
        public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails) 
        {
            if (cart.Lines.Count() == 0)
            {
                ModelState.AddModelError("", "Sorry,your cart is empty!");
            }
            if (ModelState.IsValid)
            {
                this.orderProcessor.ProcessOrder(cart, shippingDetails);
                cart.Clear();
                return this.View("Completed");
            }
            else 
            {
                return this.View(shippingDetails);
            }
        }
        public ViewResult Checkout() 
        {
            return this.View(new ShippingDetails());
        }
    }
}

然后我们需要添加一个视图页面"Completed",他表示我们已经支付成功,返回友好的信息,具体如下图。

这里我们不要想在选择强类型视图,因为我们使用它值呈现一些简单的东西,所以我们还要使用模版(_Layout)我希望他们的风格还是一样的,具体代码如下:

@{
    ViewBag.Title = "SportsStore:Order Completed";
}

<h2>Thanks!</h2>
Thanks for placing you order.We'll ship your goods as soon as possible

添加完成后运行我们的项目如下图4(展示我们验证不通过)-图5(购物成功)。

图4.

图5.

项目就简单的搞到这里,我们需要一个简单后续在补上一个简单的后台(简单项目肯定需要一个简单的后台)来管理我们项目。几天我们简单的购物流程就到这里,文章写的仓促,要是有那些地方有描述错误还是写错的地方,还请路过的前辈们多多指导批评,大家同工学习。鉴于好多朋友需要MVC基础的资料等,可以给我Email,等我整理好了一并发给大家,或者尝试用一片文章专门来放这些资料到时大家可以任意下载共同学习。

posted @ 2012-07-11 11:42  辉太  阅读(3163)  评论(5编辑  收藏  举报

路漫漫其修远兮,吾将上下而求索!