vendredi 21 décembre 2018

Implementing a generic API caller

I am trying to implement a generic caller that uses OpenWeatherMap's different weather API's, but I got stuck in regards to how I would put in the right identifier for the link.

  • .../weather?q=... returns JSON data for the current weather;
  • .../forecast?q=... returns JSON data for a five day forecast.

I am looking for the textbook way to maybe retrieve the API type of each class through accessing GetAPIType(), cast that to an int and put it in the index, so that I would be able to use identifiers[index]. Or perhaps there is an easier way to do it.

Checking for the typeof(T) also crossed my mind, and I would assign the index depending on the if(typeof(T).Equals(typeof(...))) construct, but that seems very messy and if OpenWeatherMap had 100 API's in theory, I would need 100 different if constructs. With this in mind, wouldn't creating those checks beat the purpose of Client being generic?

A third solution I thought of would be passing APIType type as a parameter for the Client constructor,

e.g. var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),

but given the fact that Client is generic and I already provide a type when I instantiate it, it would seem awfully redundant.

Client.cs

using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Reflection;

namespace Rainy.OpenWeatherMapAPI
{
    public class Client<T>
    {
        private readonly string location;
        private readonly string apiKey;
        private readonly string requestUri;
        private readonly string[] identifiers = { "weather", "forecast" };
        private readonly int index;

        public Client(string location, string apiKey)
        {
            // Get the type of API used in order to get the right identifier for the link.
            // ??? Maybe use Reflection, somehow.
            this.location = location;
            this.apiKey = apiKey;
            requestUri = $"api.openweathermap.org/data/2.5/{}?q={location}&appid={apiKey}";
        }

        public async Task<T> GetWeather(CancellationToken cancellationToken)
        {
            using (var client = new HttpClient())
            using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
            using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
            {
                var stream = await response.Content.ReadAsStreamAsync();

                if (response.IsSuccessStatusCode)
                    return DeserializeJsonFromStream<T>(stream);

                var content = await StreamToStringAsync(stream);
                throw new APIException
                {
                    StatusCode = (int)response.StatusCode,
                    Content = content
                };
            }
        }

        private U DeserializeJsonFromStream<U>(Stream stream)
        {
            if (stream == null || stream.CanRead == false)
                return default(U);

            using (var sr = new StreamReader(stream))
            using (var jtr = new JsonTextReader(sr))
            {
                var js = new JsonSerializer();
                var searchResult = js.Deserialize<U>(jtr);
                return searchResult;
            }
        }

        private async Task<string> StreamToStringAsync(Stream stream)
        {
            string content = null;

            if (stream != null)
                using (var sr = new StreamReader(stream))
                    content = await sr.ReadToEndAsync();

            return content;
        }
    }
}

APIType.cs

namespace Rainy.OpenWeatherMapAPI
{
    public enum APIType
    {
        CurrentWeather = 0,
        FiveDayForecast = 1
    }
}

IWeather.cs

namespace Rainy.OpenWeatherMapAPI
{
    public interface IWeather
    {
        APIType GetAPIType();
    }
}

CurrentWeatherDTO.cs

namespace Rainy.OpenWeatherMapAPI.CurrentWeatherData
{
    class CurrentWeatherDTO : IWeather
    {
        public APIType GetAPIType()
        {
            return APIType.CurrentWeather;
        }
    }
}

FiveDayForecastDTO.cs

namespace Rainy.OpenWeatherMapAPI.WeatherForecastData
{
    class FiveDayForecastDTO : IWeather
    {
        public APIType GetAPIType()
        {
            return APIType.FiveDayForecast;
        }
    }
}





Aucun commentaire:

Enregistrer un commentaire