摘要:

这篇文章将介绍一个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. DB Query Analyzer 6.02 is released, 71 articles concerned have been published

    DB Query Analyzer is presented by Master Genfeng, Ma from Chinese Mainland. It has English version n ...

  2. Android使用统计图AChartEngine 来展示数据

    本文采用的统计图参考:AChartEngine 访问地址 :http://code.google.com/p/achartengine/ 先给出效果图 本文的开发代码主要是这些:对一些代码进行修改 以 ...

  3. SharePoint 查找字段内部名称的小方法

    今天逛博客园,偶然看到了下面的文章,介绍不用工具查看SharePoint字段内部名称,也介绍下自己的小方法. http://www.cnblogs.com/sunjunlin/archive/2012 ...

  4. 双系统或三系统:Grub Rescue修复方法

    我是在装三系统的时候(1.WIN 7 ,2.Ubuntu 12.04 ,3.CentOS 6.4 ),中间步骤出错,造成引导区覆盖,grub乱掉了. 症状: 开机显示:GRUB loading err ...

  5. 快速掌握Oracle异常

    自定义例外是指由PL/SQL开发人员所定义的例外.预定义例外和非预定义例外都和Oracle错误有关,并且出现Oracle错误时会隐含的处罚相应例外:而自定义例外与Oracle错误没有任何关联,它是由开 ...

  6. git对远程分支和tag的操作

    技术 Git查看.删除.重命名远程分支和tag 11/17/2012zrong7条评论69,235 次查看 本站文章除注明转载外,均为本站原创或者翻译. 本站文章欢迎各种形式的转载,但请18岁以上的转 ...

  7. Struts2 中的数据传输的几种方式

    1.     如何将参数从界面传递到Action? 你可以把Struts2中的Action看做是Struts1的Action+ActionForm,即只需在Action中定义相关的属性(要有gette ...

  8. 两个Web应用必须的Servlet Filter

    其实原文是一个英文文章“Two Servlet Filters Every Web Application Should Have” 文章说了2个Filter: GzipFilter ChcheFil ...

  9. OO,OO以后,及其极限

    1.什么是软件开发? 软件开发的过程就是人们使用各种计算机语言将人们关心的现实世界映射到计算机世界的过程: 现在的计算机的数学理论基础是由计算机的开山鼻祖,大名鼎鼎的图灵于1937年提出的图灵机模型. ...

  10. 浅谈 RxAndroid + Retrofit + Databinding

    http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2016/0131/3930.html 最近 RxAndroid .MVP.MVVM 一直是 And ...