17

What is the easiest way to create and read cookies on Blazor server side.
It seems all the solutions out there is for Blazor Web-assembly, and whenever I use those the Response.Cookies.Append("") and Request.Cookies[] options do not work.

MB_18
  • 1,620
  • 23
  • 37
Riaan V
  • 265
  • 1
  • 2
  • 7

5 Answers5

10

Inside the wwwroot/index.html (Blazor WebAssembly) or Pages/_Host.cshtml (Blazor Server), write the following javascript code:

  <script>
    window.WriteCookie = {

        WriteCookie: function (name, value, days) {

            var expires;
            if (days) {
                var date = new Date();
                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
                expires = "; expires=" + date.toGMTString();
            }
            else {
                expires = "";
            }
            document.cookie = name + "=" + value + expires + "; path=/";
        }
    }
    window.ReadCookie = {
        ReadCookie: function (cname) {
        var name = cname + "=";
        var decodedCookie = decodeURIComponent(document.cookie);
        var ca = decodedCookie.split(';');
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == ' ') {
                c = c.substring(1);
            }
            if (c.indexOf(name) == 0) {
                return c.substring(name.length, c.length);
            }
        }
        return "";
    }
}
</script>

Then, write the following sample code into the Razor component (.razor):

@inject IJSRuntime JsRuntime;
<button class="btn" @onclick="WriteCookies">
    Write Cookie
</button>
<button class="btn" @onclick="ReadCookies">
    Read Cookie
</button>

<p>The cookie is @myCookieValue</p>
@code {
    public string myCookieValue { get; set; } = "";
    protected async Task WriteCookies()
    {

        await JsRuntime.InvokeAsync<object>("WriteCookie.WriteCookie", "cookieName", "cookieValue", DateTime.Now.AddMinutes(1));

    }
    protected async Task ReadCookies()
    {

        myCookieValue= await JsRuntime.InvokeAsync<string>("ReadCookie.ReadCookie", "cookieName");

    }

}
MB_18
  • 1,620
  • 23
  • 37
4

I found a great solution to the Blazor Server Side Cookie issue using local storage.

Firstly, grab the NuGet Blazored LocalStorage using the following command:

Install-Package Blazored.LocalStorage

I had to update my System.Text.Json, do this from https://www.nuget.org/packages/System.Text.Json/

Add the following to Startup.cs in:

public void ConfigureServices(IServiceCollection services)
{
    services.AddBlazoredLocalStorage();   // local storage
    services.AddBlazoredLocalStorage(config =>
        config.JsonSerializerOptions.WriteIndented = true);  // local storage
}

Or in latest .NET versions

using Blazored.LocalSorage;
var builder = WebApplication.CreateBuilder(args);
// add after builder initialization:
builder.Services.AddBlazoredLocalStorage();   // local storage
builder.Services.AddBlazoredLocalStorage(config => config.JsonSerializerOptions.WriteIndented = true);  // local storage

On your Razor page (I used Index.razor to test):

@page "/"
@inject Blazored.LocalStorage.ILocalStorageService localStorage

<button @onclick="HandleSubmit">Create Cookie</button>  @* Create your cookie *@

@code{
    public async Task HandleSubmit()
    {
        await localStorage.SetItemAsync("cookieName", "Jason Bourne");
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        var cookieContent = await localStorage.GetItemAsync<string>("cookieName");

        if (cookieContent == null)
        {
            Console.WriteLine("Cookie is blank");
        }
        else
        {
            Console.WriteLine("We have a cookie with contents: " + cookieContent);
        }
    }
}
Lucca Ferri
  • 1,308
  • 12
  • 23
Riaan V
  • 265
  • 1
  • 2
  • 7
  • 9
    LocalStorage is not the same as a cookie though. Technically the other answer is the correct answer. – Michael Z. Mar 17 '21 at 04:01
  • 2
    Both option will work only on or after OnAfterRenderAsync, if you want to call before OnAfterRenderAsync, neither will work. – Snekithan Feb 09 '23 at 00:49
2

No offense to the others, but these answers are probably more complicated than you need them.

You can use JavaScript Interop to do whatever you want client side. That way, you have direct control over everything, instead of relying on libraries or plugins.

Bennyboy1973
  • 3,413
  • 2
  • 11
  • 16
  • 1
    Couldn't agree more! No extras ever, stay bareboned as much as you can, and control everything yourself. It is an absolutely critical way to program. It is inevitable to run into issues that you will never be able to fix relying on such things. Only stick to the primary frameworks provided by Microsoft. – user2455808 Mar 29 '23 at 15:12
