4

背景设计模式从MVP WinForm的客户端消耗的WebAPI

我建立两个层次的应用:

  • 1级:使用MVP的WinForms应用程序(模型 - 视图 - 演示)设计模式。
  • 第2层:WebAPI RESTful服务。

Winforms客户端将使用HttpClient使用WebAPI服务。这两个层次巨资利用IoC和依赖注入设计模式

问题

当WinForms应用程序从的WebAPI服务需要的数据,演示者将协调请求。我的问题是,你会直接在演示者的内部使用HttpClient吗?为了保持演示者的可测试性,您如何确保您不必依靠具体的HttpClient调用?我正在想办法整合这个question的最佳答案。

+0

我投票结束这个问题作为题外话,因为这个问题属于http://programmers.stackexchange.com/ –

回答

4

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

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

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

...抽象是我从网上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客户端来测试您的服务代理。如果您需要更改/交换/维护应用程序组件,它还允许您注入这些接口的任何具体实现。

+0

梦幻般的答案,谢谢!在创建服务代理时,您是否会为每个演示者创建一个服务代理,或者为每个模型创建一个服务代理?我正在考虑演示者需要多次调用以获取不同类型的数据的场景。此外,在使用IoC容器时,如何确保正确的服务代理正在注入演示者? – Andrew

+0

这取决于您所感到的舒适程度。我通常每个域都有一个服务代理。我尽可能地坚持SRP。至于获得正确的注入,我使用ISP – Nkosi

+0

我仍然学习正确的编程技术,因此,坚实的原则。当你说你使用ISP时,你是否说你为每个服务代理创建了不同的界面? – Andrew