ASP.net Mvc SportsSpore项目开发(一)
1.首先建立一个解决方案SportsSpore:包含3个项目:域模型项目、MVC应用程序项目和单元测试项目。
解决方案模板采用空解决方案,
项目名称 | visual studio 项目模板 | 目的 |
SportsSpore.Domain | 类库 | 存放域实体与逻辑,通过一个以Entity Framework(实体框架)创建的存储库来建立持久化 |
SportsSpore.WebUI | asp.net MVC Web应用程序(选择项目模板时。选择空模板。并选中MVC项目 | 存放控制器与视图,充当SportsSpore应用程序的UI |
SportsSpore.UnitTests | 测试项目 | 存放用于上述两个项目的单元测试 |
新建完三个项目后在SportsSpore.WebUI上右键选择属性,选择web,选中特定页,其目的是使项目启动后不需要去猜测要查看的页面URL,而让浏览器直接请求应用程序的根URL。
项目中使用的库包:选择工具-库包管理器-包管理控制台,打开NuGet命令窗口
输入如下命令:Install-Package Ninject -version 3.0.1.10 -projectname SportsStore.WebUI
Install-Package Ninject.Web.Common -version 3.0.0.7 -projectname SportsStore.WebUI
Install-Package Ninject.Mvc3 -version 3.0.0.6 -projectname SportsStore.WebUI
Install-Package Ninject -version 3.0.1.10 -projectname SportsStore.UnitTests
Install-Package Ninject.Web.Common -version 3.0.0.7 -projectname SportsStore.UnitTests
Install-Package Ninject.Mvc3 -version 3.0.0.6 -projectname SportsStore.UnitTests
Install-Package Moq -version 4.1.1309.1617 -projectname SportsStore.WebUI
Install-Package Moq -version 4.1.1309.1617 -projectname SportsStore.UnitTests
Install-Package Microsoft.Aspnet.Mvc -version 5.0.0 -projectname SportsStore.Domain
然后设置一些依赖项和程序集的引用 ,在各个项目上右击-添加引用
项目名 | 解决方案依赖项 | 程序集引用 |
SportsStore.Domain |
None | System.componentModel.Data.Annotations |
SportsStore.WebUI |
SportsStore.Domain |
None |
SportsStore.UnitTests |
SportsStore.Domain SportsStore.WebUI |
System.Web Microsoft.CSharp |
另外项目间的引用
至此,项目的准备工作已经差不多了。
2.设置DI容器
此处DI容器用的使Ninject,首先使用Ninject创建一个自定义的依赖项解析器,以便MVC框架用它创建整个应用程序的实例化对象。
首先在SportsStore.WebUI项目中添加一个Infrastructure文件夹,并在其中添加一个NinjectDependencyResolver.cs的类文件。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Ninject;
using Moq;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.Domain.Concrete;
namespace SportsStore.WebUI.Infrastructure
{
public class NinjectDependencyResolver : IDependencyResolver
{
private IKernel kernel;
public NinjectDependencyResolver(IKernel kernelParam)
{
kernel = kernelParam;
AddBindings();
}
private void AddBindings()
{//绑定
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
}
}
搭建NinjectDependencyResolver 与MVC依赖项注入支持之间的桥梁,这一步操作是在App_Start/NinjectWebCommon.cs文件中完成的,该文件是由Ninject包添加到项目中的
NinjectWebCommon.cs
private static void RegisterServices(IKernel kernel)
{//手动添加
System.Web.Mvc.DependencyResolver.SetResolver(new SportsStore.WebUI.Infrastructure.NinjectDependencyResolver(kernel));
}
3.域模型
所有的MVC项目都是从创建域模型开始的,因为MVC框架应用程序中的每一件事情都是围绕域模型而展开的。
在Sports Spore.Domain 中创建一个文件夹Entities,然后创建一个新的C#类文件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; }
}
}
4.创建抽象存储库
为了获取数据库中Product的实体,同时为了使其在数据库模型实体与存储接收逻辑之间保持一定程度的分离,所以首先定义一个接口。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SportsStore.Domain.Entities;
namespace SportsStore.Domain.Abstract
{//创建一个存储库模式
public interface IProductsRepository
{//该接口使用了IQueryable<T>接口,可以让调用程序获取一个Product对象序列,而不必说明从哪或如何获取和接收数据。
IEnumerable<Product> Products { get; }
}
}
同时修改NinjectDependencyResolver类中的AddBindings方法,创建一个模仿存储库,以查看显示的效果
private void AddBindings()
{//绑定
Mock<IProductsRepository> mock = new Mock<IProductsRepository>();
mock.Setup(m => m.Products).Returns(new List<Product> {
new Product {Name="Football",Price = 25 },
new Product {Name="Surf board",Price = 179 },
new Product {Name="Running shoes",Price = 95 }
});
kernel.Bind<IProductsRepository>().ToConstant(mock.Object);
}
5.显示产品列表
右键在Sports Store.WebUI项目中的Controllers文件夹中添加控制器,选择MVC5-空,将其命名为Product Controller
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
using SportsStore.WebUI.Models;
namespace SportsStore.WebUI.Controllers
{
public class ProductController : Controller
{
private IProductsRepository repository;
public ProductController(IProductsRepository productRepository)
{
this.repository = productRepository;
}
public ViewResult List()
{
return View(repository.Products);
}
给List动作方法添加默认视图,右击List动作方法,添加视图,选择空模板,并在模型类中选择Product,另外勾选上使用布局页。
vs会创建List.cshtml的同时创建一个_ViewStart.cshtml和Shared/_Layout.cshtml文件。
可编辑_Layout.cshtml中的<body>中的内容,使其只留下
<div >
@RenderBody()
</div>
同时编辑List.cshtml
@using SportsStore.Domain.Entities
@model IEnumerable<Product>
@foreach (var p in Model)
{
<div>
<h3>@p.Name</h3>
@p.Description
<h4>@p.Price.ToString("c")</h4>
</div>
}
ToString(“c”)方法,将Price属性转换成一个字符串,它会根据服务器语言设置,将数字值渲染成货币形式。
最后在App_Start/RouteConfig.cs文件中设置一下路由即可查看一下简单的展示效果
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Product", action = "List", id = UrlParameter.Optional }
);
6.连接数据库
此处使用SQL server数据库,并用Entity Framework来访问数据库(EF是Microsoft.Net的ORM(对象关系映射)框架)
首先创建数据库SportsStore
并创建数据库表Products
create table Products
{
[ProductID] int not null primary key identity,
[Name] nvarchar(100) not null,
[Description] nvcarchar(500) not null,
[Category] nvarchar(50) not null,
[Price] decimal(16,2) not null
}
自行向数据库中添加一些测试数据即可.可设置ProductID为自增字段。
Entity Framework有一个很好的特性,Code-First(代码先行),其思想是先定义模型中的类,再通过这些类生成数据库。但目前数据库已经创建,现在将模型类与现在的数据库关联起来就可以了,在工具中选择库包管理器-包管理器控制台,输入:
Install-Package EntityFramework -projectname SportsStore.WebUI
Install-Package EntityFramework -projectname SportsStore.Domain
向Domain和web UI中添加EntityFramework包。
下面创建一个上下文类EFDbContext.cs,使模型与数据库关联起来。
在SportsStore.Domain项目中创建一个新的文件夹,名称为Concrete,在其中添加改类。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SportsStore.Domain.Entities;
namespace SportsStore.Domain.Concrete
{
public class EFDbContext : DbContext
{
public DbSet<Product> Products {get;set;}
}
}
为了利用code-first特性,需要创建一个派生于System.Data.Entity.DbContext的类。这个类会为数据库中的每个表自定义一个属性。该属性的名称为数据库表名,而DbSet结果的类型参数为模型类型,由EntityFramework用于表示数据表的各个数据行。这个例子中属性名为Products,而参数类型是Product。
SportsStore.WebUI的Web.config中配置数据库连接
<connectionStrings>
<!--<add name="EFDbContext" connectionString="server=服务器名;database=SportsStore;integrated security=SSPI;" providerName="System.Data.SqlClient" />-->
<add name="EFDbContext" connectionString="server=服务器名;database=SportsStore;uid=sa;password=密码" providerName="System.Data.SqlClient" />
</connectionStrings>
第一种是用于Windows身份验证的,第二种是用于server身份验证的。
接下来在SportsStore.Domain中的Concrete文件夹中添加一个类文件,其名称为EFProductRepository.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SportsStore.Domain.Abstract;
using SportsStore.Domain.Entities;
namespace SportsStore.Domain.Concrete
{
public class EFProductRepository : IProductsRepository
{
private EFDbContext context = new EFDbContext();
public IEnumerable<Product> Products
{
get
{
return context.Products;
}
}
}
}
这就是存储库类,实现了IProductsRepository接口,并使用了一个EFDbContext实例,以便用EntityFramework接收数据库的数据。
最后在SportsStore.WebUI下的NinjectDependencyResolver.cs文件中修改AddBindings
private void AddBindings()
{//绑定
kernel.Bind<IProductsRepository>().To<EFProductRepository>();
}
7添加分页
为了实现分页效果,在ProductController中修改List动作
public class ProductController : Controller
{
private IProductsRepository repository;
public int PageSize = 1;
public ProductController(IProductsRepository productRepository)
{
this.repository = productRepository;
}
public ViewResult List(int page = 1)
{
ProductsListViewModel model = new ProductsListViewModel
{
Products = repository.Products.OrderBy(p => p.ProductID).Skip((page - 1) * PageSize).Take(PageSize),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = repository.Products.Count()
}
};
return View(model);
}
}
为了使用HTML辅助器方法,创建一个视图模型,在webUI的Models文件夹中添加一个类文件PagingInfo
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
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); }
}
}
}
视图模型不属于域模型,此处应与Product区别开来。
添加HTML辅助器方法
在SportsStore.WebUI项目中创建一个新文件夹HtmlHelpers,并添加一个新的类文件PagingHelpers.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.WebUI.Models;
using System.Text;
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 = 1; 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());
}
}
}
至此还需要为PaginInfo视图模型类添加一个类文件,在Models文件夹中创建
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using SportsStore.WebUI.Models;
using System.Text;
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 = 1; 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());
}
}
}
在List.cshtml中调用HTML辅助器方法
@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>
在RouteConfig.cs中添加一个新路由,一定将新路由放在Default路由之前,路由按顺序进行处理。
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace SportsStore.WebUI
{
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 }
);
}
}
}
8设置内容样式
安装Bootstrap包
工具-库包管理器-包管理控制台
Install-Package -version 3.0.0 bootstrap -projectname SportsStore.WebUI
在布局中使用样式
_ViewStart.cshtml
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
属性指明,在视图未明确指定布局时,将使用_Layout.cshtml文件的布局
_Layout.cshtml文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title</title>
<link href="~/Content/bootstrap.css" rel="stylesheet"/>
<link href="~/Content/bootstrap-theme.css" rel="stylesheet" />
</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
@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>
9创建分部视图
分部视图是一种自包含的文件,且可以跨视图重用,这有助于减少重复。
在SportsStore。WebUI下载Views/Shared文件夹中添加视图
@model SportsStore.Domain.Entities.Product
<div class="well">
<h3>
<strong>@Model.Name</strong>
<span class="pull-right label label-primary">@Model.Price.ToString("c")</span>
</h3>
<span class="lead">@Model.Description</span>
</div>
在List.cshtml中使用
@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>