【问题标题】:Design pattern to consume WebAPI from MVP Winform Client从 MVP Winform 客户端使用 WebAPI 的设计模式
【发布时间】:2016-06-11 16:13:14
【问题描述】:

背景

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

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

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

问题

当 Winforms 应用程序需要来自 WebAPI 服务的数据时,演示者将协调请求。我的问题是,你会直接在演示者内部使用HttpClient 吗?为了保持演示者可测试,您如何确保不必依赖具体的HttpClient 调用?我正在考虑以某种方式整合这个question的最佳答案。

【问题讨论】:

标签: c# winforms design-patterns asp.net-web-api dotnet-httpclient


【解决方案1】:

我通过抽象一切来解决这个问题。

在表示层我会有一个服务抽象...

public interface IServiceAgent {
    Task<SomeResultObject> GetSomething(string myParameter);
}

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

服务代理实现可能需要调用不同的数据源。包括网络。所以抽象 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;
    }
}

通过抽象这些依赖关系,您可以通过允许使用虚假/模拟服务代理进行单元测试来保持演示者的可测试性。您可以使用伪造/模拟的 HTTP 客户端测试您的服务代理。如果您需要更改/交换/维护您的应用程序组件,它还允许您注入这些接口的任何具体实现。

【讨论】:

  • 很棒的答案,谢谢!创建服务代理时,您会为每个演示者创建一个服务代理,还是为每个模型创建一个服务代理?我正在考虑演示者需要进行多次调用以获取不同类型的数据的场景。此外,在使用 IoC 容器时,如何确保将正确的服务代理注入到 Presenter 中?
  • 这取决于您对什么感到满意。我通常每个域都有一个服务代理。我尽量坚持 SRP。至于获得正确的注入,我使用 ISP
  • 我仍在学习正确的编程技术,从而掌握扎实的原则。当您说您使用 ISP 时,您是说您为每个服务代理创建不同的接口吗?
  • 我将功能分解为它们自己的接口,然后根据依赖类的需要进行混合和匹配。
  • @Nkosi createHttpClient(); 这个函数在哪里,放在哪里?
猜你喜欢
  • 1970-01-01
  • 2023-04-02
  • 2017-07-05
  • 1970-01-01
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-30
相关资源
最近更新 更多