Что не так с моей реализацией Flurl?

Я использую Flurl для запроса API и испытываю очень странное поведение.

namespace StravaAPI
{
    public class StravaAPI
    {
        public StravaAPI()
        {
            _Client = new FlurlClient().WithOAuthBearerToken(ACCESS_TOKEN);
        }

        private FlurlClient _Client;

        private const string BASE_URL = "https://www.strava.com/api/v3";
        // This is my access token, should be replaced by the access token of the logged in and authenticated user
        private const string ACCESS_TOKEN = "#####removed for privacy####";

        /// <summary>
        /// Get a summary representation of an athlete
        /// </summary>
        /// <param name="id">The id of the required athlete</param>
        /// <returns>A summary representation of the athlete even if the indicated athlete matches the authenticated athlete.</returns>
        public async Task<AthleteSummary> GetAthleteSummary(int id)
        {
            var url = $"{BASE_URL}/athletes/{id}";
            return await GetData<AthleteSummary>(url);
        }

        public async Task<Athlete> GetCurrentAthlete()
        {
            var url = $"{BASE_URL}/athlete/";
            return await GetData<Athlete>(url);
        }

        public async Task<T> GetData<T>(string url) where T : IData
        {
            Console.WriteLine($"Sending request to {url}");
            T data = await _Client.WithUrl(url).GetJsonAsync<T>();
            return data;
        }
    }
}

Вызовы GetAthleteSummary успешны, но вызовы GetCurrentAthlete возвращаются с 401 Unauthorized.

Если я использую альтернативный метод передачи токена доступа, в качестве параметра запроса в запросе GET, а не в качестве заголовка, вызовы GetCurrentAthlete успешны.

Вышесказанное заставило меня поверить, что это должно быть проблемой с API, но я использовал Fiddler для отправки запроса на URL, включая заголовок "Авторизация" со значением "Bearer #### удалено для конфиденциальности ##" ## "и это успешно.

Таким образом, проблема не связана с Flurl, поскольку она правильно отправляет запросы через метод GetAthleteSummary. С API это не похоже, так как он правильно отвечает на запрос через Fiddler. Должно быть, я что-то не так делаю... но я не знаю что!

Ниже приведены примеры запросов от документов API, которые я использую:

$ curl -G https://www.strava.com/api/v3/athlete \
    -H "Authorization: Bearer 83ebeabdec09f6670863766f792ead24d61fe3f9"

$ curl -G https://www.strava.com/api/v3/athletes/227615 \
    -H "Authorization: Bearer 83ebeabdec09f6670863766f792ead24d61fe3f9"

Пожалуйста помоги!

Если потребуется дополнительная информация, просто дайте мне знать. Я ценю, что если кто-то попытается запустить этот код для его отладки, вам понадобится мой токен доступа. Любой, кто хочет, может связаться со мной лично, и я передам это.

Приветствия.

Редактировать:

Полный пример консольного приложения:

using System;
using System.Threading.Tasks;
using Flurl.Http;

