Как я могу вызвать StateHasChanged() или обновить свой пользовательский интерфейс из реализованного интерфейса на странице .net Blazor .RAZOR?

У меня есть серверное приложение Blazor. С помощью этого приложения я «общаюсь» с MistralAI. Эта конечная точка REST API имеет токен потоковой передачи, который вы можете установить, что позволит ей передавать вам ответы в потоковом режиме.

У меня в приложении есть сервис, который связывается с MistralAI для этого разговора. Я реализовал шаблон Observer, чтобы иметь возможность обновлять свою страницу .RAZOR всякий раз, когда получаю ответ от MistralAI:

МойService.cs:

public class MyService {
    private List<Reader> readers = new List<Reader>();
    private AIResponse response;
    public async Task<AIResponse> myTask(){
        Reader newReader = new MyPage();
        readers.Add(newReader);
        // Send requests to and receive responses from MistralAI
        addWord(receivedJSONString);
        notifyReaders();
    }

    public void notifyReaders()
    {
        foreach (var reader in readers) {
            reader.updateWords(response);
        }
    }

    public void addWord(AIResponse newResponse)
    {
        response = newResponse;
    }
}

public interface Reader {
    void updateResponses(AIResponse newResponse);
}

MyPage.razor

@implements Reader
<div>
    @foreach(var newResponse in responses){
        @newResponse
    }
</div>
@code {
    private List<AIResponse> responses { get; set; } = new List<AIResponse>();
    private async Task QueryAI(){
        AIResponse streamingAIResponse = await myService.myTask(someString);
    }

    public void updateResponses(AIResponse newResponse){
        responses.Add(newResponse);
        Console.Write(newResponse);
        StateHasChanged();
    }
}

Я могу успешно получать ответы на свою страницу .RAZOR, я вижу их в консоли, когда Console.Write, но не могу обновить пользовательский интерфейс новым контентом; Я продолжаю получать ошибку The render handle is not yet assigned.

Итак, как я могу использовать StateHasChanged() из моего реализованного интерфейса?

Или как я могу обновить свой пользовательский интерфейс новым контентом?


ОБНОВЛЕНИЯ:

@code {
    [Inject]
    public IMyService myService { get; set; }

    private String queryString { get; set; } = null;

    private List<AIResponse> responses { get; set; } = new List<AIResponse>();

    private Conversation conversation { get; set; }

    private ElementReference aiQuery;

    protected override void OnInitialized()
    {
        if (conversation == null)
        {
            conversation = new onversation();
            conversation.messages = new List<Message>();
        }
    }

    private async Task QueryAI()
    {
        Message message = new Message("user", queryString);
        conversation.messages.Add(message);


        StreamingQueryResponse streamingAIResponse = await myService.getStreamingAIResponseAsync(conversation);
    }

    public void updateWords(StreamingQueryResponse newResponse)
    {
        responses.Add(newResponse);
        Console.Write(newResponse);
    }
}

Вы ждете myService.MyTask в MyPage, а определения нет. Вы определяете updateResponses и QueryAI, но не показываете, как они называются. В сообщении об ошибке говорится, что вы пытаетесь визуализировать компонент до того, как он будет прикреплен к RenderTree средством рендеринга. Вы пытаетесь создать компонент вручную?

MrC aka Shaun Curtis 08.08.2024 20:17

@MrCakaShaunCurtis Я добавил раздел @code своего компонента в свой пост. Я не уверен, как можно вручную создать компонент. у моего Index.razor есть подкомпонент, который, в свою очередь, имеет подкомпонент, в котором MyPage.razor является еще одним подкомпонентом. Кроме того, я не могу дождаться возвращения MyService, поскольку он постоянно получает обновления от веб-сервиса MistralAI. Мне нужно отправить ответы моему компоненту по мере его получения моей службой.

Brian 08.08.2024 20:57

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

