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(,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(, ErrorMessage = "不得超过60个字")]
public string Name { get; set; } [DisplayName("商品简介")]
[Required(ErrorMessage = "请输入商品简介")]
[MaxLength(, ErrorMessage = "商品简介不超过250字")]
public string Description { get; set; } [DisplayName("商品颜色")]
[Required(ErrorMessage = "请选择颜色")]
public Color Color { get; set; } [DisplayName("商品售价")]
[Required(ErrorMessage = "请输入商品售价")]
[Range(,,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()[])</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(, ErrorMessage = "不得超过250个字")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; } [DisplayName("会员密码")]
[Description("密码将以SHA1进行哈西运算,通过运算后的结果转为HEX表示法的字符串长度都为40")]
[Required(ErrorMessage = "请输入密码")]
[MaxLength(, ErrorMessage = "不得超过40个字")]
[DataType(DataType.Password)]
public string Password { get; set; } [DisplayName("中文姓名")]
[Description("忽略外国人")]
[Required(ErrorMessage = "请输入中文姓名")]
[MaxLength(, ErrorMessage = "不得超过5个字")]
public string Name { get; set; } [DisplayName("网络昵称")]
[Required(ErrorMessage = "请输入网络昵称")]
[MaxLength(, ErrorMessage = "不得超过15个字")]
public string Nickname { get; set; } [DisplayName("会员注册时间")]
public DateTime RegisterOn { get; set; } //AuthCode会保存一个GUID值
[DisplayName("会员启用认证码")]
[MaxLength()]
[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 = )
{
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 += ;
}
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 = ;} @foreach (var item in Model)
{
subTotal += item.Product.Price * item.Amount;
var ddlAmountList = new SelectList(Enumerable.Range(,),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>
}
MVC编程实例----简易电子商务网站(一)的更多相关文章
- Go语言练习:网络编程实例——简易图片上传网站
1.代码结构 2.运行实例 1.代码结构 $ tree . ├── photoweb.go ├── public │ ├── css │ ├── images │ └── js ├── u ...
- 请求转发:MVC设计模式、细节、请求域属性的编程实例、请求重定向和请求转发的区别
请求转发:MVC设计模式.细节.请求域属性的编程实例.请求重定向和请求转发的区别 MVC设计模式将一次请求的响应过程分成三个功能模块(一般称之为层)来协同完成,这三个模块分别是Model(模型层) ...
- 利用MVC编程模式-开发一个简易记事本app
学了极客学院一个开发记事本的课程,利用自己对MVC编程模式的简单理解重写了一遍该app. github地址:https://github.com/morningsky/MyNote MVC即,模型(m ...
- JAVA上百实例源码网站
JAVA源码包1JAVA源码包2JAVA源码包3JAVA源码包4 JAVA开源包1 JAVA开源包2 JAVA开源包3 JAVA开源包4 JAVA开源包5 JAVA开源包6 JAVA开源包7 JAVA ...
- PHP: 手把手编写自己的 MVC 框架实例教程
1 什么是MVC MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model).视图(View)和控制器(Controller ...
- hadoop2.2编程:使用MapReduce编程实例(转)
原文链接:http://www.cnblogs.com/xia520pi/archive/2012/06/04/2534533.html 从网上搜到的一篇hadoop的编程实例,对于初学者真是帮助太大 ...
- 简易漫画网站搭建-漫画喵Server版
小喵的唠叨话:寒假的时候写了一个漫画爬虫,爬取了好几个漫画,不过一直没有找到合适的漫画阅读的工具.因此最近就试着自己写一个漫画的网站,放在公网上或者局域网里,这样就能随时随地用手机.Pad看漫画了. ...
- 【原创 Hadoop&Spark 动手实践 6】Spark 编程实例与案例演示
[原创 Hadoop&Spark 动手实践 6]Spark 编程实例与案例演示 Spark 编程实例和简易电影分析系统的编写 目标: 1. 掌握理论:了解Spark编程的理论基础 2. 搭建 ...
- ASP.NET MVC 编程参考
ASP.NET MVC 编程参考 转载请注明出处:http://surfsky.cnblogs.com MVC 参考 http://msdn.microsoft.com/zh-cn/dd40 ...
随机推荐
- Visual studio code离线安装插件
Visual studio code离线安装插件 公司研发区不能连接公网,使用Visual studio code(vsc)写Golang代码需要安装Go插件,下面介绍下,vsc离线安装插件的步骤.以 ...
- python logging一个通用的使用模板
import os import logbook from logbook.more import ColorizedStderrHandler from functools import wraps ...
- vue有关小知识
截取链接参数: //截取链接参数 this.id = this.$route.query.id;
- 房上的猫:if选择结构
一.基本if结构: 1.定义:if选择结构是根据条件判断之后再做处理的一种语法结构! 2.逻辑:首先对条件进行判断 >如果为真,则执行代码块 >如果为假,执行代码块后面的部分二.常用逻 ...
- http协议【转】
HTTP协议详解 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为 ...
- Java—javac Hello.java找不到文件
刚开始编写Java代码时,会遇到很多困难,下面来说一个比较常见的错误,如下: 对于初学者,一般都是从Hello,World开始的学起的,废了好大劲儿,铜鼓了半天,终于要在DOS上运行javac Hel ...
- Virtualbox虚拟机安装与设置
Virtualbox与VMware类似,都是虚拟机软件,在win10下安装Virtualbox直接默认安装即可.版本:VirtualBox-5.2.0-118431-Win.exe 安装完成后,点击左 ...
- rtx web 分级管理系统 二次开发
fineui + ASP.NET+rtx server sdk 修正 rtx管理器 修改用户后分级目录出错问题. 加入 单用户多个部门 添加授权关闭部分采用 rsa加密.
- iOS学习——UIAlertController详解
在开发中,弹出提示框是必不可少的.这两天项目中统一对已经被iOS API废弃的UIAlertView和UIActionSheet进行替换,我们知道,UIAlertView和UIActionSheet都 ...
- SQLAlchemy框架用法详解
介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DBAPI之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL并获取执 ...