1

If you want to access Cookies from Blazor Component you need inject IHttpContextAccessor like below

[Inject]
IHttpContextAccessor HttpContextAccessor { get; set; }

and then you can access Cookies from Request object using the injected HttpContextAccessor

var token = httpContextAccessor.HttpContext.Request.Cookies["access_token"];

To set access token, use:

CookieOptions options = new CookieOptions();
options.Expires = DateTime.Now.AddDays(1);
httpContextAccessor.HttpContext.Response.Cookies.Append("access_token", response.access_token, options);

if you want to access Cookies from Service then you need to add IHttpContextAccessor using dependency injection in constructor of your service as below:

public AccessoryService(HttpClient httpClient,
    IHttpContextAccessor HttpContextAccessor)
{
    this.httpClient = httpClient;
    httpContextAccessor = HttpContextAccessor;
}

pls add services.AddHttpContextAccessor(); on startup for dependency injection.

finally you can call api using the token that retrieve from Request object:

public async Task<IEnumerable<AccessoryDto>> GetAccessories()
{
    var token = httpContextAccessor.HttpContext.Request.Cookies["access_token"];
    httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer",token);

    var accessories = await httpClient.GetFromJsonAsync<AccessoryDto[]>("api/accessory");
    return accessories;
}
Khabir
  • 5,370
  • 1
  • 21
  • 33
  • 4
    Just a note, to use IHttpContextAccessor you need to inject `services.AddHttpContextAccessor();` on startup. – Lucca Ferri Oct 23 '21 at 04:56
  • @LuccaFerri Hi I have updated my answer as per your comment. thanks – Khabir Nov 01 '21 at 19:30
  • 5
    How is this code works? Since Blazor server side request are WebSocket based with SignalR. Since it is not HTTP based then `HttpContext` will be NULL as mentioned here https://github.com/dotnet/aspnetcore/issues/18183 – Major Jan 31 '22 at 19:15
  • 2
    You should never use IHttpContextAccessor/HttpContext directly or indirectly in the Razor components of Blazor Server apps. https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-context?view=aspnetcore-7.0#avoid-ihttpcontextaccessorhttpcontext-in-razor-components – redrobot Mar 17 '23 at 10:02
0

Using all the above examples. I came up with this

Javascript

const Empty = '';

window.Cookie = {
    Read: function () {
        return (document.cookie == Empty) ? '[name]=[value]' : decodeURIComponent(document.cookie);
    },
    Write: function (name, value, days) {
        document.cookie = name + '=' + value + this.ExpireOn(days) + ';path=/';
    },
    ExpireOn: function (days) {
        return Number.isFinite(days) ? ';expires=' + new Date().AddDay(days).toGMTString() : Empty;
    }
}
Date.prototype.AddDay = function(days) {
    this.setDate(this.getDate() + days);
    return this;
}

Add cookie.cs

namespace Blazor;
public class Cookie
{
    private IDictionary<string, string> _values;
    private readonly IJSRuntime _js;
    public Cookie(IJSRuntime js) => _js = js;
    public async Task<string> Get(string name) => (await Read()).TryGetValue(name, out var val) ? val : string.Empty;
    public async Task Set(string name, string val, int? days = null)
    {
        await Write(name, val, days);
        (await Read()).UpSert(name, val);
    }
    private async Task<IDictionary<string, string>> Read() =>
        _values ??= (await _js.InvokeAsync<string>(COOKIE.READ))
                    .Split(STR.SEMICOLON)
                    .Select(nv => nv.Split(STR.EQUAL))
                    .ToDictionary(nv => nv.First().Trim(), nv => nv.Last().Trim());
    private async Task Write(string name, string val, int? days) => await _js.InvokeAsync<string>(COOKIE.WRITE, name, val, days);
}

Add Scope

services.AddScoped<Cookie>();

Inject Cookie into component and use it inside OnAfterRenderAsync. Example

    protected async override Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender)
            return;

        var val = await Cookie.Get(COOKIE.LANG.NAME);
        _selectedValue = string.IsNullOrEmpty(val) ? Cache.DefaultLang : val;

       _mudSelect?.SelectedValuesChanged.InvokeAsync();
   }
  

This approach can be used with local storage or session storage. You just need to write minimum javascript to read/write it on client side and a wrapper on server side. Cookie.cs can be injected to any component and use as a user data session storage.

IHttpContext Accessor is not the Blazor way. Anything client side should be accessible via IJSRuntime.