实施通用API调用程序

时间:2018-12-21 23:29:36

标签: c# generics reflection

我正在尝试实现一个使用OpenWeatherMap的不同天气API的通用调用程序,但在如何为链接输入正确的标识符方面我陷入了困境。

  • ... / weather?q = ... 返回当前天气的JSON数据;
  • ... / forecast?q = ... 返回JSON数据以进行五天的预测。

我正在寻找一种教科书方式,也许可以通过访问GetAPIType()来检索每个类的API类型,并将其转换为int并将其放入索引中,以便能够使用{{1 }}。也许有更简单的方法可以做到这一点。

检查identifiers[index]也是我的主意,我将根据typeof(T)构造来分配索引,但这似乎很混乱,如果OpenWeatherMap在理论上有100个API,我将需要100个如果构造不同。考虑到这一点,创建这些支票是否会超越客户通用化的目的?

我想到的第三个解决方案是将if(typeof(T).Equals(typeof(...)))作为Client构造函数的参数传递,

例如APIType type

但是考虑到Client是泛型的,并且我在实例化它时已经提供了一个类型,所以它看起来非常多余。

Client.cs

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

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

IWeather.cs

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

CurrentWeatherDTO.cs

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

FiveDayForecastDTO.cs

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

2 个答案:

答案 0 :(得分:1)

我不会使用枚举来驱动数组的索引。

我将以静态方式直接返回字符串。

如果需要,此解决方案也可以使用数组的索引。

以下是代码和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;
        }
    }
}

答案 1 :(得分:1)

我可能会使用这样的解决方案,但可能会处理更多错误。

关于如何使用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;
    }
}