This is my first time attempting to use APIs with Blazor, and I am struggling to save info from nested JSON objects into C# objects. I have been following these guides: https://learn.microsoft.com/en-us/aspnet/core/blazor/call-web-api?view=aspnetcore-6.0&pivots=server https://alexb72.medium.com/how-to-make-an-api-call-in-blazor-server-136e4154fca6
My API returns this data:
{
"page": 1,
"results": [
{
"adult": false,
"backdrop_path": "/c6OLXfKAk5BKeR6broC8pYiCquX.jpg",
"genre_ids": [
18,
53,
35
],
"id": 550,
"original_language": "en",
"original_title": "Fight Club",
"overview": "A ticking-time-bomb insomniac and a slippery soap salesman channel primal male aggression into a shocking new form of therapy. Their concept catches on, with underground \"fight clubs\" forming in every town, until an eccentric gets in the way and ignites an out-of-control spiral toward oblivion.",
"popularity": 100.28,
"poster_path": "/pB8BM7pdSp6B6Ih7QZ4DrQ3PmJK.jpg",
"release_date": "1999-10-15",
"title": "Fight Club",
"video": false,
"vote_average": 8.4,
"vote_count": 25110
},
{
"adult": false,
"backdrop_path": null,
"genre_ids": [
28
],
"id": 347807,
"original_language": "hi",
"original_title": "Fight Club: Members Only",
"overview": "Four friends head off to Bombay and get involved in the mother and father of all gang wars.",
"popularity": 2.023,
"poster_path": "/aXFmWfWYCCxQTkCn7K86RvDiMHZ.jpg",
"release_date": "2006-02-17",
"title": "Fight Club: Members Only",
"video": false,
"vote_average": 3.4,
"vote_count": 9
}
],
"total_pages": 2,
"total_results": 36
}
I have configured an API call that can correctly deserialze the page, total_pages, and total_results headers. If I try to display the results header on my page as a string, I get the error "Cannot get the value of a token type 'StartArray' as a string". I would like to save the results header into a list which I can iterate through on my page, but I am not sure how to handle this StartArray data type.
My C# code for this page (I have also tried List and IEnumerable for the results variable):
@code {
public class MovieItem
{
public int id { get; set; }
public string title { get; set; }
}
public class APIItem
{
public int page { get; set; }
public IList<MovieItem> results { get; set; }
public int total_pages { get; set; }
public int total_results { get; set; }
}
public APIItem api_item = new APIItem();
protected override async Task OnInitializedAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.themoviedb.org/3/search/movie?api_key=HIDDEN&query=fight club");
var client = ClientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
await using var responseStream = await response.Content.ReadAsStreamAsync();
api_item = await JsonSerializer.DeserializeAsync<APIItem>(responseStream);
}
}
}
Finally, when I try to display the API results on my page using the following code, I get this error: System.NullReferenceException: 'Object reference not set to an instance of an object'. When I comment out the @foreach section then the rest of the data loads correctly:
<p>@api_item.page</p>
<p>@api_item.total_pages</p>
<p>@api_item.total_results</p>
@foreach (var movie in api_item.results)
{
@movie.id
@movie.title
}
I am not sure what to try next from here. If there is a better way to handle this API call in general then I would appreciate any suggestions; I went with this method simply because it's how it was documented on Microsoft's Blazor API guide. Thanks for the help.
CodePudding user response:
When the first awaited task (var response = await client.SendAsync(request);
) inside OnInitializedAsync
is reached, Blazor renders the UI. At this point api_item.results
is still null. So you need to check if the list is not null before iterating it.
@if (api_item.results != null)
{
@foreach (var movie in api_item.results)
{
@movie.id
@movie.title
}
}
else
{
<p>Loading...</p>
}
The other properties do not produce exception because you have already initialized api_item
with public APIItem api_item = new APIItem();
.
Another approach would be this:
@if (api_item != null)
{
<p>@api_item.page</p>
<p>@api_item.total_pages</p>
<p>@api_item.total_results</p>
@foreach (var movie in api_item.results)
{
@movie.id
@movie.title
}
}
else
{
<p>Loading...</p>
}
@code {
public APIItem api_item = null;
protected override async Task OnInitializedAsync()
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.themoviedb.org/3/search/movie?api_key=HIDDEN&query=fight club");
var client = ClientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
await using var responseStream = await response.Content.ReadAsStreamAsync();
api_item = await JsonSerializer.DeserializeAsync<APIItem>(responseStream);
}
}
}
You can find this pattern being used in the WeatherForecast
page of a Blazor project template.
CodePudding user response:
I would not recommend to use a ReadAsStream in this case, it is sometimes tricky. Try this
if (response.IsSuccessStatusCode)
{
var json = await response.Content.ReadAsStringAsync();
api_item = JsonSerializer.Deserialize<APIItem>(json);
} //check data in a debuger at this point
and fix the class. Dont use any interfaces to deserilize any data.For example for IEnumerable compiler doesn't what class to create - List, Array or something else.
public class APIItem
{
...
public List<MovieItem> results { get; set; }
.....
}