Keeping State: Creating global variables in Blazor with C#

An important tool to have when developing web applications are global variables that keep state. A global variable is an object that can be accessed anywhere within your project. Keeping state refers to the value remaining the same no matter where it is accessed in the program. There should only be one source of truth. This would be analogous to if you had money in your bank account, it should be the same whether you access it from your phone or laptop. This is the importance of keeping state.

Implementing a dummy login system in a Blazor app is a great way to display the state of a global variable and how to change its state. The code is hosted on a GitHub repository and you can test the dummy login at this free Azure hosted site Happy Snail Dummy Login. The point of this implementation isn’t to focus on the bare-bones login system, but to focus on implementing the global variable and changing state.

The core of setting up this global variable is to create a state class for it. Below is the LoginState class that should be added to the Data folder.

using System;
namespace HappySnailDummyLogin.Data
{
public class LoginState
{
public bool IsLoggedIn { get; set; }
public string username { get; set; }
public event Action OnChange;
public void SetLogin(bool login, string user)
{
IsLoggedIn = login;
username = user;
NotifyStateChanged();
}
private void NotifyStateChanged()
{
OnChange?.Invoke();
}
}
}
view raw LoginState.cs hosted with ❤ by GitHub

The properties are standard; a string for username and a bool to keep state on whether the user is logged in or not. The OnChange Action is a delegate. Delegate is a reference to a method call and/or object, similar to C/C++ function pointers. More information on delegates can be found here. The SetLogin method is how the state will be changed and it calls NotifyStateChanged, which will essentially alert that the state of the variable has been changed. This is the login state variable that will be injected throughout the project.

Next up is the Login razor file.

@page "/"
@inject Data.LoginState loginState;
@if (loginState.IsLoggedIn.Equals(false))
{
<h1 style="text-align:center">Login</h1>
<div style="text-align:center">
<label>Username </label>
<input id="UserName: " name="UserName" type="text" @bind-value="username" />
</div>
<div style=" text-align:center">
<label>Password </label>
<input id="Password" name="Password" type="password" @bind-value="password" />
</div>
<div style="text-align:center">
<button type="submit" @onclick=@HandleLogin>
Submit
</button>
</div>
<p style="text-align:center">
Username: 'Happy' <br />
Password: 'Snail'
</p>
}
else
{
<h1 style="text-align:center">Successful Login!</h1>
<div style="text-align:center">
<p> Thanks for logging in @loginState.username!</p>
<button type="submit" @onclick=@HandleLogout>
Logout
</button>
</div>
}
@code {
string username = "";
string password = "";
string correctPassword = "Snail";
void HandleLogin()
{
if (password.Equals(correctPassword))
{
if (username.Equals("Happy"))
{
loginState.SetLogin(true, username);
}
}
}
void HandleLogout()
{
username = "";
password = "";
loginState.SetLogin(false, "");
}
}
view raw Login.razor hosted with ❤ by GitHub

Notice the inject statement of the loginState at the top, this is how the global variable is being distributed throughout the project. The if statement below it is one of the ways the logged in state is checked. 

@if (loginState.IsLoggedIn.Equals(false))
{
...code...
}

This bit of code essentially checks the login state to see if the user is NOT logged in. if the user is NOT logged in, then show the code within the curly braces. HandleLogin sets the login bool to true and stores username, if the username and password is correct. HandleLogout essentially sets state back to default.

Below is the main layout razor file which displays the Navbar and body of components.

@inherits LayoutComponentBase
@implements IDisposable
@inject HappySnailDummyLogin.Data.LoginState loginState;
<div class="sidebar">
@if (loginState.IsLoggedIn)
{
<NavMenu />
}
</div>
<div class="main">
<div class="top-row px-4">
<a href="https://docs.microsoft.com/aspnet/&quot; target="_blank">About</a>
</div>
<div class="content px-4">
@Body
</div>
</div>
@code {
protected override void OnInitialized()
{
loginState.OnChange += StateHasChanged;
}
public void Dispose()
{
loginState.OnChange -= StateHasChanged;
}
}

Once again, loginState is injected, along with IDisposable which is implemented at the top. An if statement is used again to hide the Navbar dependent on loginState status. The code section has two important methods, OnInitialized and Dispose. OnInitialized is automatically called once the component is loaded with any initial parameters from its parent component. In it we are letting the OnChange action aware that the state has changed. The Dispose method is similar, inheriting from IDisposable, it will update state in case the object’s destructor is called.

The final code to enable the global variable resides in the Startup.cs file.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using HappySnailDummyLogin.Data;
namespace HappySnailDummyLogin
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
services.AddScoped<LoginState>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
}
view raw Startup.cs hosted with ❤ by GitHub

In the ConfigureServices method, we use

AddScoped<LoginState>()

to add login state with a scoped service life. A scoped service life means that the service is only created once per client request/connection, which is good for a global variable. Dependency Injection is a complicated topic and further information on it can be found here.

Through the code shown, global variables are possible to be implemented into Blazor apps. While a dummy login system was used to demonstrate this capability, this concept can be applied to a myriad of projects that require some state to be accessed throughout the entirety of the project. Adding this skill will allow greater fidelity and consistency to create more detail-oriented projects!

3 thoughts on “Keeping State: Creating global variables in Blazor with C#

  1. Only issue and not a big one is that users that manually refresh their browser session you’ll get kicked out to the login page again as in the state is lost in the Login class . I researched a bit and found that the SignalIR token gets reset if you refresh the browser and apparently this will cause Blazor apps to reinitialize and thus kill the session so to speak in regards to state. Haven’t found a solution yet and I don’t want to use cookies. Cheers.

    Like

    1. I’ll preface this by saying the login feature was merely used as a quick demonstration. There are a ton of security risks for login authentication, and passwords should NEVER be stored in plain text, as I have here. Passwords should be hashed, unreadable, and undecipherable to a human.

      If you are creating an ASP.NET Core app, I’d highly recommend using Microsoft’s Identity framework (https://docs.microsoft.com/en-us/aspnet/core/security/authentication/?view=aspnetcore-5.0). For my Chrono Clash Deck Builder web app (https://chronoclashdecks.azurewebsites.net/), I use the identity framework to handle all user accounts stored on a secure SQL server. I don’t know any of my user’s passwords because they are hashed, so even if someone somehow hacked into my database, they wouldn’t be able to decipher the passwords. I’m not very knowledgeable on cybersecurity, so I trusted Microsoft to supply a secure user authentication framework.

      Addressing your second part, this kind of global variable does go away when the session is reloaded. I’m not currently familiar with a way for the state to be kept without the use of cookies, although there’s a fair chance that one exists! Is there a particular reason you want to avoid using cookies?

      Thanks for your comment!

      Like

Leave a reply to Alex W Cancel reply