Ola Berntsson
Ola Berntsson

Reputation: 1236

Get cookie in Blazor .NET 8 before first render

After migrating my Blazor .Net6 project to .Net8 my cookie management is broken and I can't find a way around it.

Due to theming and avoiding FOUC I need to access a cookie in the HTTP request before first render. Naively I have tried storing the cookie in a scoped ThemeService injected in the root App component, since only the root can access the HttpContext as a CascadingParameter. However, the same ThemeService injected into any other page or component will just instantiate a second scoped service... I'm using RenderMode.InteractiveServer with prerender disabled globally at the HeadOutlet/Router level, by the way.

My theory is that the DI scope of the root App component is based on the original HTTP request, while all other component DI scopes are based on the Blazor SignalR connection which is only established later.

So my question is: How can I pass cookie data from the first scope to the other, before my components render?

The only thing I can think of is creating a Singleton service where I store a dictionary of IP/cookie or similar, insert it from the root App component and then somehow get hold of the IP from SignalR and look it up in the Singleton. Is that even possible/viable? I'd really appreciate any help or input on this one, it seems so basic but I'm stumped.

Thanks in advance!

Upvotes: 6

Views: 3016

Answers (3)

F Snyman
F Snyman

Reputation: 509

In Blazor .NET 8 Interactive WebAssembly with prerendering enabled, HTTP requests made in OnAfterRenderAsync will include the cookie request header.

E.g.

private List<PersonVM>? PeopleList { get; set; }

[Inject]
HttpClient httpClient { get; set; } = default!;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (firstRender)
    {
        await GetData();
    }
}

private async Task GetData()
{
    var response = await httpClient.GetFromJsonAsync<List<PersonVM>>("api/data/getpeoplelist");
    PeopleList = response ?? new List<PersonVM>();
    StateHasChanged();
}

Upvotes: 0

Ola Berntsson
Ola Berntsson

Reputation: 1236

I figured out a workaround. Injecting the state container directly into the App component never worked out for me; it always instantiates in non-user-scope and never re-initializes, so the persist pattern is not viable.

Eventually I just stored the cookie as a regular member of the App component and transferred it by normal parameter binding to a child component. That child component does render in user-scope, so I was able to inject the state container there.

It's not as clean and reusable as I would have liked, but at least it works.

Upvotes: 0

Ruikai Feng
Ruikai Feng

Reputation: 11826

You may try:

@page "/"
@inject StateContainer StateContainer
@inject PersistentComponentState ApplicationState

<PageTitle>Home</PageTitle>

<h1>Hello, world!</h1>

CookieVal:@data

@code{

    [CascadingParameter]

    public HttpContext? httpContext { get; set; }

    private string data;

    private PersistingComponentStateSubscription persistingSubscription;


    protected override void OnInitialized()
    {
        StateContainer.OnChange += StateHasChanged;
        if (httpContext is not null)
        {

            var existTargetCookie = httpContext.Request.Cookies.TryGetValue("CookieKey", out data);
        }
        else
        {
            if(ApplicationState.TryTakeFromJson<string>(
            "Cookie", out data))
            {
                StateContainer.Property = data;
            }
            else
            {
                data = StateContainer.Property;
            }
        }
        persistingSubscription =
        ApplicationState.RegisterOnPersisting(PersistData);
    }

    private Task PersistData()
    {
        ApplicationState.PersistAsJson("Cookie", data);

        return Task.CompletedTask;
    }



    public void Dispose()
    {
        persistingSubscription.Dispose();
        StateContainer.OnChange -= StateHasChanged;
    }
    
}


public class StateContainer
{
    private string? savedString;

    public string Property
    {
        get => savedString ?? string.Empty;
        set
        {
            savedString = value;
            NotifyStateChanged();
        }
    }

    public event Action? OnChange;

    private void NotifyStateChanged() => OnChange?.Invoke();
}

Result:

enter image description here

Upvotes: 1

Related Questions