传递两个 Action<T> 参数并根据异步 API 调用的 JSON 结果执行其中之一
Passing two Action<T> parameters and executing one of them based on the JSON result of an async API call
在我的 Blazor WASM 应用程序中,我编写了一个(客户端)服务 class,其中包含一个 API 调用 Web API 的方法。服务器将 return IEnumerable<WeatherForecast>
的预期结果或 Microsoft.AspNetCore.Mvc.ProblemDetails
解释错误原因的对象。
调用方法时,UI (FetchData.razor) 传递一个 Action<IEnumerable<WeatherForecast>>
和一个 Action<ProblemDetails>
。根据服务器 return 编辑的内容,只应执行其中一项操作。这允许服务 class 根据 API 调用的反序列化 JSON 结果选择要执行的操作。
用法(在FetchData.razor):
@page "/fetchdata"
@using BlazorApp1.Shared
@inject HttpClient Http
@inject WeatherForecastsService Service
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private IEnumerable<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
await Service.GetAllAsync(
success => forecasts = success,
problem => Console.WriteLine("Handle this problem: " + problem.Detail));
}
}
我在下面的实施尝试不起作用。我确定 API 调用到达正确的 API 端点并返回 JSON,但我的剃须刀页面没有填充 WeatherForecasts,也没有将问题详细信息写入控制台。在 Blazor WASM 中调试(虽然有很大改进)仍然相当困难。
几天来我一直在摆弄这段代码,但都失败了。谁能帮我看看我做错了什么?
public class WeatherForecastsService : ServiceBase
{
public WeatherForecastsService(
HttpClient client) : base(client)
{
}
public async Task GetAllAsync(
Action<IEnumerable<WeatherForecast>> actionOnSuccess,
Action<ProblemDetails> actionOnFailure,
CancellationToken cancellationToken = default)
{
await GetManyAsync("weatherforecast",
actionOnSuccess,
actionOnFailure,
cancellationToken);
}
}
public abstract class ServiceBase
{
public ServiceBase(HttpClient client)
{
Client = client;
}
protected HttpClient Client
{
get;
}
protected virtual async Task GetManyAsync<TExpected>(
string path,
Action<IEnumerable<TExpected>> actionOnSuccess,
Action<ProblemDetails> actionOnProblem,
CancellationToken cancellationToken = default)
where TExpected : class
{
string json = await GetJsonAsync(path, cancellationToken);
ProblemDetails? problem = Deserialize<ProblemDetails>(json);
if (problem is { })
{
var taskOnProblem = TaskFromAction(actionOnProblem, problem);
await taskOnProblem;
}
else
{
IEnumerable<TExpected>? expected = Deserialize<IEnumerable<TExpected>>(json);
expected = EnsureNotNull(expected);
var taskOnSuccess = TaskFromAction(actionOnSuccess, expected);
await taskOnSuccess;
}
}
private Task TaskFromAction<T>(Action<T> action, T state)
{
return new Task(ActionOfObjectFromActionOfT(action), state);
}
private Action<object> ActionOfObjectFromActionOfT<T>(Action<T> actionOfT)
{
return new Action<object>(o => actionOfT((T)o));
}
private IEnumerable<T> EnsureNotNull<T>(IEnumerable<T>? enumerable)
{
if (enumerable is null)
{
enumerable = new List<T>();
}
return enumerable;
}
private async Task<string> GetJsonAsync(string path, CancellationToken cancellationToken = default)
{
var response = await Client.GetAsync(path, cancellationToken);
return await response.Content.ReadAsStringAsync();
}
private T? Deserialize<T>(string json)
where T : class
{
try
{
return JsonSerializer.Deserialize<T>(json, null);
}
catch (JsonException)
{
return default;
}
}
}
可以在此处找到我尝试解决此问题的失败的最小可重现示例:
https://github.com/BenjaminCharlton/AsyncBlazorRepro
谢谢!
已修复!
此问题与异步等待问题无关。这一切都与反序列化问题有关。
在此处查看 ASP .NET Core 源代码:
你会注意到 Microsoft.AspNetCore.Components.HttpClientJsonExtensions
中的方法都将 JsonSerializerOptions
传递给 Deserialize
方法,但在我的代码中我只是传递 null 因为我不认为这很重要。由于区分大小写,JsonSerializer
忽略了每一个 属性!
我改变了我的反序列化方法如下:
private T? Deserialize<T>(string json)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
try
{
return JsonSerializer.Deserialize<T>(json, jsonOptions);
}
catch (JsonException)
{
return default;
}
}
正如 Henk 在评论中指出的那样,我也写了一些不必要的复杂内容。我不需要使用毫无意义的 TaskFromAction
方法将 Action
s 变成 Task
s。您可以将它们保留为 Action
s。如果您也想为调用者提供异步选项,您还可以创建一个采用 Func<TExpected, Task>
的重载。
我已经用工作代码更新了 GitHub 上的重现项目,以防其他人希望以这种方式封装他们的 Blazor API 调用。
在我的 Blazor WASM 应用程序中,我编写了一个(客户端)服务 class,其中包含一个 API 调用 Web API 的方法。服务器将 return IEnumerable<WeatherForecast>
的预期结果或 Microsoft.AspNetCore.Mvc.ProblemDetails
解释错误原因的对象。
调用方法时,UI (FetchData.razor) 传递一个 Action<IEnumerable<WeatherForecast>>
和一个 Action<ProblemDetails>
。根据服务器 return 编辑的内容,只应执行其中一项操作。这允许服务 class 根据 API 调用的反序列化 JSON 结果选择要执行的操作。
用法(在FetchData.razor):
@page "/fetchdata"
@using BlazorApp1.Shared
@inject HttpClient Http
@inject WeatherForecastsService Service
<h1>Weather forecast</h1>
<p>This component demonstrates fetching data from the server.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private IEnumerable<WeatherForecast> forecasts;
protected override async Task OnInitializedAsync()
{
await Service.GetAllAsync(
success => forecasts = success,
problem => Console.WriteLine("Handle this problem: " + problem.Detail));
}
}
我在下面的实施尝试不起作用。我确定 API 调用到达正确的 API 端点并返回 JSON,但我的剃须刀页面没有填充 WeatherForecasts,也没有将问题详细信息写入控制台。在 Blazor WASM 中调试(虽然有很大改进)仍然相当困难。
几天来我一直在摆弄这段代码,但都失败了。谁能帮我看看我做错了什么?
public class WeatherForecastsService : ServiceBase
{
public WeatherForecastsService(
HttpClient client) : base(client)
{
}
public async Task GetAllAsync(
Action<IEnumerable<WeatherForecast>> actionOnSuccess,
Action<ProblemDetails> actionOnFailure,
CancellationToken cancellationToken = default)
{
await GetManyAsync("weatherforecast",
actionOnSuccess,
actionOnFailure,
cancellationToken);
}
}
public abstract class ServiceBase
{
public ServiceBase(HttpClient client)
{
Client = client;
}
protected HttpClient Client
{
get;
}
protected virtual async Task GetManyAsync<TExpected>(
string path,
Action<IEnumerable<TExpected>> actionOnSuccess,
Action<ProblemDetails> actionOnProblem,
CancellationToken cancellationToken = default)
where TExpected : class
{
string json = await GetJsonAsync(path, cancellationToken);
ProblemDetails? problem = Deserialize<ProblemDetails>(json);
if (problem is { })
{
var taskOnProblem = TaskFromAction(actionOnProblem, problem);
await taskOnProblem;
}
else
{
IEnumerable<TExpected>? expected = Deserialize<IEnumerable<TExpected>>(json);
expected = EnsureNotNull(expected);
var taskOnSuccess = TaskFromAction(actionOnSuccess, expected);
await taskOnSuccess;
}
}
private Task TaskFromAction<T>(Action<T> action, T state)
{
return new Task(ActionOfObjectFromActionOfT(action), state);
}
private Action<object> ActionOfObjectFromActionOfT<T>(Action<T> actionOfT)
{
return new Action<object>(o => actionOfT((T)o));
}
private IEnumerable<T> EnsureNotNull<T>(IEnumerable<T>? enumerable)
{
if (enumerable is null)
{
enumerable = new List<T>();
}
return enumerable;
}
private async Task<string> GetJsonAsync(string path, CancellationToken cancellationToken = default)
{
var response = await Client.GetAsync(path, cancellationToken);
return await response.Content.ReadAsStringAsync();
}
private T? Deserialize<T>(string json)
where T : class
{
try
{
return JsonSerializer.Deserialize<T>(json, null);
}
catch (JsonException)
{
return default;
}
}
}
可以在此处找到我尝试解决此问题的失败的最小可重现示例: https://github.com/BenjaminCharlton/AsyncBlazorRepro
谢谢!
已修复!
此问题与异步等待问题无关。这一切都与反序列化问题有关。
在此处查看 ASP .NET Core 源代码:
你会注意到 Microsoft.AspNetCore.Components.HttpClientJsonExtensions
中的方法都将 JsonSerializerOptions
传递给 Deserialize
方法,但在我的代码中我只是传递 null 因为我不认为这很重要。由于区分大小写,JsonSerializer
忽略了每一个 属性!
我改变了我的反序列化方法如下:
private T? Deserialize<T>(string json)
where T : class
{
var jsonOptions = new JsonSerializerOptions()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true
};
try
{
return JsonSerializer.Deserialize<T>(json, jsonOptions);
}
catch (JsonException)
{
return default;
}
}
正如 Henk 在评论中指出的那样,我也写了一些不必要的复杂内容。我不需要使用毫无意义的 TaskFromAction
方法将 Action
s 变成 Task
s。您可以将它们保留为 Action
s。如果您也想为调用者提供异步选项,您还可以创建一个采用 Func<TExpected, Task>
的重载。
我已经用工作代码更新了 GitHub 上的重现项目,以防其他人希望以这种方式封装他们的 Blazor API 调用。