I am working on an internal console app that allows users to get a full set of results from our paged JSON API. We have many different types but all fall into the same base structure.
public class Tickets
{
public Ticket[] tickets { get; set; }
public string nextPageURL { get; set; }
public string previousPageURL { get; set; }
public int recordCount { get; set; }
}
public class Ticket
{
...
}
I have an Async task to make the call and loop through the paged results and fire the results into a single JSON file. But I'd like to make it generic instead of essentially the same code repeated 17 times, one for each type.
My current code is:
private static async Task<List<Ticket>> GetTicketsAsync(Action<Tickets> callBack = null)
{
var tickets = new List<Ticket>();
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"-----")));
httpClient.BaseAddress = new Uri("-----");
var nextUrl = "/api/tickets";
do
{
await httpClient.GetAsync(nextUrl)
.ContinueWith(async (ticketSearchTask) =>
{
var response = await ticketSearchTask;
if (response.IsSuccessStatusCode)
{
string jsonString = await response.Content.ReadAsStringAsync();
try
{
var result = JsonSerializer.Deserialize<Tickets>(jsonString);
if (result != null)
{
// Build the full list to return later after the loop.
if (result.tickets.Any())
tickets.AddRange(result.tickets.ToList());
// Run the callback method, passing the current page of data from the API.
if (callBack != null)
callBack(result);
// Get the URL for the next page
nextUrl = (result.nextPageURL != null) ? result.nextPageURL : string.Empty;
}
} catch (Exception ex)
{
Console.WriteLine($"\n We ran into an error: {ex.Message}");
nextUrl = string.Empty;
}
}
else
{
// End loop if we get an error response.
nextUrl = string.Empty;
}
});
} while (!string.IsNullOrEmpty(nextUrl));
return tickets;
}
private static void TicketsCallBack(Tickets tickets)
{
if (tickets != null && tickets.count > 0)
{
foreach (var ticket in tickets.tickets)
{
Console.WriteLine($"fetched ticket: {ticket.id}");
}
}
}
I've made a start into a generic method but referencing the second level (In his instance the ticket object) is causing me problems as well as getting the nextPageURL. It would also be nice to keep the call back structure to keep the console ticking over with with the data that has been processed.
private static async Task<List<T>> GetAsync<T>(string type, Action<T> callBack = null, string ticketId = null)
{
var results = new List<T>();
HttpClient httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"-----")));
httpClient.BaseAddress = new Uri("-----");
var nextUrl = $"/api/{type}.json";
do
{
await httpClient.GetAsync(nextUrl)
.ContinueWith(async (searchTask) =>
{
var response = await searchTask;
if (response.IsSuccessStatusCode)
{
string jsonString = await response.Content.ReadAsStringAsync();
try
{
var result = JsonSerializer.Deserialize<T>(jsonString);
if (result != null)
{
// Build the full list to return later after the loop.
if (result.tickets.Any())
results.AddRange(result.tickets.ToList());
// Run the callback method, passing the current page of data from the API.
if (callBack != null)
callBack(result);
// Get the URL for the next page
nextUrl = (result.GetType().GetProperty("nextPageURL") != null) ? result.GetType().GetProperty("nextPageURL").ToString() : string.Empty;
}
}
catch (Exception ex)
{
Console.WriteLine($"\nWe ran into an error: {ex.Message}");
nextUrl = string.Empty;
}
}
else
{
// End loop if we get an error response.
nextUrl = string.Empty;
}
});
} while (!string.IsNullOrEmpty(nextUrl));
return results;
}
Any help greatly appreciated.
.ContinueWithmeans, you really don't need that here. JustawaittheGetAsyncand use the result directly. Next is that you have so many levels of nesting here and that to me is a huge code smell.