{"record":{"id":"9d2d081b62bd8af1","repo":"dotnet/AspNetCore.Docs","slug":"no-weather-forecast","errorCode":null,"errorMessage":"No weather forecast!","messagePattern":"No weather forecast!","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"aspnetcore/blazor/call-web-api.md","lineNumber":1072,"sourceCode":"\n> [!NOTE]\n> In non-`Production` environments, the preceding example uses <xref:Azure.Identity.DefaultAzureCredential> to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the <xref:Azure.Identity.ManagedIdentityCredential> shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity).\n\nInject <xref:Microsoft.Identity.Abstractions.IDownstreamApi> and call <xref:Microsoft.Identity.Abstractions.IDownstreamApi.CallApiForUserAsync%2A> when calling on behalf of a user:\n\n```csharp\ninternal sealed class ServerWeatherForecaster(IDownstreamApi downstreamApi) : IWeatherForecaster\n{\n    public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()\n    {\n        var response = await downstreamApi.CallApiForUserAsync(\"DownstreamApi\",\n            options =>\n            {\n                options.RelativePath = \"/weather-forecast\";\n            });\n\n        return await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ??\n            throw new IOException(\"No weather forecast!\");\n    }\n}\n```\n\nThis approach is used by the `BlazorWebAppEntra` and `BlazorWebAppEntraBff` sample apps described in the *Sample apps* section of this article.\n\nFor more information, see the following resources:\n\n* <xref:security/data-protection/implementation/key-storage-providers#azure-storage>\n* <xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault>\n* [Use the Azure SDK for .NET in ASP.NET Core apps](/dotnet/azure/sdk/aspnetcore-guidance?tabs=api)\n* [Web API documentation | Microsoft identity platform](/entra/identity-platform/index-web-api)\n* [A web API that calls web APIs: Call an API: Option 2: Call a downstream web API with the helper class](/entra/identity-platform/scenario-web-api-call-api-call-api?tabs=aspnetcore#option-2-call-a-downstream-web-api-with-the-helper-class)\n* <xref:Microsoft.Identity.Abstractions.IDownstreamApi>\n* *Secure an ASP.NET Core Blazor Web App with Microsoft Entra ID*\n  * [With YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=with-yarp-and-aspire)\n  * [Without YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=without-yarp-and-aspire)\n* [Host ASP.NET Core in a web farm: Data Protection](xref:host-and-deploy/web-farm#data-protection)","sourceCodeStart":1054,"sourceCodeEnd":1090,"githubUrl":"https://github.com/dotnet/AspNetCore.Docs/blob/c67a80103a1a74db20784debd919c7fdda96c510/aspnetcore/blazor/call-web-api.md#L1054-L1090","documentation":"The `ServerWeatherForecaster` sample deserializes the downstream API response as `WeatherForecast[]` and throws `IOException(\"No weather forecast!\")` when the result is null. ReadFromJsonAsync returns null for an empty body or JSON `null`, so this converts a silent null into an explicit failure.","triggerScenarios":"The downstream `/weather-forecast` endpoint returns an empty response body, a JSON `null`, or a payload that deserializes to null. The null-coalescing throw then fires.","commonSituations":"Downstream API returned 204 No Content or an empty 200; route mismatch returning a different shape; auth/token failure resulting in a non-JSON error page that deserializes to null; deserialization type mismatch (private setter, missing parameterless constructor) collapsing the array to null.","solutions":["Check the downstream endpoint actually returns a JSON array — call it directly and inspect the body/status.","Verify the access token was obtained (`GetTokenAsync(\"access_token\")`) and the downstream accepted it.","Ensure `WeatherForecast` has a parameterless constructor and settable properties so deserialization succeeds.","Handle the IOException at the call site and fall back to cached/empty data only if appropriate."],"exampleFix":"// before\nreturn await response.Content.ReadFromJsonAsync<WeatherForecast[]>()\n    ?? throw new IOException(\"No weather forecast!\");\n\n// after (surface the real cause)\nresponse.EnsureSuccessStatusCode();\nvar forecasts = await response.Content.ReadFromJsonAsync<WeatherForecast[]>()\n    ?? throw new IOException($\"No weather forecast! Status {(int)response.StatusCode}, length {response.Content.Headers.ContentLength}\");","handlingStrategy":"validation","validationCode":"public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()\n{\n    var response = await downstreamApi.CallApiForUserAsync(\"DownstreamApi\",\n        o => o.RelativePath = \"/weather-forecast\");\n    response.EnsureSuccessStatusCode();\n    var body = await response.Content.ReadAsStringAsync();\n    if (string.IsNullOrWhiteSpace(body))\n        throw new IOException($\"Empty body, status {(int)response.StatusCode}\");\n    return JsonSerializer.Deserialize<WeatherForecast[]>(body)\n        ?? throw new IOException(\"Deserialized to null\");\n}","typeGuard":"static bool IsForecastPayload(WeatherForecast[]? arr) => arr is { Length: > 0 };","tryCatchPattern":"try\n{\n    return await forecaster.GetWeatherForecastAsync();\n}\ncatch (IOException ex) when (ex.Message.Contains(\"No weather forecast\"))\n{\n    logger.LogWarning(ex, \"Downstream returned no forecast\");\n    return Array.Empty<WeatherForecast>();\n}","preventionTips":["Call `EnsureSuccessStatusCode` before deserializing.","Verify WeatherForecast has a parameterless constructor and public setters.","Log status + body length when null is returned to find the root cause."],"tags":["blazor","web-api","http","deserialization","entra"],"backgroundTag":null,"analyzedSha":"c67a80103a1a74db20784debd919c7fdda96c510","analyzedAt":"2026-08-13T17:46:11.763Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}