Beforehand i apologize for any mistakes, as English is not my first language.I'm creating a razor component library to be implemented in an existent .NET 7 Blazor Server application. Basically a simple IMAP e-mail client, I'm using MailKit library.I have used the MailKit documentation example for windows forms, and adjusted to work in razor.It is populating the folders as expected, but when I try to update an MessageInfo item, as an example, changing to flagged or unflagged, I'm trying to udate the UI accordingly, but the changes are only applied on page refresh.
I tried StateHasChanged, InvokeAsync(StateHasChanged), event Action, all without success.This is what i got so far, serviçe:
using BlazorEmailLib.Models;using MailKit;using MailKit.Net.Imap;namespace BlazorEmailLib.Services;public interface IMessageListService{ event EventHandler<MessageSelectedEventArgs>? MessageSelected; event Action? OnMessagesUpdated; Task OpenFolderAsync(IMailFolder folder); void AddMessageSummaries(IMailFolder folder, IEnumerable<IMessageSummary> summaries); void SelectMessage(MessageInfo messageInfo); List<MessageInfo> GetMessages(); Task ToggleFlagAsync(MessageInfo messageInfo, MessageFlags flags);}public class MessageListService : IMessageListService{ private static readonly FetchRequest _request = new(MessageSummaryItems.UniqueId | MessageSummaryItems.Envelope | MessageSummaryItems.Flags | MessageSummaryItems.BodyStructure); private const int _batchSize = 512; private readonly List<MessageInfo> _messages = new(); private IMailFolder? _folder; public event EventHandler<MessageSelectedEventArgs>? MessageSelected; public event Action? OnMessagesUpdated; public async Task OpenFolderAsync(IMailFolder folder) { if (this._folder != null) { this._folder.MessageFlagsChanged -= OnMessageFlagsChanged; this._folder.MessageExpunged -= OnMessageExpunged; this._folder.CountChanged -= OnCountChanged; } folder.MessageFlagsChanged += OnMessageFlagsChanged; folder.MessageExpunged += OnMessageExpunged; this._folder = folder; lock (_messages) { _messages.Clear(); } try { if (!folder.IsOpen) await folder.OpenAsync(FolderAccess.ReadWrite); if (folder.Count > 0) { var summaries = await folder.FetchAsync(0, -1, _request); AddMessageSummaries(folder, summaries); } } catch (ImapCommandException ex) when (ex.Message.Contains("NO")) { Console.WriteLine("Algumas mensagens não existem mais. Atualizando a lista..."); await folder.OpenAsync(FolderAccess.ReadWrite); } catch (Exception ex) { Console.WriteLine($"Erro inesperado: {ex.Message}"); } folder.CountChanged += OnCountChanged; } public void AddMessageSummaries(IMailFolder folder, IEnumerable<IMessageSummary> summaries) { if (folder != this._folder) return; foreach (var message in summaries) { var info = new MessageInfo(message); _messages.Add(info); } if (_messages.Count < folder.Count) FetchNewMessages(folder); } private void FetchNewMessages(IMailFolder folder) { Task.Run(async () => { if (!folder.IsOpen) await folder.OpenAsync(FolderAccess.ReadWrite); if (folder.Count > 0) { int currentCount; lock (_messages) { currentCount = _messages.Count; } var summaries = await folder.FetchAsync(currentCount, Math.Min(folder.Count - 1, currentCount + _batchSize - 1), _request); AddMessageSummaries(folder, summaries); } }); } private void OnMessageFlagsChanged(object? sender, MessageFlagsChangedEventArgs e) { lock (_messages) { if (e.Index < _messages.Count) { var info = _messages[e.Index]; info.Flags = e.Flags; } } } private void OnMessageExpunged(object? sender, MessageEventArgs e) { lock (_messages) { if (e.Index < _messages.Count) { _messages.RemoveAt(e.Index); } } } private void OnCountChanged(object? sender, EventArgs e) { var folder = (IMailFolder)sender!; FetchNewMessages(folder); } public void SelectMessage(MessageInfo messageInfo) { if (_folder != null) { MessageSelected?.Invoke(this, new MessageSelectedEventArgs(_folder, messageInfo.Summary.UniqueId, messageInfo.Summary.Body)); } } public List<MessageInfo> GetMessages() { lock (_messages) { return new List<MessageInfo>(_messages); } } public async Task ToggleFlagAsync(MessageInfo messageInfo, MessageFlags flags) { if (_folder != null) { if (messageInfo.Flags.HasFlag(flags)) { await _folder.RemoveFlagsAsync(messageInfo.Summary.UniqueId, flags, true); } else { await _folder.AddFlagsAsync(messageInfo.Summary.UniqueId, flags, true); } // Notifica que as mensagens mudaram OnMessagesUpdated?.Invoke(); } }}public class MessageSelectedEventArgs : EventArgs{ public IMailFolder Folder { get; } public UniqueId UniqueId { get; } public BodyPart Body { get; } public MessageSelectedEventArgs(IMailFolder folder, UniqueId uniqueId, BodyPart body) { Folder = folder; UniqueId = uniqueId; Body = body; }}And the razor component:
@using Microsoft.AspNetCore.Components.Web@using Microsoft.Extensions.Logging@using BlazorEmailLib.Services@using BlazorEmailLib.Helpers@using BlazorEmailLib.Models@using MailKit@using MailKit.Net.Imap;@using MimeKit@inject IImapClientService ImapClientService@inject IMessageListService MessageListService@inject ILogger<MessageListView> Logger@namespace BlazorEmailLib.Components<div> @if (IsLoading) { foreach (var i in Enumerable.Range(0, 10)) {<div class="email-item"><input type="checkbox" class="skeleton" /><div class="email-item-info"><div class="email-item-info__header skeleton" style="height: 20px; margin-bottom: 0.25rem;"></div><div class="email-item-subject skeleton" style="height: 20px;"></div><div class="email-item-details skeleton" style="height: 40px;"></div></div></div> } } else if (_messages != null && _messages.Any()) { @foreach (var message in _messages) {<div @key="@message.Summary.UniqueId" class="email-item @(message.Flags.HasFlag(MessageFlags.Seen) ? "seen" : "") @(IsCurrent ? "email-item__selected":"")"><input type="checkbox" name="@message.Summary.UniqueId" id="@message.Summary.UniqueId" value="@message.Summary.UniqueId" checked="@IsSelected" @onchange="async (e) => await IsSelectedChanged.InvokeAsync(e.Value != null && (bool)e.Value)" /><div class="email-item-info" @onclick=HandleClick><div class="email-item-info__header"><span class="email-item-info__header-from">@message.Summary.Envelope.From.Mailboxes.FirstOrDefault()!.Address</span><span class="email-item-info__header-datetime"> @if (message.Summary.Envelope.Date?.Date == DateTimeOffset.Now.Date) { @message.Summary.Envelope.Date?.DateTime.ToString("HH:mm") } else { @message.Summary.Envelope.Date?.DateTime.ToString("dd/MM/yyyy") }</span></div><div class="email-item-subject"> @CommonHelpers.TruncateText(message.Summary.NormalizedSubject)</div><div class="email-item-details"><span>@CommonHelpers.ConvertMessageSize(message.Summary.Size.GetValueOrDefault())</span><span> @if (message.Summary.Attachments.Any(a => a.IsAttachment)) {<i class="fa-solid fa-paperclip"></i> } @if (message.Summary.Flags != null) { @if (message.Summary.Flags.Value.HasFlag(MessageFlags.Answered)) {<i class="fa fa-check"></i> }<span @onclick:stopPropagation @onclick="@(async () => await HandleFlagClick(message, MessageFlags.Flagged))"><i class="fa fa-flag icon-button @(message.Flags.HasFlag(MessageFlags.Flagged) ? "text-danger" : "")"></i></span> }</span></div></div></div> } } else {<p>No messages found.</p> }</div>@code { [Parameter] public IMailFolder? Folder { get; set; } private List<MessageInfo>? _messages; private bool IsLoading { get; set; } = true; private bool IsCurrent { get; set; } public bool IsSelected { get; set; } public EventCallback<bool> IsSelectedChanged { get; set; } protected override async Task OnParametersSetAsync() { if (Folder != null) { IsLoading = true; await MessageListService.OpenFolderAsync(Folder); _messages = new List<MessageInfo>(MessageListService.GetMessages()); IsLoading = false; } } protected override void OnInitialized() { MessageListService.OnMessagesUpdated += async () => await InvokeAsync(StateHasChanged); } private async void HandleMessagesUpdated() { await InvokeAsync(StateHasChanged); } private void SelectMessage(MessageInfo messageInfo) { MessageListService.SelectMessage(messageInfo); } private async Task HandleClick(MouseEventArgs e) { } private async Task HandleFlagClick(MessageInfo message, MessageFlags flags) { await MessageListService.ToggleFlagAsync(message, flags); // Atualiza o estado do objeto (Blazor só deteta mudanças em propriedades e não referências) message.Flags = message.Flags.HasFlag(flags) ? message.Flags & ~flags // Remove flag : message.Flags | flags; // Adiciona flag // Força o Blazor a redesenhar o componente await InvokeAsync(StateHasChanged); } public void Dispose() { MessageListService.OnMessagesUpdated -= HandleMessagesUpdated; }}I appreciate any help, thank you in advance.