# [.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
## 一、前言
在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里。
## 二、规约模式的引入
在[第三专题](http://www.cnblogs.com/zhili/p/SpecificationPattern.html)我们已经详细介绍了什么是规约模式,没看过的朋友首先去了解下。下面让我们一起看看如何在网上书店案例中引入规约模式。在网上书店案例中规约模式的实现兼容了2种模式的实现,兼容了传统和轻量的实现,包括传统模式的实现,主要是为了实现一些共有规约的重用,不然的话可能就要重复写这些表达式。下面让我们具体看看在该项目中的实现。
首先是ISpecification接口的定义以及其抽象类的实现
```
public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
Expression<Func<T, bool>> Expression { get; }
}
public abstract class Specification<T> : ISpecification<T>
{
public static Specification<T> Eval(Expression<Func<T, bool>> expression)
{
return new ExpressionSpecification<T>(expression);
}
#region ISpecification<T> Members
public bool IsSatisfiedBy(T candidate)
{
return this.Expression.Compile()(candidate);
}
public abstract Expression<Func<T, bool>> Expression { get; }
#endregion
}
```
上面的实现稍微对传统规约模式进行了一点修改,添加 Expression属性来获得规约的表达式树。另外,在该案例中还定义了一个包装表达式树的规约类和没有任何条件的规约类AnySpecification。其具体实现如下:
```
public sealed class ExpressionSpecification<T> : Specification<T>
{
private readonly Expression<Func<T, bool>> _expression;
public ExpressionSpecification(Expression<Func<T, bool>> expression)
{
this._expression = expression;
}
public override Expression<Func<T, bool>> Expression
{
get { return _expression; }
}
}
public sealed class AnySpecification<T> : Specification<T>
{
public override Expression<Func<T, bool>> Expression
{
get { return o => true; }
}
}
```
接下来就是轻量规约模式的实现了,该实现涉及2个类,一个是包含扩展方法的类和一个实现统一表达式树参数的类。具体实现代码如下所示:
```
public static class SpecExprExtensions
{
public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> one)
{
var candidateExpr = one.Parameters[0];
var body = Expression.Not(one.Body);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one,
Expression<Func<T, bool>> another)
{
// 首先定义好一个ParameterExpression
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
// 将表达式树的参数统一替换成我们定义好的candidateExpr
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
}
public class ParameterReplacer : ExpressionVisitor
{
public ParameterReplacer(ParameterExpression paramExpr)
{
this.ParameterExpression = paramExpr;
}
public ParameterExpression ParameterExpression { get; private set; }
public Expression Replace(Expression expr)
{
return this.Visit(expr);
}
protected override Expression VisitParameter(ParameterExpression p)
{
return this.ParameterExpression;
}
}
```
这样,规约模式在案例中的实现就完成了,下面具体介绍下工作单元模式是如何在该项目中实现的。
## 三、工作单元模式的引入
工作单元模式,主要是为了保证数据的一致性,一些涉及到多个实体的操作我们希望它们一起被提交,从而保证数据的正确性和一致性。例如,在该项目,用户成功注册的同时需要为用户创建一个购物车对象,这里就涉及到2个实体,一个是用户实体,一个是购物车实体,所以此时必须保证这个操作必须作为一个操作被提交,这样就可以保证要么一起提交成功,要么都失败,不存在其中一个被提交成功的情况,否则就会出现数据不正确的情况,上一专题的转账业务也是这个道理,只是转账业务涉及的是2个相同的实体,都是账户实体。
从上面描述可以发现,**要保证数据的一致性,必须要有一个类统一管理提交操作,而不能由其仓储实现来提交数据改变**。根据上一专题我们可以知道,首先需要定义一个工作单元接口IUnitOfWork,工作单元接口的定义通常放在基础设施层,其定义代码如下所示:
在该项目中,对工作单元接口的方法进行了一个分离,把其方法分别定义在2个接口中,工作单元接口中仅仅定义了一个Commit方法,RegisterNew, RegisterModified和RegisterDelete方法定义在IRepositoryContext接口中。当然我觉得也可以把这4个操作都定义在IUnitOfWork接口中。这里只是遵循dax.net案例中设计来实现的。然后EntityFrameworkRepositoryContext来实现这4个操作。工作单元模式在项目中的实现代码如下所示:
```
// 仓储上下文接口
// 这里把传统的IUnitOfWork接口中方法分别在2个接口定义:一个是IUnitOfWork,另一个就是该接口
public interface IRepositoryContext : IUnitOfWork
{
// 用来标识仓储上下文
Guid Id { get; }
void RegisterNew<TAggregateRoot>(TAggregateRoot entity)
where TAggregateRoot : class, IAggregateRoot;
void RegisterModified<TAggregateRoot>(TAggregateRoot entity)
where TAggregateRoot : class, IAggregateRoot;
void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity)
where TAggregateRoot : class, IAggregateRoot;
}
public interface IEntityFrameworkRepositoryContext : IRepositoryContext
{
#region Properties
OnlineStoreDbContext DbContex { get; }
#endregion
}
// IEntityFrameworkRepositoryContext接口的实现
public class EntityFrameworkRepositoryContext : IEntityFrameworkRepositoryContext
{
// ThreadLocal代表线程本地存储,主要相当于一个静态变量
// 但静态变量在多线程访问时需要显式使用线程同步技术。
// 使用ThreadLocal变量,每个线程都会一个拷贝,从而避免了线程同步带来的性能开销
private readonly ThreadLocal<OnlineStoreDbContext> _localCtx = new ThreadLocal<OnlineStoreDbContext>(() => new OnlineStoreDbContext());
public OnlineStoreDbContext DbContex
{
get { return _localCtx.Value; }
}
private readonly Guid _id = Guid.NewGuid();
#region IRepositoryContext Members
public Guid Id
{
get { return _id; }
}
public void RegisterNew<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, Domain.IAggregateRoot
{
_localCtx.Value.Set<TAggregateRoot>().Add(entity);
}
public void RegisterModified<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, Domain.IAggregateRoot
{
_localCtx.Value.Entry<TAggregateRoot>(entity).State = EntityState.Modified;
}
public void RegisterDeleted<TAggregateRoot>(TAggregateRoot entity) where TAggregateRoot : class, Domain.IAggregateRoot
{
_localCtx.Value.Set<TAggregateRoot>().Remove(entity);
}
#endregion
#region IUnitOfWork Members
public void Commit()
{
var validationError = _localCtx.Value.GetValidationErrors();
_localCtx.Value.SaveChanges();
}
#endregion
}
```
到此,工作单元模式的引入也就完成了,接下面,让我们继续完成网上书店案例。
## 四、购物车的实现
在前一个版本中,只是实现了商品的展示和详细信息等功能。在网上商店中,都有购物车这个功能,作为一个完整的案例,该案例也不能少了这个功能。在实现购物车之前,我们首先理清下业务逻辑:访问者看到商品,然后点击商品下的加入购物车按钮,把商品加入购物车。
在上面的业务逻辑中,涉及了下面几个更细的业务逻辑:
* 如果用户没注册时,访客点击加入购物车按钮应跳转到注册界面,这样就涉及到用户注册功能的实现
* 用户注册成功后需要同时为用户创建一个购物车实例与该用户进行绑定,之后用户就可以把商品加入自己的购物车
* 加入购物车之后,用户可以查看购物车中的商品,同时也应该可以进行更新和移除操作。
通过上面的描述,大家应该自然明白了我们接下来需要哪些功能了吧,下面我们来理理:
1. 用户注册功能,涉及用户注册页面。自然就涉及用户注册服务和用户仓储的实现
2. 注册成功同时创建购物车实例。自然涉及创建购物车服务方法和购物车仓储的实现
3. 加入购物车成功后,可以查看购物车中的商品、更新和移除操作,自然涉及到购物车页面的实现。这里自然涉及到获得购物车和更新商品数量和删除购物项的服务方法。
理清了思路之后,接下来就应该去实现功能了,首先应该实现自然就是用户注册模块。实现功能模块都从底向上来实现,首先应该先定义用户聚合根,接着实现用户仓储和用户服务,最后实现控制器和视图。下面是用户注册涉及的主要类的实现:
```
// 用户聚合根
public class User : AggregateRoot
{
public string UserName { get; set; }
public string Password { get; set; }
public string Email { get; set; }
public string PhoneNumber { get; set; }
public bool IsDisabled { get; set; }
public DateTime RegisteredDate { get; set; }
public DateTime? LastLogonDate { get; set; }
public string Contact { get; set; }
//用户的联系地址
public Address ContactAddress { get; set; }
//用户的发货地址
public Address DeliveryAddress { get; set; }
public IEnumerable<Order> Orders
{
get
{
IEnumerable<Order> result = null;
//DomainEvent.Publish<GetUserOrdersEvent>(new GetUserOrdersEvent(this),
// (e, ret, exc) =>
// {
// result = e.Orders;
// });
return result;
}
}
public override string ToString()
{
return this.UserName;
}
#region Public Methods
public void Disable()
{
this.IsDisabled = true;
}
public void Enable()
{
this.IsDisabled = false;
}
// 为当前用户创建购物篮。
public ShoppingCart CreateShoppingCart()
{
var shoppingCart = new ShoppingCart { User = this };
return shoppingCart;
}
#endregion
}
public interface IUserRepository : IRepository<User>
{
bool CheckPassword(string userName, string password);
}
public class UserRepository : EntityFrameworkRepository<User>, IUserRepository
{
public UserRepository(IRepositoryContext context)
: base(context)
{
}
public bool CheckPassword(string userName, string password)
{
Expression<Func<User, bool>> userNameExpression = u => u.UserName == userName;
Expression<Func<User, bool>> passwordExpression = u => u.Password == password;
return Exists(new ExpressionSpecification<User>(userNameExpression.And(passwordExpression)));
}
}
// 用户服务契约
[ServiceContract(Namespace = "")]
public interface IUserService
{
#region Methods
[OperationContract]
[FaultContract(typeof (FaultData))]
IList<UserDto> CreateUsers(List<UserDto> userDtos);
[OperationContract]
[FaultContract(typeof(FaultData))]
bool ValidateUser(string userName, string password);
[OperationContract]
[FaultContract(typeof(FaultData))]
bool DisableUser(UserDto userDto);
[OperationContract]
[FaultContract(typeof(FaultData))]
bool EnableUser(UserDto userDto);
[OperationContract]
[FaultContract(typeof(FaultData))]
void DeleteUsers(UserDto userDto);
[OperationContract]
[FaultContract(typeof(FaultData))]
IList<UserDto> UpdateUsers(List<UserDto> userDataObjects);
[OperationContract]
[FaultContract(typeof (FaultData))]
UserDto GetUserByKey(Guid id);
[OperationContract]
[FaultContract(typeof(FaultData))]
UserDto GetUserByEmail(string email);
[OperationContract]
[FaultContract(typeof(FaultData))]
UserDto GetUserByName(string userName);
#endregion
}
public class **UserServiceImp** :ApplicationService, IUserService
{
private readonly IUserRepository _userRepository;
private readonly IShoppingCartRepository _shoppingCartRepository;
public UserServiceImp(IRepositoryContext repositoryContext,
IUserRepository userRepository,
IShoppingCartRepository shoppingCartRepository)
: base(repositoryContext)
{
_userRepository = userRepository;
_shoppingCartRepository = shoppingCartRepository;
}
public IList<UserDto> CreateUsers(List<UserDto> userDtos)
{
if (userDtos == null)
throw new ArgumentNullException("userDtos");
return PerformCreateObjects<List<UserDto>, UserDto, User>(userDtos,
_userRepository,
dto =>
{
if (dto.RegisterDate == null)
dto.RegisterDate = DateTime.Now;
},
ar =>
{
var shoppingCart = ar.CreateShoppingCart();
_shoppingCartRepository.Add(shoppingCart);
});
}
public bool ValidateUser(string userName, string password)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentNullException("userName");
if (string.IsNullOrEmpty(password))
throw new ArgumentNullException("password");
return _userRepository.CheckPassword(userName, password);
}
public bool DisableUser(UserDto userDto)
{
if(userDto == null)
throw new ArgumentNullException("userDto");
User user;
if (!IsEmptyGuidString(userDto.Id))
user = _userRepository.GetByKey(new Guid(userDto.Id));
else if (!string.IsNullOrEmpty(userDto.UserName))
user = _userRepository.GetByExpression(u=>u.UserName == userDto.UserName);
else if (!string.IsNullOrEmpty(userDto.Email))
user = _userRepository.GetByExpression(u => u.Email == userDto.Email);
else
throw new ArgumentNullException("userDto", "Either ID, UserName or Email should be specified.");
user.Disable();
_userRepository.Update(user);
RepositorytContext.Commit();
return user.IsDisabled;
}
public bool EnableUser(UserDto userDto)
{
if (userDto == null)
throw new ArgumentNullException("userDto");
User user;
if (!IsEmptyGuidString(userDto.Id))
user = _userRepository.GetByKey(new Guid(userDto.Id));
else if (!string.IsNullOrEmpty(userDto.UserName))
user = _userRepository.GetByExpression(u => u.UserName == userDto.UserName);
else if (!string.IsNullOrEmpty(userDto.Email))
user = _userRepository.GetByExpression(u => u.Email == userDto.Email);
else
throw new ArgumentNullException("userDto", "Either ID, UserName or Email should be specified.");
user.Enable();
_userRepository.Update(user);
RepositorytContext.Commit();
return user.IsDisabled;
}
public IList<UserDto> UpdateUsers(List<UserDto> userDataObjects)
{
throw new NotImplementedException();
}
public void DeleteUsers(UserDto userDto)
{
throw new System.NotImplementedException();
}
public UserDto GetUserByKey(Guid id)
{
var user = _userRepository.GetByKey(id);
var userDto = Mapper.Map<User, UserDto>(user);
return userDto;
}
public UserDto GetUserByEmail(string email)
{
if(string.IsNullOrEmpty(email))
throw new ArgumentException("email");
var user = _userRepository.GetByExpression(u => u.Email == email);
var userDto = Mapper.Map<User, UserDto>(user);
return userDto;
}
public UserDto GetUserByName(string userName)
{
if (string.IsNullOrEmpty(userName))
throw new ArgumentException("userName");
var user = _userRepository.GetByExpression(u => u.UserName == userName);
var userDto = Mapper.Map<User, UserDto>(user);
return userDto;
}
}
// UserService.svc, WCF服务
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class UserService : IUserService
{
private readonly IUserService _userServiceImp;
public UserService()
{
_userServiceImp = ServiceLocator.Instance.GetService<IUserService>();
}
public IList<UserDto> CreateUsers(List<UserDto> userDtos)
{
try
{
return _userServiceImp.CreateUsers(userDtos);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public bool ValidateUser(string userName, string password)
{
try
{
return _userServiceImp.ValidateUser(userName, password);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public bool DisableUser(UserDto userDto)
{
try
{
return _userServiceImp.DisableUser(userDto);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public bool EnableUser(UserDto userDto)
{
try
{
return _userServiceImp.EnableUser(userDto);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public void DeleteUsers(UserDto userDto)
{
throw new NotImplementedException();
}
public IList<UserDto> UpdateUsers(List<UserDto> userDataObjects)
{
throw new NotImplementedException();
}
public **UserDto** GetUserByKey(Guid id)
{
try
{
return _userServiceImp.GetUserByKey(id);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public **UserDto** GetUserByEmail(string email)
{
try
{
return _userServiceImp.GetUserByEmail(email);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
public **UserDto** GetUserByName(string userName)
{
try
{
return _userServiceImp.GetUserByName(userName);
}
catch (Exception ex)
{
throw new FaultException<FaultData>(FaultData.CreateFromException(ex), FaultData.CreateFaultReason(ex));
}
}
}
```
从上面代码可以看出,这个版本应用服务的实现和前一个版本有一个很大的不同,首先应用接口的定义采用了数据传输对象,Data Transfer Object(DTO)。DTO对象作用是为了隔离Domain Model,让Domain Model的改动不会直接影响到UI,保证Domain Model的安全,不暴露业务逻辑。通过DTO我们实现了表现层与Model之间的解耦,表现层不引用Model,如果开发过程中我们的模型改变了,而界面没变,我们就只需要改Model而不需要去改表现层中的东西。关于DTO更详细的介绍可以参考:http://www.cnblogs.com/ego/archive/2009/05/13/1456363.html
其次,目前WCF服务并没有对WCF接口进行直接实现,而是通过引用WCF接口的实现类来完成的。之前的设计把WCF实现直接在WCF服务里面进行实现的。
用户注册成功之后,就可以用对应的账号进行登录,登录成功之后,就可以把对应的商品添加进购物车中,下面分别介绍登录功能和加入购物车功能的具体实现。
首先是登录功能的实现,其实现所涉及的代码如下所示:
```
[Authorize]
[HandleError]
public class AccountController : Controller
{
// 登录按钮触发的操作
[HttpPost]
[AllowAnonymous]
public ActionResult Login(LoginViewModel model, string returnUrl)
{
if (ModelState.IsValid)
{
if (Membership.ValidateUser(model.UserName, model.Password))
{
FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
&& !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
{
return Redirect(returnUrl);
}
else
{
return RedirectToAction("Index", "Home");
}
}
else
{
ModelState.AddModelError("", "用户名或密码不正确!");
}
}
return View();
}
}
```
登录成功后,用户就可以把商品添加入购物车了,具体涉及的代码实现如下所示:
下面是HomeController中AddToCart操作的实现
```
public class HomeController : Controller
{
#region Protected Properties
protected Guid UserId
{
get
{
if (Session["UserId"] != null)
{
return (Guid) Session["UserId"];
}
else
{
var id = new Guid(Membership.GetUser().ProviderUserKey.ToString());
Session["UserId"] = id;
return id;
}
}
}
#endregion
public ActionResult Index()
{
return View();
}
[Authorize]
public ActionResult AddToCart(string productId, string items)
{
using (var proxy = new OrderServiceClient())
{
int quantity = 0;
if (!int.TryParse(items, out quantity))
quantity = 1;
proxy.AddProductToCart(UserId, new Guid(productId), quantity);
return RedirectToAction("ShoppingCart");
}
}
[Authorize]
public ActionResult ShoppingCart()
{
using (var proxy = new OrderServiceClient())
{
var model = proxy.GetShoppingCart(UserId);
return View(model);
}
}
[Authorize]
public ActionResult UpdateShoppingCartItem(string shoppingCartItemId, int quantity)
{
using (var proxy = new OrderServiceClient())
{
proxy.UpdateShoppingCartItem(new Guid(shoppingCartItemId), quantity);
return Json(null);
}
}
[Authorize]
public ActionResult DeleteShoppingCartItem(string shoppingCartItemId)
{
using (var proxy = new OrderServiceClient())
{
proxy.DeleteShoppingCartItem(new Guid(shoppingCartItemId));
return Json(null);
}
}
}
```
从上面代码可以看出,HomeController中的AddToCart操作是通过调用应用层的OrderService来完成,而OrderService又是调用对应的仓储接口来完成数据的持久化的,即把对应的商品放进对应的用户的购物车对象中。关于应用层和仓储层的具体实现如下所示:
```
**// OrderService的实现**
public class OrderServiceImp : ApplicationService, IOrderService
{
#region Private Fileds
private readonly IShoppingCartRepository _shoppingCartRepository;
private readonly IShoppingCartItemRepository _shoppingCartItemRepository;
private readonly IUserRepository _userRepository;
private readonly IProductRepository _productRepository;
#endregion
#region Ctor
public OrderServiceImp(IRepositoryContext context,
IUserRepository userRepository,
IShoppingCartRepository shoppingCartRepository,
IProductRepository productRepository,
IShoppingCartItemRepository shoppingCartItemRepository):base(context)
{
_userRepository = userRepository;
_shoppingCartRepository = shoppingCartRepository;
_productRepository = productRepository;
_shoppingCartItemRepository = shoppingCartItemRepository;
}
#endregion
#region IOrderService Members
public void AddProductToCart(Guid customerId, Guid productId, int quantity)
{
var user = _userRepository.GetByKey(customerId);
var shoppingCart = _shoppingCartRepository.GetBySpecification(new ExpressionSpecification<ShoppingCart>(s=>s.User.Id == user.Id));
if (shoppingCart == null)
throw new DomainException("用户{0}不存在购物车.", customerId);
var product = _productRepository.GetByKey(productId);
var shoppingCartItem = _shoppingCartItemRepository.FindItem(shoppingCart, product);
if (shoppingCartItem == null)
{
shoppingCartItem = new ShoppingCartItem()
{
Product = product,
ShoopingCart = shoppingCart,
Quantity = quantity
};
_shoppingCartItemRepository.Add(shoppingCartItem);
}
else
{
shoppingCartItem.UpdateQuantity(shoppingCartItem.Quantity + quantity);
_shoppingCartItemRepository.Update(shoppingCartItem);
}
RepositorytContext.Commit();
}
public ShoppingCartDto GetShoppingCart(Guid customerId)
{
var user = _userRepository.GetByKey(customerId);
var shoppingCart = _shoppingCartRepository.GetBySpecification(
new ExpressionSpecification<ShoppingCart>(s => s.User.Id == user.Id));
if (shoppingCart == null)
throw new DomainException("用户{0}不存在购物车.", customerId);
var shoppingCartItems =
_shoppingCartItemRepository.GetAll(
new ExpressionSpecification<ShoppingCartItem>(s => s.ShoopingCart.Id == shoppingCart.Id));
var shoppingCartDto = Mapper.Map<ShoppingCart, ShoppingCartDto>(shoppingCart);
shoppingCartDto.Items = new List<ShoppingCartItemDto>();
if (shoppingCartItems != null && shoppingCartItems.Any())
{
shoppingCartItems
.ToList()
.ForEach(s => shoppingCartDto.Items.Add(Mapper.Map<ShoppingCartItem, ShoppingCartItemDto>(s)));
shoppingCartDto.Subtotal = shoppingCartDto.Items.Sum(p => p.ItemAmount);
}
return shoppingCartDto;
}
public int GetShoppingCartItemCount(Guid userId)
{
var user = _userRepository.GetByKey(userId);
var shoppingCart = _shoppingCartRepository.GetBySpecification(new ExpressionSpecification<ShoppingCart>(s => s.User.Id == user.Id));
if(shoppingCart == null)
throw new InvalidOperationException("没有可用的购物车实例.");
var shoppingCartItems =
_shoppingCartItemRepository.GetAll(new ExpressionSpecification<ShoppingCartItem>(s => s.ShoopingCart.Id == shoppingCart.Id));
return shoppingCartItems.Sum(s => s.Quantity);
}
public void UpdateShoppingCartItem(Guid shoppingCartItemId, int quantity)
{
var shoppingCartItem = _shoppingCartItemRepository.GetByKey(shoppingCartItemId);
shoppingCartItem.UpdateQuantity(quantity);
_shoppingCartItemRepository.Update(shoppingCartItem);
RepositorytContext.Commit();
}
public void DeleteShoppingCartItem(Guid shoppingCartItemId)
{
var shoppingCartItem = _shoppingCartItemRepository.GetByKey(shoppingCartItemId);
_shoppingCartItemRepository.Remove(shoppingCartItem);
RepositorytContext.Commit();
}
public OrderDto Checkout(Guid customerId)
{
throw new NotImplementedException();
}
#endregion
}
**// 加入购物车所涉及仓储的实现** public class ShoppingCartRepository : EntityFrameworkRepository<ShoppingCart>, IShoppingCartRepository
{
public ShoppingCartRepository(IRepositoryContext context) : base(context)
{
}
}
public class ShoppingCartItemRepository : EntityFrameworkRepository<ShoppingCartItem>, IShoppingCartItemRepository
{
public ShoppingCartItemRepository(IRepositoryContext context)
: base(context)
{
}
#region IShoppingCartItemRepository Members
public ShoppingCartItem FindItem(ShoppingCart shoppingCart, Product product)
{
return GetBySpecification(Specification<ShoppingCartItem>.Eval
(sci => sci.ShoopingCart.Id == shoppingCart.Id &&
sci.Product.Id == product.Id));
}
#endregion
}
```
这样,也就完成购物车的实现,下面让我们要一起看看完善后网上书店的运行效果,
首先,如果没有登录的话,当用户点击商品上的购买按钮时,会自动跳转到登录界面,具体登录界面如下所示:
![](http://images0.cnblogs.com/blog2015/383187/201505/272153393761951.png)
这里由于我演示的时候已经注册过一个账号了,这时候我就用注册好的账号进行登录,如果你没有账号的话,可以直接注册一个账号。登录成功之后,你就可以把对应商品添加进购物车,具体运行效果如下图所示:
![](http://images0.cnblogs.com/blog2015/383187/201505/272158168296714.png)
并且,你还可以对购物车中商品进行操作,例如移除,数量更新操作等,如果此时更新Asp.net设计模式的数量为1的话,此时的运行效果如下图所示:
![](http://images0.cnblogs.com/blog2015/383187/201505/272201194077691.png)
从上图可以发现,当我们更新商品的数量时,对应的总数量和总价也相应地进行了更新。当然你还可以对商品进行删除操作。这里就不一一贴图了。大家可以自行从github上下载源码运行看看。
## 五、总结
到这里,网上书店的购物车功能的实现就完成了,在接下来的系列中,我会继续完善这个DDD系列,在接下来的一个系列中将会对加入订单功能。
网上书店v0.2版Github下载地址:[https://github.com/lizhi5753186/OnlineStore_Second](https://github.com/lizhi5753186/OnlineStore_Second)
- C# 基础知识系列
- C# 基础知识系列 专题一:深入解析委托——C#中为什么要引入委托
- C# 基础知识系列 专题二:委托的本质论
- C# 基础知识系列 专题三:如何用委托包装多个方法——委托链
- C# 基础知识系列 专题四:事件揭秘
- C# 基础知识系列 专题五:当点击按钮时触发Click事件背后发生的事情
- C# 基础知识系列 专题六:泛型基础篇——为什么引入泛型
- C# 基础知识系列 专题七: 泛型深入理解(一)
- C# 基础知识系列 专题八: 深入理解泛型(二)
- C# 基础知识系列 专题九: 深入理解泛型可变性
- C#基础知识系列 专题十:全面解析可空类型
- C# 基础知识系列 专题十一:匿名方法解析
- C#基础知识系列 专题十二:迭代器
- C#基础知识 专题十三:全面解析对象集合初始化器、匿名类型和隐式类型
- C# 基础知识系列 专题十四:深入理解Lambda表达式
- C# 基础知识系列 专题十五:全面解析扩展方法
- C# 基础知识系列 专题十六:Linq介绍
- C#基础知识系列 专题十七:深入理解动态类型
- 你必须知道的异步编程 C# 5.0 新特性——Async和Await使异步编程更简单
- 全面解析C#中参数传递
- C#基础知识系列 全面解析C#中静态与非静态
- C# 基础知识系列 C#中易混淆的知识点
- C#进阶系列
- C#进阶系列 专题一:深入解析深拷贝和浅拷贝
- C#进阶系列 专题二:你知道Dictionary查找速度为什么快吗?
- C# 开发技巧系列
- C# 开发技巧系列 使用C#操作Word和Excel程序
- C# 开发技巧系列 使用C#操作幻灯片
- C# 开发技巧系列 如何动态设置屏幕分辨率
- C# 开发技巧系列 C#如何实现图片查看器
- C# 开发技巧 如何防止程序多次运行
- C# 开发技巧 实现属于自己的截图工具
- C# 开发技巧 如何使不符合要求的元素等于离它最近的一个元素
- C# 线程处理系列
- C# 线程处理系列 专题一:线程基础
- C# 线程处理系列 专题二:线程池中的工作者线程
- C# 线程处理系列 专题三:线程池中的I/O线程
- C# 线程处理系列 专题四:线程同步
- C# 线程处理系列 专题五:线程同步——事件构造
- C# 线程处理系列 专题六:线程同步——信号量和互斥体
- C# 多线程处理系列专题七——对多线程的补充
- C#网络编程系列
- C# 网络编程系列 专题一:网络协议简介
- C# 网络编程系列 专题二:HTTP协议详解
- C# 网络编程系列 专题三:自定义Web服务器
- C# 网络编程系列 专题四:自定义Web浏览器
- C# 网络编程系列 专题五:TCP编程
- C# 网络编程系列 专题六:UDP编程
- C# 网络编程系列 专题七:UDP编程补充——UDP广播程序的实现
- C# 网络编程系列 专题八:P2P编程
- C# 网络编程系列 专题九:实现类似QQ的即时通信程序
- C# 网络编程系列 专题十:实现简单的邮件收发器
- C# 网络编程系列 专题十一:实现一个基于FTP协议的程序——文件上传下载器
- C# 网络编程系列 专题十二:实现一个简单的FTP服务器
- C# 互操作性入门系列
- C# 互操作性入门系列(一):C#中互操作性介绍
- C# 互操作性入门系列(二):使用平台调用调用Win32 函数
- C# 互操作性入门系列(三):平台调用中的数据封送处理
- C# 互操作性入门系列(四):在C# 中调用COM组件
- CLR
- 谈谈: String 和StringBuilder区别和选择
- 谈谈:程序集加载和反射
- 利用反射获得委托和事件以及创建委托实例和添加事件处理程序
- 谈谈:.Net中的序列化和反序列化
- C#设计模式
- UML类图符号 各种关系说明以及举例
- C#设计模式(1)——单例模式
- C#设计模式(2)——简单工厂模式
- C#设计模式(3)——工厂方法模式
- C#设计模式(4)——抽象工厂模式
- C#设计模式(5)——建造者模式(Builder Pattern)
- C#设计模式(6)——原型模式(Prototype Pattern)
- C#设计模式(7)——适配器模式(Adapter Pattern)
- C#设计模式(8)——桥接模式(Bridge Pattern)
- C#设计模式(9)——装饰者模式(Decorator Pattern)
- C#设计模式(10)——组合模式(Composite Pattern)
- C#设计模式(11)——外观模式(Facade Pattern)
- C#设计模式(12)——享元模式(Flyweight Pattern)
- C#设计模式(13)——代理模式(Proxy Pattern)
- C#设计模式(14)——模板方法模式(Template Method)
- C#设计模式(15)——命令模式(Command Pattern)
- C#设计模式(16)——迭代器模式(Iterator Pattern)
- C#设计模式(17)——观察者模式(Observer Pattern)
- C#设计模式(18)——中介者模式(Mediator Pattern)
- C#设计模式(19)——状态者模式(State Pattern)
- C#设计模式(20)——策略者模式(Stragety Pattern)
- C#设计模式(21)——责任链模式
- C#设计模式(22)——访问者模式(Vistor Pattern)
- C#设计模式(23)——备忘录模式(Memento Pattern)
- C#设计模式总结
- WPF快速入门系列
- WPF快速入门系列(1)——WPF布局概览
- WPF快速入门系列(2)——深入解析依赖属性
- WPF快速入门系列(3)——深入解析WPF事件机制
- WPF快速入门系列(4)——深入解析WPF绑定
- WPF快速入门系列(5)——深入解析WPF命令
- WPF快速入门系列(6)——WPF资源和样式
- WPF快速入门系列(7)——深入解析WPF模板
- WPF快速入门系列(8)——MVVM快速入门
- WPF快速入门系列(9)——WPF任务管理工具实现
- ASP.NET 开发
- ASP.NET 开发必备知识点(1):如何让Asp.net网站运行在自定义的Web服务器上
- ASP.NET 开发必备知识点(2):那些年追过的ASP.NET权限管理
- ASP.NET中实现回调
- 跟我一起学WCF
- 跟我一起学WCF(1)——MSMQ消息队列
- 跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(3)——利用Web Services开发分布式应用
- 跟我一起学WCF(4)——第一个WCF程序
- 跟我一起学WCF(5)——深入解析服务契约 上篇
- 跟我一起学WCF(6)——深入解析服务契约 下篇
- 跟我一起学WCF(7)——WCF数据契约与序列化详解
- 跟我一起学WCF(8)——WCF中Session、实例管理详解
- 跟我一起学WCF(9)——WCF回调操作的实现
- 跟我一起学WCF(10)——WCF中事务处理
- 跟我一起学WCF(11)——WCF中队列服务详解
- 跟我一起学WCF(12)——WCF中Rest服务入门
- 跟我一起学WCF(13)——WCF系列总结
- .NET领域驱动设计实战系列
- .NET领域驱动设计实战系列 专题一:前期准备之EF CodeFirst
- .NET领域驱动设计实战系列 专题二:结合领域驱动设计的面向服务架构来搭建网上书店
- .NET领域驱动设计实战系列 专题三:前期准备之规约模式(Specification Pattern)
- .NET领域驱动设计实战系列 专题四:前期准备之工作单元模式(Unit Of Work)
- .NET领域驱动设计实战系列 专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
- .NET领域驱动设计实战系列 专题六:DDD实践案例:网上书店订单功能的实现
- .NET领域驱动设计实战系列 专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
- .NET领域驱动设计实战系列 专题八:DDD案例:网上书店分布式消息队列和分布式缓存的实现
- .NET领域驱动设计实战系列 专题九:DDD案例:网上书店AOP和站点地图的实现
- .NET领域驱动设计实战系列 专题十:DDD扩展内容:全面剖析CQRS模式实现
- .NET领域驱动设计实战系列 专题十一:.NET 领域驱动设计实战系列总结