跟我学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框架。
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开始的更多相关文章
- 跟我学ASP.NET MVC之三:完整的ASP.NET MVC程序-PartyInvites
摘要: 在这篇文章中,我将在一个例子中实际地展示MVC. 场景 假设一个朋友决定举办一个新年晚会,她邀请我创建一个用来邀请朋友参加晚会的WEB程序.她提出了四个注意的需求: 一个首页展示这个晚会 一个 ...
- 跟我学ASP.NET MVC之二:第一个ASP.NET MVC程序
摘要: 本篇文章带你一步一步创建一个简单的ASP.NET MVC程序. 创建新ASP.NET MVC工程 点击“OK”按钮后,打开下面的窗口: 这里选择“Empty”模板以及“MVC”选项.这次不创 ...
- 跟我学ASP.NET MVC之一:开篇有益
摘要: ASP.NET MVC是微软的Web开发框架,结合了模型-视图-控制器(MVC)架构的有效性和整洁性,敏捷开发最前沿的思想和技术,以及现存的ASP.NET平台最好的部分.它是传统ASP.NET ...
- [转]我要学ASP.NET MVC 3.0(十二): MVC 3.0 使用自定义的Html控件
本文转自:http://www.cnblogs.com/lukun/archive/2011/08/05/2128693.html 概述 在ASP.NET MVC框架中已经封装了很多基于Html标 ...
- 跟我学ASP.NET MVC之十一:URL路由
摘要: 在MVC框架之前,ASP.NET假定在请求的URLs和服务器硬盘文件之间有直接的关系.服务器的职责是接收浏览器请求,从相应的文件发送输出. 这种方法只能工作于Web表单,每一个ASPX页面既是 ...
- 跟我学ASP.NET MVC之八:SportsStrore移动设备
摘要: 现在的web程序开发避免不了智能手机和平板电脑上的使用,如果你希望发布你的应用程序给更广大客户使用的话,你将要拥抱可移动web浏览器的世界.向移动设备用户发布一个好的使用体验是很困难的-比只是 ...
- 跟我学ASP.NET MVC之六:SportsStrore添加产品目录导航
摘要: 上一篇文章,我建立了SportsStore应用程序的核心架构.现在我将使用这个架构向这个应用程序添加功能,你将开始看到这个基础架构的作用.我将添加重要的面向客户的简单功能,在这个过程中,你将看 ...
- 跟我学ASP.NET MVC之十:SportsStrore安全
摘要: 在之前的文章中,我给SportsStore应用程序添加了产品管理功能,这样一旦我发布了网站,任何人都可能修改产品信息,而这是你必须考虑的.他们只需要知道你的网站有这个功能,以及功能的访问路径是 ...
- 跟我学ASP.NET MVC之七:SportsStrore一个完整的购物车
摘要: SportsStore应用程序进展很顺利,但是我不能销售产品直到设计了一个购物车.在这篇文章里,我就将创建一个购物车. 在目录下的每个产品旁边添加一个添加到购物车按钮.点击这个按钮将显示客户到 ...
随机推荐
- spring注解关键字
spring注解: (1)@Controller 控制器 (2)@Autowired 按照类型匹配,可以完成对类成员变量,方法及构造函数进行标注,完成自动装配的工作 @Autowired ...
- ROS探索总结(十三)——导航与定位框架
导航与定位是机器人研究中的重要部分. 一般机器人在陌生的环境下需要使用激光传感器(或者深度传感器转换成激光数据),先进行地图建模,然后在根据建立的地图进行导航.定位.在ROS中也有很多 ...
- Angular使用总结 --- 模版驱动表单
表单的重要性就不多说了,Angular支持表单的双向数据绑定,校验,状态管理等,总结下. 获取用户输入 <div class="container-fluid login-page&q ...
- JQuery(二)---- JQ的事件与动画详解
JQuery的事件 /** * 1.事件绑定的快捷方式: */ $("button:eq(0)").dblclick(function(){ alert("hahaah& ...
- JavaScript脚本放在哪里用户体验好
javascript代码写在<head>里面: 由于这时候网页主体(body)还未加载,所以这里适合放一些不是立即执行的自定义函数,立即执行的语句则很可能会出错(视浏览器而定) javas ...
- SQL 逻辑优化 case when 转为 union all
通常数据库的优化从硬件层面去考虑可分为4个方面: CPU:即降低计算复杂度,如减少sql各类聚合函数,窗口函数,case when等. IO :(较少查询结果集过程中对数据的访问量.数据优化很大程度从 ...
- Day8 面向对象反射 item方法 打印对象信息__str__ 构析方法__del__ 程序的异常处理
反射:通过字符串来访问到所对应的值(反射到真实的属性上). eg: class Foo: x=1 def __init__(self,name): self.name=name def f1(self ...
- 浅析fork()和底层实现
记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎.不过,“黄天不负留心人".哈~ 终于在学习进程和进程创建fork相关知识后,总算是大致摸清了其中的 ...
- 用calc()绘制手机图案解锁的九宫格样式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Linux基础命令归纳大全
Linux发行版本:基于linux内核提供桌面环境及办公套件的操作系统 (Linux内核只有一个) 1. 启动终端的快捷键: ctr + alt + t 2. 终端字体放大: ctr+shift+ ...