# [.NET领域驱动设计实战系列]专题九:DDD案例:网上书店AOP和站点地图的实现
## 一、引言
在前面一专题介绍到,要让缓存生效还需要实现对AOP(面向切面编程)的支持。所以本专题将介绍了网上书店案例中AOP的实现。关于AOP的概念,大家可以参考文章:[http://www.cnblogs.com/jin-yuan/p/3811077.html](http://www.cnblogs.com/jin-yuan/p/3811077.html)。这里我简单介绍下AOP:AOP可以理解为对方法进行截获,这样就可以在方法调用前或调用后插入需要的逻辑。例如可以在方法调用前,加入缓存查找逻辑等。这里缓存查找逻辑就在方法调用前被执行。通过对AOP的支持,每个方法就可以分为3部分了,方法调用前逻辑->具体需要调用的方法->方法调用后的逻辑。也就是在方法调用的时候“切了一刀”。
## 二、网上书店AOP的实现
你可以从零开始去实现AOP,但是目前已经存在很多AOP框架了,所以在本案例中将直接通过Unity的AOP框架(Unity.Interception)来实现网上书店对AOP的支持。通常AOP的实现放在基础设施层进行实现,因为可能其他所有层都需要加入对AOP的支持。本案例中将对两个方面的AOP进行实现,一个是方法调用前缓存的记录或查找,另一个是方法调用后异常信息的记录。在实现具体代码之前,我们需要在基础设施层通过Nuget来引入Unity.Interception包。添加成功之后,我们需要定义两个类分别去实现AOP框架中**IInterceptionBehavior**接口。由于本案例中需要对缓存和异常日志功能进行AOP实现,自然就需要定义CachingBehavior和ExceptionLoggingBehavior两个类去实现**IInterceptionBehavior**接口。首先让我们看看CachingBehavior类的实现,具体实现代码如下所示:
```
// 缓存AOP的实现
public class CachingBehavior : IInterceptionBehavior
{
private readonly ICacheProvider _cacheProvider;
public CachingBehavior()
{
_cacheProvider = ServiceLocator.Instance.GetService<ICacheProvider>();
}
// 生成缓存值的键值
private string GetValueKey(CacheAttribute cachingAttribute, IMethodInvocation input)
{
switch (cachingAttribute.Method)
{
// 如果是Remove,则不存在特定值键名,所有的以该方法名称相关的缓存都需要清除
case CachingMethod.Remove:
return null;
// 如果是Get或者Update,则需要产生一个针对特定参数值的键名
case CachingMethod.Get:
case CachingMethod.Update:
if (input.Arguments != null &&
input.Arguments.Count > 0)
{
var sb = new StringBuilder();
for (var i = 0; i < input.Arguments.Count; i++)
{
sb.Append(input.Arguments[i]);
if (i != input.Arguments.Count - 1)
sb.Append("_");
}
return sb.ToString();
}
else
return "NULL";
default:
throw new InvalidOperationException("无效的缓存方式。");
}
}
#region IInterceptionBehavior Members
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
// 获得被拦截的方法
var method = input.MethodBase;
var key = method.Name; // 获得拦截的方法名
// 如果拦截的方法定义了Cache属性,说明需要对该方法的结果需要进行缓存
if (!method.IsDefined(typeof (CacheAttribute), false))
return getNext().Invoke(input, getNext);
var cachingAttribute = (CacheAttribute)method.GetCustomAttributes(typeof (CacheAttribute), false)[0];
var valueKey = GetValueKey(cachingAttribute, input);
switch (cachingAttribute.Method)
{
case CachingMethod.Get:
try
{
// 如果缓存中存在该键值的缓存,则直接返回缓存中的结果退出
if (_cacheProvider.Exists(key, valueKey))
{
var value = _cacheProvider.Get(key, valueKey);
var arguments = new object[input.Arguments.Count];
input.Arguments.CopyTo(arguments, 0);
return new VirtualMethodReturn(input, value, arguments);
}
else // 否则先调用方法,再把返回结果进行缓存
{
var methodReturn = getNext().Invoke(input, getNext);
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
return methodReturn;
}
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
case CachingMethod.Update:
try
{
var methodReturn = getNext().Invoke(input, getNext);
if (_cacheProvider.Exists(key))
{
if (cachingAttribute.IsForce)
{
_cacheProvider.Remove(key);
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
}
else
_cacheProvider.Update(key, valueKey, methodReturn);
}
else
_cacheProvider.Add(key, valueKey, methodReturn.ReturnValue);
return methodReturn;
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
case CachingMethod.Remove:
try
{
var removeKeys = cachingAttribute.CorrespondingMethodNames;
foreach (var removeKey in removeKeys)
{
if (_cacheProvider.Exists(removeKey))
_cacheProvider.Remove(removeKey);
}
**// 执行具体截获的方法** var methodReturn = getNext().Invoke(input, getNext);
return methodReturn;
}
catch (Exception ex)
{
return new VirtualMethodReturn(input, ex);
}
default: break;
}
return getNext().Invoke(input, getNext);
}
public bool WillExecute
{
get { return true; }
}
#endregion
}
```
从上面代码可以看出,通过Unity.Interception框架来实现AOP变得非常简单了,我们只需要实现IInterceptionBehavior接口中的Invoke方法和WillExecute属性即可。并且从上面代码可以看出,AOP的支持最核心代码实现在于Invoke方法的实现。既然我们需要在方法调用前查找缓存,如果缓存不存在再调用方法从数据库中进行查找,如果存在则直接从缓存中进行读取数据即可。自然需要在 getNext().Invoke(input, getNext)代码执行前进缓存进行查找,然而上面CachingBehavior类正式这样实现的。
介绍完缓存功能AOP的实现之后,下面具体看看异常日志的AOP实现。具体实现代码如下所示:
```
// 用于异常日志记录的拦截行为
public class ExceptionLoggingBehavior :IInterceptionBehavior
{
/// <summary>
/// 需要拦截的对象类型的接口
/// </summary>
/// <returns></returns>
public IEnumerable<Type> GetRequiredInterfaces()
{
return Type.EmptyTypes;
}
/// <summary>
/// 通过该方法来拦截调用并执行所需要的拦截行为
/// </summary>
/// <param name="input">调用拦截目标时的输入信息</param>
/// <param name="getNext">通过行为链来获取下一个拦截行为的委托</param>
/// <returns>从拦截目标获得的返回信息</returns>
public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)
{
// 执行目标方法
var methodReturn = getNext().Invoke(input, getNext);
// 方法执行后的处理
if (methodReturn.Exception != null)
{
Utils.Log(methodReturn.Exception);
}
return methodReturn;
}
// 表示当拦截行为被调用时,是否需要执行某些操作
public bool WillExecute
{
get { return true; }
}
}
```
异常日志功能的AOP实现与缓存功能的AOP实现类似,只是一个需要在方法执行前注入,而一个是在方法执行后进行注入罢了,其实现原理都是在截获的方法前后进行。方法截获功能AOP框架已经帮我们实现了。
到此,我们网上书店AOP的实现就已经完成了,但要正式生效还需要通过配置文件把AOP的实现注入到需要截获的方法当中去,这样执行这些方法才会执行注入的行为。对应的配置文件如下标红部分所示:
```
<!--Unity的配置信息-->
<unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
<sectionExtension type="Microsoft.Practices.Unity.InterceptionExtension.Configuration.InterceptionConfigurationExtension, Microsoft.Practices.Unity.Interception.Configuration" />
<container>
<extension type="Interception" />
<!--Cache Provider-->
<register type="OnlineStore.Infrastructure.Caching.ICacheProvider, OnlineStore.Infrastructure" mapTo="OnlineStore.Infrastructure.Caching.EntLibCacheProvider, OnlineStore.Infrastructure" />
<!--仓储接口的注册-->
<register type="OnlineStore.Domain.Repositories.IRepositoryContext, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.EntityFrameworkRepositoryContext, OnlineStore.Repositories">
<lifetime type="singleton" />
</register>
<register type="OnlineStore.Domain.Repositories.IProductRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.ICategoryRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.CategoryRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IProductCategorizationRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ProductCategorizationRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IShoppingCartRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IShoppingCartItemRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.ShoppingCartItemRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IOrderRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.OrderRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IUserRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.UserRoleRepository, OnlineStore.Repositories" />
<register type="OnlineStore.Domain.Repositories.IRoleRepository, OnlineStore.Domain" mapTo="OnlineStore.Repositories.EntityFramework.RoleRepository, OnlineStore.Repositories" />
<!--Domain Services-->
<register type="OnlineStore.Domain.Services.IDomainService, OnlineStore.Domain" mapTo="OnlineStore.Domain.Services.DomainService, OnlineStore.Domain" />
** <!--应用服务的注册-->
<register type="OnlineStore.ServiceContracts.IProductService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.ProductServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register>
<register type="OnlineStore.ServiceContracts.IOrderService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.OrderServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register>
<register type="OnlineStore.ServiceContracts.IUserService, OnlineStore.ServiceContracts" mapTo="OnlineStore.Application.ServiceImplementations.UserServiceImp, OnlineStore.Application">
<!--注入AOP功能的实现-->
<interceptor type="InterfaceInterceptor" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.CachingBehavior, OnlineStore.Infrastructure" />
<interceptionBehavior type="OnlineStore.Infrastructure.InterceptionBehaviors.ExceptionLoggingBehavior, OnlineStore.Infrastructure" />
</register>**
<!--Domain Event Handlers-->
<register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderDispatchedEventHandler, OnlineStore.Domain" name="OrderDispatchedEventHandler" />
<register type="OnlineStore.Domain.Events.IDomainEventHandler`1[[OnlineStore.Domain.Events.OrderConfirmedEvent, OnlineStore.Domain]], OnlineStore.Domain" mapTo="OnlineStore.Domain.Events.EventHandlers.OrderConfirmedEventHandler, OnlineStore.Domain" name="OrderConfirmedEventHandler" />
<!--Event Handlers-->
<register name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" mapTo="OnlineStore.Events.Handlers.SendEmailHandler, OnlineStore.Events.Handlers" />
<!--Event Aggregator-->
<register type="OnlineStore.Events.IEventAggregator, OnlineStore.Events" mapTo="OnlineStore.Events.EventAggregator, OnlineStore.Events">
<constructor>
<param name="handlers">
<array>
<dependency name="orderSendEmailHandler" type="OnlineStore.Events.IEventHandler`1[[OnlineStore.Domain.Events.OrderDispatchedEvent, OnlineStore.Domain]], OnlineStore.Events" />
</array>
</param>
</constructor>
</register>
<!--Event Bus-->
<!--<register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events" mapTo="OnlineStore.Events.Bus.EventBus, OnlineStore.Events">
<lifetime type="singleton" />
</register>-->
<!--注入MsmqEventBus-->
<register type="OnlineStore.Events.Bus.IEventBus, OnlineStore.Events"
mapTo="OnlineStore.Events.Bus.MsmqEventBus, OnlineStore.Events">
<lifetime type="singleton" />
<constructor>
<param name="path" value=".\Private$\OnlineStoreQueue" />
</constructor>
</register>
</container>
</unity>
<!--END: Unity-->
```
到此,网上书店案例中AOP的实现就完成了。通过上面的配置可以看出,客户端在调用应用服务方法前后会调用我们注入的行为,即缓存行为和异常日志行为。通过对AOP功能的支持,就不需要为每个需要进行缓存或需要异常日志行为的方法来重复写这些相同的逻辑了。从而避免了重复代码的重复实现,提高了代码的重用性和降低了模块之间的依赖性。
## 三、网上书店案例中站点地图的实现
在大部分网站中都实现了站点地图的功能,在Asp.net中,我们可以通过SiteMap模块来实现站点地图的功能,在Asp.net MVC也可以通过MvcSiteMapProvider第三方开源框架来实现站点地图。所以针对网上书店案例,站点地图的支持也是必不可少的。下面让我们具体看看站点地图在本案例中是如何去实现的呢?
在看实现代码之前,让我们先来理清下实现思路。
本案例中站点地图的实现,并没有借助MvcSiteMapProvider第三方框架来实现。其实现原理首先获得用户的路由请求,然后根据用户请求根据站点地图的配置获得对应的配置节点,接着根据站点地图的节点信息生成类似">首页>"这样带标签的字符串;如果获得的节点是配置文件中某个父节点的子节点,此时会通过递归的方式找到其父节点,然后递归地生成对应带标签的字符串,从而完成站点地图的功能。分析完实现思路之后,下面让我们再对照下具体的实现代码来加深理解。具体的实现代码如下所示:
```
public class MvcSiteMap
{
private static readonly MvcSiteMap _instance = new MvcSiteMap();
private static readonly XDocument Doc = XDocument.Load(HttpContext.Current.Server.MapPath(@"~/SiteMap.xml"));
private UrlHelper _url = null;
private string _currentUrl;
public static MvcSiteMap Instance
{
get { return _instance;}
}
private MvcSiteMap()
{
}
public MvcHtmlString Navigator()
{
// 获得当前请求的路由信息
_url = new UrlHelper(HttpContext.Current.Request.RequestContext);
var routeUrl = _url.RouteUrl(HttpContext.Current.Request.RequestContext.RouteData.Values);
if (routeUrl != null)
_currentUrl = routeUrl.ToLower();
// 从配置的站点Xml文件中找到当前请求的Url相同的节点
var c = FindNode(Doc.Root);
var temp = GetPath(c);
return MvcHtmlString.Create(BuildPathString(temp));
}
// 从SitMap配置文件中找到当前请求匹配的节点
private XElement FindNode(XElement node)
{
// 如果xml节点对应的url是否与当前请求的节点相同,如果相同则直接返回xml对应的节点
// 如果不同开始递归子节点
return IsUrlEqual(node) == true ? node : RecursiveNode(node);
}
// 判断xml节点对应的url是否与当前请求的url一样
private bool IsUrlEqual(XElement c)
{
var a = GetNodeUrl(c).ToLower();
return a == _currentUrl;
}
// 递归Xml节点
private XElement RecursiveNode(XElement node)
{
foreach (var c in node.Elements())
{
if (IsUrlEqual(c) == true)
{
return c;
}
else
{
var x = RecursiveNode(c);
if (x != null)
{
return x;
}
}
}
return null;
}
// 获得xml节点对应的请求url
private string GetNodeUrl(XElement c)
{
return _url.Action(c.Attribute("action").Value, c.Attribute("controller").Value,
new {area = c.Attribute("area").Value});
}
// 根据对应请求url对应的Xml节点获得其在Xml中的路径,即获得其父节点有什么
// SiteMap.xml 中节点的父子节点一定要配置对
private Stack<XElement> GetPath(XElement c)
{
var temp = new Stack<XElement>();
while (c != null)
{
temp.Push(c);
c = c.Parent;
}
return temp;
}
// 根据节点的路径来拼接带标签的字符串
private string BuildPathString(Stack<XElement> m)
{
var sb = new StringBuilder();
var tc = new TagBuilder("span");
tc.SetInnerText(">");
var sp = tc.ToString();
var count = m.Count;
for (var x = 1; x <= count; x++)
{
var c = m.Pop();
TagBuilder tb;
if (x == count)
{
tb = new TagBuilder("span");
}
else
{
tb = new TagBuilder("a");
tb.MergeAttribute("href", GetNodeUrl(c));
}
tb.SetInnerText(c.Attribute("title").Value);
sb.Append(tb);
sb.Append(sp);
}
return sb.ToString();
}
}
```
对应的站点地图配置信息如下所示:
```
<?xml version="1.0" encoding="utf-8" ?>
<node title="首页" area="" action="Index" controller="Home">
<node title="我的" area="UserCenter" action="Manage" controller="Account">
<node title="订单" area="" action="Orders" controller="Home" />
<node title="账户" area="" action="Manage" controller="Account" />
<node title="购物车" area="" action="ShoppingCart" controller="Home" />
</node>
<node title="关于" area="AboutCenter" action="About" controller="Home" >
<node title="Online Store 项目" area="" action="About" controller="Home" />
<node title="联系方式" area="" action="Contact" controller="Home" />
</node>
</node>
```
实现完成之后,下面让我们具体看看本案例中站点地图的实现效果看看,具体运行效果如下图所示:
![](http://images0.cnblogs.com/blog2015/383187/201506/192128197483378.png)
## 四、小结
到这里,本专题的内容就结束了。本专题主要借助Unity.Interception框架在网上书店中引入了AOP功能,并且最后简单介绍了站点地图的实现。在下一专题将对CQRS模式做一个全面的介绍。
本案例所有源码:[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 领域驱动设计实战系列总结