WCF 示例 – 第五章 – 上下文






4.95/5 (17投票s)
服务器端资源的全局和请求上下文模式
![]() |
![]() |
|
第四章 | 第六章 |
系列文章
WCF by example 是一系列文章,描述如何设计和开发一个 WPF 客户端,使用 WCF 进行通信,并使用 NHibernate 进行持久化。 系列介绍 描述了文章的范围,并从高层次讨论了架构解决方案。
Sharpy - 一个 Metro 项目
10 月 23 日:我目前正在为一个 Metro 应用程序进行一项新项目:Sharpy。我打算在应用程序的服务端使用 WCF by Example 中讨论的模式;目标是展示 Metro 应用程序的开发与我们迄今为止看到的应用程序类型有多么相似。目前还没有代码,但希望很快会有。希望您喜欢。 文章在这里。
章节概述
在“第一章 - 基线”中,定义了 CustomerService
的草稿版本。在“第三章 - 响应”中,重构了服务,以便业务警告和异常始终可以在客户端访问。在“第四章 - 事务管理器”中,我们讨论了管理事务、警告和异常的必要性;引入了两个组件:事务管理器工厂和服务基类。我们没有完全解决业务逻辑处理过程中业务警告的记录方式。
目前,我们的服务引用了事务管理器工厂的一个实例,该工厂的实现根据应用程序是以内存模式还是 NHibernate 模式运行而不同。这个技术要求通常通过依赖注入来解决。但是,当工厂的单个实例满足需求时,就没有必要让容器管理服务的创建,即使在多请求场景下也是如此。因此,我们将采用一种不同的方法,提供一个全局上下文,以便服务器组件可以访问工厂的单个实例。在后面的章节中,我们将看到如何使用 DI 来确定全局上下文公开的工厂实现。
我们在上一章中还提到,需要一种机制,以便服务和业务逻辑可以记录业务警告。在第四章中,我们在事务管理器上定义了一个机制,该机制检查在业务处理过程中是否创建了业务警告。在本章中,我们将通过引入一个新的组件 BusinessNotifier
来展示如何实现这一方面。
由于跨服务器层需要一种新型服务,我们将讨论对“垂直”服务容器的需求。其中一些服务会在应用程序的整个生命周期内创建一次,而另一些则仅在请求期间创建。
本章的源代码可以在 Codeplex 更改集 73565 中找到。eDirectory 解决方案的最新代码可以在 Codeplex 中找到。
上下文
与传统的 Web 应用程序和/或 Web 服务一样,我们会处理需要以隔离的方式访问服务和数据的传入请求,这些服务和数据是在请求处理过程中创建的,并且必须在请求得到服务时终止。在 WCF 中,实例上下文代表了服务实例的上下文信息,它可以用于提供我们请求需求所需的确切内容。我们将在后续章节中看到如何使用 WCF 扩展来实现请求上下文。目前,在本章中,我们将定义容器和上下文的基线。
全局上下文
全局上下文公开了我们的服务器组件可以使用的类和数据,它们与请求无关,并与应用程序一起保持活动状态。对于这些全局资源和服务,一个好的方法是尽可能不保留任何状态,如果设计不正确,可能会出现并发问题。锁定机制可以帮助处理并发,但可能会导致性能下降。
事务管理器工厂是存储在全局上下文中的理想选择。工厂类不管理任何状态,并且单个实例即使在多请求场景下也能满足应用程序创建事务管理器的需求。
public interface IGlobalContext
{
ITransFactory TransFactory { get; }
}
需要 IGlobalContext
的一个单一实现
public class GlobalContext
: IGlobalContext
{
static readonly Object LocatorLock = new object();
private static GlobalContext InternalInstance;
private GlobalContext() { }
public static GlobalContext Instance()
{
if (InternalInstance == null)
{
lock (LocatorLock)
{
// in case of a race scenario ... check again
if (InternalInstance == null)
{
InternalInstance = new GlobalContext();;
}
}
}
return InternalInstance;
}
#region IGlobalContext Members
public ITransFactory TransFactory { get; set; }
#endregion
}
值得注意的是,TransFactory
属性有一个 public
设置器,我们将在后续章节介绍依赖注入时解决这个问题。
请求上下文
与全局上下文不同,请求上下文完全了解传入的请求。它的主要作用是为单个请求提供资源和服务,确保请求不会干扰其他请求的资源。请求资源在请求得到服务时终止。
BusinessNotifier
需要在请求的持续时间内存储业务警告,因为多个请求可能并发处理,因此需要为每个请求创建一个 BusinessNotifier
实例。请求上下文提供了一个单一的入口点来获取诸如 BusinessNotifier
之类的请求资源。
public interface IRequestContext
{
IBusinessNotifier Notifier { get; }
}
值得注意的是,当其他人创建 NHibernate 会话时,会将 NHibernate 会话范围限定为单个 WCF 调用,而我们对这个问题的处理方式将有所不同。第四章中提出的工作单元解决方案依赖于服务使用工厂类创建事务管理器。请记住,工厂的职责是为事务管理器提供一个 RepositoryLocator
。然后,服务将委托给事务管理器来执行其命令,这样命令就会获得访问后端存储库所需的 RepositoryLocator
。这个过程涉及一些部分,但该设计以一种巧妙的方式解耦了职责,从而实现了非常灵活的设计,并且易于与我们的工厂结合进行测试,我们的服务很好地处理了这个问题。
有关上述不同方法的示例,您可以参考以下链接
容器
容器只是一个包装全局和请求上下文的辅助类,它有助于定位请求和全局资源。

