admin管理员组

文章数量:1122832

I am using ASP.NET Core to build a simple login with multiple Blazor components.

I want to store my AppState such as IsLoggedIn inside my AppState code and subscribe to the OnChange method for UI updates.

namespace PulseVenture.PhotoGallery.Components.Services.State
{
    public class AppState
    {
        public Guid InstanceId { get; } = Guid.NewGuid();

        public AppState()
        {
            Console.WriteLine($"AppState instance created at {DateTime.Now} with ID: {InstanceId}");
        }

        public bool IsLoggedIn { get; private set; }
        public string? UserName { get; private set; }

        public event Action? OnChange;

        // LogIn Methode needs Validation @ToDo
        public void LogIn(string? userName)
        {
            IsLoggedIn = true;
            UserName = userName;
            NotifyStateChanged();
        }

        public void LogOut()
        {
            IsLoggedIn = false;
            UserName = null;
            NotifyStateChanged();
        }

        // Invoke, updating the UI or performing other actions
        //private void NotifyStateChanged() => OnChange?.Invoke();
        private void NotifyStateChanged()
        {
            Console.WriteLine($"NotifyStateChanged invoked. OnChange is {(OnChange == null ? "null" : "not null")}. with Instance: {InstanceId}");

            if (OnChange == null)
            {
                Console.WriteLine($"Warning: OnChange is null when NotifyStateChanged is called. with Instance: {InstanceId}");
            }

            OnChange?.Invoke();
        }
    }
}

Inside my components such as the NavMenu I am injecting the AppState.

@using Components.Services.State
@implements IDisposable
@inject AppState AppState

...

@if (AppState.IsLoggedIn)
{
    <Layout.NavComponents.UploadButton />
}
else
{
    <Layout.NavComponents.LogInButton  />
}

...

@code {
    //SingOut Logic
    private void LogOut() => AppState.LogOut();
    
    // subscribe to the OnChange event and trigger a re-render with StateHasChanged
    protected override void OnInitialized()
    {
        Console.WriteLine($"Component initialized with AppState instance: {AppState.InstanceId}");

        if (AppState != null)
        {
            AppState.OnChange += () =>
                    {
                        Console.WriteLine($"State changed detected in component. with Instance: {AppState.InstanceId}");
                        StateHasChanged();
                    };

            Console.WriteLine($"Subscribed to AppState.OnChange with Instance: {AppState.InstanceId}");
        }
        else
        {
            Console.WriteLine("AppState is null in OnInitialized");
        }
    }

    public void Dispose()
    {
        if (AppState != null)
        {
            Console.WriteLine($"Disposed instance: {AppState.InstanceId}");
            AppState.OnChange -= StateHasChanged;
        }
    }
}

And inside my LogInButton is a child component named LogInPopUp which also uses the AppState:

@rendermode InteractiveServer
@using Components.Services.State
@implements IDisposable
@inject AppState AppState

<div class="popup-backdrop" style="@(Show ? "display:block;" : "display:none;")">
    <div class="card popup-content">

        <button class="exit-btn" @onclick="ClosePopup">
      <svg xmlns="; height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed">
        <path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z" />
      </svg>
    </button>

    <span class="card_title">Log In</span>
    <p class="card_content">Upload Documents to share straight to your cliends.
    </p>
    <div class="card_form">
            <input placeholder="Your Email" type="text" @bind="UserEmail">
      <button class="sign-in" @onclick="LogIn"> Sign In</button>
    </div>
    <p class="signup-link">
      No Account?
            <a href="">Request Access</a>
    </p>
  </div>
</div>

@code {
    [Parameter]
    public bool Show { get; set; }

    [Parameter]
    public EventCallback<bool> ShowChanged { get; set; }

    private async Task ClosePopup()
    {
        Show = false;
        if (ShowChanged.HasDelegate)
        {
            await ShowChanged.InvokeAsync(Show);
        }
    }

    // LogIn Logic
    private string UserEmail = string.Empty;

    private void LogIn()
    {
        AppState.LogIn(UserEmail);
        ClosePopup();
    }

    protected override void OnInitialized()
    {
        Console.WriteLine($"Component initialized with AppState instance: {AppState.InstanceId}");
        if (AppState != null)
        {
            AppState.OnChange += () =>
    {
        Console.WriteLine($"State changed detected in component. with Instance: {AppState.InstanceId}");
        StateHasChanged();
    };
            Console.WriteLine($"Subscribed to AppState.OnChange with Instance: {AppState.InstanceId}");
        }
        else
        {
            Console.WriteLine("AppState is null in OnInitialized");
        }
    }

    public void Dispose()
    {
        if (AppState != null)
        {
            Console.WriteLine($"Disposed instance: {AppState.InstanceId}");
            AppState.OnChange -= StateHasChanged;
        }
    }
}

But when I run my code, I get the output that both elements subscribe to the appState and when they are done rendering they immediately dispose of the AppState and create a new instance.

Output:

AppState instance created at 21.11.2024 18:17:02 with ID: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Component initialized with AppState instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Subscribed to AppState.OnChange with Instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Component initialized with AppState instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Subscribed to AppState.OnChange with Instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Disposed instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45
Disposed instance: 872221af-23da-4427-8dc5-c5b5ac0e9f45

I inject the AppState as a scoped DI.

I already tried using different DI injections but I technically need to use

builder.Services.AddScoped<AppState>();

because my application can have more than one user.

Furthermore I tried passing the AppState down from the parent which also doesn't work.

本文标签: cASPNET Core Blazor gt Scoped AppState lifecycle of Blazor componentsStack Overflow