摘要:

这篇文章将介绍一个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框架。

 using Ninject;
using System;
using System.Collections.Generic;
using System.Web.Mvc; namespace SportsStore.WebUI.Infrastructure
{
public class NinjectDependencyResolver : IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver(IKernel kernelParam)
{
kernel = kernelParam;
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
// put bindings here
}
}
}

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

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

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

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

创建Domain Model

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

代码:

 namespace SportsStore.Domain.Entities
{
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string Category { get; set; }
}
}

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

创建抽象业务逻辑层

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

代码:

 using SportsStore.Domain.Entities;
using System.Collections.Generic; namespace SportsStore.Domain.Abstract
{
public interface IProductRepository
{
IEnumerable<Product> Products { get; }
}
}

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

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

 创建数据库

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

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

创建数据访问层

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

以及

 创建数据访问层基础代码

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

EFDbContext.cs代码:

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

添加连接字符串:

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

注意两点:

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

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

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

创建数据访问层代码

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

 using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using System.Collections.Generic; namespace SportsStore.Domain.Concrete
{
public class EFProductRepository : IProductRepository
{
private EFDbContext context = new EFDbContext();
public IEnumerable<Product> Products
{
get
{
try
{
return context.Products;
}
catch (System.Exception e)
{
return null;
}
}
}
}
}
  • EFProductRepository类继承接口IProductRepository。
  • EFProductRepository类里包含了一个EFDbContext对象context,使用new关键字实例化context对象。
  • Products属性中,调用context.Products属性从数据库返回实体类Product的集合。

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

 using Ninject;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Concrete;
using System;
using System.Collections.Generic;
using System.Web.Mvc; namespace SportsStore.WebUI.Infrastructure
{
public class NinjectDependencyResolver : IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver(IKernel kernelParam)
{
kernel = kernelParam;
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<IProductRepository>().To<EFProductRepository>();
}
}
}

添加ProductController

 using SportsStore.Domain.Abstract;
using System.Web.Mvc; namespace SportsStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductRepository repository;

public ProductController(IProductRepository productRepository)
{
this.repository = productRepository;
} public ViewResult List()
{
return View(repository.Products);
}
}
}
  • ProductController类中,定义一个IProductRepository接口对象,使用DI的构造函数方式实例化这个对象。
  • List视图方法调用接口的Products属性和View函数,返回ViewResult视图。

添加布局视图

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

 <!DOCTYPE html>

 <html>
<head>
<meta name="viewport" content="width=device-width" />
<title>@ViewBag.Title</title>
</head>
<body>
<div>
@RenderBody()
</div>
</body>
</html>

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

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

添加List视图:

 @using SportsStore.Domain.Entities
@model IEnumerable<Product>
@{
ViewBag.Title = "Products";
} @foreach (var p in Model) {
<div>
<h3>@p.Name</h3>
@p.Description
<h4>@p.Price.ToString("c")</h4>
</div>
}

@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。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing; namespace SportsStore
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
}
}
}

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。

 using System;

 namespace SportsStore.WebUI.Models
{
public class PagingInfo
{
public int TotalItems { get; set; }
public int ItemsPerPage { get; set; }
public int CurrentPage { get; set; }
public int TotalPages
{
get
{
return (int)Math.Ceiling((decimal)TotalItems / ItemsPerPage);
}
}
}
}

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

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

 using SportsStore.Domain.Entities;
using System.Collections.Generic; namespace SportsStore.WebUI.Models
{
public class ProductsListViewModel
{
public IEnumerable<Product> Products { get; set; }
public PagingInfo PagingInfo { get; set; } }
}

创建后的文件:

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

 using SportsStore.Domain.Abstract;
using SportsStore.WebUI.Models;
using System.Web.Mvc;
using System.Linq;

namespace SportsStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductRepository repository; public int PageSize = ;

public ProductController(IProductRepository productRepository)
{
this.repository = productRepository;
} public ViewResult List(int page = )
{
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - ) * PageSize).Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
}
};
return View(model);
}
}
}
  • 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。

 using SportsStore.WebUI.Models;
