一步步打造一个简单的 MVC 电商网站 - BooksStore(一)
本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore
《一步步打造一个简单的 MVC 电商网站 - BooksStore(一)》
《一步步打造一个简单的 MVC 电商网站 - BooksStore(二)》
《一步步打造一个简单的 MVC 电商网站 - BooksStore(三)》
《一步步打造一个简单的 MVC 电商网站 - BooksStore(四)》
简介
主要功能与知识点如下:
分类、产品浏览、购物车、结算、CRUD(增删改查) 管理、发邮件、分页、模型绑定、认证过滤器和单元测试等(预计四篇、周五、下周一和周二)。
【备注】项目使用 VS2015 + C#6 进行开发,有问题请发表在留言区哦,还有,页面长得比较丑,请见谅。
目录
- 创建项目架构
- 创建域模型实体
- 创建单元测试
- 创建控制器与视图
- 创建分页
- 加入样式
一、创建项目架构
1.新建一个解决方案“BooksStore”,并添加以下项目:
BooksStore.Domain:类库,存放域模型和逻辑,使用 EF; BooksStore.WebUI:Web MVC 应用程序,存放视图和控制器,充当显示层,使用了 Ninject 作为 DI 容器; BoosStore.UnitTest:单元测试,对上述两个项目进行测试。
Web MVC 为一个空的 MVC 项目:
2.添加项目引用(需要使用 NuGet):
这是不同项目需要引用的类库和项目
3.设置 DI 容器
我们通过 Ninject ,创建一个自定义的工厂,一个名为NinjectControllerFactory 的类继承DefaultControllerFactory(默认的控制器工厂)。你也可以在里面添加自定义的代码,改变 MVC 框架的默认行为。
AddBindings() 添加绑定方法,先留空。
public class NinjectControllerFactory : DefaultControllerFactory { private readonly IKernel _kernel; public NinjectControllerFactory() { _kernel = new StandardKernel(); AddBindings(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null "color: #800000">ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory());public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RouteConfig.RegisterRoutes(RouteTable.Routes); ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); } }二、创建域模型实体
1.在图中位置创建一个名为 Book 的实体类。
public class Book { /// <summary> /// 标识 /// </summary> public int Id { get; set; } /// <summary> /// 名称 /// </summary> public string Name { get; set; } /// <summary> /// 描述 /// </summary> public string Description { get; set; } /// <summary> /// 价格 /// </summary> public decimal Price { get; set; } /// <summary> /// 分类 /// </summary> public string Category { get; set; } }有了实体之后,我们应该创建一个“库”对该实体进行操作,而这种持久化逻辑操作也应该和域模型是进行隔离的。
2.先定义一个接口 IbookRepository,在根目录创建一个名为 Abstract 的文件夹,顾名思义就是应该放置一些抽象的类,如接口。
public interface IBookRepository { IQueryable<Book> Books { get; } }我们通过该接口就可以得到对应类的相关信息,而不需要去管该数据如何存储,以及存储的位置,这就是存储库模式的本质。
3.接下来,我们就需要对数据库进行操作了,我们使用简单的EF(ORM 对象关系模型) 去对数据库进行操作,所以需要自己通过 Nuget 下载 EF 的类库。
4.因为之前定义了接口类,接下来就应该定义实现该接口的类了
安装完之后,再次建立一个名为 Concrete 的文件夹,存放实例。
在里面创建一个 EfDbContext 的类,派生于 DbContext,该类会为用户要使用的数据库中的每个表自动的定义一个属性。该属性名为 Books,指定了表名,DbSet<Book> 表示为 Book 实体的表模型,Book 对象相当于 Books 表中的行(记录)。
public class EfDbContext : DbContext { public DbSet<Book> Books { get; set; } }再创建一个EfBookRepository 存储库类,它实现 IBookRepository 接口,使用了上文创建的 EfDbContext 上下文对象,包含了具体的方法定义。
public class EfBookRepository : IBookRepository { private readonly EfDbContext _context = new EfDbContext(); public IQueryable<Book> Books => _context.Books; }5.现在只差在数据库新建一张表了。
CREATE TABLE Book ( Id INT IDENTITY PRIMARY KEY, Name NVARCHAR(100), Description NVARCHAR(MAX), Price DECIMAL, Category NVARCHAR(50) )并插入测试数据:
INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'C#从入门到精通' , -- Name - nvarchar(100) N'好书-C#从入门到精通' , -- Description - nvarchar(max) , -- Price - decimal N'.NET' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'ASP.NET从入门到精通' , -- Name - nvarchar(100) N'好书-ASP.NET从入门到精通' , -- Description - nvarchar(max) , -- Price - decimal N'.NET' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'多线程从入门到精通' , -- Name - nvarchar(100) N'好书-多线程从入门到精通' , -- Description - nvarchar(max) , -- Price - decimal N'.NET' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'java从入门到放弃' , -- Name - nvarchar(100) N'好书-java从入门到放弃' , -- Description - nvarchar(max) , -- Price - decimal N'java' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'sql从入门到放弃' , -- Name - nvarchar(100) N'好书-sql从入门到放弃' , -- Description - nvarchar(max) , -- Price - decimal N'sql' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'sql从入门到出家' , -- Name - nvarchar(100) N'好书-sql从入门到出家' , -- Description - nvarchar(max) , -- Price - decimal N'sql' -- Category - nvarchar(50) ) INSERT INTO dbo.Book ( Name , Description , Price , Category ) VALUES ( N'php从入门到出家' , -- Name - nvarchar(100) N'好书-php从入门到出家' , -- Description - nvarchar(max) , -- Price - decimal N'php' -- Category - nvarchar(50) )测试数据
因为我希望表名为 Book,而不是 Books,所以我在之前的 Book 类上加上特性 [Table("Book")]:
三、创建单元测试
1.做完预热操作后,你可能想立即以界面的的方式进行显示,别急,先用单元测试检查一下我们对数据库的操作是否正常,通过对数据进行简单的读取,检查下连接是否成功。
2.单元测试也需要引入 ef 类库(Nuget)。
3.安装完之后会生成一个 app.config 配置文件,需要额外添加一行连接字符串(在后续的 Web UI 项目里,也需要加上这条信息,不然会提示对应的错误信息)。
<connectionStrings> <add name="EfDbContext" connectionString="server=.;database=TestDb;uid=sa;pwd=123" providerName="System.Data.SqlClient"/> </connectionStrings>4.当所有前置工作都准备好了的时候,就应该填写测试方法了,因为我插入了 7 条数据,这里我就判断一下从数据库读取出的行数是否为 7 :
[TestMethod] public void BooksCountTest() { var bookRepository=new EfBookRepository(); var books = bookRepository.Books; Assert.AreEqual(books.Count(),7); }5.在该方法体的内部单击右键,你可以看到一个“运行测试”的选项,这时你可以尝试单击它:
从这个符号可以看到,是成功了!
接下来,我们要正式从页面显示我们想要的信息了。
四、创建控制器与视图
1.先新建一个空的控制器:BookController:
2.需要我们自定义一个 Details 方法,用于后续与界面进行交互。
public class BookController : Controller { private readonly IBookRepository _bookRepository; public BookController(IBookRepository bookRepository) { _bookRepository = bookRepository; } /// <summary> /// 详情 /// </summary> /// <returns></returns> public ActionResult Details() { return View(_bookRepository.Books); } }3.接下来,要创建一个视图 View 了。
4.将 Details.cshtml 的内容替换为下面的:
@model IEnumerable<Wen.BooksStore.Domain.Entities.Book> @{ ViewBag.Title = "Books"; } @foreach (var item in Model) { <div> <h3>@item.Name</h3> @item.Description <h4>@item.Price.ToString("C")</h4> <br /> <hr /> </div> }5.改下默认的路由机制,让他默认跳转到该页面。
6.还有一点需要注意的是,因为我们使用了 Ninject 容器,并且需要对控制器中的构造函数中的参数IBookRepository 进行解析,告诉他将使用哪个对象对该接口进行服务,也就是需要修改之前的 AddBindings 方法:
7.运行的效果大致如下(因为加了点 CSS 样式,所以显示的效果可能有些许不同),结果是一致的。
五、创建分页
1.在 Models 文件夹新增一个 PagingInfo.cs 分页信息类。
/// <summary> /// 分页信息 /// </summary> public class PagingInfo { /// <summary> /// 总数 /// </summary> public int TotalItems { get; set; } /// <summary> /// 页容量 /// </summary> public int PageSize { get; set; } /// <summary> /// 当前页 /// </summary> public int PageIndex { get; set; } /// <summary> /// 总页数 /// </summary> public int TotalPages => (int)Math.Ceiling((decimal)TotalItems / PageSize); }2.新增一个 HtmlHelpers 文件夹存放一个基于 Html 帮助类的扩展方法:
public static class PagingHelper { /// <summary> /// 分页 /// </summary> /// <param name="helper"></param> /// <param name="pagingInfo"></param> /// <param name="func"></param> /// <returns></returns> public static MvcHtmlString PageLinks(this HtmlHelper helper, PagingInfo pagingInfo, Func<int, string> func) { var sb = new StringBuilder(); for (var i = 1; i <= pagingInfo.TotalPages; i++) { //创建 <a> 标签 var tagBuilder = new TagBuilder("a"); //添加特性 tagBuilder.MergeAttribute("href", func(i)); //添加值 tagBuilder.InnerHtml = i.ToString(); if (i == pagingInfo.PageIndex) { tagBuilder.AddCssClass("selected"); } sb.Append(tagBuilder); } return MvcHtmlString.Create(sb.ToString()); } }3.添加完毕后需要在配置文件内加入该命名空间
4.现在要重新修改BookController.cs 控制器内的的代码,并添加新的视图模型类BookDetailsViewModels.cs,让它继承之前的分页类。
public class BookDetailsViewModels : PagingInfo { public IEnumerable<Book> Books { get; set; } }修改后的控制器代码:
public class BookController : Controller { private readonly IBookRepository _bookRepository; public int PageSize = 5; public BookController(IBookRepository bookRepository) { _bookRepository = bookRepository; } /// <summary> /// 详情 /// </summary> /// <param name="pageIndex"></param> /// <returns></returns> public ActionResult Details(int pageIndex = 1) { var model = new BookDetailsViewModels() { Books = _bookRepository.Books.OrderBy(x => x.Id).Skip((pageIndex - 1) * PageSize).Take(PageSize), PageSize = PageSize, PageIndex = pageIndex, TotalItems = _bookRepository.Books.Count() }; return View(model); } }5.修改视图模型后,对应的视图页也需要修改
@model Wen.BooksStore.WebUI.Models.BookDetailsViewModels @{ ViewBag.Title = "Books"; } @foreach (var item in Model.Books) { <div> <h3>@item.Name</h3> @item.Description <h4>@item.Price.ToString("C")</h4> <br /> <hr /> </div> } <div class="pager"> @Html.PageLinks(Model, x => Url.Action("Details", new { pageIndex = x })) </div>六、加入样式
1.页面的样式简单的设计为 3 大板块,顶部为标题,左侧边栏为分类,主模块将显示具体内容。
我们现在要在 Views 文件夹下创建一个文件 _ViewStart.cshtml,再创建一个 Shared 的文件夹和文件 _Layout.cshtml。
2._Layout.cshtml 这是布局页,当代码执行到 @RenderBody() 时,就会负责将之前 Details.cshtml 的内容进行渲染:
<!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width" /> <title>@ViewBag.Title</title> <link href="~/Contents/Site.css" rel="stylesheet" /> </head> <body> <div id="header"> <div class="title">图书商城</div> </div> <div id="sideBar">分类</div> <div id="content"> @RenderBody() </div> </body> </html>_ViewStart.cshtml 该文件表示默认的布局页为该视图文件:
@{ Layout = "~/Views/Shared/_Layout.cshtml"; }3.网站的根目录下也要添加一个名为 Contents 的文件夹,用于存放 CSS。
body { } #header, #content, #sideBar { display: block; } #header { background-color: green; border-bottom: 2px solid #111; color: White; } #header, .title { font-size: 1.5em; padding: .5em; } #sideBar { float: left; width: 8em; padding: .3em; } #content { border-left: 2px solid gray; margin-left: 10em; padding: 1em; } .pager { text-align: right; padding: .5em 0 0 0; margin-top: 1em; } .pager A { font-size: 1.1em; color: #666; padding: 0 .4em 0 .4em; } .pager A:hover { background-color: Silver; } .pager A.selected { background-color: #353535; color: White; }现在,分页也已经有了效果,基本界面就出来了。
本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
稳了!魔兽国服回归的3条重磅消息!官宣时间再确认!
昨天有一位朋友在大神群里分享,自己亚服账号被封号之后居然弹出了国服的封号信息对话框。
这里面让他访问的是一个国服的战网网址,com.cn和后面的zh都非常明白地表明这就是国服战网。
而他在复制这个网址并且进行登录之后,确实是网易的网址,也就是我们熟悉的停服之后国服发布的暴雪游戏产品运营到期开放退款的说明。这是一件比较奇怪的事情,因为以前都没有出现这样的情况,现在突然提示跳转到国服战网的网址,是不是说明了简体中文客户端已经开始进行更新了呢?
更新日志
- 许秋怡.1995-电影少女【丽音唱片】【FLAC分轨】
- 【中国艺术歌曲典藏】温雅欣《她比烟花寂寞》紫银合金SQCD【低速原抓WAV+CUE】
- 张国荣《FinalEncounter》头版限量编号MQA-UHQ[低速原抓WAV+CUE].
- 发烧萨克斯-雪国之春(SRS+WIZOR)[原抓WAV+CUE]
- 王铮亮《慢人理论》[320K/MP3][175.31MB]
- 王铮亮《慢人理论》[FLAC/分轨][524.11MB]
- 陈致逸《赴梦之约 游戏主题原声音乐》[320K/MP3][35.82MB]
- 沈文程.1998-历年畅销歌曲精选3CD旧情也绵绵【乡城】【WAV+CUE】
- 群星.2022-福茂巨星·时空之轮日本唱片志系列DISC1黄舒骏-为你疯狂【福茂】【WAV+CUE】
- 群星.2022-福茂巨星·时空之轮日本唱片志系列DISC2范晓萱-RAIN【福茂】【WAV+CUE】
- 王闻-《男人四十4》[正版CD低速原抓WAV+CUE]
- 青燕子-八只眼演唱组《爱心》[WAV+CUE]
- 祁露想着你的好》WAV+CUE
- 陈致逸《赴梦之约 游戏主题原声音乐》[FLAC/分轨][159.96MB]
- 贵族音乐《睡眠自然流水声 ASMR白噪音背景音》[320K/MP3][155.72MB]