public class Container
{
private static Container InternalInstace;
private Container(){}
private static Container Instance()
{
if (InternalInstace != null) return InternalInstace;
InternalInstace = new Container();
return InternalInstace;
}
public static IGlobalContext GlobalContext
{
get
{
return AppServices.GlobalContext.Instance();
}
}
public static IRequestContext RequestContext { get; set; }
}
GlobalContext
属性不需要公开设置器,因为我们将始终使用相同的实现。但是,我们提到我们需要不同的 IRequestContext
实现,具体取决于我们是在内存模式还是 NHibernate 模式下运行。如上所述,我们将在后面的章节中解决 public
设置器的问题。
BusinessNotifier
业务警告只是 string
消息,用于在业务处理过程中发生的某些事件或相关情况通知用户。BusinessNotifier
为服务和领域实体提供了一个方法来添加新消息,事务管理器会检查警告并将其添加到响应中,以便可以在客户端进行处理。
public class BusinessNotifier
: IBusinessNotifier
{
private readonly IList<BusinessWarning> WarningList = new List<BusinessWarning>();
#region Implementation of IBusinessNotifier
...
public IEnumerable<BusinessWarning> RetrieveWarnings()
{
if (!HasWarnings) return null;
var results = WarningList.ToList();
WarningList.Clear();
return results;
}
#endregion
}
ServiceBase 重构
现在我们可以移除 ServiceBase
类中的 Factory 属性,并使用新的 Container
类获取事务管理器实例。
以前

操作后

事务管理器更改
现在 BusinessNotifier
可用了,我们可以增强事务管理器,实现我们留空的 CheckForWarnings
方法。

因此,新实现会检查 BusinessNotifier
方法中是否添加了警告,如果添加了,则将其添加到响应实例中。

新测试
我们添加了一些新的核心功能,因此增加测试覆盖率是一个好主意。让我们修改 CustomerService
中的 FindAll
服务方法,以便在找不到客户实例时创建一个业务警告。
public class CustomerService
:ServiceBase, ICustomerService
{
...
public CustomerDtos FindAll()
{
return ExecuteCommand(locator => FindAllCommand(locator));
}
private CustomerDtos FindAllCommand(IRepositoryLocator locator)
{
var result = new CustomerDtos { Customers = new List<CustomerDto>() };
locator.FindAll<Customer>().ToList()
.ForEach(c => result.Customers.Add(Customer_to_Dto(c)));
if (result.Customers.Count() == 0)
{
Container.RequestContext.Notifier.AddWarning(
BusinessWarningEnum.Default, "No customer instances were found");
}
return result;
}
...
}
那么新的测试是
[TestMethod]
public void CheckFindAllNotification()
{
var result = Service.FindAll();
Assert.IsTrue(result.Customers.Count == 0, "No customers were expected");
Assert.IsTrue(result.Response.HasWarning, "Warning flag is not set");
Assert.IsTrue(result.Response.BusinessWarnings.Count() == 1,
"One warning was only expected");
Assert.AreSame(result.Response.BusinessWarnings.Single().Message,
"No customer instances were found");
CreateCustomer();
result = Service.FindAll();
Assert.IsFalse(result.Response.HasWarning, "Warning flag is set");
}
章节总结
通过本章,我们完成了服务器端组件基线的设置。我们现在已经准备好开始开发富客户端了,因为我们已经在服务器端建立了一个全面的基础设施供客户端使用。我们将看到客户端如何在不需要 NHibernate 或 WCF 的情况下与我们的服务和业务领域类进行交互。这种方法符合敏捷开发方法,因此我们可以开始业务探索并快速向客户提供反馈,而无需部署昂贵的后端基础设施。
下一章将介绍富客户端和 MVVM 在 WPF 中的基础知识。我们需要另外三到四章来定义富客户端的基本基础设施。