说明
本文翻译自一个国外大牛的博客,主要介绍了如何有效利用MV3中新推出的一些功能来完善项目的架构的方式,除了内容翻译,同时也加入了我的一些理解和项目实践。本文涉及到的知识有: DDD, SOA,EF code first, Dependency Injection pattern, Repository pattern,这其中主要提到的是DI,
Repository pattern和Service Location先给出一些大家可以参考的有关本文一些知识的专题介绍地址,不熟悉的同学可以先去学习一下:
Modeling
项目的创建首先是用POCO的方式建立data model classes,这里主要会创建Category和Product两个类.
View Code
Repository Pattern with EF Code first
EF 4.1的推出完美的支持了用POCO方式建立DAL,在定义数据库对象的时候不再需要继承Entity,这给创建干净的repository接口提供了极大的帮助,下面的代码具有很强的通用性:
View Code
public interface IRepository<T>: IDisposable where T : class
{
/// <summary>
/// Gets all objects from database
/// </summary>
IQueryable<T> All();
/// <summary>
/// Gets objects from database by filter.
/// </summary>
/// <param name="predicate">Specified a filter</param>
IQueryable<T> Filter(Expression<Func<T, bool>> predicate);
/// <summary>
/// Gets objects from database with filting and paging.
/// </summary>
/// <typeparam name="Key"></typeparam>
/// <param name="filter">Specified a filter</param>
/// <param name="total">Returns the total records count of the filter.</param>
/// <param name="index">Specified the page index.</param>
/// <param name="size">Specified the page size</param>
IQueryable<T> Filter<Key>(Expression<Func<T, bool>> filter , out int total, int index = 0, int size = 50);
/// <summary>
/// Gets the object(s) is exists in database by specified filter.
/// </summary>
/// <param name="predicate">Specified the filter expression</param>
bool Contains(Expression<Func<T, bool>> predicate);
/// <summary>
/// Find object by keys.
/// </summary>
/// <param name="keys">Specified the search keys.</param>
T Find(params object[] keys);
/// <summary>
/// Find object by specified expression.
/// </summary>
/// <param name="predicate"></param>
T Find(Expression<Func<T, bool>> predicate);
/// <summary>
/// Create a new object to database.
/// </summary>
/// <param name="t">Specified a new object to create.</param>
T Create(T t);
/// <summary>
/// Delete the object from database.
/// </summary>
/// <param name="t">Specified a existing object to delete.</param>
void Delete(T t);
/// <summary>
/// Delete objects from database by specified filter expression.
/// </summary>
/// <param name="predicate"></param>
int Delete(Expression<Func<T, bool>> predicate);
/// <summary>
/// Update object changes and save to database.
/// </summary>
/// <param name="t">Specified the object to save.</param>
int Update(T t);
/// <summary>
/// Get the total objects count.
/// </summary>
int Count { get; }
}
下面创建上面这个接口的实现类
View Code
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data;
namespace Demo.DAL
{
public class Repository<TObject> : IRepository<TObject>
where TObject : class
{
protected DB Context;
protected DB Context = null;
private bool shareContext = false;
public Repository()
{
Context = new DB();
}
public Repository(DB context)
{
Context = context;
shareContext = true;
}
protected DbSet<TObject> DbSet
{
get
{
return Context.Set<TObject>();
}
}
public void Dispose()
{
if (shareContext && (Context != null))
Context.Dispose();
}
public virtual IQueryable<TObject> All()
{
return DbSet.AsQueryable();
}
public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> predicate)
{
return DbSet.Where(predicate).AsQueryable<TObject>();
}
public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> filter, out int total, int index = 0, int size = 50)
{
int skipCount = index * size;
var _resetSet = filter != null ? DbSet.Where(filter).AsQueryable() : DbSet.AsQueryable();
_resetSet = skipCount == 0 ? _resetSet.Take(size) : _resetSet.Skip(skipCount).Take(size);
total = _resetSet.Count();
return _resetSet.AsQueryable();
}
public bool Contains(Expression<Func<TObject, bool>> predicate)
{
return DbSet.Count(predicate) > 0;
}
public virtual TObject Find(params object[] keys)
{
return DbSet.Find(keys);
}
public virtual TObject Find(Expression<Func<TObject, bool>> predicate)
{
return DbSet.FirstOrDefault(predicate);
}
public virtual TObject Create(TObject TObject)
{
var newEntry = DbSet.Add(TObject);
if (!shareContext)
Context.SaveChanges();
return newEntry;
}
public virtual int Count
{
get
{
return DbSet.Count();
}
}
public virtual int Delete(TObject TObject)
{
DbSet.Remove(TObject);
if (!shareContext)
return Context.SaveChanges();
return 0;
}
public virtual int Update(TObject TObject)
{
var entry = Context.Entry(TObject);
DbSet.Attach(TObject);
entry.State = EntityState.Modified;
if (!shareContext)
return Context.SaveChanges();
return 0;
}
public virtual int Delete(Expression<Func<TObject, bool>> predicate)
{
var objects = Filter(predicate);
foreach (var obj in objects)
DbSet.Remove(obj);
if (!shareContext)
return Context.SaveChanges();
return 0;
}
}
}
这里的Repository有两种运行模式,独占模式和共享模式
- 独占模式:这种模式中,data context 由Repository内部产生, data objects仅由Repository内部的data context产生和使用. (Update,Delete)
- 共享模式:在涉及到多对象操作的很多情况下,我们都会同时使用超过一个的repositorie,如果还是采用类似上面的独占方式,就会造成数据重复提交的问题,所以这里就提供了shared data context的方式,从而可以应用到类似事务之类的场景
为了更加的贴近现实,我在下面为Category和Product创建了两个扩展接口,配合IRepository接口共同规划这两个具体类。
View Code
public interface ICategoryRepository:IRepository<Category>
{
string GetUrl();
}
public interface IProductRepository : IRepository<Product>
{
string ResolvePicture();
}
public class CategoryRepository : Repository<Category>, ICategoryRepository
{
public CategoryRepository(DB context) : base(context) { }
public string GetUrl()
{
return "";
}
}
public class ProductRepostiroy : Repository<Product>, IProductRepository
{
public ProductRepostiroy(DB context) : base(context) { }
public string ResolvePicture()
{
return "";
}
}
为了共享data context,我在这里使用了UnitOfWork design模式来进行生命周期管理。
下面先引用一段大牛Martin Fowler关于UnitOfWork模式的定义:
Unit of work-- Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
A Unit of Work keeps track of everything you do during a business transaction that can affect the database. When you're done, it figures out everything that needs to be done to alter the database as a result of your work.
从Martin Flower的说明我们可以理解UnitOfWork的主要用途:
(1)管理事务 。
(2)实现数据的插入,删除和更新。
(3)防止重复更新。通过对保持对象的跟踪,防止多次提交数据库操作,提高性能。
我们知道Unit of Work pattern维护者一张与业务逻辑相关的对象的列表,这样的方式将使得业务的改变对代码的改变影响减少到最小。
这里有两种方式来使用UnitOfWork模式,
- 一种方式是实现一个通用的Repository类,超入到UnitOfWork. 然后在具体扩展方法中调用这个类,但是我觉得这并不是一个最值得推荐的方式,这样UnitOfWork将出现在项目的很多地方,我们将很难再使用Repository的子类来进行扩展。
- 直接把IRepository的继承接口插入UnitOfWork而不是通用的具体Repository类。
View Code
public interface IUnitOfWork:IDisposable
{
int SaveChanges();
}
public interface IDALContext : IUnitOfWork
{
ICategoryRepository Categories { get; }
IProductRepository Products { get; }
}
IUnitOfWork非常简单 , 它仅仅放入了save changes方法,IDALContext接口则用于定义IRepository的后续继承接口,用来定义和维护相关继承接口的data context和Repostiory类。
View Code
public class DALContext : IDALContext
{
private DB dbContext;
private ICategoryRepository categories;
private IProductRepository products;
public DALContext()
{
dbContext = new DB();
}
public ICategoryRepository Categories
{
get
{
if (categories == null)
categories = new CategoryRepository(dbContext);
return categories;
}
}
public IProductRepository Products
{
get
{
if (products == null)
products = new ProductRepostiroy(dbContext);
return products;
}
}
public int SaveChanges()
{
throw new NotImplementedException();
}
public void Dispose()
{
if (categories != null)
categories.Dispose();
if (products != null)
products.Dispose();
if (dbContext != null)
dbContext.Dispose();
GC.SuppressFinalize(this);
}
}
上面的代码具体实现了
IDALContext接口。
Service层
DAL已经构建完毕,下面需要建立SL (Service Layer),这里先建立一个新的service接口用来定义categories和products对象的创建或者获取。
SL层通常包括了所有数据操作的细节,Service的调用要设计的越简单越好。Service通常是被MVC中的Controller调用,为了简化逻辑,我们通常让Controller调用Service接口,然后通过DI的方式只想具体的Service实现方式,这种方式下的Controller和Data层的耦合将会大大降低。
View Code
Controller
通过构造插入的方式将ICategoryService实例插入到controller中:
View Code
public class HomeController : Controller
{
private ICatalogService service;
public HomeController(ICatalogService srv)
{
service = srv;
}
public ActionResult Index()
{
ViewData.Model = service.GetCategories();
return View();
}
}
最后要做的就是DI了。
Dependency Injection in MVC3
MVC3中定义了DependencyResolver.SetResolver(IDependencyResolver resolver)的方式来注册Dependency Injection容器,同事用IDependencyResolver.GetService() 方法来定位已注册的service实例,这是MVC3这个版本中提供的一种非常优雅的DI注入方式。要了解相关的信息,请参考下面的内容。
在MVC3出来之前,很多人采用创建新的ControllerFactory的方式来执行Controller的DI操作,实际上在MVC3出现以后已经没有必要这样做,因为DependencyResolver.GetService ,所以这里需要做的只是实现 IDependencyResolver.
当今业界我们可以找到很多流行的DI框架,比如 Castle Windsor, Structuremap,ObjectBuilder, Managed Extensibility Framework (MEF) and Microsoft Unity,但我觉得Microsoft Unity 2.0应该是最合适用在MVC中的DI框架,因为它最方便实施。
下面提供一段MSDN对Unity的解释:
Unity is a lightweight, extensible dependency injection container that supports interception, constructor injection, property injection, and method call injection. You can use Unity in a variety of different ways to help
decouple the components of your applications, to maximize coherence in components, and to simplify design, implementation, testing, and administration of these applications.
Unity is a general-purpose container for use in any type of Microsoft® .NET Framework-based application. It provides all of the features commonly found in dependency injection mechanisms, including methods to
register type mappings and object instances, resolve objects, manage object lifetimes, and inject dependent objects into the parameters of constructors and methods and as the value of properties of objects it resolves. In addition, Unity is extensible. You can write container extensions that change the behavior of the container, or add new capabilities. For example, the interception feature provided by Unity, which you can use to add policies to objects, is implemented as a container extension. Implement IDependencyResolver
下面代码将在MVC中实现
IDependencyResolver接口:
View Code
namespace Demo.Web
{
public class UnityDependencyResolver : IDependencyResolver
{
readonly IUnityContainer _container;
public UnityDependencyResolver(IUnityContainer container)
{
this._container = container;
}
public object GetService(Type serviceType)
{
try
{
return _container.Resolve(serviceType);
}
catch
{
return null;
}
}
public IEnumerable<object> GetServices(Type serviceType)
{
try
{
return _container.ResolveAll(serviceType);
}
catch
{
return new List<object>();
}
}
}
}
在Global.asax进行注册:
View Code
protected void Application_Start() {
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var container = new UnityContainer();
container.RegisterType<ICatalogService, CatalogService>(new PerThreadLifetimeManager())
.RegisterType<IDALContext, DALContext>();
DependencyResolver.SetResolver(new UnityDependencyResolver(container));
}
最后是项目实现的截图: