# [.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
}
```
這樣,也就完成購物車的實現,下面讓我們要一起看看完善后網上書店的運行效果,
首先,如果沒有登錄的話,當用戶點擊商品上的購買按鈕時,會自動跳轉到登錄界面,具體登錄界面如下所示:

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

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

從上圖可以發現,當我們更新商品的數量時,對應的總數量和總價也相應地進行了更新。當然你還可以對商品進行刪除操作。這里就不一一貼圖了。大家可以自行從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 領域驅動設計實戰系列總結