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

时间:2017-12-04 19:36:24

标签: c# wpf wcf mvvm dependency-injection

问题是与MVC中的控制器相反,WPF中的MVVM模型被实例化并且那里是好的。实际上,这对我来说意味着如果我的viemodel中有私有财产,我的代理将会开放很长时间,例如:

//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();         (......)     }

并在Service Wrapper中:

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容器处理该实例 - 不需要抽象工厂模式。 显然,我的理解是错误的。

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

1 个答案:

答案 0 :(得分:3)

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

工厂界面可能如下所示:

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

假设您通过构造函数注入此内容并向您的类添加私有只读字段,如下所示:

private readonly IMyServiceFactory _myServiceFactory;

然后,当您需要该服务时,您可以这样做:

var myService = _myServiceFactory.Create();
try
{
   // do something with the service
}
finally
{
   _myService.Release(myService);
}

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

您没有提到您正在使用的容器,但其中许多容器将为您创建WCF客户端以实现接口。

  • Autofac
  • Windsor - 此时他们的文档缺乏。如果您使用Windsor,我可以提供更多细节。

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

以下是使用Windsor构建示例实现的更多部分。这假设您有一个实现IMyService的WCF服务。

这是服务接口,工厂接口以及使用它的类:

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容器。您的所有类都知道它正在调用工厂来获取服务实例。

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的实现是使用app.config / web.config中指定名为“MyServiceEndpointName”的端点的WCF客户端。
  • 告诉容器IMyServiceFactory的实现是Windsor创建的工厂。 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客户端的详细信息。已经有一段时间了,我生锈了。)

但是习惯使用温莎之后,它就容易多了。

这样做了,假设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.
    }
}

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