[.NET领域驱动设计实战系列]专题五:网上书店规约模式、工作单元模式的引入以及购物车的实现
一、前言
在前面2篇博文中,我分别介绍了规约模式和工作单元模式,有了前面2篇博文的铺垫之后,下面就具体看看如何把这两种模式引入到之前的网上书店案例里。
二、规约模式的引入
在第三专题我们已经详细介绍了什么是规约模式,没看过的朋友首先去了解下。下面让我们一起看看如何在网上书店案例中引入规约模式。在网上书店案例中规约模式的实现兼容了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,工作单元接口的定义通常放在基础设施层,其定义代码如下所示:
public interface IUnitOfWork { void Commit(); }
在该项目中,对工作单元接口的方法进行了一个分离,把其方法分别定义在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 }
到此,工作单元模式的引入也就完成了,接下面,让我们继续完成网上书店案例。
四、购物车的实现
在前一个版本中,只是实现了商品的展示和详细信息等功能。在网上商店中,都有购物车这个功能,作为一个完整的案例,该案例也不能少了这个功能。在实现购物车之前,我们首先理清下业务逻辑:访问者看到商品,然后点击商品下的加入购物车按钮,把商品加入购物车。
在上面的业务逻辑中,涉及了下面几个更细的业务逻辑:
- 如果用户没注册时,访客点击加入购物车按钮应跳转到注册界面,这样就涉及到用户注册功能的实现
- 用户注册成功后需要同时为用户创建一个购物车实例与该用户进行绑定,之后用户就可以把商品加入自己的购物车
- 加入购物车之后,用户可以查看购物车中的商品,同时也应该可以进行更新和移除操作。
通过上面的描述,大家应该自然明白了我们接下来需要哪些功能了吧,下面我们来理理:
- 用户注册功能,涉及用户注册页面。自然就涉及用户注册服务和用户仓储的实现
- 注册成功同时创建购物车实例。自然涉及创建购物车服务方法和购物车仓储的实现
- 加入购物车成功后,可以查看购物车中的商品、更新和移除操作,自然涉及到购物车页面的实现。这里自然涉及到获得购物车和更新商品数量和删除购物项的服务方法。
理清了思路之后,接下来就应该去实现功能了,首先应该实现自然就是用户注册模块。实现功能模块都从底向上来实现,首先应该先定义用户聚合根,接着实现用户仓储和用户服务,最后实现控制器和视图。下面是用户注册涉及的主要类的实现:
// 用户聚合根 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 }
这样,也就完成购物车的实现,下面让我们要一起看看完善后网上书店的运行效果,
首先,如果没有登录的话,当用户点击商品上的购买按钮时,会自动跳转到登录界面,具体登录界面如下所示:
这里由于我演示的时候已经注册过一个账号了,这时候我就用注册好的账号进行登录,如果你没有账号的话,可以直接注册一个账号。登录成功之后,你就可以把对应商品添加进购物车,具体运行效果如下图所示:
并且,你还可以对购物车中商品进行操作,例如移除,数量更新操作等,如果此时更新Asp.net设计模式的数量为1的话,此时的运行效果如下图所示:
从上图可以发现,当我们更新商品的数量时,对应的总数量和总价也相应地进行了更新。当然你还可以对商品进行删除操作。这里就不一一贴图了。大家可以自行从github上下载源码运行看看。
五、总结
到这里,网上书店的购物车功能的实现就完成了,在接下来的系列中,我会继续完善这个DDD系列,在接下来的一个系列中将会对加入订单功能。
网上书店v0.2版Github下载地址:https://github.com/lizhi5753186/OnlineStore_Second