c# - 使用 DI 在 WPF 应用程序中使用 WCF 服务的正确方法

标签 c# wpf wcf mvvm dependency-injection

问题在于,与 MVC 中的 Controller 相反,WPF 中的 MVVM 模型是实例化的并且永远存在。实际上,这对我来说意味着如果我的 viemodel 中有私有(private)属性(property),我的代理将开放很长时间,例如:

//Some example after googling for "consuming wcf services in wpf app~"
private FootballerServices.FootballerServiceClient footballersService = null;

private void Window_Loaded(object sender, RoutedEventArgs e)
{

    footballersService = new FootballerServices.FootballerServiceClient();
    try
    {
        FootballerBox.ItemsSource = footballersService.GetFootballers();
    }
    catch (Exception ex)
    {

        MessageBox.Show(ex.Message);
    }

}

我该如何正确解决这个问题?

第一个解决方案:
private void button_Click(object sender, RoutedEventArgs e)
{
    MyServicesClient proxy = new MyServicesClient();

    try
    {
        MyServicesData data = proxy.GetDataFromMyService();

        proxy.Close();
    }
    catch (FaultException ex)
    {
        MessageBox.Show("Fault Exception: " + ex.Message);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

然后我可以创建一个像 ServiceWrapper 这样的新类,在其中封装所有方法调用:
private void button_Click(object sender, RoutedEventArgs e)
{
var t = serviceWrapper.GetDataFromMyService();
(...)
}

在服务包装器中:
private void button_Click(object sender, RoutedEventArgs e)
{
    MyServicesClient proxy = new MyServicesClient();

    try
    {
        MyServicesData data = proxy.GetDataFromMyService();

        proxy.Close();
    }
    catch (FaultException ex)
    {
        (...)
    }
    catch (Exception ex)
    { 
        (...)
    }

    return data;
}

现在最后一件事是将DI添加到该解决方案中

第二种解决方案

我发现的另一种方法是将抽象工厂模式与 DI 一起使用,就像这里回答的那样:

Dependency Injection wcf

但是,我并不完全理解这个解决方案。
我的理解是它是这样发生的:
DI 解析我的 ServiceClient 的一个实例,并且在执行超出使用解析实例的方法的范围后,DI 容器处理该实例 - 不需要抽象工厂模式。
显然,我的理解是错误的。

问题:在这种情况下,正确的做法是什么?

最佳答案

您很可能不想注入(inject) WCF 客户端,因为您只需要很短的时间。你可以注入(inject)的是一个工厂,它会在需要时返回服务。

工厂界面可能如下所示:

public interface IMyServiceFactory
{
    IMyService Create();
    void Release(IMyService created);
}

假设您通过构造函数注入(inject)它并向您的类添加私有(private)只读字段,如下所示:
private readonly IMyServiceFactory _myServiceFactory;

然后,当您需要该服务时,请执行以下操作:
var myService = _myServiceFactory.Create();
try
{
   // do something with the service
}
finally
{
   _myService.Release(myService);
}

一个很大的好处是你的类完全依赖于抽象。它不“知道”服务是由 WCF 客户端实现的,或者工厂正在调用 DI 容器。您可以使用 IMyService 的模拟来测试它。 .如果您的类(class)直接创建自己的 WCF 客户端,这是不可能的。

您没有提及您使用的是哪个容器,但其中许多会为您创建 WCF 客户端来实现接口(interface)。
  • Autofac
  • Windsor - 他们的文件在这一点上是缺乏的。如果您使用 Windsor,我可以提供更多详细信息。

  • 关于温莎的另一个不错的细节是它还将 create the abstract factory for you .

    这里还有一些组成使用 Windsor 的示例实现的部分。这假设您有一个实现 IMyService 的 WCF 服务。 .

    这是服务接口(interface)、工厂接口(interface)和使用它的类:
    public interface IMyService
    {
        IEnumerable<string> GetSomeData();
    }
    
    public interface IMyServiceFactory
    {
        IMyService Create();
        void Release(IMyService created);
    }
    
    public class ClassThatConsumesService
    {
        private readonly IMyServiceFactory _serviceFactory;
    
        public ClassThatConsumesService(IMyServiceFactory myServiceFactory)
        {
            _serviceFactory = myServiceFactory;
        }
    
        public void MethodThatDoesSomething()
        {
            var service = _serviceFactory.Create();
            try
            {
                var data = service.GetSomeData();
                // do whatever
            }
            finally
            {
                _serviceFactory.Release(service);
            }
        }
    }
    

    然后这里是使用 Windsor 的一些实现细节的示例。但一个重要的细节是,以上任何内容都取决于温莎。您可以使用不同的 DI 容器来执行此操作。你甚至根本不需要 DI 容器。您的类(class)所知道的是,它正在调用工厂来获取服务实例。
    public class ClassThatConsumesService
    {
        private readonly IMyServiceFactory _serviceFactory;
    
        public ClassThatConsumesService(IMyServiceFactory myServiceFactory)
        {
            _serviceFactory = myServiceFactory;
        }
    
        public void MethodThatDoesSomething()
        {
            var service = _serviceFactory.Create();
            try
            {
                var data = service.GetSomeData();
                // do whatever
            }
            finally
            {
                _serviceFactory.Release(service);
            }
        }
    }
    

    要使用 Windsor,您需要添加 Windsor WCF Facility nuget 包,它是 Windsor 加上一些用于处理 WCF 服务的附加类。

    您的容器注册可能如下所示:
    container.AddFacility<TypedFactoryFacility>();
    container.AddFacility<WcfFacility>();
    container.Register(Component.For<IMyService>()
        .AsWcfClient(WcfEndpoint.FromConfiguration("MyServiceEndpointName")));
    container.Register(Component.For<IMyServiceFactory>().AsFactory());
    

    这将执行以下操作:
  • 为容器添加“设施”或能力以提供抽象工厂(我会回到那个。)
  • 添加用于管理 WCF 服务的 WCF 工具
  • 告诉容器 IMyService 的实现是使用名为“MyServiceEndpointName”的 app.config/web.config 中指定的端点的 WCF 客户端。
  • 告诉容器 IMyServiceFactory 的实现是温莎创建的工厂。 Here's that documentation again. .这意味着您实际上并没有创建一个类来实现 IMyServiceFactory .容器做到了。当您调用 Create工厂创建客户端。当您调用 Release它处理客户端。

  • 如果您愿意,可以编写自己的工厂实现,如下所示:
    public class WcfClientMyServiceFactory : IMyServiceFactory
    {
        public IMyService Create()
        {
            return new MyServiceClient();
        }
    
        public void Release(IMyService created)
        {
            var client = (MyServiceClient) created;
            try
            {
                try
                {
                    client.Close();
                }
                catch
                {
                    client.Abort();
                }
            }
            finally
            {
                client.Dispose();
            }
        }
    }
    

    (不要引用我关于如何正确关闭 WCF 客户端的详细信息。已经有一段时间了,我生疏了。)
    但是习惯了使用 Windsor 之后,它就容易多了。

    以这种方式完成后,如果假设 IMyService 假设你想对你的类进行单元测试怎么办?会返回具体数据吗?现在真的好用了Moq或者像这样编写测试双类:
    public class MyServiceDouble : IMyService
    {
        public IEnumerable<string> GetSomeData()
        {
            return new [] {"x", "y", "z"};
        }
    }
    
    public class MyServiceFactoryDouble : IMyServiceFactory
    {
        public IMyService Create()
        {
            return new MyServiceDouble();
        }
    
        public void Release(IMyService created)
        {
            // Nothing to clean up.
        }
    }
    

    因为你的类(class)对IMyService一无所知- 它不知道“正常”实现是 WCF 客户端 - 很容易用其他东西替换它。

    关于c# - 使用 DI 在 WPF 应用程序中使用 WCF 服务的正确方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47640718/

    相关文章:

    c# - UICollectionView - 一个 View 上的更新动画太多

    c# - 通过 C# 使用间接 url 下载

    C# 语法 if (value=this || this|| this) do this

    wcf - WCF 和 SSL 的问题

    c# - 使用 Gmail 设置发送电子邮件时出现问题

    c# - 全局范围附加依赖属性

    c# - WPF应用程序在并发环境中被PropertyChangedEventManager挂起

    wpf - 如何将 WPF 控件绑定(bind)到后面的代码?

    c# - 从 wcf 绑定(bind) transferMode 从 "Buffered"更改为 "Streamed"是否被视为客户端的重大更改?

    c# - 使用 wsHttpBinding 和 Message Security 使用客户端凭据类型窗口对 WCF 进行负载平衡