Fengzhi Zhou 09.08.2024 11:44
Для развертывания Сайтов с использованием Blazor, Angular и React с репозиторием на GitHub на Cloudflare
Для развертывания Сайтов с использованием Blazor, Angular и React с репозиторием на GitHub на Cloudflare
Как развернуть сайты с помощью Blazor, Angular и React с репозиторием на GitHub на Cloudflare.
0
3
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Так сделать нельзя — создайте компонент вручную. Это работа рендерера.

Reader newReader = new MyPage();

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

Для этого ваш сервис предоставляет NewMessage event, на который подписывается любой, кто хочет получать уведомления — в данном случае ваши компоненты отображения.

Это шаблон уведомлений Blazor. Здесь вы можете увидеть пример реализации, который вы можете адаптировать под свои нужды.

Но я сделал это. Я реализовал шаблон наблюдателя, и теперь моя служба успешно отправляет обновления моему компоненту пользовательского интерфейса. Все, что мне нужно сделать, это указать пользовательскому интерфейсу обновиться.

Brian 09.08.2024 15:04

Кроме того, все три статьи, на которые вы ссылаетесь, связаны с взаимодействием компонентов. Это не то, чего я хочу. Мне нужно взаимодействие между сервисом и компонентом.

Brian 09.08.2024 15:36

[Вежливо] Они актуальны. Это Компонент -> Сервис -> Компонент. Они демонстрируют, как реализовать уведомления от службы к компоненту, а также шаблон для использования в компоненте. Как показано в ответе Бенни.

MrC aka Shaun Curtis 09.08.2024 17:58

Я ценю ваш вклад и переключился на решение @Bennyboy1973.

Brian 09.08.2024 18:08
Ответ принят как подходящий

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

Следующий код не работает, но должен содержать все элементы для понимания связи сервис/страница:

-сервис выполняет все взаимодействия с API. Он устанавливает асинхронный вызов, и каждый раз, когда поступает новая информация о сообщении, он запускает событие.

- страница или компонент подписывается на событие службы, добавляя обработчик событий для обработки каждого сообщения и (что немаловажно) удаляет его в Dispose.

МистральService.cs

namespace MyApp.Code
{
    public class MistralService
    {
            public event Action<string> OnMessageReceived;

            public async Task StartStreamingAsync(string apiKey)
            {
                using var httpClient = new HttpClient();
                var request = new HttpRequestMessage(HttpMethod.Get, "mistral.api/endpoint");
                request.Headers.Add("Authorization", $"Bearer {apiKey}");

                using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

                using var stream = await response.Content.ReadAsStreamAsync();
                using var reader = new StreamReader(stream);

                while (!reader.EndOfStream)
                {
                    var line = await reader.ReadLineAsync();
                    OnMessageReceived?.Invoke(line);
                }
        }
    }
}

ПотреблениеPage.razor

@using MyApp.Code
@inject MistralService MS

<h3>ConsumingPage</h3>
<button @onclick=StartStreaming>Start Streaming</button>
<div>@streamedText</div>
@code {
    string streamedText = "";
    protected override void OnInitialized()
    {
        MS.OnMessageReceived += HandleMessage;
    }
    async Task StartStreaming()
    {
        await MS.StartStreamingAsync("theAPIkey");
    }
    async void HandleMessage(string Message)
    {
        streamedText += Message + "<br/>";
        await InvokeAsync(StateHasChanged);
    }
    public void Dispose()
    {
        MS.OnMessageReceived -= HandleMessage;
    }
}

Не забудьте добавить MistralService в свой сервис в Program.cs:

// Add services to the container.
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents();
builder.Services.AddScoped<MistralService>();

Это сработало отлично, спасибо! Однако есть одна особенность Dispose(): его никогда не вызывают. Есть идеи, почему?

Brian 09.08.2024 17:52

Dispose следует вызывать, когда вы закончите работу со страницей, например, когда вы перемещаетесь по ней или закрываете ее. Или, если вы используете службу в компоненте, когда компонент удаляется из пользовательского интерфейса.

Bennyboy1973 10.08.2024 02:01

Другие вопросы по теме