using System;
using System.Text;
using System.Web.Mvc; namespace SportsStore.WebUI.HtmlHelpers
{
public static class PagingHelpers
{
public static MvcHtmlString PageLinks(this HtmlHelper html, PagingInfo pagingInfo, Func<int, string> pageUrl)
{
StringBuilder result = new StringBuilder();
for (int i = ; i <= pagingInfo.TotalPages; i++)
{
TagBuilder tag = new TagBuilder("a");
tag.MergeAttribute("href", pageUrl(i));
tag.InnerHtml = i.ToString();
if (i == pagingInfo.CurrentPage)
{
tag.AddCssClass("selected");
tag.AddCssClass("btn-primary");
}
tag.AddCssClass("btn btn-default");
result.Append(tag.ToString());
}
return MvcHtmlString.Create(result.ToString());
}
}
}
  • 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视图文件。

 @model SportsStore.WebUI.Models.ProductsListViewModel

@{
ViewBag.Title = "Products";
} @foreach (var p in Model.Products) {
<div>
<h3>@p.Name</h3>
@p.Description
<h4>@p.Price.ToString("c")</h4>
</div>
}
<div>
@Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
</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方法。

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing; namespace SportsStore
{
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute(
name: null,
url: "Page{page}",
defaults: new { Controller = "Product", action = "List" }
);

routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
}
}
}
  • 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。

 <!DOCTYPE html>

 <html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="~/Content/bootstrap.css" rel="stylesheet" />
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
<title>@ViewBag.Title</title>
</head>
<body>
<div class="navbar navbar-inverse" role="navigation">
<a class="navbar-brand" href="#">SPORTS STORE</a>
</div>
<div class="row panel">
<div id="categories" class="col-xs-3">
Put something useful here later
</div>
<div class="col-xs-8">
@RenderBody()
</div>
</div>
</body>
</html>
修改List.cshtml文件,应用bootstrap。
 @model SportsStore.WebUI.Models.ProductsListViewModel

 @{
ViewBag.Title = "Products";
} @foreach (var p in Model.Products)
{
<div class="well">
<h3>
<strong>@p.Name</strong>
<span class="pull-right label label-primary">@p.Price.ToString("c")</span>
</h3>
<span class="lead"> @p.Description</span>
</div>
}
<div>
@Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
</div>

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

创建Partial视图

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

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

 @model SportsStore.Domain.Entities.Product

<div class="well">
<h3>
<strong>@Model.Name</strong>
<span class="pull-right label labelprimary">@Model.Price.ToString("c")</span>
</h3>
<span class="lead">@Model.Description</span>
</div>

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

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

 @model SportsStore.WebUI.Models.ProductsListViewModel

 @{
ViewBag.Title = "Products";
} @foreach (var p in Model.Products)
{
@Html.Partial("ProductSummary", p)
}
<div>
@Html.PageLinks(Model.PagingInfo, x => Url.Action("List", new { page = x }))
</div>

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

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

