Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Design pattern to consume WebAPI from MVP Winform Client

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.

like image 373
Andrew Avatar asked Jun 11 '16 16:06

Andrew


1 Answers

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.

like image 133
Nkosi Avatar answered Nov 06 '22 21:11

Nkosi