使用设计模式从MVP Winform客户端消费WebAPI

6

背景

我正在构建一个两层应用程序:

  • 第一层:使用MVP(Model-View-Presenter)设计模式的Winforms应用程序。
  • 第二层:WebAPI RESTful服务。

Winforms客户端将使用HttpClient消费WebAPI服务。这两个层都大量使用IoC和依赖注入设计模式。

问题

当Winforms应用程序需要从WebAPI服务获取数据时,Presenter将协调请求。我的问题是,您是否会直接在Presenter中使用HttpClient?为了保持Presenter的可测试性,如何确保您不必依赖于具体的HttpClient调用?我想着以某种方式还要整合来自此问题的最佳答案。


我投票关闭此问题,因为此问题属于http://programmers.stackexchange.com/。 - Rosdi Kasim
1个回答

8
我通过抽象化一切来解决此问题。
在表示层中,我会使用服务抽象...
public interface IServiceAgent {
    Task<SomeResultObject> GetSomething(string myParameter);
}

...抽象出我从Web API中所需的内容。演示者不需要协调请求。演示者不关心数据来自哪里。它只知道想要某些东西并请求它(SoC)。这是服务代理的工作(SRP)。

服务代理实现可能需要调用不同的数据源,包括Web。因此,抽象HttpClient将减少对该实现的耦合。

一个简单的例子如下...

public interface IHttpClient {
    System.Threading.Tasks.Task<T> GetAsync<T>(string uri) where T : class;
    System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class;
    //...other members as needed : DeleteAsync, PostAsync, PutAsync...etc
}

一些示例实现可以看起来像这样...
public class MyPresenter {
    public MyPresenter(IServiceAgent services) {...}
}

public class MyDefaultServiceAgent : IServiceAgent {
    IHttpClient httpClient;

    public MyDefaultServiceAgent (IHttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public async Task<SomeResultObject> GetSomething(string myParameter) {
          var url = "http://localhost/my_web_api_endpoint?q=" + myParameter;
          var result = await httpClient.GetAsync<SomeResultObject>(url);
          return result;
    }
}

public class MyDefaultHttpClient : IHttpClient {
    HttpClient httpClient; //The real thing

    public MyDefaultHttpClient() {
        httpClient = createHttpClient();
    }

    /// <summary>
    ///  Send a GET request to the specified Uri as an asynchronous operation.
    /// </summary>
    /// <typeparam name="T">Response type</typeparam>
    /// <param name="uri">The Uri the request is sent to</param>
    /// <returns></returns>
    public System.Threading.Tasks.Task<T> GetAsync<T>(string uri) where T : class {
        return GetAsync<T>(new Uri(uri));
    }

    /// <summary>
    ///  Send a GET request to the specified Uri as an asynchronous operation.
    /// </summary>
    /// <typeparam name="T">Response type</typeparam>
    /// <param name="uri">The Uri the request is sent to</param>
    /// <returns></returns>
    public async System.Threading.Tasks.Task<T> GetAsync<T>(Uri uri) where T : class {
        var result = default(T);
        //Try to get content as T
        try {
            //send request and get the response
            var response = await httpClient.GetAsync(uri).ConfigureAwait(false);
            //if there is content in response to deserialize
            if (response.Content.Headers.ContentLength.GetValueOrDefault() > 0) {
                //get the content
                string responseBodyAsText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                //desrialize it
                result = deserializeJsonToObject<T>(responseBodyAsText);
            }
        } catch (Exception ex) {
            Log.Error(ex);
        }
        return result;
    }

    private static T deserializeJsonToObject<T>(string json) {
        var result = JsonSerializer.Deserialize<T>(json);
        return result;
    }
}

通过抽象这些依赖项,您可以使用假/模拟服务代理进行单元测试,从而使Presenter可测试。您可以使用假/模拟HTTP客户端来测试服务代理。它还允许您注入这些接口的任何具体实现,以便在需要更改/交换/维护应用程序组件时使用。


太好了,谢谢!在创建服务代理时,您会为每个Presenter创建一个服务代理还是为每个Model创建一个服务代理?我正在考虑这样一种情况:Presenter需要进行多次调用以获取不同类型的数据。此外,在使用IoC容器时,您如何确保正确的服务代理被注入到Presenter中? - Andrew
这取决于你擅长什么。我通常每个域名有一个服务代理。我尽力遵守SRP原则。至于获取正确的注入,我使用ISP。 - Nkosi
我仍在学习正确的编程技术和稳固的原则。当你说你使用ISP时,是指你为每个服务代理创建不同的接口吗? - Andrew
我将功能分解为它们自己的接口,然后根据依赖类的需求进行混合和匹配。 - Nkosi
@Nkosi createHttpClient(); 这个函数在哪里,应该放在哪里? - Twix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接