zoukankan      html  css  js  c++  java
  • 跟我学ASP.NET MVC之五:SportsStrore开始

    摘要:

    这篇文章将介绍一个ASP.NET应用程序SportsStore的开发过程。

    开始

    创建解决方案

    创建工程

    在New ASP.NET Project - SportsStore窗口中,选择Empty模板和MVC folders。其他的模板将自动给你创建一些文件夹和文件,这里我选择Empty,从干净的工程里开始,演示如何将模板的东西加进来。

      创建后,将SportsStore工程改名为SportsStore.UI。

     创建另一个Class Library工程:SportsStore.Domain。

    创建后的工程结构:

    安装Package,注意版本冲突:

    Ninject:

     Ninject.Web.Common:

       Ninject.MVC3:

     

    安装完Package后的SportsStore.WebUI引用:

    在工程SportsStore.WebUI中添加SportsStore.Domain的引用:

    添加引用后:

     

    设置DI容器

    在SportsStore.WebUI工程中添加文件夹Infrastructure,在文件夹中添加代码文件NinjectDependencyResolver.cs。用来实例化DI容器定义的对象。

    在我的博客文章:Ninject之旅之十三:Ninject在ASP.NET MVC程序上的应用(附程序下载)介绍了如何在ASP.NET项目中运用Ninject框架。

     1 using Ninject;
     2 using System;
     3 using System.Collections.Generic;
     4 using System.Web.Mvc;
     5 
     6 namespace SportsStore.WebUI.Infrastructure
     7 {
     8     public class NinjectDependencyResolver : IDependencyResolver
     9     {
    10         private IKernel kernel;
    11         public NinjectDependencyResolver(IKernel kernelParam)
    12         {
    13             kernel = kernelParam;
    14             AddBindings();
    15         }
    16         public object GetService(Type serviceType)
    17         {
    18             return kernel.TryGet(serviceType);
    19         }
    20         public IEnumerable<object> GetServices(Type serviceType)
    21         {
    22             return kernel.GetAll(serviceType);
    23         }
    24         private void AddBindings()
    25         {
    26             // put bindings here
    27         }
    28     }
    29 }

    修改代码NinjectWebCommon.cs内的方法:RegisterServices。(安装Ninject Package时自动在文件夹App_Start中创建了这个代码文件),建立ASP.NET MVC应用程序和Ninject框架之间的桥梁。

    1         /// <summary>
    2         /// Load your modules or register your services here!
    3         /// </summary>
    4         /// <param name="kernel">The kernel.</param>
    5         private static void RegisterServices(IKernel kernel)
    6         {
    7             System.Web.Mvc.DependencyResolver.SetResolver(new SportsStore.WebUI.Infrastructure.NinjectDependencyResolver(kernel));
    8         }   

    加粗行的代码就是用于建立ASP.NET MVC应用程序和Ninject框架之间的桥梁。

    至此,一个简单的ASP.NET MVC应用程序的框架部分完成了。

    创建Domain Model

    在工程SportsStore.Domain工程内创建文件夹Entities,在文件夹中创建代码文件Product.cs。

    代码: 

     1 namespace SportsStore.Domain.Entities
     2 {
     3     public class Product
     4     {
     5         public int ProductID { get; set; }
     6         public string Name { get; set; }
     7         public string Description { get; set; }
     8         public decimal Price { get; set; }
     9         public string Category { get; set; }
    10     }
    11 }

    Product类是一个简单实体类,注意类的访问属性改成了public。

    创建抽象业务逻辑层

    在SportsStore.Domain工程里,添加文件夹Abstract,用于放置抽象访问层的接口代码。

     

    代码:

     1 using SportsStore.Domain.Entities;
     2 using System.Collections.Generic;
     3 
     4 namespace SportsStore.Domain.Abstract
     5 {
     6     public interface IProductRepository
     7     {
     8         IEnumerable<Product> Products { get; }
     9     }
    10 }

     接口IProductRepository目前只定义了一个接口属性Products,用于枚举所有的产品集合。

    定义接口,有利于使用Mock工具定义单元测试。

     创建数据库

     使用SQL Server数据库,创建数据库SportsStore。在数据库里创建表Products。

    在Produts表里添加一些测试数据:

    创建数据访问层

    本文将使用EntityFramework框架作为数据访问层底层框架,首先为工程SportsStore.WebUI和工程SportsStore.Domain安装EntityFramework框架。

    以及

     创建数据访问层基础代码

    在SportsStore.Domain工程里添加文件夹Concrete,在文件夹中添加代码文件EFDbContext.cs。

    EFDbContext.cs代码:

     1 using SportsStore.Domain.Entities;
     2 using System.Data.Entity;
     3 
     4 namespace SportsStore.Domain.Concrete
     5 {
     6     public class EFDbContext : DbContext
     7     {
     8         public DbSet<Product> Products { get; set; }
     9     }
    10 }
    EFDbContext类继承DbContext类,作为数据访问层容器。每一个表定义一个DbSet的泛型属性。
    public DbSet<Product> Products { get; set; } 表示将Products属性映射到数据库的Products表,他的实体类是Product。

    添加连接字符串:

    修改SportsStore.WebUI工程根目录下的文件Web.config,添加connectionStrings节点:
    1   <connectionStrings>
    2     <add name="EFDbContext" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=SportsStore;Integrated Security=True" />
    3   </connectionStrings>

    注意两点:

    1. 根目录下的Web.config文件。

    2. add name属性需要跟数据访问层的类类名相同,这里是EFDbContext。

    3. connectionStrings节点必须在configSections节点的下方。或者说configSections必须是Web.config文件的第一个节点。

    创建数据访问层代码

    在Concrete文件夹中创建代码文件EFProductRepository.cs。

     1 using SportsStore.Domain.Abstract;
     2 using SportsStore.Domain.Entities;
     3 using System.Collections.Generic;
     4 
     5 namespace SportsStore.Domain.Concrete
     6 {
     7     public class EFProductRepository : IProductRepository
     8     {
     9         private EFDbContext context = new EFDbContext();
    10         public IEnumerable<Product> Products
    11         {
    12             get
    13             {
    14                 try
    15                 {
    16                     return context.Products;
    17                 }
    18                 catch (System.Exception e)
    19                 {
    20                     return null;
    21                 }
    22             }
    23         }
    24     }
    25 }
    • EFProductRepository类继承接口IProductRepository。
    • EFProductRepository类里包含了一个EFDbContext对象context,使用new关键字实例化context对象。
    • Products属性中,调用context.Products属性从数据库返回实体类Product的集合。

    要使用这个repository类,我需要使用Ninject容器添加对这个类EFProductRepository的绑定。在类NinjectDependencyResolver中修改方法AddBindings,添加EFProductRepository绑定到接口EFProductRepository。

     1 using Ninject;
     2 using SportsStore.Domain.Abstract;
     3 using SportsStore.Domain.Concrete;
     4 using System;
     5 using System.Collections.Generic;
     6 using System.Web.Mvc;
     7 
     8 namespace SportsStore.WebUI.Infrastructure
     9 {
    10     public class NinjectDependencyResolver : IDependencyResolver
    11     {
    12         private IKernel kernel;
    13         public NinjectDependencyResolver(IKernel kernelParam)
    14         {
    15             kernel = kernelParam;
    16             AddBindings();
    17         }
    18         public object GetService(Type serviceType)
    19         {
    20             return kernel.TryGet(serviceType);
    21         }
    22         public IEnumerable<object> GetServices(Type serviceType)
    23         {
    24             return kernel.GetAll(serviceType);
    25         }
    26         private void AddBindings()
    27         {
    28             kernel.Bind<IProductRepository>().To<EFProductRepository>();
    29         }
    30     }
    31 }

    添加ProductController

     1 using SportsStore.Domain.Abstract;
     2 using System.Web.Mvc;
     3 
     4 namespace SportsStore.WebUI.Controllers
     5 {
     6     public class ProductController : Controller
     7     {
     8         private IProductRepository repository;
     9 
    10         public ProductController(IProductRepository productRepository)
    11         {
    12             this.repository = productRepository;
    13         }
    14 
    15         public ViewResult List()
    16         {
    17             return View(repository.Products);
    18         }
    19     }
    20 }
    • ProductController类中,定义一个IProductRepository接口对象,使用DI的构造函数方式实例化这个对象。
    • List视图方法调用接口的Products属性和View函数,返回ViewResult视图。

    添加布局视图

    在SportsStore.WebUI工程的Views文件夹里,添加文件夹Shared。在文件夹内创建文件_Layout.cshtml。

     1 <!DOCTYPE html>
     2 
     3 <html>
     4 <head>
     5     <meta name="viewport" content="width=device-width" />
     6     <title>@ViewBag.Title</title>
     7 </head>
     8 <body>
     9     <div>
    10         @RenderBody()
    11     </div>
    12 </body>
    13 </html>

    在Views文件夹的根目录下,添加_ViewStart.cshtml文件。

    1 @{
    2     Layout = "~/Views/Shared/_Layout.cshtml";
    3 }

    添加List视图:

     1 @using SportsStore.Domain.Entities
     2 @model IEnumerable<Product>
     3 @{
     4     ViewBag.Title = "Products";
     5 } 
     6 
     7 @foreach (var p in Model) {
     8 <div>
     9     <h3>@p.Name</h3>
    10     @p.Description
    11     <h4>@p.Price.ToString("c")</h4>
    12 </div>
    13 }

    @model IEnumerable<Product>指定该视图的数据模型是IEnumerable<Product>类型。

    Model是一个Product集合,通过foreach访问这个集合。

    p.Price.ToString("c")是按当前应用程序的文化信息格式化显示产品金额。可以修改Web.config文件的system.web节点下的globalization信息修改格式化信息。例如下面将修改成英镑格式:

    <globalization culture="en-GB" uiCulture="en-GB" />

    运行程序,访问url路径 /Product/List,得到运行结果:

     设置默认路由

    上面运行结果,如果删除/Product/List,将得到404页面。

    这时候,我们需要修改默认路由,网站访问默认访问路径/Product/List。

    在文件夹App_Start下,找到代码文件RouteConfig.cs,修改方法RegisterRoutes。

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Web;
     5 using System.Web.Mvc;
     6 using System.Web.Routing;
     7 
     8 namespace SportsStore
     9 {
    10     public class RouteConfig
    11     {
    12         public static void RegisterRoutes(RouteCollection routes)
    13         {
    14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    15 
    16             routes.MapRoute(
    17                 name: "Default",
    18                 url: "{controller}/{action}/{id}",
    19                 defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
    20             );
    21         }
    22     }
    23 }

    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }

    修改成

    defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }

    修改默认路由后,默认的访问路径就变成了Product/List。

    再次运行程序,得到运行结果:

    添加分页

    现在在一个页面中显示了所有产品,但是在应用程序中如果产品数量比较多,一般要使用分页技术对列表进行分页。

    首先需要添加视图模型类PageInfo。

    为了支持HTML helper,我将向视图传递这些信息:页面数量、当前页数、总页数和产品数量。最简单的方法就是创建一个视图模型。

    在工程SportsStore.WebUI里找到文件夹Models,在文件夹内添加代码文件PagingInfo.cs。

     1 using System;
     2 
     3 namespace SportsStore.WebUI.Models
     4 {
     5     public class PagingInfo
     6     {
     7         public int TotalItems { get; set; }
     8         public int ItemsPerPage { get; set; }
     9         public int CurrentPage { get; set; }
    10         public int TotalPages
    11         {
    12             get
    13             {
    14                 return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
    15             }
    16         }
    17     }
    18 }

    这也是一个简单类,只包含了模型属性,不包含方法。

    有了分页模型类,还需要定义一个包含产品列表和分页信息对象的模型类ProductsListViewModel。

     1 using SportsStore.Domain.Entities;
     2 using System.Collections.Generic;
     3 
     4 namespace SportsStore.WebUI.Models
     5 {
     6     public class ProductsListViewModel
     7     {
     8         public IEnumerable<Product> Products { get; set; }
     9         public PagingInfo PagingInfo { get; set; }10     }
    11 }

    创建后的文件:

     有了视图模型类,现在需要修改ProductController,使用这两个模型类。

     1 using SportsStore.Domain.Abstract;
     2 using SportsStore.WebUI.Models;
     3 using System.Web.Mvc;
     4 using System.Linq;
     5 
     6 namespace SportsStore.WebUI.Controllers
     7 {
     8     public class ProductController : Controller
     9     {
    10         private IProductRepository repository;
    11 
    12         public int PageSize = 4;
    13 
    14         public ProductController(IProductRepository productRepository)
    15         {
    16             this.repository = productRepository;
    17         }
    18 
    19         public ViewResult List(int page = 1)
    20         {
    21             ProductsListViewModel model = new ProductsListViewModel
    22             {
    23                 Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize),
    24                 PagingInfo = new PagingInfo
    25              {
    26                     CurrentPage = page,
    27                     ItemsPerPage = PageSize,
    28                     TotalItems = repository.Products.Count()
    29              }
    30           };
    31             return View(model);
    32         }
    33     }
    34 }
    • using SportsStore.WebUI.Models;:使用视图模型类,需要添加引用
    • public int PageSize = 4;:定义分页记录数为4。
    • List(int page = 1):List方法默认参数是1,如果访问url:Product/List,将默认返回第一页产品。
    • using System.Linq;:分页调用了Linq的扩展方法,所以需要添加对Linq的引用。
    • ProductsListViewModel model = new ProductsListViewModel:生成视图模型对象,包含Products对象和PagingInfo对象。
    • Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize):获得本页中的Product列表。
    • return View(model);:将新的视图模型返回至视图。

    下面需要创建帮助类,生成分页HTML元素。

    创建文件夹HtmlHelpers。

    在文件夹内创建代码文件PagingHelpers.cs。

     1 using SportsStore.WebUI.Models;
     2 using System;
     3 using System.Text;
     4 using System.Web.Mvc;
     5 
     6 namespace SportsStore.WebUI.HtmlHelpers
     7 {
     8     public static class PagingHelpers
     9     {
    10         public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl)
    11         {
    12             StringBuilder result = new StringBuilder();
    13             for (int i = 1; i <= pagingInfo.TotalPages; i++)
    14             {
    15                 TagBuilder tag = new TagBuilder("a");
    16                 tag.MergeAttribute("href", pageUrl(i));
    17                 tag.InnerHtml = i.ToString();
    18                 if (i == pagingInfo.CurrentPage)
    19                 {
    20                     tag.AddCssClass("selected");
    21                     tag.AddCssClass("btn-primary");
    22                 }
    23                 tag.AddCssClass("btn btn-default");
    24                 result.Append(tag.ToString());
    25             }
    26             return MvcHtmlString.Create(result.ToString());
    27         }
    28     }
    29 }
    • PageLinks扩展方法使用PagingInfo对象的信息生成HTML的分页链接。
    • Func参数接受一个用于向视图生成链接的代理方法。
    • 这里还利用了TagBuilder类,调用ToString方法生成HTML的链接字符串。

    有个这个扩展方法后,还需要在视图文件夹Views里的web.config文件中对定义这个方法的所在类进行声明,声明后的视图才能够使用这个扩展方法。

    在Views文件夹内找到文件web.config。

    找到节点system.web.webPages.razor,在namespaces里添加:<add namespace="SportsStore.WebUI.HtmlHelpers"/>。

    最后是修改List.cshtml视图文件。

     1 @model SportsStore.WebUI.Models.ProductsListViewModel
     2 
     3 @{
     4     ViewBag.Title = "Products";
     5 } 
     6 
     7 @foreach (var p in Model.Products) {
     8 <div>
     9     <h3>@p.Name</h3>
    10     @p.Description
    11     <h4>@p.Price.ToString("c")</h4>
    12 </div>
    13 }
    14 <div>
    15 @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
    16 </div>
    • @model SportsStore.WebUI.Models.ProductsListViewModel:修改视图声明使用新的视图模型。
    • Model.Products:Model代表了新的ProductListViewModel类型对象,通过他获得Products列表
    • @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x })):调用Html的扩展方法PageLinks,获得分页的HTML字符串,显示到页面上。第一个参数是Model.PagingInfo,第二个参数是一个Func委托,委托使用Url.Action方法生成超链接。

    运行程序,得到运行结果:

    第一页:

    第二页:

    第三页:

    改进分页链接

     现在的分页url是这样的:http://localhost:17596/?page=2,需要传入一个request参数。这样可读性不强。

    可以将分页的url改成:http://localhost:17596/page2,这样可读性更好。

    需要修改路由信息达到这个目的。

    还是找到文件RouteConfig.cs,修改RegisterRoutes方法。

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Web;
     5 using System.Web.Mvc;
     6 using System.Web.Routing;
     7 
     8 namespace SportsStore
     9 {
    10     public class RouteConfig
    11     {
    12         public static void RegisterRoutes(RouteCollection routes)
    13         {
    14             routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    15 
    16             routes.MapRoute(
    17                 name: null,
    18                 url: "Page{page}",
    19                 defaults: new { Controller = "Product", action = "List" }
    20          );
    21 
    22             routes.MapRoute(
    23                 name: "Default",
    24                 url: "{controller}/{action}/{id}",
    25                 defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
    26             );
    27         }
    28     }
    29 }
    • routes方法MapRoute在路由表对象routes里的首部插入新的路由信息,路由将从上往下匹配url,将找到的第一个路由信息返回,生成url字符串,然后结束查找。
    • 这里定义新的路由url是:Page{page},{page}跟传入action方法的参数int page = 1对应。

    运行程序,得到运行结果,注意生成的链接指向的url变成了Page{page}。

    翻到第二页:

    第三页:

     为应用程序添加样式

     到目前为止,这个应用程序没有应用任何样式。我将使用BootStrap作为视图的样式表。

    BootStrap是Twitter公司在2012年开发的一个前端样式表框架,现在已经广泛使用在web应用程序中。

    添加BootStrap的package。

    安装后展开Content文件夹,自动给应用程序添加了bootstrap样式表。fonts文件夹下添加了bootstrap字体,Scripts文件夹下添加了bootstrap的JavaScript文件。

    修改_Layout.cshtml文件,应用bootstrap。

     1 <!DOCTYPE html>
     2 
     3 <html>
     4 <head>
     5     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     6     <link href="~/Content/bootstrap.css" rel="stylesheet" />
     7     <link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
     8     <title>@ViewBag.Title</title>
     9 </head>
    10 <body>
    11     <div class="navbar navbar-inverse" role="navigation">
    12         <a class="navbar-brand" href="#">SPORTS STORE</a>
    13     </div>
    14     <div class="row panel">
    15         <div id="categories" class="col-xs-3">
    16             Put something useful here later
    17         </div>
    18         <div class="col-xs-8">
    19             @RenderBody()
    20         </div>
    21     </div>
    22 </body>
    23 </html>
    修改List.cshtml文件,应用bootstrap。
     1 @model SportsStore.WebUI.Models.ProductsListViewModel
     2 
     3 @{
     4     ViewBag.Title = "Products";
     5 } 
     6 
     7 @foreach (var p in Model.Products)
     8 {
     9     <div class="well">
    10         <h3>
    11             <strong>@p.Name</strong>
    12             <span class="pull-right label label-primary">@p.Price.ToString("c")</span>
    13         </h3>
    14         <span class="lead"> @p.Description</span>
    15     </div>
    16 }
    17 <div>
    18 @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
    19 </div>

    运行程序,得到运行结果。

    创建Partial视图

    下面我将使用Partial视图来简化List.cshtml视图。Partial视图是一个可以嵌入到另一个视图的内容的片段。Partial视图被他们自己的文件所包含,在多个视图中被重用。这样,如果你需要在应用程序中的许多地方呈现相同样子的数据的时候,可以帮助减少很多重复的代码。

    在Views文件夹中创建代码文件ProductSummary.cshtml。将List.cshtml代码中,foreach包含的代码复制到ProductSummary.cshtml中,并将 p 改成Model。

    1 @model SportsStore.Domain.Entities.Product
    2 
    3 <div class="well">
    4     <h3>
    5         <strong>@Model.Name</strong>
    6         <span class="pull-right label labelprimary">@Model.Price.ToString("c")</span>
    7     </h3>
    8     <span class="lead">@Model.Description</span>
    9 </div>

    这里,也需要在第一行中声明Partial视图使用的模型类类型,此处是SportsStore.Domain.Entities.Product。

    修改List.cshtml文件,调用Partial视图。

     1 @model SportsStore.WebUI.Models.ProductsListViewModel
     2 
     3 @{
     4     ViewBag.Title = "Products";
     5 } 
     6 
     7 @foreach (var p in Model.Products)
     8 {
     9     @Html.Partial("ProductSummary", p)
    10 }
    11 <div>
    12 @Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
    13 </div>

    调用Html帮助类的Partial方法,呈现Partial视图。第一个参数是Partial视图的名称,也就是Partial视图的文件名。第二个参数传入传给这个视图的模型对象,这里是Product对象。

    运行程序,得到相同的运行结果。

  • 相关阅读:
    中文乱码总结之web乱码情景
    微信小程序实现navbar导航栏
    boostrap table接收到后台返回的数据格式不一致的解决方法
    bootstrap让footer固定在顶部和底部
    在vue中让某个组件重新渲染的笨方法
    网页打印事件的监听
    关于JavaScript的词法作用域及变量提升的个人理解
    函数节流之debounce
    HTML5 a标签的down属性进行图片下载
    Jquery的深浅拷贝涉及到的知识点
  • 原文地址:https://www.cnblogs.com/uncle_danny/p/9029386.html
Copyright © 2011-2022 走看看