从MVP Winform Client使用WebAPI的设计模式

And*_*rew 5 c# design-patterns winforms asp.net-web-api dotnet-httpclient

背景

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

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

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

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

Nko*_*osi 6

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

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

public interface IServiceAgent {
    Task<SomeResultObject> GetSomething(string myParameter);
}
Run Code Online (Sandbox Code Playgroud)

...从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
}
Run Code Online (Sandbox Code Playgroud)

一些示例实现可能看起来像这样......

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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