[.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;
        }
    }
View Code

  這樣,規約模式在案例中的實現就完成了,下面具體介紹下工作單元模式是如何在該項目中實現的。

三、工作單元模式的引入

   工作單元模式,主要是為了保證數據的一致性,一些涉及到多個實體的操作我們希望它們一起被提交,從而保證數據的正確性和一致性。例如,在該項目,用戶成功注冊的同時需要為用戶創建一個購物車對象,這里就涉及到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 
    }

  到此,工作單元模式的引入也就完成了,接下面,讓我們繼續完成網上書店案例。

四、購物車的實現

   在前一個版本中,只是實現了商品的展示和詳細信息等功能。在網上商店中,都有購物車這個功能,作為一個完整的案例,該案例也不能少了這個功能。在實現購物車之前,我們首先理清下業務邏輯:訪問者看到商品,然后點擊商品下的加入購物車按鈕,把商品加入購物車。

  在上面的業務邏輯中,涉及了下面幾個更細的業務邏輯:

  • 如果用戶沒注冊時,訪客點擊加入購物車按鈕應跳轉到注冊界面,這樣就涉及到用戶注冊功能的實現
  • 用戶注冊成功后需要同時為用戶創建一個購物車實例與該用戶進行綁定,之后用戶就可以把商品加入自己的購物車
  • 加入購物車之后,用戶可以查看購物車中的商品,同時也應該可以進行更新和移除操作。

  通過上面的描述,大家應該自然明白了我們接下來需要哪些功能了吧,下面我們來理理:

  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 }

  這樣,也就完成購物車的實現,下面讓我們要一起看看完善后網上書店的運行效果,

  首先,如果沒有登錄的話,當用戶點擊商品上的購買按鈕時,會自動跳轉到登錄界面,具體登錄界面如下所示:

  這里由於我演示的時候已經注冊過一個賬號了,這時候我就用注冊好的賬號進行登錄,如果你沒有賬號的話,可以直接注冊一個賬號。登錄成功之后,你就可以把對應商品添加進購物車,具體運行效果如下圖所示:

  並且,你還可以對購物車中商品進行操作,例如移除,數量更新操作等,如果此時更新Asp.net設計模式的數量為1的話,此時的運行效果如下圖所示:

  從上圖可以發現,當我們更新商品的數量時,對應的總數量和總價也相應地進行了更新。當然你還可以對商品進行刪除操作。這里就不一一貼圖了。大家可以自行從github上下載源碼運行看看。

五、總結

   到這里,網上書店的購物車功能的實現就完成了,在接下來的系列中,我會繼續完善這個DDD系列,在接下來的一個系列中將會對加入訂單功能。

  網上書店v0.2版Github下載地址:https://github.com/lizhi5753186/OnlineStore_Second

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM