【问题标题】:Passing two Action<T> parameters and executing one of them based on the JSON result of an async API call传递两个 Action<T> 参数并根据异步 API 调用的 JSON 结果执行其中一个
【发布时间】:2020-04-27 07:56:34
【问题描述】:

在我的 Blazor WASM 应用程序中,我编写了一个(客户端)服务类,其中包含一种对 Web API 进行 API 调用的方法。服务器将返回 IEnumerable&lt;WeatherForecast&gt; 的预期结果或 Microsoft.AspNetCore.Mvc.ProblemDetails 对象来解释问题所在。

调用该方法时,UI (FetchData.razor) 会传递一个Action&lt;IEnumerable&lt;WeatherForecast&gt;&gt; 和一个Action&lt;ProblemDetails&gt;。根据服务器返回的内容,应该只执行这些操作中的一个。这允许服务类根据 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,但是我的 razor 页面没有填充 WeatherForecast,它也没有将问题详细信息写入控制台。在 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

谢谢!

【问题讨论】:

  • 您好 Henk,感谢您的关注!到目前为止,我在浏览器中得到的最好结果是看到它显示正确数量的 WeatherForecast,但它们的字符串属性全部为空白,并且它们的日期属性全部显示 DateTime.MinValueTaskFromAction 是我试图完成这项工作的几个实验之一,因为我认为问题可能与同步性有关,所以我尝试异步运行这些操作。我还尝试通过Func&lt;TExpected, Task&gt; 而不是Action&lt;TExpected&gt;,它的效果没有好坏之分。
  • 知道了! :-D 我按照您的要求仔细查看了浏览器控制台,发现了一些我以前没有想到的东西:什么都没有。那里什么都没有。所以我开始思考在我无法控制的幕后发生的事情:反序列化。 Microsoft.AspNetCore.Components.HttpClientJsonExtensions 中的方法都将JsonSerializerOptions 传递给Deserialize 方法,但在我的代码中,我只是传递了null,因为我认为这并不重要。由于区分大小写,JsonSerializer 忽略了每个属性!

标签: c# asp.net-core async-await blazor blazor-client-side


【解决方案1】:

修好了!

这个问题与异步等待问题无关。这完全与反序列化问题有关。

在此处查看 ASP .NET Core 源代码:

https://github.com/dotnet/aspnetcore/blob/master/src/Components/Blazor/Http/src/HttpClientJsonExtensions.cs

您会注意到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 在 cmets 中指出的那样,我还写了一些不必要的复杂性。我不需要使用我毫无意义的TaskFromAction 方法将Actions 变成Tasks。您可以将它们保留为Actions。如果你也想给调用者一个异步选项,你也可以创建一个采用 Func&lt;TExpected, Task&gt; 的重载。

我已经用工作代码更新了 GitHub 上的 repro 项目,以防其他人希望以这种方式封装他们的 Blazor API 调用。

https://github.com/BenjaminCharlton/AsyncBlazorRepro

【讨论】:

    猜你喜欢
    • 2019-05-02
    • 2018-01-31
    • 2020-12-21
    • 2016-05-23
    • 2021-05-24
    • 1970-01-01
    • 1970-01-01
    • 2013-04-24
    • 2016-04-14
    相关资源
    最近更新 更多