跟我学ASP.NET MVC之五:一个完整的应用程序 - SportsStrore

Posted 丹尼大叔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跟我学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方法生成超链接。

运行程序,得到运行结果:以上是关于跟我学ASP.NET MVC之五:一个完整的应用程序 - SportsStrore的主要内容,如果未能解决你的问题,请参考以下文章

跟我学ASP.NET MVC之十一:URL路由

跟我学ASP.NET MVC之二:第一个ASP.NET MVC程序

显示从 ASP.NET MVC 中的命令行进程获取的数据

跟我一起学习ASP.NET 4.5 MVC4.0

ASP.NET + MVC5 入门完整教程八 -—-- 一个完整的应用程序(下)

跟我一起学习ASP.NET 4.5 MVC4.0