Реализация общего вызова API - PullRequest
0 голосов
/ 22 декабря 2018

Я пытаюсь реализовать универсальный вызывающий объект, который использует различные погодные API OpenWeatherMap, но я застрял в связи с тем, как ввести правильный идентификатор для ссылки.

  • .../weather?q=... возвращает данные JSON для текущей погоды;
  • ... / прогноз? q = ... возвращает данные JSON для пятипрогноз на день.

Я ищу способ из учебника, возможно, получить тип API каждого класса путем доступа к GetAPIType(), привести его к типу int и поместить его в индекс, чтобы я могбыть в состоянии использовать identifiers[index].Или, возможно, есть более простой способ сделать это.

Проверка на typeof(T) также приходила мне в голову, и я назначал бы индекс в зависимости от конструкции if(typeof(T).Equals(typeof(...))), но это выглядит очень грязно, и если бы у OpenWeatherMap было 100 API в теории, мне понадобилось бы 100 различныхесли конструирует.Имея это в виду, разве создание этих проверок не превзойдет цель универсальности Client?

Третье решение, о котором я подумал, это передача APIType type в качестве параметра для конструктора Client,

например var client = new Client<CurrentWeatherDTO>(APIType.CurrentWeather, location, apiKey),

, но учитывая тот факт, что Client является универсальным, и я уже предоставляю тип, когда я его создаю, он будет казаться ужасно избыточным.

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

Ответы [ 2 ]

0 голосов
/ 22 декабря 2018

Я бы, вероятно, использовал бы подобное решение, но, возможно, немного больше обработки ошибок.

Есть несколько ссылок на то, как использовать HttpClient.

Я не совсем понимаючасть в requestUri с {}, может быть, это часть вашей проблемы, я изменил его на {???} в моем примере кода.

class Client
{
    // Problems using HttpClient and look into using IHttpClientFactory...
    // http://byterot.blogspot.com/2016/07/singleton-httpclient-dns.html
    // https://www.hanselman.com/blog/HttpClientFactoryForTypedHttpClientInstancesInASPNETCore21.aspx
    static HttpClient _httpClient = new HttpClient();
    readonly string WeatherUri = $"api.openweathermap.org/data/2.5/{???}?q={0}&appid={1}";
    public async Task<T> GetWeather<T>(string location, CancellationToken cancellationToken)
    {
        var apiKey = ApiKeyAttribute.GetApiKey<T>();
        if (apiKey == null) throw new Exception("ApiKeyAttirbute missing");
        var requestUri = string.Format(WeatherUri, location, apiKey);
        return await GetItem<T>(requestUri, cancellationToken);
    }
    public async Task<T> GetItem<T>(string requestUri, CancellationToken cancellationToken)
    {
        var httpRequestMessage = new HttpRequestMessage(HttpMethod.Get, requestUri);
        var response = await _httpClient.SendAsync(httpRequestMessage, cancellationToken);
        if (!response.IsSuccessStatusCode) throw new Exception("Error requesting data");
        if (response.Content == null) return default(T);
        var content = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(content);
    }
}
[ApiKeyAttribute("weather")]
class CurrentWeatherDTO { /* add appropriat properties */ }
[ApiKeyAttribute("forecast")]
class FiveDayForecastDTO { /* add appropriat properties */ }

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
class ApiKeyAttribute : Attribute
{
    public string Name { get; private set; }
    public ApiKeyAttribute(string name)
    {
        Name = name;
    }
    public static string GetApiKey<T>()
    {
        var attribute = typeof(T).GetCustomAttribute<ApiKeyAttribute>();
        return attribute?.Name;
    }
}
0 голосов
/ 22 декабря 2018

Я бы не использовал enum для управления индексом массива.

Я бы напрямую возвращал строку статическим способом.

Это решение также может работать с индексоммассив, если хотите.

Вот код и dotnetfiddle :

using System;

public class Program
{
    public static void Main()
    {
        var client1 = new Client<CurrentWeatherDTO>(null);
        Console.WriteLine("Client CurrentWeather type: " + client1.Type);

        var client2 = new Client<FiveDayForecastDTO>(null);
        Console.WriteLine("Client FiveDay type: " + client2.Type);
    }

    public class Client<T> where T : IWeather, new()
    {
        public string Type { get; set; }

        public Client(string apiKey)
        {
            var dto = (IWeather)new T();
            this.Type = dto.GetAPIType();
        }
    }

    public static class APIType
    {
        public static string CurrentWeather = "weather";
        public static string FiveDayForecast = "forecast";
    }

    public interface IWeather
    {
        string GetAPIType();
    }

    class CurrentWeatherDTO : IWeather
    {
        public string GetAPIType()
        {
            return APIType.CurrentWeather;
        }
    }

    class FiveDayForecastDTO : IWeather
    {
        public string GetAPIType()
        {
            return APIType.FiveDayForecast;
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...