Передача двух параметров Action <T>и выполнение одного из них на основе результата JSON вызова API asyn c - PullRequest
0 голосов
/ 27 апреля 2020

В моем приложении Blazor WASM я написал (на стороне клиента) класс обслуживания с методом для вызова API веб-API. Сервер вернет ожидаемый результат IEnumerable<WeatherForecast> или объект Microsoft.AspNetCore.Mvc.ProblemDetails, объясняющий, что пошло не так.

При вызове метода пользовательский интерфейс (FetchData.razor) передает Action<IEnumerable<WeatherForecast>> и * 1006. *. Только одно из этих действий должно быть выполнено, в зависимости от того, что возвращает сервер. Это позволяет классу обслуживания выбирать, что делать, основываясь на десериализованном JSON результате вызова API.

Использование (в 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

Спасибо!

1 Ответ

0 голосов
/ 28 апреля 2020

Исправлено!

Эта проблема не имела ничего общего с проблемами asyn c -wait. Все это было связано с проблемами десериализации.

Глядя на исходный код 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;
            }
        }

Как указал Хенк в комментариях, я также написал в какой-то ненужной сложности. Мне не нужно было превращать Action s в Task s, используя мой бессмысленный метод TaskFromAction. Вы можете просто оставить их как Action с. Вы также можете создать перегрузку, которая принимает Func<TExpected, Task>, если вы хотите предоставить вызывающим абонентам также асинхронную опцию.

Я обновил проект repro на GitHub с рабочим кодом на тот случай, если кто-то еще захочет инкапсулировать их вызовы API Blazor. таким образом.

https://github.com/BenjaminCharlton/AsyncBlazorRepro

...