# [.NET领域驱动设计实战系列]专题七:DDD实践案例:引入事件驱动与中间件机制来实现后台管理功能
## 一、引言
在当前的电子商务平台中,用户下完订单之后,然后店家会在后台看到客户下的订单,然后店家可以对客户的订单进行发货操作。此时客户会在自己的订单状态看到店家已经发货。从上面的业务逻辑可以看出,当用户下完订单之后,店家或管理员可以对客户订单进行跟踪和操作。上一专题我们已经实现创建订单的功能,则接下来自然就是后台管理功能的实现了。所以在这一专题中将详细介绍如何在网上书店案例中实现后台管理功能。
## 二、后台管理中的权限管理的实现
后台管理中,首先需要实现的自然就是权限管理了,因为要进行商品管理等操作的话,则必须对不同的用户指定的不同角色,然后为不同角色指定不同的权限。这样才能确保普通用户不能进行一些后台操作。
然而角色和权限的赋予一般都是由系统管理员来操作。所以在最开始创建一个管理员用户,之后就可以以管理员的账号进行登录来进行后台操作的管理,包括添加角色,为用户分配角色、添加用户等操作。
这里就牵涉到一个权限管理的问题了。系统如何针对不同用户的全新进行管理呢?
其权限管理一个实现思路其实如下:
* 不同角色可以看到不同的链接,只有指定权限的用户才可以看到与其对应权限的操作。如只有管理员才可以添加用户和为用户赋予权限,而卖家只能对消费者订单的处理和对自己商店添加商品等操作。
从上面的描述可以发现,权限管理的实现主要包括两部分:
1. 为不同用户指定不同的链接显示。如管理员可以看到后台管理的所有链接:包括角色管理,商品管理,用户管理、订单管理,商品分类管理,而卖家只能看到订单管理,商品管理和商品类别管理等。其实现就是为这些链接的生成指定不同的权限,只有达到权限用户才进行生成该链接
2. 既然要为不同用户指定不同的权限,则首先要获得用户的权限,然后根据用户的权限来动态生成对应的链接。
有了上面的思路,下面就让我们一起为网上书店案例加入权限管理的功能:
首先,我在Layout.cshtml页面加入指定权限的链接,具体的代码如下所示:
```
<table width="996" border="0" cellspacing="0" cellpadding="0" align="center">
<tr>
<td height="607" valign="top">
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="300" height="55" class="logo"></td>
<td width="480" class="menu">
<ul class="sf-menu">
<li>@Html.ActionLink("首页", "Index", "Home")</li>
@if (User.Identity.IsAuthenticated)
{
<li>@Html.ActionLink("我的", "Manage", "Account")
<ul>
<li>@Html.ActionLink("订单", "Orders", "Home")</li>
<li>@Html.ActionLink("账户", "Manage", "Account")</li>
<li>@Html.ActionLink("购物车", "ShoppingCart", "Home")</li>
</ul>
</li>
}
**@if (User.Identity.IsAuthenticated)
{** **<li>@Html.ActionLinkWithPermission("管理", "Administration", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers | PermissionKeys.SalesReps)
<ul>
<li>@Html.ActionLinkWithPermission("销售订单管理", "Orders", "Admin", PermissionKeys.Administrators | PermissionKeys.SalesReps)</li>
<li>@Html.ActionLinkWithPermission("商品分类管理", "Categories", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers)</li>
<li>@Html.ActionLinkWithPermission("商品信息管理", "Products", "Admin", PermissionKeys.Administrators | PermissionKeys.Buyers)</li>
<li>@Html.ActionLinkWithPermission("用户账户管理", "UserAccounts", "Admin", PermissionKeys.Administrators)</li>
<li>@Html.ActionLinkWithPermission("用户角色管理", "Roles", "Admin", PermissionKeys.Administrators)</li>
</ul>
</li>
}** <li>@Html.ActionLink("关于", "About", "Home")
<ul>
<li>@Html.ActionLink("Online Store 项目", "About", "Home")</li>
<li>@Html.ActionLink("联系方式", "Contact", "Home")</li>
</ul>
</li>
</ul>
</td>
<td width="216" class="menu">
@{Html.RenderAction("_LoginPartial", "Layout");}
</td>
</tr>
</table>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td width="100%" height="10px" />
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<img src="/images/header.jpg" alt="" width="996" height="400" border="0"></td>
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr align="left" valign="top">
<td width="202" height="334">
@{Html.RenderAction("CategoriesPartial", "Layout");}
</td>
<td width="20"> </td>
<td width="774">
<table width="774" border="0" cellspacing="0" cellpadding="0">
<tr>
@(MvcSiteMap.Instance.Navigator())
</tr>
<tr>
<td>
@RenderBody()
</td>
</tr>
</table>
</td>
</tr>
</table>
<table width="996" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<img src="/images/footer.jpg" alt="" width="996" height="5"></td>
</tr>
<tr>
<td height="76">
<table width="996" border="0" cellspacing="0" cellpadding="0" align="center">
<tr>
<td width="329" height="78" align="right"></td>
<td width="14"> </td>
<td width="653"><span class="style7">@Html.ActionLink("主页", "Index", "Home") | @Html.ActionLink("所有分类", "Category", "Home", null, null) | @Html.ActionLink("我的账户", "Account", "Account") | @Html.ActionLink("联系我们", "Contact", "Home") | @Html.ActionLink("关于本站", "About", "Home")</span><br>
版权所有 © 2014-2015, Online Store, 保留所有权利。 </td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
```
上面红色加粗部分就是设置不同角色的不同权限。其中ActionLinkWithPermission是一个扩展方法,其具体实现就是获得登陆用户的角色,然后把用户的角色与当前的需要权限进行比较,如果相同,则通过HtmlHelper.GenerateLink方法来生成对应的链接。该方法的实现代码如下所示:
```
public static MvcHtmlString ActionLinkWithPermission(this HtmlHelper helper, string linkText, string action, string controller, PermissionKeys required)
{
if (helper == null ||
helper.ViewContext == null ||
helper.ViewContext.RequestContext == null ||
helper.ViewContext.RequestContext.HttpContext == null ||
helper.ViewContext.RequestContext.HttpContext.User == null ||
helper.ViewContext.RequestContext.HttpContext.User.Identity == null)
return MvcHtmlString.Empty;
using (var proxy = new UserServiceClient())
{
var role = proxy.GetRoleByUserName(helper.ViewContext.RequestContext.HttpContext.User.Identity.Name);
if (role == null)
return MvcHtmlString.Empty;
var keyName = role.Name;
var permissionKey = (PermissionKeys)Enum.Parse(typeof(PermissionKeys), keyName);
// 通过用户的角色和对应对应的权限进行与操作
// 与结果等于用户角色时,表示用户角色与所需要的权限一样,则创建对应权限的链接
return (permissionKey & required) == permissionKey ?
MvcHtmlString.Create(HtmlHelper.GenerateLink(helper.ViewContext.RequestContext, helper.RouteCollection, linkText, null, action, controller, null, null))
: MvcHtmlString.Empty;
}
}
```
通过上面的代码,我们就已经完成了权限管理的实现了。
## 三、后台管理中商品管理的实现
如果你是管理员的话,这样你就可以进入后台页面对商品、用户、订单等进行管理了。在上面我们已经完成了权限管理的实现。接下来,我们可以用一个管理员账号登陆之后,你可以看到管理员对应的权限。这里我直接在数据库中添加了一条管理员账号,其账号信息是admin,密码也是admin。下面我就这个账号后看到的界面如下图所示:
![](http://images0.cnblogs.com/blog2015/383187/201506/131522390199370.png)
从上图可以看出,后台管理包括销售订单管理、商品类别管理、商品信息管理等。这些都是一些类似的实现,都是一些增、删、改功能的实现。这里就是商品信息管理为例来介绍下。点击商品信息管理后,将可以看到所有商品列表,在该页面可以进商品进行添加、修改和删除等操作。其实现主要是通过应用服务来调用仓储来实现商品的信息的持久化罢了,下面就具体介绍下商品添加功能的实现。因为商品的添加需要首先把上传的图片先添加到服务器上的Images文件夹下,然后通过控制器来调用ProductService的CreateProducts方法来把商品保存到数据库中。
首先是图片上传功能的实现,其实现代码如下所示:
```
[HandleError]
public class AdminController : ControllerBase
{
#region Common Utility Actions
// 保存图片到服务器指定目录下
[NonAction]
private void SaveFile(HttpPostedFileBase postedFile, string filePath, string saveName)
{
string phyPath = Request.MapPath("~" + filePath);
if (!Directory.Exists(phyPath))
{
Directory.CreateDirectory(phyPath);
}
try
{
postedFile.SaveAs(phyPath + saveName);
}
catch (Exception e)
{
throw new ApplicationException(e.Message);
}
}
// 图片上传功能的实现
[HttpPost]
public ActionResult Upload(HttpPostedFileBase fileData, string folder)
{
var result = string.Empty;
if (fileData != null)
{
string ext = Path.GetExtension(fileData.FileName);
result = Guid.NewGuid()+ ext;
SaveFile(fileData, Url.Content("~/Images/Products/"), result);
}
return Content(result);
}
}
```
图片上传成功之后,接下来点击保存按钮则把商品进行持久化到数据库中。其实现逻辑主要是调用商品仓储的实现类来完成商品的添加。主要的实现代码如下所示:
```
[HandleError]
public class AdminController : ControllerBase
{
[HttpPost]
[Authorize]
public ActionResult AddProduct(ProductDto product)
{
using (var proxy = new ProductServiceClient())
{
if (string.IsNullOrEmpty(product.ImageUrl))
{
var fileName = Guid.NewGuid() + ".png";
System.IO.File.Copy(Server.MapPath("~/Images/Products/ProductImage.png"), Server.MapPath(string.Format("~/Images/Products/{0}", fileName)));
product.ImageUrl = fileName;
}
var addedProducts = proxy.CreateProducts(new List<ProductDto> { product }.ToArray());
if (product.Category != null &&
product.Category.Id != Guid.Empty.ToString())
proxy.CategorizeProduct(new Guid(addedProducts[0].Id), new Guid(product.Category.Id));
return RedirectToSuccess("添加商品信息成功!", "Products", "Admin");
}
}
}
// 商品服务的实现
public class ProductServiceImp : ApplicationService, IProductService
{
public List<ProductDto> CreateProducts(List<ProductDto> productsDtos)
{
return PerformCreateObjects<List<ProductDto>, ProductDto, Product>(productsDtos, _productRepository);
}
}
```
到此,我们已经完成了商品添加功能的实现,下面让我们看看商品添加的具体效果如何。添加商品页面:
![](http://images0.cnblogs.com/blog2015/383187/201506/131613046444421.png)
点击保存更改按钮后,则进行商品的添加,添加成功后界面效果:
![](http://images0.cnblogs.com/blog2015/383187/201506/131614524885112.png)
## 四、后台管理中发货操作和确认收货的实现
当消费者创建订单之后,然后卖家或管理员可以通过订单管理页面来对订单进行发货处理操作。以通知购买者该商品已发货了。在当前的电子商务网站中,除了更新订单的状态外,还会发邮件或短信通知购买者。为了保证这两个操作同时完成,此时需要将这两个放在同一个事务中进行提交。
这里为了使系统有更好地可扩展性,采用了基于消息队列和事件驱动的方式来完成发货操作。在看具体实现代码之前,我们先来分析下实现思路:
* 卖家或管理员在订单管理页面,点击发货按钮后,此时相当于订单的状态进行了更新,从已付款状态到已发货状态。这里当然你可以采用传统的方式来实现,即调用订单仓储来更新对应订单的状态。但是这样的实现方式,邮件发送操作可能会嵌套在应用服务层了。这样的设计显然不适合扩展。所以这里采用基于事件驱动和消息队列方式来改进这种方式。
1. 首先,当商家点击发货操作,此时会产生一个发货事件;
2. 接着由注册的领域事件处理程序进行对该领域事件处理,处理逻辑主要是更新订单的状态和更新时间;
3. 然后再将该事件发布到EventBus,EventBus中保存了一个队列来存放事件,发布操作的实现就是往该队列插入一个待处理的事件;
4. 最后在EventBus中的Commit方法中对队列中的事件进行出队列操作,通过事件聚合类来获得对应事件处理器来对出队列的事件进行处理。
* 事件聚合器通过Unity注入(应用)事件的处理器。在EventAggregator类中定义_eventHandlers来保存所有(应用)事件的处理器,在EventAggregator的构造函数中通过调用其Register方法把对应的事件处理器添加到_eventHandlers字典中。然后在EventBus中的Commit方法中通过找到EventAggregator中的Handle方法来触发事件处理器来处理对应事件,即发出邮件通知。这里事件聚合器起到映射的功能,映射应用事件到对应的事件处理器来处理。
通过上面的分析可以发现,发货操作和收货操作都涉及2类事件,一类是领域事件,另一类处于应用事件,领域事件的处理由领域事件处理器来处理,而应用事件的处理不能定义在领域层,所以我们这里新建了一个应用事件处理层,叫OnlineStore.Events.Handlers,已经新建了一个对EventBus支持的层,叫OnlineStore.Events。经过上面的分析,实现发货操作和收货操作是不是有点清晰了呢?如果不是的话也没关系,我们可以结合下面具体的实现代码再来理解下上面分析的思路。因为收货操作和发货操作的实现非常类似,这里只贴出发货操作实现的主要代码进行演示。
首先是AdminController中DispatchOrder操作的实现:
```
public ActionResult DispatchOrder(string id)
{
using (var proxy = new OrderServiceClient())
{
proxy.Dispatch(new Guid(id));
return RedirectToSuccess(string.Format("订单 {0} 已成功发货!", id.ToUpper()), "Orders", "Admin");
}
}
```
接下来便是OrderService中Dispatch方法的实现:
```
public void Dispatch(Guid orderId)
{
using (var transactionScope = new TransactionScope())
{
var order = _orderRepository.GetByKey(orderId);
order.Dispatch();
_orderRepository.Update(order);
RepositorytContext.Commit();
_eventBus.Commit();
transactionScope.Complete();
}
}
```
下面是Order实体类中Dispatch方法的实现:
```
/// <summary>
/// 处理发货。
/// </summary>
public void Dispatch()
{
// 处理领域事件
DomainEvent.Handle<OrderDispatchedEvent>(new OrderDispatchedEvent(this) { DispatchedDate = DateTime.Now, OrderId = this.Id, UserEmailAddress = this.User.Email });
}
```
接下来便是领域事件中Handle方法的实现了,其实现逻辑就是获得所有已注册的领域事件处理器,然后分别事件处理器进行调用。具体的实现代码如下所示:
```
public static void Handle<TDomainEvent>(TDomainEvent domainEvent)
where TDomainEvent : class, IDomainEvent
{
// 找到对应的事件处理器来对事件进行处理
var handlers = ServiceLocator.Instance.ResolveAll<IDomainEventHandler<TDomainEvent>>();
foreach (var handler in handlers)
{
if (handler.GetType().IsDefined(typeof(HandlesAsynchronouslyAttribute), false))
Task.Factory.StartNew(() => handler.Handle(domainEvent));
else
handler.Handle(domainEvent);
}
}
```
对应OrderDispatchedEventHandler类中Handle方法的实现如下所示:
```
// 发货事件处理器
public class OrderDispatchedEventHandler : IDomainEventHandler<OrderDispatchedEvent>
{
private readonly IEventBus _bus;
public OrderDispatchedEventHandler(IEventBus bus)
{
_bus = bus;
}
public void Handle(OrderDispatchedEvent @event)
{
// 获得事件源对象
var order = @event.Source as Order;
// 更新事件源对象的属性
if (order == null) return;
order.DispatchedDate = @event.DispatchedDate;
order.Status = OrderStatus.Dispatched;
// 这里把领域事件认为是一种消息,推送到EventBus中进行进一步处理。
_bus.Publish<OrderDispatchedEvent>(@event);
}
}
```
从上面代码中可以发现,领域事件处理器中只是简单更新订单状态的状态为Dispatched和更新订单发货时间,之后就把该事件继续发布到EventBus中进一步进行处理。EventBus类的具体实现代码如下所示:
```
// 领域事件处理器只是对事件对象的状态进行更新
// 后续的事件处理操作交给EventBus进行处理
// 本案例中EventBus主要处理的任务就是发送邮件通知,
// 在EventBus一般处理应用事件,而领域事件处理器一般处理领域事件
public class EventBus : DisposableObject, IEventBus
{
public EventBus(IEventAggregator aggregator)
{
this._aggregator = aggregator;
// 获得EventAggregator中的Handle方法
_handleMethod = (from m in aggregator.GetType().GetMethods()
let parameters = m.GetParameters()
let methodName = m.Name
where methodName == "Handle" &&
parameters != null &&
parameters.Length == 1
select m).First();
}
public void Publish<TMessage>(TMessage message)
where TMessage : class, IEvent
{
_messageQueue.Value.Enqueue(message);
_committed.Value = false;
}
// 触发应用事件处理器对事件进行处理
public void Commit()
{
while (_messageQueue.Value.Count > 0)
{
var evnt = _messageQueue.Value.Dequeue();
var evntType = evnt.GetType();
var method = _handleMethod.MakeGenericMethod(evntType);
// 调用应用事件处理器来对应用事件进行处理
method.Invoke(_aggregator, new object[] { evnt });
}
_committed.Value = true;
}
}
```
其EventAggregator类的实现如下所示:
```
public class EventAggregator : IEventAggregator
{
private readonly object _sync = new object();
private readonly Dictionary<Type, List<object>> _eventHandlers = new Dictionary<Type, List<object>>();
private readonly MethodInfo _registerEventHandlerMethod;
public EventAggregator()
{
// 通过反射获得EventAggregator的Register方法
_registerEventHandlerMethod = (from p in this.GetType().GetMethods()
let methodName = p.Name
let parameters = p.GetParameters()
where methodName == "Register" &&
parameters != null &&
parameters.Length == 1 &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IEventHandler<>)
select p).First();
}
public EventAggregator(object[] handlers)
: this()
{
// 遍历注册的EventHandler来把配置文件中具体的EventHanler通过Register添加进_eventHandlers字典中
foreach (var obj in handlers)
{
var type = obj.GetType();
var implementedInterfaces = type.GetInterfaces();
foreach (var implementedInterface in implementedInterfaces)
{
if (implementedInterface.IsGenericType &&
implementedInterface.GetGenericTypeDefinition() == typeof(IEventHandler<>))
{
var eventType = implementedInterface.GetGenericArguments().First();
var method = _registerEventHandlerMethod.MakeGenericMethod(eventType);
// 调用Register方法将EventHandler添加进_eventHandlers字典中
method.Invoke(this, new object[] { obj });
}
}
}
}
public void Register<TEvent>(IEventHandler<TEvent> eventHandler)
where TEvent : class, IEvent
{
lock (_sync)
{
var eventType = typeof(TEvent);
if (_eventHandlers.ContainsKey(eventType))
{
var handlers = _eventHandlers[eventType];
if (handlers != null)
{
handlers.Add(eventHandler);
}
else
{
handlers = new List<object> {eventHandler};
}
}
else
_eventHandlers.Add(eventType, new List<object> { eventHandler });
}
}
public void Register<TEvent>(IEnumerable<IEventHandler<TEvent>> eventHandlers)
where TEvent : class, IEvent
{
foreach (var eventHandler in eventHandlers)
Register<TEvent>(eventHandler);
}
// 调用具体的EventHanler的Handle方法来对事件进行处理
public void Handle<TEvent>(TEvent evnt)
where TEvent : class, IEvent
{
if (evnt == null)
throw new ArgumentNullException("evnt");
var eventType = evnt.GetType();
if (_eventHandlers.ContainsKey(eventType) &&
_eventHandlers[eventType] != null &&
_eventHandlers[eventType].Count > 0)
{
var handlers = _eventHandlers[eventType];
foreach (var handler in handlers)
{
var eventHandler = handler as IEventHandler<TEvent>;
if(eventHandler == null)
continue;
// 异步处理
if (eventHandler.GetType().IsDefined(typeof(HandlesAsynchronouslyAttribute), false))
{
Task.Factory.StartNew((o) => eventHandler.Handle((TEvent)o), evnt);
}
else
{
eventHandler.Handle(evnt);
}
}
}
}
```
至于确认收货操作的实现也是类似,大家可以自行参考Github源码进行实现。到此,我们商品发货和确认收货的功能就实现完成了。此时,我们解决方案已经调整为:
![](http://images0.cnblogs.com/blog2015/383187/201506/132309078324198.png)
经过本专题后,我们网上书店案例的业务功能都完成的差不多了,后面添加的一些功能都是附加功能,例如分布式缓存的支持、分布式消息队列的支持以及面向切面编程的支持等功能。既然业务功能都完成的差不多了,下面让我们具体看看发货操作的实现效果吧。
首先是销售订单管理首页,在这里可以看到所有用户的订单状态。具体效果如下图示所示:
![](http://images0.cnblogs.com/blog2015/383187/201506/132316554109450.png)
点击上图的发货按钮后便可以完成商品发货操作,此时创建该订单的用户邮箱中会收到一份发货邮件通知,具体实现效果截图如下所示:
![](http://images0.cnblogs.com/blog2015/383187/201506/132320055351466.png)
其确认收货操作实现的效果与发货操作的效果差不多,这里就不一一截图了,大家可以自行到github上下载源码进行运行查看。
## 五、总结
到这里,该专题的介绍的内容就结束。本专题主要介绍后台管理中权限管理的实现、商品管理、类别管理、角色管理、用户角色管理和订单管理等功能。正如上面所说的,到此,本网上书店的DDD案例一些业务功能都实现的差不多了,接下来需要完善的功能主要是一些附加功能,这些功能主要是为了提高网站的可扩展性和可伸缩性。这些主要包括缓存的支持、分布式消息队列的支持以及AOP的支持。在下一个专题将介绍分布式缓存和分布式消息队列的支持,请大家继续关注。
本专题的所有源码下载:[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 领域驱动设计实战系列总结