《Pro ASP.NET MVC 3 Framework》学习笔记之十六【示例项目SportsStore】

简介: 提交订单:这是SportsStore项目的最后一个功能了,结算并完成我们的订单。 在Entities文件夹里添加一个ShippingDetails.cs,代码如下: View Code public class ShippingDetails { [Requ...

提交订单:这是SportsStore项目的最后一个功能了,结算并完成我们的订单。

在Entities文件夹里添加一个ShippingDetails.cs,代码如下:

View Code
    public class ShippingDetails
{
[Required(ErrorMessage = "Please enter a name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter the first address line")]
public string Line1 { get; set; }
public string Line2 { get; set; }
public string Line3 { 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; }
}

注意这里给属性添加了一些validation attributes,需要引入一个命名空间:System.ComponentModel.DataAnnotations。

添加结算处理:我们的目标是能够让用户添加快递信息和提交订单。首先我们在购物车详情页面添加一个Checkout按钮,修改Views/Cart/Index.cshtml如下:

<p align="center" class="actionButtons">
<a href="@Model.ReturnUrl">Continue shopping</a>
@Html.ActionLink("Checkout now", "Checkout")
</p>

接着我们在CartController里面添加一个Checkout action方法,如下:

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

右键添加一个视图Checkout,如下:

Checkout.cshtml的代码如下:

View Code
@model SportsStore.Domain.Entities.ShippingDetails
@{
ViewBag.Title = "SportsStore:Checkout";
}
<h2>
Check out now</h2>
Please enter your details,and we'll ship your goods right way!
@using (Html.BeginForm())
{
@Html.ValidationSummary()
<h3>
Ship to</h3>
<div>
Name:@Html.EditorFor(x => x.Name)</div>
<h3>
Address</h3>
<div>
Line 1:@Html.EditorFor(x => x.Line1)</div>
<div>
Line 2:@Html.EditorFor(x => x.Line2)</div>
<div>
Line 3:@Html.EditorFor(x => x.Line3)</div>
<div>
City:@Html.EditorFor(x => x.City)</div>
<div>
State:@Html.EditorFor(x => x.State)</div>
<div>
Zip:@Html.EditorFor(x => x.Zip)</div>
<div>
Country:@Html.EditorFor(x => x.Country)</div>

<h3>
Options</h3>
<label>
@Html.EditorFor(x => x.GiftWrap)
Gift wrap these items
</label>

<p align="center">
<input class="actionButtons" type="submit" value="Complete order" />
</p>
}

我们使用Html.EditorFor辅助方法为每一个表单字段呈现input元素,我们让MVC框架能够算出view model属性需要哪一种input元素,而不是显示的指定。Html.EditorFor方法是Template View Helper一个例子,书后面的章节会进行详细的讲解。这里我们能体验到,MVC框架非常智能,会自动为bool类型的属性呈现一个Checkbox(这里的Gift Wrap).

Tip:我们可以使用一个简单的方法Html.EditorForModel来为所有的属性创建HTML元素.将上面的代码注释,然后加上@Html.EditorForModel().运行程序可以看看效果。我们这是分开写的,为的是能够直接引用每一个属性。

接着实现订单处理过程

我们需要一个组件来处理订单的详情,为了保持MVC model的严则,首先定义一个接口,并实现该接口。然后使用我们的DI容器--Ninject
在 SportsStore.Domain/Abstract的文件夹里面定义一个IOrderProcessor接口,如下所示:

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

接着实现该接口,这里比较简单的处理,仅仅发一封邮件告之。在 SportsStore.Domain/Concrete里面创建一个EmailOrderProcessor类来实现该接口,如下所示:

View Code
using System.Net.Mail;
using System.Text;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Net;

namespace SportsStore.Domain.Concrete
{
public class EmailSettings
{
public string MailToAddress = "**@**";
public string MailFromAddress = "mszhangxuefei@qq.com";
public bool UseSsl = false;
public string UserName = "mszhangxuefei@qq.com";
public string Password = "这个不能说";
public string ServerName = "smtp.qq.com";
public int ServerPort = 25;
public bool WriteAsFile = false;
public string FileLocation = @"D:\Study\Projects\SportsStore\sports_store_emails";

}

public class EmailOrderProcessor : IOrderProcessor
{
private EmailSettings emailSettings;
public EmailOrderProcessor(EmailSettings settings)
{
emailSettings = settings;
}
public void ProcessOrder(Cart cart, ShippingDetails shippingDetails)
{
using (var smtpClient = new SmtpClient())
{
smtpClient.EnableSsl = emailSettings.UseSsl;
smtpClient.Host = emailSettings.ServerName;
smtpClient.Port = emailSettings.ServerPort;
smtpClient.UseDefaultCredentials = false;
smtpClient.Credentials = new NetworkCredential(emailSettings.UserName, emailSettings.Password);

if (emailSettings.WriteAsFile)
{
//如果你没有可用的smtp服务器,则将邮件复制到指定目录
smtpClient.DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory;
smtpClient.PickupDirectoryLocation = emailSettings.FileLocation;
smtpClient.EnableSsl = false;
}

StringBuilder body = new StringBuilder()
.AppendLine("A new order has been submitted")
.AppendLine("---")
.AppendLine("Items:");

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

body.AppendFormat("Total order value:{0:c}", cart.ComputeTotalValue())
.AppendLine("---")
.AppendLine("Ship to:")
.AppendLine(shippingDetails.Name)
.AppendLine(shippingDetails.Line1)
.AppendLine(shippingDetails.Line2 ?? "")
.AppendLine(shippingDetails.Line3 ?? "")
.AppendLine(shippingDetails.City)
.AppendLine(shippingDetails.State ?? "")
.AppendLine(shippingDetails.Country)
.AppendLine(shippingDetails.Zip)
.AppendLine("---")
.AppendFormat("Gift wrap:{0}", shippingDetails.GiftWrap ? "Yes" : "No");

MailMessage mailMessage = new MailMessage(
emailSettings.MailFromAddress, //From
emailSettings.MailToAddress, //To
"New order submitted",//Subject
body.ToString());//Body

if (emailSettings.WriteAsFile)
{
mailMessage.BodyEncoding = Encoding.ASCII;
}
smtpClient.Send(mailMessage);
}
}
}
}

接着使用Ninject绑定,在NinjectControllerFactory的修改AddBindings()如下:

        private void AddBindings()
{
ninjectKernel.Bind<IProductsRepository>().To<ProductsRepository>();

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

对了,这里的Email.WriteAsFile在配置文件里面配置的,主要是考虑没有smtp服务器时,将邮件复制到指定目录。其实一般的邮箱都开通了smtp服务的,所以我们将这里的默认值设为false。在Web.config里面配置<add key="Email.WriteAsFile" value="false"/>
在Ninject里面绑定时,这里是带了构造器参数,如果初次路过的朋友不是很清楚,可以看前面的比较,有针对Ninject的介绍。

接着完成CartController

我们需要修改CartController的构造器,现在需要增加IOrderProcessor类型的参数,并且需要增加一个新的action方法来处理当用户点击完成订单按钮后的post请求。修改后如下:

        private IProductsRepository repository;
private IOrderProcessor orderProcessor;
public CartController(IProductsRepository repo, IOrderProcessor proc)
{
repository = repo;
orderProcessor = proc;
}

[HttpPost]
public ViewResult Checkout(Cart cart, ShippingDetails shippingDetails)
{
if (cart.Lines.Count() == 0)
{
ModelState.AddModelError("", "Sorry,your cart is empty!");
}
if (ModelState.IsValid)
{
orderProcessor.ProcessOrder(cart, shippingDetails);
cart.Clear();
return View("Completed");
}
else
{
return View(shippingDetails);
}
}

这里可以发现有这样一个[HttpPost]特性修饰Checkout方法,表示该方法只有发起post请求时才会被调用。当用户提交表单以后,这里又一次用到了Model Binding系统,分别针对ShippingDetails自动来自http的数据创建参数和Cart使用我们自定义的绑定模型创建参数。如果你使用了单元测试,这个是需要修改CartController的构造器,可以增加一个null参数来使编译通过。

MVC框架通过使用data annotation attributes检查我们对ShippingDetails应用的验证约束,任何的验证都会通过ModelState传递给action方法。我可以通过检查ModelState.IsValid属性来确定验证过程产生的问题,比如是否为空等等。注意到这里,如果没有任何的项在购物车里面,我们能够调用ModelState.AddModelError方法来注册一个错误的信息.关于Model Binding和validation,书中第二部分有章节进行专门的讲解,如果你这里跟我一样不太清楚,没问题的。

展示验证信息

当用户输入了不合法的数据时,我们可以使用@Html.ValidationSummary()在显示,当然这是在一个地方统一显示。你也可以借助其他的方法来分开显示不同的错误信息。比如这修改Checkout.cshtml如下:

... 
<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(x => x.Name)</div>
...

展示效果如下:


接下进行最后一步了,当用户填完了Ship信息时,给用户显示一个完结的页面Summary。右键Checkout方法,添加视图Completed,这里不用强类型视图。因为就显示一个成功信息而已。代码如下:

View Code
@model SportsStore.Domain.Entities.Cart
@{
Layout = null;
}
<div id="cart">
<span class="caption"><b>Your cart:</b>
@Model.Lines.Sum(x => x.Quantity) item(s),
@Model.ComputeTotalValue().ToString("c")
</span>
@Html.ActionLink("Checkout", "Index", "Cart", new { returnUrl = Request.Url.PathAndQuery }, null)
</div>

好啦,今天的笔记就到这里。

下一章也是关于该项目的,也是第一部分的最后一章了,正在学习mvc的朋友请一起坚持。到了第二部分,就进入mvc详细讲解部分了,我觉得是最核心的部分,我相信在这个项目里面我们所有的疑惑和问题都会在第二部分的内容得到答案。你相信吗?呵呵!

晚安!

相关文章
|
4月前
|
API
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
|
29天前
|
存储 前端开发 数据可视化
在实际项目中,如何选择使用 Flux 架构或传统的 MVC 架构
在实际项目中选择使用Flux架构或传统MVC架构时,需考虑项目复杂度、团队熟悉度和性能需求。Flux适合大型、高并发应用,MVC则适用于中小型、逻辑简单的项目。
|
2月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
39 1
Spring MVC——项目创建和建立请求连接
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
60 2
|
4月前
|
XML API 图形学
【Azure Developer】.Net 简单示例 "文字动图显示" Typing to SVG
【Azure Developer】.Net 简单示例 "文字动图显示" Typing to SVG
【Azure Developer】.Net 简单示例 "文字动图显示" Typing to SVG
|
4月前
|
前端开发 Java 测试技术
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
单元测试问题之在Spring MVC项目中添加JUnit的Maven依赖,如何操作
|
6月前
|
XML API 图形学
.Net 简单示例 "文字动图显示" Typing to SVG “
该文描述了一个.NET API的实现过程,该API能将输入的文字转换成SVG动态图。首先,作者展示了示例网站(&lt;https://readme-typing-svg.demolab.com/&gt;)的功能,它能将文字转化为可自定义样式的SVG动画。接着分析了示例URL的响应,发现其内容类型为`image/svg+xml`,主要由SVG、path、animate和text元素组成。通过创建一个.NET Core Web API项目,作者设置了响应内容类型为`image/svg+xml`,并将示例URL的SVG内容直接输出,成功实现了相同效果。
|
7月前
|
前端开发 数据库连接 数据库
ASP.NETMVC数据库完整CRUD操作示例
ASP.NETMVC数据库完整CRUD操作示例
70 0
|
7月前
|
SQL 开发框架 .NET
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
83 0
|
3月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
44 7