如何关闭通过接口实例化的WCF客户端连接?
问题场景
在MVC 4项目的控制器中,我通过接口实例化WCF客户端,代码如下:
private readonly ILMS_Service lms_client; public ProductsController(ILMS_Service client) { lms_client = client; } public ProductsController() { lms_client = new LMS_ServiceClient(); }
目前能正常访问操作协定,但这种方式下,客户端没有常规实例化(LMS_Client lms_client = new LMS_ServiceClient();)时的.Open()和.Close()方法。因为单元测试需要传入Mock WCF客户端,所以无法使用常规初始化方式。
我需要明确:
- 该场景下开闭WCF客户端连接的正确方式
Dispose()方法的实现位置(控制器还是WCF项目的.svc文件)- 通过析构函数关闭连接是否合理
解决方案与示例
核心思路
让操作协定接口支持资源管理,通过封装类处理WCF客户端的开闭逻辑,同时保证控制器的可测试性。
1. 扩展操作协定接口继承IDisposable
修改ILMS_Service接口,让它继承IDisposable,这样真实客户端和Mock对象都能统一支持资源释放:
public interface ILMS_Service : IDisposable { // 你的操作协定方法 Task<Product> GetProductById(int id); }
2. 封装WCF客户端的安全开闭逻辑
创建一个包装类,接管WCF客户端的生命周期管理,处理Open/Close/Abort的安全逻辑:
public class LmsServiceClientWrapper : ILMS_Service { private readonly LMS_ServiceClient _innerClient; private bool _disposed = false; public LmsServiceClientWrapper() { _innerClient = new LMS_ServiceClient(); // 初始化时打开连接,也可以延迟到第一次调用服务方法时执行 _innerClient.Open(); } // 转发操作协定方法到内部WCF客户端 public Task<Product> GetProductById(int id) { return _innerClient.GetProductById(id); } // 实现IDisposable接口 public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 安全关闭连接:先尝试Close,失败则用Abort兜底 if (_innerClient.State == System.ServiceModel.CommunicationState.Opened) { try { _innerClient.Close(); } catch { _innerClient.Abort(); } } else if (_innerClient.State != System.ServiceModel.CommunicationState.Closed) { _innerClient.Abort(); } _innerClient.Dispose(); } _disposed = true; } // 析构函数作为兜底,防止未主动调用Dispose导致资源泄漏 ~LmsServiceClientWrapper() { Dispose(false); } }
3. 控制器中依赖注入并管理资源
MVC控制器本身继承IDisposable,重写它的Dispose方法,主动释放WCF客户端资源:
public class ProductsController : Controller { private readonly ILMS_Service _lmsClient; private bool _disposed = false; // 构造函数注入:用于单元测试传入Mock对象 public ProductsController(ILMS_Service client) { _lmsClient = client; } // 默认构造函数:生产环境使用包装类实例 public ProductsController() : this(new LmsServiceClientWrapper()) { } // 示例Action public async Task<ActionResult> Details(int id) { var product = await _lmsClient.GetProductById(id); return View(product); } // 重写控制器的Dispose方法,释放WCF客户端 protected override void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _lmsClient.Dispose(); } base.Dispose(disposing); _disposed = true; } }
4. 单元测试的Mock实现
使用Moq等框架MockILMS_Service,无需处理真实WCF连接:
[Test] public async Task Details_ReturnsProductView() { // Mock服务接口 var mockService = new Mock<ILMS_Service>(); mockService.Setup(s => s.GetProductById(It.IsAny<int>())) .ReturnsAsync(new Product { Id = 1, Name = "Test Product" }); // 传入Mock对象初始化控制器 var controller = new ProductsController(mockService.Object); // 执行Action var result = await controller.Details(1) as ViewResult; // 断言验证 Assert.IsNotNull(result); Assert.IsInstanceOf<Product>(result.Model); }
关键说明
- Dispose实现位置:
Dispose逻辑放在WCF客户端包装类中,控制器仅负责触发客户端的Dispose(MVC框架会在请求结束时自动调用控制器的Dispose)。 - 析构函数的合理性:析构函数是兜底方案,防止忘记主动调用
Dispose导致资源泄漏,但优先依赖主动调用(控制器的Dispose会被MVC自动触发),不是必须但能提升安全性。 - 开闭时机:建议在包装类构造时打开连接,或延迟到第一次调用服务方法时;关闭操作必须在
Dispose中处理,并且要用Abort作为Close失败的兜底方案,避免资源泄漏。
内容的提问来源于stack exchange,提问作者jdistro07
相关产品推荐
相关产品推荐

