Blazor - предотвращение двойного HTTP-вызова и ожидание первого

В моем приложении Blazor (WebAssembly) мне нужно загрузить данные пользователя в свой ApplicationState. Многие из моих страниц нуждаются в этих данных пользователя, поэтому в их методе OnAfterRenderAsync я загружаю данные пользователя, если они не загружены:

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        loadingSession = true;
        if (!sessionState.UserDataLoaded)
            await sessionState.LoadUserData();
        loadingSession = false;

    }
    await base.OnAfterRenderAsync(firstRender);
}

Но если я быстро перейду на другую страницу, пользовательские данные еще не будут загружены, и страница запросит новый запрос.

Так что хотелось бы дождаться, если уже есть запрос, а не начинать новый.

    public async Task LoadUserData()
    {
        try
        {
            if (dataLoading)
            {
                // Need to wait the previous request
            }

            if (dataLoaded)
                return;

            dataLoading = true;
            User = await httpClient.GetFromJsonAsync<User>("users", options);
        }
        catch (Exception e)
        {
            OnException(e);
        }
        finally
        {
            if (User != null)
                dataLoaded = true;
            dataLoading = false;
        }
    }

Я пробовал много вещей, ManualResetEvent, Thread.Sleep в задаче, ... но мне не удается сделать это правильно. Какой будет лучший метод?


person Bisjob    schedule 24.09.2020    source источник


Ответы (1)


OnAfterRenderAsync и OnAfterRender вызываются после завершения рендеринга компонента.

OnInitializedAsync и OnInitialized вызываются, когда компонент инициализируется после получения его начальных параметров от родительского компонента в SetParametersAsync.

При переносе кода в метод OnInitializedAsync пользовательские данные будут загружены до того, как элемент управления будет отрисован.

Изменить: (вы хотите передавать состояние между компонентами)

Документация Microsoft по управлению состоянием

Еще один пример сохранения состояния в памяти и передачи его компонентам через внедрение зависимостей.

Чтобы использовать «Государственное управление», вы должны зарегистрировать «Сервис», который можно использовать для государственного управления.

Например; Создайте класс с именем AppState со значениями, состояние которых вы хотите сохранить.

public class AppState
{
    public AppState()
    {
        //this class is to help maintain application state.
    }

    public UserData UserData { get; set; }

    public string SomeOtherStateValue { get; set; }
}

затем зарегистрируйте этот класс в своем Services как Singleton (см. Срок службы):

public static async Task Main(string[] args)
{
    var builder = WebAssemblyHostBuilder.CreateDefault(args);
    builder.RootComponents.Add<App>("app");
    builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
   //...other services
   //...

   // add AppState as singleton
    builder.Services.AddSingleton<AppState>(); 

   //...
   //...more stuff
    await builder.Build().RunAsync();
}

Затем в вашем Index.razor файле (или там, где вам нужно вызвать его в первый раз) используйте внедрение зависимостей, чтобы получить доступ к AppState, а в OnInitialzedAsync() вызовите данные пользователя с помощью HttpContent.

@page "/"
@using System.Text.Json
@using System.Text
@inject AppState appState
@inject HttpClient httpClient

<h1>..all your html or components on the page...</h1>

@code{

protected override async Task OnInitializedAsync()
{
    var userRequestData = new UserDataRequest(options);
    var request = new StringContent(JsonSerializer.Serialize(userRequestData), Encoding.UTF8, "application/json");

    var response = await httpClient.PostAsync($"api/users/", request);
    if (response.IsSuccessStatusCode)
    {
        appState.UserData = await JsonSerializer.DeserializeAsync<UserData>(await response.Content.ReadAsStreamAsync(), new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
    }

    await base.OnInitializedAsync();
}

}

Это будет ждать вызова API, пока не получит ответ. Если ответ был успешным, например: response.IsSuccessStatusCode верно. извлеките данные из ответного сообщения и сохраните их в appState.UserDate.

В любом последующем компоненте вы можете получить AppState путем инъекции, используя @inject AppState appState или если он находится в файле кода [Inject] AppState appState;

В последующих компонентах вы можете добавить проверку в OnInitializedAsync методе:

.
.
if (appState?.UserData == null)
{
    //appData.UserData = await request user data...
}
// more code..
.
.

!! Но это не то, как следует обрабатывать аутентификацию !!

Если вам нужна аутентификация, посмотрите ASP.NET Проверка подлинности и авторизация Core Blazor, поскольку в нем используется AuthenticationStateProvider.

Что касается проверки того, был ли уже сделан вызов http-клиенту, await должен дождаться ответа от httpClient, прежде чем продолжить.

Если appState.UserData имеет какое-то значение, значит вызов был успешным.

person CobyC    schedule 28.09.2020
comment
Да, я знаю, но у меня много подкомпонентов, которые вызывают одну и ту же процедуру при запуске, им нужны пользовательские данные. А с методом OnInitializedAsync я получил исключение при первой загрузке. Но мой вопрос был непонятен: как узнать, был ли уже отправлен HTTP-запрос, и подождать его ответа, чтобы начать новый запрос? - person Bisjob; 28.09.2020
comment
Итак, у вас есть «служба», которая действует как состояние приложения? Вы получаете свои пользовательские данные после входа в систему, а затем вам просто нужно использовать пользовательские данные в других компонентах? - person CobyC; 28.09.2020
comment
Да, у меня есть ApplicationState, в котором хранятся UserData. Я также использую IdentityServer, но я не знаю, где я могу обработать событие входа в систему и запросить UserData в данный момент. Вот почему я прошу UserData на страницах. Но если я открою страницу и сразу же вторую, оба вызовут API для получения UserData. - person Bisjob; 29.09.2020
comment
Так вызывает ли двойная нагрузка проблемы? Обычно после входа в систему вы будете перенаправлены на страницу индекса через что-то вроде Response.Redirect(*redirect-url*) на странице входа. Как вы «быстро» переходите на другую страницу? нажав ссылку? другая вкладка и URL? используете панель навигации вне <Authorized> области? - person CobyC; 29.09.2020
comment
На самом деле это не проблема, это скорее оптимизация. Я быстро перемещаюсь, например, щелкая на панели навигации. И да, после входа в систему вы будете перенаправлены, но на запрашиваемую страницу (например, если вы сохраняете быструю ссылку на страницу панели инструментов). Но вы заставляете меня думать о пустом компоненте в mainLayout внутри AuthorizeView, который является уникальной ролью для загрузки данных при его инициализации. Может быть, это хорошая идея? - person Bisjob; 29.09.2020
comment
Вы можете @inject указать свой httpClient и выполнить вызов OnInitializedAsync в файле MainLayout.Razor. Дайте мне знать, если это сработает, я обновлю ответ, включив и это. - person CobyC; 29.09.2020
comment
Он работает на MainLayout, но только при автоматическом входе в мой браузер. Кажется, что MainLayout снова не инициализируется. Но я попробовал пустой компонент, окруженный ‹AuthorizeView›, и, похоже, он работает. - person Bisjob; 29.09.2020