Search code examples
razorblazorstatic-variables

My static variables in my Blazor Server app are keeping their values, even if I refresh the page or even I close the tab and login again. Why?


I have a Blazor server app. Some variables on a specific razor page (main.razor) are defined as static because I want that these variables keep their values when the client navigates to other pages in the same project and comes back again to main.razor. So far it is working good. But when I refresh the complete page, or even close the tab and reopen my app (login again), I see that the static variables still keep their values. How can prevent this? Of course I want that the values return to their default values (like 0 or ""), when the client makes a login or refreshes the page with F5. How can I do that? I have defined the related variables in the following way:

private static StringBuilder log = new StringBuilder(); 
public static string testvar1= "";
public static int testvar2= 0;

Solution

  • Statics exist for the lifetime of the application instance which explains the behaviour you see.

    You need to be maintaining state. At one end of the spectrum you can implement a State Management system such as Fluxor. At the other just create a user class, set it up as a service and inject it as a Scoped Service. Or you can build a middle-of-the-road solution.

    This is mine.

    A generic UIStateService that maintains a Dictionary of (state)objects against a Guid.

    public class UIStateService
    {
        private Dictionary<Guid, object> _stateItems = new Dictionary<Guid, object>();
    
        public void AddStateData(Guid Id, object value)
        {
            if (_stateItems.ContainsKey(Id))
                _stateItems[Id] = value;
            else
                _stateItems.Add(Id, value);
        }
    
        public void ClearStateData(Guid Id)
        {
            if (_stateItems.ContainsKey(Id))
                _stateItems.Remove(Id);
        }
    
        public bool TryGetStateData<T>(Guid Id, out T? value)
        {
            value = default;
            
            if (Id == Guid.Empty)
                return false;
    
            var isdata = _stateItems.ContainsKey(Id);
    
            var val = isdata
                ? _stateItems[Id]
                : default;
    
            if (val is T)
            {
                value = (T)val;
                return true;
            }
    
            return false;
        }
    }
    

    Set it up as a service:

    builder.Services.AddScoped<UIStateService>();
    

    Next define a simple template ComponentBase page that contains the common page code:

    using Blazr.UI;
    using Microsoft.AspNetCore.Components;
    
    namespace BlazorApp2.Pages
    {
        public class StatePage : ComponentBase
        {
            // this provides a guid for this specific page during the lifetime of the application runtime
            // we use this as the reference to store the state data against 
            private static Guid RouteId = Guid.NewGuid();
    
            [Inject] protected UIStateService UIStateService { get; set; } = default!;
    
            protected void SaveState<T>(T state) where T : class, new()
            {
                if (RouteId != Guid.Empty)
                    this.UIStateService.AddStateData(RouteId, state);
            }
    
            protected bool GetState<T>( out T value) where T : class, new()
            {
                value = new T();
                if (RouteId != Guid.Empty && this.UIStateService.TryGetStateData<T>(RouteId, out T? returnedState))
                {
                    value = returnedState ?? new T();
                    return true;
                }
                else
                    return false;
            }
        }
    }
    

    And use it in a page:

    @page "/"
    @inherits StatePage
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    <div class="p-2">
        <button class="btn btn-primary" @onclick=SetData>Set Data</button>
    </div>
    
    <div class="p-3 text-primary">
        State Time : @stateData.StateTime;
    </div>
    
    @code {
        private MyStateData stateData = new MyStateData();
    
        protected override void OnInitialized()
        {
            if (this.GetState<MyStateData>(out MyStateData value))
                this.stateData = value;
            else
                this.SaveState<MyStateData>(this.stateData);
        }
        private void SetData()
        {
            this.stateData.StateTime = DateTime.Now.ToLongTimeString();
            SaveState<MyStateData>(this.stateData);
        }
    
        public class MyStateData
        {
            public string StateTime { get; set; } = DateTime.Now.ToLongTimeString();
        }
    }
    

    You can now navigate around the application and the state will be maintained for the page.

    You can apply an observer/notification pattern to the state object to trigger automatic state updates if you wish.