namespace SOExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var api = new StravaAPI();
            var summaryUser = api.GetAthleteSummary().Result;
            Console.WriteLine($"Welcome {summaryUser.FirstName}");
            Athlete currentUser = new Athlete();
            try
            {
                currentUser = api.GetCurrentAthlete().Result;
                Console.WriteLine($"Welcome {currentUser.FirstName}");
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex.InnerException.Message);
            }
            var stats = api.GetCurrentAthleteStats().Result;
            Console.WriteLine($"This year you have done {stats.Ytd_Run_Totals.Count} runs covering {stats.Ytd_Run_Totals.Distance} metres!");
            Console.ReadKey();
        }

        public class StravaAPI
        {
            public StravaAPI()
            {
                _Client = new FlurlClient().WithOAuthBearerToken(ACCESS_TOKEN);
            }

            private FlurlClient _Client;

            private const string BASE_URL = "https://www.strava.com/api/v3";
            // This is my access token, should be replaced by the access token of the logged in and authenticated user
            private const string ACCESS_TOKEN = "####removed for privacy####";
            // This is my account id, should be replaced by the ID of the logged in and authenticated user
            private const int USER_ID = ####removed for privacy####;

            /// <summary>
            /// Get a summary representation of an athlete
            /// </summary>
            /// <param name="id">The id of the required athlete</param>
            /// <returns>A summary representation of the athlete even if the indicated athlete matches the authenticated athlete.</returns>
            public async Task<AthleteSummary> GetAthleteSummary(int id = USER_ID)
            {
                var url = $"{BASE_URL}/athletes/{id}";
                return await GetData<AthleteSummary>(url);
            }

            public async Task<Athlete> GetCurrentAthlete()
            {
                var url = $"{BASE_URL}/athlete/";
                return await GetData<Athlete>(url);
            }

            //TODO: Make this based on the currentAthlete rather than a supplied ID
            public async Task<Stats> GetCurrentAthleteStats(int id = USER_ID)
            {
                var url = $"{BASE_URL}/athletes/{id}/stats";
                return await GetData<Stats>(url);
            }

            public async Task<T> GetData<T>(string url) where T : IData
            {
                Console.WriteLine($"Sending request to {url}");
                T data = await new FlurlClient().WithOAuthBearerToken(ACCESS_TOKEN).WithUrl(url).GetJsonAsync<T>();
                return data;
            }
        }

        public class Athlete : AthleteSummary
        {
            public int? FollowerCount { get; set; }
            public int? FriendCount { get; set; }
            public int? MutualFriendCount { get; set; }
            public AthleteType? AthleteType { get; set; }
            public string DatePreference { get; set; }
            /// <summary>
            /// feet or metres
            /// </summary>
            public string MeasurementPreference { get; set; }
            public string Email { get; set; }
            public int? Ftp { get; set; }
            /// <summary>
            /// Athlete's weight in kilograms
            /// </summary>
            public double? Weight { get; set; }
            // TODO: Add these below once we have created the required classes
            // CLUBS
            // BIKES
            // SHOES
        }

        public class AthleteSummary : IData
        {
            public int? Id { get; set; }
            public ResourceState? ResourceState { get; set; }
            public string FirstName { get; set; }
            public string LastName { get; set; }
            /// <summary>
            /// URL to a 62x62 pixel profile picture
            /// </summary>
            public string ProfileMedium { get; set; }
            /// <summary>
            /// URL to a 126x124 pixel profile picture
            /// </summary>
            public string Profile { get; set; }
            public string City { get; set; }
            public string State { get; set; }
            public string Country { get; set; }
            public string Sex { get; set; }
            /// <summary>
            /// pending, accepted, blocked or null - the authenticated athlete’s following status of this athlete
            /// </summary>
            public string Friend { get; set; }
            /// <summary>
            /// pending, accepted, blocked or null - the authenticated athlete’s following status of this athlete
            /// </summary>
            public string Follower { get; set; }
            public bool? Premium { get; set; }
            public DateTime? CreatedAt { get; set; }
            public DateTime? UpdatedAt { get; set; }
        }

        public interface IData
        {
        }

        public class Stats : IData
        {
            public double? Biggest_Ride_Distance { get; set; }
            public double? Biggest_Climb_Elevation_Gain { get; set; }
            public Totals Recent_Ride_Totals { get; set; }
            public Totals Recent_Run_Totals { get; set; }
            public Totals Recent_Swim_Totals { get; set; }
            public Totals Ytd_Ride_Totals { get; set; }
            public Totals Ytd_Run_Totals { get; set; }
            public Totals Ytd_Swim_Totals { get; set; }
            public Totals All_Ride_Totals { get; set; }
            public Totals All_Run_Totals { get; set; }
            public Totals All_Swim_Totals { get; set; }
        }

        public class Totals
        {
            public int? Count { get; set; }
            /// <summary>
            /// Distance in metres
            /// </summary>
            public double? Distance { get; set; }
            /// <summary>
            /// Moving time in seconds
            /// </summary>
            public int? Moving_Time { get; set; }
            /// <summary>
            /// Elapsed time in seconds
            /// </summary>
            public int? Elapsed_Time { get; set; }
            public double? Elevation_Gain { get; set; }
            public int? Achievement_Count { get; set; }
        }

        public enum AthleteType
        {
            Cyclist = 0,
            Runner = 1
        }

        public enum ResourceState
        {
            Meta = 1,
            Summary = 2,
            Detailed = 3
        }
    }
}

Результат: Вывод

0 ответов

Другие вопросы по тегам