6

Background

I'm building a two-tiered application:

  • Tier 1: Winforms application using the MVP (Model-View-Presenter) design pattern.
  • Tier 2: WebAPI RESTful service.

The Winforms client will consume the WebAPI service using HttpClient. Both tiers heavily make use of IoC and Dependency Injection design patterns

Question

When the Winforms application needs data from the WebAPI service, the presenter will be coordinating the request. My question is, would you use the HttpClient directly inside of the presenter? In the interest of keeping the presenter testable, how do you make sure that you don't have to rely on a concrete HttpClient call? I was thinking of somehow also integrating the top answer from this question.

Community
  • 1
  • 1
Andrew
  • 893
  • 12
  • 28
  • I'm voting to close this question as off-topic because this question belongs to http://programmers.stackexchange.com/ – Rosdi Kasim Jun 11 '16 at 16:56

1 Answers1

8

I get around this by abstracting everything.

In the presentation layer I would have a service abstraction...

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

...that abstracts what I want from the web API. The presenter does not need to be coordinating the request. The presenter doesn't concern itself with where the data is coming from. All it knows is that it wants something and asks for it (SoC). It's the service agent's job to do that (SRP).

The service agent implementation may need to make calls to different sources for data. Including the web. So abstracting HttpClient will loosen the coupling to that implementation.

A simple example like...

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
}

Some example implementations can look like this...

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;
    }
}

By abstracting those dependencies you keep the presenter testable by allowing for unit tests with a fake/mocked service agent. You can test your service agent with a fake/mocked HTTP client. It also allows you to inject any concrete implementation of those interfaces if you need to change/swap/maintain your application components.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
  • Fantastic answer, thank you! When creating service agents, would you create one service agent per presenter, or one service agent per model? I'm thinking about the scenario where a presenter would need to make multiple calls to grab different types of data. Furthermore, when using an IoC container, how would you ensure the right service agent(s) is getting injected into the presenter? – Andrew Jun 11 '16 at 18:17
  • It varies on what you are comfortable with. I usually have one service agent per domain. I try to stick to SRP as best I can. as for getting the correct injection , I use ISP – Nkosi Jun 11 '16 at 18:22
  • I'm still learning correct programming techniques, and thus, solid principles. When you say you use ISP, are you saying that you create a different interface for each service agent? – Andrew Jun 11 '16 at 18:36
  • I break functionality into their own interfaces and then mix and match based on what the dependent class needs. – Nkosi Jun 11 '16 at 18:38
  • @Nkosi Where is `createHttpClient();` this function and where to put it? – Twix Jun 24 '19 at 08:37