From 48fae5371c4e096513f43719f949a22fbcfbdc3e Mon Sep 17 00:00:00 2001 From: Adrian Clark Date: Wed, 16 Aug 2023 01:25:32 +1000 Subject: [PATCH 1/3] Implement Query String Builder Method --- .../UrlExtensionsTests.cs | 19 +++++++++++ src/Aydsko.iRacingData/UrlExtensions.cs | 33 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs create mode 100644 src/Aydsko.iRacingData/UrlExtensions.cs diff --git a/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs b/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs new file mode 100644 index 0000000..4c35ae4 --- /dev/null +++ b/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs @@ -0,0 +1,19 @@ +namespace Aydsko.iRacingData.UnitTests; + +internal class UrlExtensionsTests +{ + [Test, TestCaseSource(nameof(ValidateToUrlWithQueryMethodTestCases))] + public Uri ValidateToUrlWithQueryMethod(string url, IEnumerable> parameters) + { + return url.ToUrlWithQuery(parameters); + } + + private static IEnumerable ValidateToUrlWithQueryMethodTestCases() + { + yield return new TestCaseData("https://example.com", Array.Empty>()) { ExpectedResult = new Uri("https://example.com") }; + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar") }) { ExpectedResult = new Uri("https://example.com?foo=bar") }; + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("=&?", "=?&=?&") }) { ExpectedResult = new Uri("https://example.com?%3D%26%3F=%3D%3F%26%3D%3F%26") }; + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar"), new("baz", "bat") }) { ExpectedResult = new Uri("https://example.com?foo=bar&baz=bat") }; + yield return new TestCaseData("https://example.com?a=b", new KeyValuePair[] { new("foo", "bar"), new("baz", "bat") }) { ExpectedResult = new Uri("https://example.com?a=b&foo=bar&baz=bat") }; + } +} diff --git a/src/Aydsko.iRacingData/UrlExtensions.cs b/src/Aydsko.iRacingData/UrlExtensions.cs new file mode 100644 index 0000000..1f9ccd3 --- /dev/null +++ b/src/Aydsko.iRacingData/UrlExtensions.cs @@ -0,0 +1,33 @@ +// © 2023 Adrian Clark +// This file is licensed to you under the MIT license. + +using System.Text; + +namespace Aydsko.iRacingData; + +internal static class UrlExtensions +{ + public static Uri ToUrlWithQuery(this string url, IEnumerable> parameters) + { + var builder = new UriBuilder(url); + + var queryBuilder = new StringBuilder(); + queryBuilder.Append(builder.Query.TrimStart('?')); + + foreach (var parameter in parameters) + { + if (queryBuilder.Length > 0) + { + _ = queryBuilder.Append('&'); + } + + _ = queryBuilder.Append(Uri.EscapeDataString(parameter.Key)); + _ = queryBuilder.Append('='); + _ = queryBuilder.Append(Uri.EscapeDataString(parameter.Value)); + } + + builder.Query = queryBuilder.ToString(); + + return builder.Uri; + } +} From 2ca78d1b599c5b60ae9d62740d83f978b866f0e2 Mon Sep 17 00:00:00 2001 From: Adrian Clark Date: Wed, 16 Aug 2023 22:34:25 +1000 Subject: [PATCH 2/3] Use New Query String Builder --- .../DictionaryExtensionTests.cs | 28 +- .../UrlExtensionsTests.cs | 17 +- .../Aydsko.iRacingData.csproj | 1 - src/Aydsko.iRacingData/DataClient.cs | 326 +++++++++--------- src/Aydsko.iRacingData/Extensions.cs | 107 ++++-- src/Aydsko.iRacingData/UrlExtensions.cs | 33 -- 6 files changed, 266 insertions(+), 246 deletions(-) delete mode 100644 src/Aydsko.iRacingData/UrlExtensions.cs diff --git a/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs b/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs index 059a536..c6a12ab 100644 --- a/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs +++ b/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs @@ -3,22 +3,32 @@ public class DictionaryExtensionTests { [Test, TestCaseSource(nameof(GetTestCases))] - public KeyValuePair CheckAddParameterIfNotNull(T value) + public KeyValuePair CheckAddParameterIfNotNull(ExampleData example) { - var parameters = new Dictionary(); - parameters.AddParameterIfNotNull("value", value); + var parameters = new Dictionary(); + parameters.AddParameterIfNotNull(() => example.Value); return parameters.ElementAt(0); } #pragma warning disable CA1861 // Avoid constant arrays as arguments - not worth it for these unit test cases. public static IEnumerable GetTestCases() { - yield return new TestCaseData("foo").Returns(new KeyValuePair("value", "foo")); - yield return new TestCaseData(new DateTime(2023, 4, 22, 11, 12, 13)).Returns(new KeyValuePair("value", "2023-04-22T11:12Z")); - yield return new TestCaseData(new[] { 1, 2, 3 }).Returns(new KeyValuePair("value", "1,2,3")); - yield return new TestCaseData(new string[] { "a", "b", "c" }.AsEnumerable()).Returns(new KeyValuePair("value", "a,b,c")); - yield return new TestCaseData(true).Returns(new KeyValuePair("value", "true")); - yield return new TestCaseData(Common.EventType.Practice).Returns(new KeyValuePair("value", "2")); + yield return new TestCaseData(new ExampleData("foo")).Returns(new KeyValuePair("value", "foo")); + yield return new TestCaseData(new ExampleData(new DateTime(2023, 4, 22, 11, 12, 13))).Returns(new KeyValuePair("value", new DateTime(2023, 4, 22, 11, 12, 13))); + yield return new TestCaseData(new ExampleData(new[] { 1, 2, 3 })).Returns(new KeyValuePair("value", new[] { 1, 2, 3 })); + yield return new TestCaseData(new ExampleData>(new string[] { "a", "b", "c" }.AsEnumerable())).Returns(new KeyValuePair("value", new string[] { "a", "b", "c" }.AsEnumerable())); + yield return new TestCaseData(new ExampleData(true)).Returns(new KeyValuePair("value", true)); + yield return new TestCaseData(new ExampleData(Common.EventType.Practice)).Returns(new KeyValuePair("value", Common.EventType.Practice)); } #pragma warning restore CA1861 // Avoid constant arrays as arguments } + +public class ExampleData +{ + public T? Value { get; } + + public ExampleData(T value) + { + Value = value; + } +} diff --git a/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs b/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs index 4c35ae4..9645119 100644 --- a/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs +++ b/src/Aydsko.iRacingData.UnitTests/UrlExtensionsTests.cs @@ -1,19 +1,22 @@ namespace Aydsko.iRacingData.UnitTests; -internal class UrlExtensionsTests +internal sealed class UrlExtensionsTests { [Test, TestCaseSource(nameof(ValidateToUrlWithQueryMethodTestCases))] - public Uri ValidateToUrlWithQueryMethod(string url, IEnumerable> parameters) + public Uri ValidateToUrlWithQueryMethod(string url, IEnumerable> parameters) { return url.ToUrlWithQuery(parameters); } private static IEnumerable ValidateToUrlWithQueryMethodTestCases() { - yield return new TestCaseData("https://example.com", Array.Empty>()) { ExpectedResult = new Uri("https://example.com") }; - yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar") }) { ExpectedResult = new Uri("https://example.com?foo=bar") }; - yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("=&?", "=?&=?&") }) { ExpectedResult = new Uri("https://example.com?%3D%26%3F=%3D%3F%26%3D%3F%26") }; - yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar"), new("baz", "bat") }) { ExpectedResult = new Uri("https://example.com?foo=bar&baz=bat") }; - yield return new TestCaseData("https://example.com?a=b", new KeyValuePair[] { new("foo", "bar"), new("baz", "bat") }) { ExpectedResult = new Uri("https://example.com?a=b&foo=bar&baz=bat") }; + yield return new TestCaseData("https://example.com", Array.Empty>()) { ExpectedResult = new Uri("https://example.com") }; + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar" ) }) { ExpectedResult = new Uri("https://example.com?foo=bar") }; +#pragma warning disable CA1861 // Avoid constant arrays as arguments + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", new[] { "bar", "baz" } ) }) { ExpectedResult = new Uri("https://example.com?foo=bar,baz") }; +#pragma warning restore CA1861 // Avoid constant arrays as arguments + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("=&?", "=?&=?&" ) }) { ExpectedResult = new Uri("https://example.com?%3D%26%3F=%3D%3F%26%3D%3F%26") }; + yield return new TestCaseData("https://example.com", new KeyValuePair[] { new("foo", "bar" ), new("baz", "bat" ) }) { ExpectedResult = new Uri("https://example.com?foo=bar&baz=bat") }; + yield return new TestCaseData("https://example.com?a=b", new KeyValuePair[] { new("foo", "bar" ), new("baz", "bat" ) }) { ExpectedResult = new Uri("https://example.com?a=b&foo=bar&baz=bat") }; } } diff --git a/src/Aydsko.iRacingData/Aydsko.iRacingData.csproj b/src/Aydsko.iRacingData/Aydsko.iRacingData.csproj index dbe3c08..86583aa 100644 --- a/src/Aydsko.iRacingData/Aydsko.iRacingData.csproj +++ b/src/Aydsko.iRacingData/Aydsko.iRacingData.csproj @@ -70,7 +70,6 @@ - diff --git a/src/Aydsko.iRacingData/DataClient.cs b/src/Aydsko.iRacingData/DataClient.cs index f994498..5b49beb 100644 --- a/src/Aydsko.iRacingData/DataClient.cs +++ b/src/Aydsko.iRacingData/DataClient.cs @@ -22,7 +22,6 @@ using Aydsko.iRacingData.Stats; using Aydsko.iRacingData.TimeAttack; using Aydsko.iRacingData.Tracks; -using Microsoft.AspNetCore.WebUtilities; namespace Aydsko.iRacingData; @@ -161,16 +160,16 @@ public async Task> ListHostedSessionsCombin await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (packageId is not null) { queryParameters.Add("package_id", packageId.Value.ToString(CultureInfo.InvariantCulture)); } - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/hosted/combined_sessions", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/hosted/combined_sessions".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, CombinedSessionsResultContext.Default.CombinedSessionsResult, cancellationToken).ConfigureAwait(false); } @@ -196,15 +195,15 @@ public async Task> GetLeagueAsync(int leagueId, bool includ await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["league_id"] = leagueId.ToString(CultureInfo.InvariantCulture), ["include_licenses"] = includeLicenses.ToString(), }; - var getLeagueUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/get", queryParameters); + var getLeagueUrl = "https://members-ng.iracing.com/data/league/get".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(getLeagueUrl), + return await CreateResponseViaInfoLinkAsync(getLeagueUrl, LeagueContext.Default.League, cancellationToken).ConfigureAwait(false); } @@ -217,7 +216,7 @@ public async Task> GetLeaguePointsSystemsAsync( await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["league_id"] = leagueId.ToString(CultureInfo.InvariantCulture), }; @@ -227,9 +226,9 @@ public async Task> GetLeaguePointsSystemsAsync( queryParameters.Add("season_id", seasonId.Value.ToString(CultureInfo.InvariantCulture)); } - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/get_points_systems", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/league/get_points_systems".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), LeagePointsSystemsContext.Default.LeagePointsSystems, cancellationToken).ConfigureAwait(false); + return await CreateResponseViaInfoLinkAsync(queryUrl, LeagePointsSystemsContext.Default.LeagePointsSystems, cancellationToken).ConfigureAwait(false); } /// @@ -239,21 +238,17 @@ public async Task> GetCustomerLeagueSession { await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryUrl = "https://members-ng.iracing.com/data/league/cust_league_sessions"; + ; - var queryParams = new Dictionary + var queryParameters = new Dictionary { - ["mine"] = mine.ToString(CultureInfo.InvariantCulture), + ["mine"] = mine, + ["package_id"] = packageId, }; - if (packageId is not null) - { - queryParams.Add("package_id", packageId.Value.ToString(CultureInfo.InvariantCulture)); - } - - queryUrl = QueryHelpers.AddQueryString(queryUrl, queryParams); + var queryUrl = "https://members-ng.iracing.com/data/league/cust_league_sessions".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, CustomerLeagueSessionsContext.Default.CustomerLeagueSessions, cancellationToken).ConfigureAwait(false); } @@ -279,15 +274,15 @@ public async Task> GetClubHistoryLookupsAsync( await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["season_year"] = seasonYear.ToString(CultureInfo.InvariantCulture), - ["season_quarter"] = seasonQuarter.ToString(CultureInfo.InvariantCulture), + ["season_year"] = seasonYear, + ["season_quarter"] = seasonQuarter, }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/lookup/club_history", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/lookup/club_history".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, ClubHistoryLookupArrayContext.Default.ClubHistoryLookupArray, cancellationToken).ConfigureAwait(false); } @@ -300,7 +295,7 @@ public async Task> SearchDriversAsync(string await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["search_term"] = searchTerm }; @@ -310,9 +305,9 @@ public async Task> SearchDriversAsync(string queryParameters.Add("league_id", leagueId.Value.ToString(CultureInfo.InvariantCulture)); } - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/lookup/drivers", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/lookup/drivers".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, DriverSearchResultContext.Default.DriverSearchResultArray, cancellationToken).ConfigureAwait(false); } @@ -343,15 +338,15 @@ public async Task> GetDriverInfoAsync(int[] customerI await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["cust_ids"] = string.Join(",", customerIds), - ["include_licenses"] = includeLicenses ? "true" : "false", + ["cust_ids"] = customerIds, + ["include_licenses"] = includeLicenses, }; - var driverInfoRequestUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/member/get", queryParameters); + var driverInfoRequestUrl = "https://members-ng.iracing.com/data/member/get".ToUrlWithQuery(queryParameters); - var driverInfoResponse = await CreateResponseViaInfoLinkAsync(new Uri(driverInfoRequestUrl), + var driverInfoResponse = await CreateResponseViaInfoLinkAsync(driverInfoRequestUrl, DriverInfoResponseContext.Default.DriverInfoResponse, cancellationToken).ConfigureAwait(false); @@ -373,16 +368,16 @@ public async Task> GetDriverAwardsAsync(int? custome await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/member/awards", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/member/awards".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, MemberAwardArrayContext.Default.MemberAwardArray, cancellationToken).ConfigureAwait(false); } @@ -407,16 +402,16 @@ public async Task> GetMemberProfileAsync(int? custom { await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); } - var memberProfileUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/member/profile", queryParameters); + var memberProfileUrl = "https://members-ng.iracing.com/data/member/profile".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberProfileUrl), + return await CreateResponseViaInfoLinkAsync(memberProfileUrl, MemberProfileContext.Default.MemberProfile, cancellationToken).ConfigureAwait(false); } @@ -429,15 +424,15 @@ public async Task> GetSubSessionResultAsync(int s await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["subsession_id"] = subSessionId.ToString(CultureInfo.InvariantCulture), - ["include_licenses"] = includeLicenses ? "true" : "false", + ["subsession_id"] = subSessionId, + ["include_licenses"] = includeLicenses, }; - var subSessionResultUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/get", queryParameters); + var subSessionResultUrl = "https://members-ng.iracing.com/data/results/get".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(subSessionResultUrl), + return await CreateResponseViaInfoLinkAsync(subSessionResultUrl, SubSessionResultContext.Default.SubSessionResult, cancellationToken).ConfigureAwait(false); } @@ -450,15 +445,15 @@ public async Task> GetSubSessionResultAsync(int s await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["subsession_id"] = subSessionId.ToString(CultureInfo.InvariantCulture), ["simsession_number"] = simSessionNumber.ToString(CultureInfo.InvariantCulture), }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/lap_chart_data", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/results/lap_chart_data".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SubsessionLapsHeaderContext.Default.SubsessionLapsHeader, cancellationToken).ConfigureAwait(false); @@ -507,15 +502,15 @@ public async Task> GetSubSessionResultAsync(int s await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["subsession_id"] = subSessionId.ToString(CultureInfo.InvariantCulture), - ["simsession_number"] = simSessionNumber.ToString(CultureInfo.InvariantCulture), + ["subsession_id"] = subSessionId, + ["simsession_number"] = simSessionNumber, }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/event_log", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/results/event_log".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SubsessionEventLogHeaderContext.Default.SubsessionEventLogHeader, cancellationToken).ConfigureAwait(false); @@ -590,16 +585,16 @@ public async Task>> GetSer await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["subsession_id"] = subSessionId.ToString(CultureInfo.InvariantCulture), - ["simsession_number"] = simSessionNumber.ToString(CultureInfo.InvariantCulture), - ["cust_id"] = customerId.ToString(CultureInfo.InvariantCulture), + ["subsession_id"] = subSessionId, + ["simsession_number"] = simSessionNumber, + ["cust_id"] = customerId, }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/lap_data", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/results/lap_data".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SubsessionLapsHeaderContext.Default.SubsessionLapsHeader, cancellationToken).ConfigureAwait(false); @@ -648,16 +643,16 @@ public async Task>> GetSer await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["subsession_id"] = subSessionId.ToString(CultureInfo.InvariantCulture), - ["simsession_number"] = simSessionNumber.ToString(CultureInfo.InvariantCulture), - ["team_id"] = teamId.ToString(CultureInfo.InvariantCulture), + ["subsession_id"] = subSessionId, + ["simsession_number"] = simSessionNumber, + ["team_id"] = teamId, }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/lap_data", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/results/lap_data".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SubsessionLapsHeaderContext.Default.SubsessionLapsHeader, cancellationToken).ConfigureAwait(false); @@ -706,15 +701,15 @@ public async Task> GetMemberDivisionAsync(int seaso await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), + ["season_id"] = seasonId, ["event_type"] = eventType.ToString("D"), }; - var memberDivisionUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_division", queryParameters); + var memberDivisionUrl = "https://members-ng.iracing.com/data/stats/member_division".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberDivisionUrl), + return await CreateResponseViaInfoLinkAsync(memberDivisionUrl, MemberDivisionContext.Default.MemberDivision, cancellationToken).ConfigureAwait(false); } @@ -740,20 +735,16 @@ public async Task> GetMemberChartData(int? customerId, await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var parameters = new Dictionary + var parameters = new Dictionary { - ["category_id"] = categoryId.ToString(CultureInfo.InvariantCulture), - ["chart_type"] = chartType.ToString("D"), + ["category_id"] = categoryId, + ["chart_type"] = chartType, + ["cust_id"] = customerId }; - if (customerId is not null) - { - parameters["cust_id"] = customerId.Value.ToString(CultureInfo.InvariantCulture); - } - - var memberChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/member/chart_data", parameters); + var memberChartUrl = "https://members-ng.iracing.com/data/member/chart_data".ToUrlWithQuery(parameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberChartUrl), MemberChartContext.Default.MemberChart, cancellationToken).ConfigureAwait(false); + return await CreateResponseViaInfoLinkAsync(memberChartUrl, MemberChartContext.Default.MemberChart, cancellationToken).ConfigureAwait(false); } /// @@ -764,7 +755,7 @@ public async Task> GetMemberChartData(int? customerId, await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["car_id"] = carId.ToString(CultureInfo.InvariantCulture), ["track_id"] = trackId.ToString(CultureInfo.InvariantCulture), @@ -784,9 +775,9 @@ public async Task> GetMemberChartData(int? customerId, queryParameters.Add("season_quarter", seasonQuarter.Value.ToString(CultureInfo.InvariantCulture)); } - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/world_records", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/stats/world_records".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(queryUrl, WorldRecordsHeaderContext.Default.WorldRecordsHeader, cancellationToken).ConfigureAwait(false); @@ -835,14 +826,14 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["team_id"] = teamId.ToString(CultureInfo.InvariantCulture), }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/team/get", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/team/get".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, TeamInfoContext.Default.TeamInfo, cancellationToken).ConfigureAwait(false); } @@ -880,7 +871,7 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["car_class_id"] = carClassId.ToString(CultureInfo.InvariantCulture), @@ -889,9 +880,9 @@ public async Task> GetTeamAsync(int teamId, CancellationT ["division"] = (division ?? -1).ToString(CultureInfo.InvariantCulture), }; - var seasonDriverStandingsUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/season_driver_standings", queryParameters); + var seasonDriverStandingsUrl = "https://members-ng.iracing.com/data/stats/season_driver_standings".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(seasonDriverStandingsUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(seasonDriverStandingsUrl, SeasonDriverStandingsHeaderContext.Default.SeasonDriverStandingsHeader, cancellationToken).ConfigureAwait(false); @@ -965,7 +956,7 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["car_class_id"] = carClassId.ToString(CultureInfo.InvariantCulture), @@ -974,9 +965,9 @@ public async Task> GetTeamAsync(int teamId, CancellationT ["division"] = (division ?? -1).ToString(CultureInfo.InvariantCulture), }; - var qualifyResultsUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/season_qualify_results", queryParameters); + var qualifyResultsUrl = "https://members-ng.iracing.com/data/stats/season_qualify_results".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(qualifyResultsUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(qualifyResultsUrl, SeasonQualifyResultsHeaderContext.Default.SeasonQualifyResultsHeader, cancellationToken).ConfigureAwait(false); @@ -1050,7 +1041,7 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["car_class_id"] = carClassId.ToString(CultureInfo.InvariantCulture), @@ -1059,9 +1050,9 @@ public async Task> GetTeamAsync(int teamId, CancellationT ["division"] = (division ?? -1).ToString(CultureInfo.InvariantCulture), }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/season_tt_results", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/stats/season_tt_results".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SeasonTimeTrialResultsHeaderContext.Default.SeasonTimeTrialResultsHeader, cancellationToken).ConfigureAwait(false); @@ -1135,7 +1126,7 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["car_class_id"] = carClassId.ToString(CultureInfo.InvariantCulture), @@ -1144,9 +1135,9 @@ public async Task> GetTeamAsync(int teamId, CancellationT ["division"] = (division ?? -1).ToString(CultureInfo.InvariantCulture), }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/season_tt_standings", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/stats/season_tt_standings".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SeasonTimeTrialStandingsHeaderContext.Default.SeasonTimeTrialStandingsHeader, cancellationToken).ConfigureAwait(false); @@ -1209,16 +1200,16 @@ public async Task> GetTeamAsync(int teamId, CancellationT await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["car_class_id"] = carClassId.ToString(CultureInfo.InvariantCulture), ["race_week_num"] = (raceWeekIndex ?? -1).ToString(CultureInfo.InvariantCulture), }; - var subSessionLapChartUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/season_team_standings", queryParameters); + var subSessionLapChartUrl = "https://members-ng.iracing.com/data/stats/season_team_standings".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(subSessionLapChartUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(subSessionLapChartUrl, SeasonTeamStandingsHeaderContext.Default.SeasonTeamStandingsHeader, cancellationToken).ConfigureAwait(false); @@ -1267,17 +1258,16 @@ public async Task> GetSeasonResultsAsync(int seasonI await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { - ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture) + ["season_id"] = seasonId, + ["event_type"] = eventType, + ["race_week_num"] = raceWeekNumber, }; - queryParameters.AddParameterIfNotNull("event_type", eventType); - queryParameters.AddParameterIfNotNull("race_week_num", raceWeekNumber); - - var seasonResultsUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/season_results", queryParameters); + var seasonResultsUrl = "https://members-ng.iracing.com/data/results/season_results".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(seasonResultsUrl), + return await CreateResponseViaInfoLinkAsync(seasonResultsUrl, SeasonResultsContext.Default.SeasonResults, cancellationToken).ConfigureAwait(false); } @@ -1290,14 +1280,14 @@ public async Task> GetSeasonsAsync(bool includeSeri await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["include_series"] = includeSeries ? "true" : "false", }; - var seasonSeriesUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/series/seasons", queryParameters); + var seasonSeriesUrl = "https://members-ng.iracing.com/data/series/seasons".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(seasonSeriesUrl), + return await CreateResponseViaInfoLinkAsync(seasonSeriesUrl, SeasonSeriesArrayContext.Default.SeasonSeriesArray, cancellationToken).ConfigureAwait(false); } @@ -1323,7 +1313,7 @@ public async Task> GetBestLapStatisticsAsync(int? cust await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { @@ -1335,9 +1325,9 @@ public async Task> GetBestLapStatisticsAsync(int? cust queryParameters.Add("car_id", carId.Value.ToString(CultureInfo.InvariantCulture)); } - var careerStatisticsUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_bests", queryParameters); + var careerStatisticsUrl = "https://members-ng.iracing.com/data/stats/member_bests".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(careerStatisticsUrl), + return await CreateResponseViaInfoLinkAsync(careerStatisticsUrl, MemberBestsContext.Default.MemberBests, cancellationToken).ConfigureAwait(false); } @@ -1350,16 +1340,16 @@ public async Task> GetCareerStatisticsAsync(int? cust await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); } - var careerStatisticsUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_career", queryParameters); + var careerStatisticsUrl = "https://members-ng.iracing.com/data/stats/member_career".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(careerStatisticsUrl), + return await CreateResponseViaInfoLinkAsync(careerStatisticsUrl, MemberCareerContext.Default.MemberCareer, cancellationToken).ConfigureAwait(false); } @@ -1372,16 +1362,16 @@ public async Task> GetMemberRecentRacesAsync(int await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); } - var memberRecentRacesUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_recent_races", queryParameters); + var memberRecentRacesUrl = "https://members-ng.iracing.com/data/stats/member_recent_races".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberRecentRacesUrl), + return await CreateResponseViaInfoLinkAsync(memberRecentRacesUrl, MemberRecentRacesContext.Default.MemberRecentRaces, cancellationToken).ConfigureAwait(false); } @@ -1394,16 +1384,16 @@ public async Task> GetMemberSummaryAsync(int? custom await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (customerId is not null) { queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); } - var memberSummaryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_summary", queryParameters); + var memberSummaryUrl = "https://members-ng.iracing.com/data/stats/member_summary".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberSummaryUrl), + return await CreateResponseViaInfoLinkAsync(memberSummaryUrl, MemberSummaryContext.Default.MemberSummary, cancellationToken).ConfigureAwait(false); } @@ -1471,7 +1461,7 @@ public async Task>> GetTra await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); queryParameters.AddParameterIfNotNull(() => searchParameters.StartRangeBegin); queryParameters.AddParameterIfNotNull(() => searchParameters.StartRangeEnd); queryParameters.AddParameterIfNotNull(() => searchParameters.FinishRangeBegin); @@ -1486,9 +1476,9 @@ public async Task>> GetTra queryParameters.AddParameterIfNotNull(() => searchParameters.TrackId); queryParameters.AddParameterIfNotNull(() => searchParameters.CategoryIds); - var searchHostedUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/search_hosted", queryParameters); + var searchHostedUrl = "https://members-ng.iracing.com/data/results/search_hosted".ToUrlWithQuery(queryParameters); - return await CreateResponseFromChunkedDataAsync(new Uri(searchHostedUrl), HostedResultsHeaderContext.Default.HostedResultsHeader, HostedResultItemContext.Default.HostedResultItemArray, cancellationToken).ConfigureAwait(false); + return await CreateResponseFromChunkedDataAsync(searchHostedUrl, HostedResultsHeaderContext.Default.HostedResultsHeader, HostedResultItemContext.Default.HostedResultItemArray, cancellationToken).ConfigureAwait(false); } /// @@ -1530,7 +1520,7 @@ public async Task>> GetTra await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); // Properties from the SearchParameters basic object queryParameters.AddParameterIfNotNull(() => searchParameters.StartRangeBegin); @@ -1549,9 +1539,9 @@ public async Task>> GetTra queryParameters.AddParameterIfNotNull(() => searchParameters.OfficialOnly); queryParameters.AddParameterIfNotNull(() => searchParameters.EventTypes); - var searchHostedUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/results/search_series", queryParameters); + var searchHostedUrl = "https://members-ng.iracing.com/data/results/search_series".ToUrlWithQuery(queryParameters); - return await CreateResponseFromChunkedDataAsync(new Uri(searchHostedUrl), + return await CreateResponseFromChunkedDataAsync(searchHostedUrl, OfficialSearchResultHeaderContext.Default.OfficialSearchResultHeader, OfficialSearchResultItemArrayContext.Default.OfficialSearchResultItemArray, cancellationToken).ConfigureAwait(false); @@ -1574,7 +1564,7 @@ public async Task> SearchLeagueDirectory await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); queryParameters.AddParameterIfNotNull(() => searchParameters.Search); queryParameters.AddParameterIfNotNull(() => searchParameters.Tag); queryParameters.AddParameterIfNotNull(() => searchParameters.RestrictToMember); @@ -1618,9 +1608,9 @@ public async Task> SearchLeagueDirectory } } - var searchLeagueDirectoryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/directory", queryParameters); + var searchLeagueDirectoryUrl = "https://members-ng.iracing.com/data/league/directory".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(searchLeagueDirectoryUrl), + return await CreateResponseViaInfoLinkAsync(searchLeagueDirectoryUrl, LeagueDirectoryResultPageContext.Default.LeagueDirectoryResultPage, cancellationToken).ConfigureAwait(false); } @@ -1633,15 +1623,15 @@ public async Task> ListSeasonsAsync(int seasonYear, await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["season_year"] = seasonYear.ToString(CultureInfo.InvariantCulture), ["season_quarter"] = seasonQuarter.ToString(CultureInfo.InvariantCulture), }; - var memberSummaryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/season/list", queryParameters); + var memberSummaryUrl = "https://members-ng.iracing.com/data/season/list".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(memberSummaryUrl), + return await CreateResponseViaInfoLinkAsync(memberSummaryUrl, ListOfSeasonsContext.Default.ListOfSeasons, cancellationToken).ConfigureAwait(false); } @@ -1709,7 +1699,7 @@ private async Task> GetLeagueMembershipInternal await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["include_league"] = includeLeague ? "1" : "0" }; @@ -1719,9 +1709,9 @@ private async Task> GetLeagueMembershipInternal queryParameters.Add("cust_id", customerId.Value.ToString(CultureInfo.InvariantCulture)); } - var getMembershipUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/membership", queryParameters); + var getMembershipUrl = "https://members-ng.iracing.com/data/league/membership".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(getMembershipUrl), + return await CreateResponseViaInfoLinkAsync(getMembershipUrl, LeagueMembershipArrayContext.Default.LeagueMembershipArray, cancellationToken).ConfigureAwait(false); } @@ -1734,15 +1724,15 @@ public async Task> GetLeagueSeasonsAsync(int leagueI await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["league_id"] = leagueId.ToString(CultureInfo.InvariantCulture), ["retired"] = includeRetired ? "1" : "0" }; - var getLeagueSeasons = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/seasons", queryParameters); + var getLeagueSeasons = "https://members-ng.iracing.com/data/league/seasons".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(getLeagueSeasons), + return await CreateResponseViaInfoLinkAsync(getLeagueSeasons, LeagueSeasonsContext.Default.LeagueSeasons, cancellationToken).ConfigureAwait(false); } @@ -1755,7 +1745,7 @@ public async Task> GetRaceGuideAsync(DateTimeOffs await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); + var queryParameters = new Dictionary(); if (from is not null) { @@ -1770,9 +1760,9 @@ public async Task> GetRaceGuideAsync(DateTimeOffs #pragma warning restore CA1308 // Normalize strings to uppercase } - var raceGuideUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/season/race_guide", queryParameters); + var raceGuideUrl = "https://members-ng.iracing.com/data/season/race_guide".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(raceGuideUrl), + return await CreateResponseViaInfoLinkAsync(raceGuideUrl, RaceGuideResultsContext.Default.RaceGuideResults, cancellationToken).ConfigureAwait(false); } @@ -1811,16 +1801,16 @@ public async Task> GetLeagueSeasonSessionsAsy await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["league_id"] = leagueId.ToString(CultureInfo.InvariantCulture), ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), ["results_only"] = resultsOnly ? "1" : "0" }; - var getLeagueSeasonSessions = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/season_sessions", queryParameters); + var getLeagueSeasonSessions = "https://members-ng.iracing.com/data/league/season_sessions".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(getLeagueSeasonSessions), + return await CreateResponseViaInfoLinkAsync(getLeagueSeasonSessions, LeagueSeasonSessionsContext.Default.LeagueSeasonSessions, cancellationToken).ConfigureAwait(false); } @@ -1833,14 +1823,14 @@ public async Task> GetPastSeasonsForSeriesAsync(i await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["series_id"] = seriesId.ToString(CultureInfo.InvariantCulture), }; - var getPastSeasonsForSeriesUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/series/past_seasons", queryParameters); + var getPastSeasonsForSeriesUrl = "https://members-ng.iracing.com/data/series/past_seasons".ToUrlWithQuery(queryParameters); - var intermediateResponse = await CreateResponseViaInfoLinkAsync(new Uri(getPastSeasonsForSeriesUrl), + var intermediateResponse = await CreateResponseViaInfoLinkAsync(getPastSeasonsForSeriesUrl, PastSeriesResultContext.Default.PastSeriesResult, cancellationToken).ConfigureAwait(false); @@ -1862,7 +1852,7 @@ public async Task> GetSeasonStandingsAsync(int lea await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary + var queryParameters = new Dictionary { ["league_id"] = leagueId.ToString(CultureInfo.InvariantCulture), ["season_id"] = seasonId.ToString(CultureInfo.InvariantCulture), @@ -1878,9 +1868,9 @@ public async Task> GetSeasonStandingsAsync(int lea queryParameters.Add("car_id", carId.Value.ToString(CultureInfo.InvariantCulture)); } - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/league/season_standings", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/league/season_standings".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, SeasonStandingsContext.Default.SeasonStandings, cancellationToken).ConfigureAwait(false); } @@ -1925,12 +1915,14 @@ public async Task> GetTimeAttackMem await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); - queryParameters.AddParameterIfNotNull("ta_comp_season_id", competitionSeasonId); + var queryParameters = new Dictionary + { + ["ta_comp_season_id"] = competitionSeasonId, + }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/time_attack/member_season_results", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/time_attack/member_season_results".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, TimeAttackMemberSeasonResultArrayContext.Default.TimeAttackMemberSeasonResultArray, cancellationToken).ConfigureAwait(false); } @@ -1943,14 +1935,16 @@ public async Task> GetMemberRecapAsync(int? customerId await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); - queryParameters.AddParameterIfNotNull("cust_id", customerId); - queryParameters.AddParameterIfNotNull("year", seasonYear); - queryParameters.AddParameterIfNotNull("season", seasonQuarter); + var queryParameters = new Dictionary + { + ["cust_id"] = customerId, + ["year"] = seasonYear, + ["season"] = seasonQuarter, + }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/stats/member_recap", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/stats/member_recap".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, MemberRecapContext.Default.MemberRecap, cancellationToken).ConfigureAwait(false); } @@ -1963,12 +1957,14 @@ public async Task> GetSpectatorSubsessionId await LoginInternalAsync(cancellationToken).ConfigureAwait(false); } - var queryParameters = new Dictionary(); - queryParameters.AddParameterIfNotNull("event_types", eventTypes); + var queryParameters = new Dictionary + { + ["event_types"] = eventTypes, + }; - var queryUrl = QueryHelpers.AddQueryString("https://members-ng.iracing.com/data/season/spectator_subsessionids", queryParameters); + var queryUrl = "https://members-ng.iracing.com/data/season/spectator_subsessionids".ToUrlWithQuery(queryParameters); - return await CreateResponseViaInfoLinkAsync(new Uri(queryUrl), + return await CreateResponseViaInfoLinkAsync(queryUrl, SpectatorSubsessionIdsContext.Default.SpectatorSubsessionIds, cancellationToken).ConfigureAwait(false); } diff --git a/src/Aydsko.iRacingData/Extensions.cs b/src/Aydsko.iRacingData/Extensions.cs index b7f5c7f..8cddb0e 100644 --- a/src/Aydsko.iRacingData/Extensions.cs +++ b/src/Aydsko.iRacingData/Extensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq.Expressions; using System.Reflection; +using System.Text; #if NET6_0_OR_GREATER using System.Reflection.Metadata; @@ -16,31 +17,12 @@ namespace Aydsko.iRacingData; static internal class Extensions { - static internal void AddParameterIfNotNull(this IDictionary parameters, string parameterName, T parameterValue) - { -#if (NET6_0_OR_GREATER) - ArgumentNullException.ThrowIfNull(parameterName); -#else - if (parameterName is null) - { - throw new ArgumentNullException(nameof(parameterName)); - } -#endif - if (string.IsNullOrWhiteSpace(parameterName)) - { - throw new ArgumentException("Parameter name cannot be whitespace.", nameof(parameterName)); - } - - var parameterStringValue = GetParameterAsStringValue(parameterValue); - parameters.Add(parameterName, parameterStringValue ?? string.Empty); - } - /// Add the parameter with the property's as the key and it's value if that value is not null. /// Type of the property. /// Collection of parameters to add to. /// An expression which accesses the property. /// The expression couldn't be properly understood by the method. - static internal void AddParameterIfNotNull(this IDictionary parameters, Expression> parameter) + static internal void AddParameterIfNotNull(this IDictionary parameters, Expression> parameter) { #if (NET6_0_OR_GREATER) ArgumentNullException.ThrowIfNull(parameter); @@ -70,23 +52,86 @@ static internal void AddParameterIfNotNull(this IDictionary p var propNameAttribute = parameterMemberExp.Member.GetCustomAttributes().FirstOrDefault(); var parameterName = propNameAttribute?.Name ?? parameterMemberExp.Member.Name; - var parameterStringValue = GetParameterAsStringValue(parameterValue); + parameters.Add(new(parameterName, parameterValue)); + } + + static internal Uri ToUrlWithQuery(this string url, IEnumerable> parameters) + { + var builder = new UriBuilder(url); + + var queryBuilder = new StringBuilder(); + queryBuilder.Append(builder.Query.TrimStart('?')); + + foreach (var parameter in parameters) + { + var values = GetParameterAsStringValues(parameter.Value); + if (values is { Length: 0 }) + { + continue; // Don't add anything if there aren't any values to include. + } + + if (queryBuilder.Length > 0) + { + _ = queryBuilder.Append('&'); + } - parameters.Add(new(parameterName, parameterStringValue ?? string.Empty)); + _ = queryBuilder.Append(Uri.EscapeDataString(parameter.Key)); + _ = queryBuilder.Append('='); + + if (values is { Length: 1 }) + { + _ = queryBuilder.Append(Uri.EscapeDataString(values[0])); + } + else + { + for (var i = 0; i < values.Length; i++) + { + if (i > 0) + { + _ = queryBuilder.Append(','); + } + _ = queryBuilder.Append(Uri.EscapeDataString(values[i])); + } + } + } + + builder.Query = queryBuilder.ToString(); + + return builder.Uri; } - private static string? GetParameterAsStringValue(T parameterValue) + private static string[] GetParameterAsStringValues(T parameterValue) { #pragma warning disable CA1308 // Normalize strings to uppercase - return parameterValue switch + switch (parameterValue) { - string stringParam => stringParam, - DateTime dateTimeParam => dateTimeParam.ToString("yyyy-MM-dd\\THH:mm\\Z", CultureInfo.InvariantCulture), - Array arrayParam => string.Join(",", GetNonNullValues(arrayParam)), - IEnumerable enumerableOfString => string.Join(",", enumerableOfString), - bool boolParam => boolParam.ToString().ToLowerInvariant(), - Enum @enum => @enum.ToString("D"), - _ => Convert.ToString(parameterValue, CultureInfo.InvariantCulture) + case string stringParam: + return new[] { stringParam }; + + case DateTime dateTimeParam: + return new[] { dateTimeParam.ToString("yyyy-MM-dd\\THH:mm\\Z", CultureInfo.InvariantCulture) }; + + case Array arrayParam: + return GetNonNullValues(arrayParam).ToArray(); + + case IEnumerable enumerableOfString: + return enumerableOfString.ToArray(); + + case bool boolParam: + return new[] { boolParam.ToString().ToLowerInvariant() }; + + case Enum @enum: + return new[] { @enum.ToString("D") }; + + default: + if (Convert.ToString(parameterValue, CultureInfo.InvariantCulture) is string parameterStringValue) + { + return new[] { parameterStringValue }; + } + else + { + return Array.Empty(); + } }; #pragma warning restore CA1308 // Normalize strings to uppercase diff --git a/src/Aydsko.iRacingData/UrlExtensions.cs b/src/Aydsko.iRacingData/UrlExtensions.cs deleted file mode 100644 index 1f9ccd3..0000000 --- a/src/Aydsko.iRacingData/UrlExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -// © 2023 Adrian Clark -// This file is licensed to you under the MIT license. - -using System.Text; - -namespace Aydsko.iRacingData; - -internal static class UrlExtensions -{ - public static Uri ToUrlWithQuery(this string url, IEnumerable> parameters) - { - var builder = new UriBuilder(url); - - var queryBuilder = new StringBuilder(); - queryBuilder.Append(builder.Query.TrimStart('?')); - - foreach (var parameter in parameters) - { - if (queryBuilder.Length > 0) - { - _ = queryBuilder.Append('&'); - } - - _ = queryBuilder.Append(Uri.EscapeDataString(parameter.Key)); - _ = queryBuilder.Append('='); - _ = queryBuilder.Append(Uri.EscapeDataString(parameter.Value)); - } - - builder.Query = queryBuilder.ToString(); - - return builder.Uri; - } -} From b029815969a403d12bb23f0a4d55f20ef034ff44 Mon Sep 17 00:00:00 2001 From: Adrian Clark Date: Wed, 16 Aug 2023 22:54:32 +1000 Subject: [PATCH 3/3] Resolve Test Issues --- .../DictionaryExtensionTests.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs b/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs index c6a12ab..246bad5 100644 --- a/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs +++ b/src/Aydsko.iRacingData.UnitTests/DictionaryExtensionTests.cs @@ -13,12 +13,12 @@ public class DictionaryExtensionTests #pragma warning disable CA1861 // Avoid constant arrays as arguments - not worth it for these unit test cases. public static IEnumerable GetTestCases() { - yield return new TestCaseData(new ExampleData("foo")).Returns(new KeyValuePair("value", "foo")); - yield return new TestCaseData(new ExampleData(new DateTime(2023, 4, 22, 11, 12, 13))).Returns(new KeyValuePair("value", new DateTime(2023, 4, 22, 11, 12, 13))); - yield return new TestCaseData(new ExampleData(new[] { 1, 2, 3 })).Returns(new KeyValuePair("value", new[] { 1, 2, 3 })); - yield return new TestCaseData(new ExampleData>(new string[] { "a", "b", "c" }.AsEnumerable())).Returns(new KeyValuePair("value", new string[] { "a", "b", "c" }.AsEnumerable())); - yield return new TestCaseData(new ExampleData(true)).Returns(new KeyValuePair("value", true)); - yield return new TestCaseData(new ExampleData(Common.EventType.Practice)).Returns(new KeyValuePair("value", Common.EventType.Practice)); + yield return new TestCaseData(new ExampleData("foo")).Returns(new KeyValuePair("Value", "foo")); + yield return new TestCaseData(new ExampleData(new DateTime(2023, 4, 22, 11, 12, 13))).Returns(new KeyValuePair("Value", new DateTime(2023, 4, 22, 11, 12, 13))); + yield return new TestCaseData(new ExampleData(new[] { 1, 2, 3 })).Returns(new KeyValuePair("Value", new[] { 1, 2, 3 })); + yield return new TestCaseData(new ExampleData>(new string[] { "a", "b", "c" }.AsEnumerable())).Returns(new KeyValuePair("Value", new string[] { "a", "b", "c" }.AsEnumerable())); + yield return new TestCaseData(new ExampleData(true)).Returns(new KeyValuePair("Value", true)); + yield return new TestCaseData(new ExampleData(Common.EventType.Practice)).Returns(new KeyValuePair("Value", Common.EventType.Practice)); } #pragma warning restore CA1861 // Avoid constant arrays as arguments }