跟我学ASP.NET MVC之五:SportsStrore开始的更多相关文章

  1. 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites

    摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...

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

    摘要: 本篇文章带你一步一步创建一个简单的ASP.NET MVC程序.  创建新ASP.NET MVC工程 点击“OK”按钮后,打开下面的窗口: 这里选择“Empty”模板以及“MVC”选项.这次不创 ...

  3. 跟我学ASP.NET MVC之一:开篇有益

    摘要: ASP.NET MVC是微软的Web开发框架,结合了模型-视图-控制器(MVC)架构的有效性和整洁性,敏捷开发最前沿的思想和技术,以及现存的ASP.NET平台最好的部分.它是传统ASP.NET ...

  4. [转]我要学ASP.NET MVC 3.0(十二): MVC 3.0 使用自定义的Html控件

    本文转自:http://www.cnblogs.com/lukun/archive/2011/08/05/2128693.html 概述   在ASP.NET MVC框架中已经封装了很多基于Html标 ...

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

    摘要: 在MVC框架之前,ASP.NET假定在请求的URLs和服务器硬盘文件之间有直接的关系.服务器的职责是接收浏览器请求,从相应的文件发送输出. 这种方法只能工作于Web表单,每一个ASPX页面既是 ...

  6. 跟我学ASP.NET MVC之八:SportsStrore移动设备

    摘要: 现在的web程序开发避免不了智能手机和平板电脑上的使用,如果你希望发布你的应用程序给更广大客户使用的话,你将要拥抱可移动web浏览器的世界.向移动设备用户发布一个好的使用体验是很困难的-比只是 ...

  7. 跟我学ASP.NET MVC之六:SportsStrore添加产品目录导航

    摘要: 上一篇文章,我建立了SportsStore应用程序的核心架构.现在我将使用这个架构向这个应用程序添加功能,你将开始看到这个基础架构的作用.我将添加重要的面向客户的简单功能,在这个过程中,你将看 ...

  8. 跟我学ASP.NET MVC之十:SportsStrore安全

    摘要: 在之前的文章中,我给SportsStore应用程序添加了产品管理功能,这样一旦我发布了网站,任何人都可能修改产品信息,而这是你必须考虑的.他们只需要知道你的网站有这个功能,以及功能的访问路径是 ...

  9. 跟我学ASP.NET MVC之七:SportsStrore一个完整的购物车

    摘要: SportsStore应用程序进展很顺利,但是我不能销售产品直到设计了一个购物车.在这篇文章里,我就将创建一个购物车. 在目录下的每个产品旁边添加一个添加到购物车按钮.点击这个按钮将显示客户到 ...

随机推荐

  1. spring注解关键字

    spring注解: (1)@Controller   控制器 (2)@Autowired    按照类型匹配,可以完成对类成员变量,方法及构造函数进行标注,完成自动装配的工作   @Autowired ...

  2. ROS探索总结(十三)——导航与定位框架

    导航与定位是机器人研究中的重要部分.         一般机器人在陌生的环境下需要使用激光传感器(或者深度传感器转换成激光数据),先进行地图建模,然后在根据建立的地图进行导航.定位.在ROS中也有很多 ...

  3. Angular使用总结 --- 模版驱动表单

    表单的重要性就不多说了,Angular支持表单的双向数据绑定,校验,状态管理等,总结下. 获取用户输入 <div class="container-fluid login-page&q ...

  4. JQuery(二)---- JQ的事件与动画详解

    JQuery的事件 /** * 1.事件绑定的快捷方式: */ $("button:eq(0)").dblclick(function(){ alert("hahaah& ...

  5. JavaScript脚本放在哪里用户体验好

    javascript代码写在<head>里面: 由于这时候网页主体(body)还未加载,所以这里适合放一些不是立即执行的自定义函数,立即执行的语句则很可能会出错(视浏览器而定) javas ...

  6. SQL 逻辑优化 case when 转为 union all

    通常数据库的优化从硬件层面去考虑可分为4个方面: CPU:即降低计算复杂度,如减少sql各类聚合函数,窗口函数,case when等. IO :(较少查询结果集过程中对数据的访问量.数据优化很大程度从 ...

  7. Day8 面向对象反射 item方法 打印对象信息__str__ 构析方法__del__ 程序的异常处理

    反射:通过字符串来访问到所对应的值(反射到真实的属性上). eg: class Foo: x=1 def __init__(self,name): self.name=name def f1(self ...

  8. 浅析fork()和底层实现

    记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎.不过,“黄天不负留心人".哈~ 终于在学习进程和进程创建fork相关知识后,总算是大致摸清了其中的 ...

  9. 用calc()绘制手机图案解锁的九宫格样式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. Linux基础命令归纳大全

    Linux发行版本:基于linux内核提供桌面环境及办公套件的操作系统 (Linux内核只有一个)   1. 启动终端的快捷键: ctr + alt + t 2. 终端字体放大: ctr+shift+ ...