• [.NET] 一步步打造一个简单的 MVC 电商网站


    http://www.cnblogs.com/liqingwen/p/6640861.html

    一步步打造一个简单的 MVC 电商网站 - BooksStore(一)

      本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.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
                    ? null
                    : (IController) _kernel.Get(controllerType);
            }
    
            /// <summary>
            /// 添加绑定
            /// </summary>
            private void AddBindings()
            {
                
            }
        }
    复制代码
      4.并且在 Global.asax 中加入一行代码,告诉 MVC 用新建的类来创建控制器对象。
    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());
            }
        }
    Global.asax

    二、创建域模型实体

         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)
              50 , -- 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)
              60 , -- Price - decimal
              N'.NET'  -- Category - nvarchar(50)
            )
    
    INSERT INTO dbo.Book
            ( 
              Name ,
              Description ,
              Price ,
              Category
            )
    VALUES  ( 
              N'多线程从入门到精通' , -- Name - nvarchar(100)
              N'好书-多线程从入门到精通' , -- Description - nvarchar(max)
              65 , -- 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)
              65 , -- 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)
              45 , -- 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)
              45 , -- 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)
              45 , -- 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;
         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;
            }
    Site.css

      现在,分页也已经有了效果,基本界面就出来了。

      本系列的 GitHub地址:https://github.com/liqingwen2015/Wen.BooksStore


    【博主】反骨仔

    【原文】http://www.cnblogs.com/liqingwen/p/6640861.html

    【参考】《精通 ASP.NT MVC ...》

     
    • 感谢您的阅读。喜欢的、有用的就请大哥大嫂们赏几个小钱花花,没钱的就请高抬贵手“推荐一下”吧!你的物质和精神支持是博主强大的写作动力。欢迎转载!
    • 博主的文章没有高度、深度和广度,只是凑字数。由于博主的水平不高(其实是个菜B),不足和错误之处在所难免,希望大家能够批评指出。
    • 我的博客:http://www.cnblogs.com/liqingwen/
    • 博主是利用读书、参考、引用、抄袭、复制和粘贴等多种方式打造成自己的纯镀 24k 文章,请原谅博主成为一个无耻的文档搬运工!
     
     
    好文要顶 关注我 收藏该文
    12
    0
     
     
     
    « 上一篇:[SQL] SQL 基础知识梳理(七)- 集合运算
  • 相关阅读:
    models F Q查询
    Django models 多对多 操作
    Cookies与session的区别
    Form表单验证
    图片上传
    Django进阶
    sql 单个字段去重查询 distinc 和 group by的效率问题
    Django知识点整理
    Web应用请求和响应 HTTP相关
    Django中的几种重定向方式
  • 原文地址:https://www.cnblogs.com/blsong/p/6645943.html
Copyright © 2020-2023  润新知