20

I have a login page that goes off to the server gets a bunch of data, then I want to take some of that data and save it into a cookie using Blazor on the client.

So To start I have successfully injected IHttpContextAccessor. and for now in my Blazor function I have:

httpContextAccessor.HttpContext.Response.Cookies.Append("test", "ddd");

in debug when I hit the above line of code it errors with:

"Headers are read-only, response has already started."

Of course I will not be saving "test" with "ddd" in the cookie, I'm just trying to get a cookie to save at the moment.

Vibeeshan Mahadeva
  • 7,147
  • 8
  • 52
  • 102
bilpor
  • 3,467
  • 6
  • 32
  • 77
  • 1
    An alternative is to use `LocalStorage` via [this Blazor library](https://github.com/cloudcrate/BlazorStorage). IMO, it's a more suitable client storage vehicle for Blazor apps as cookies are sent on every request whereas local storage is data available to your Blazor app to use as you see fit. – Kirk Woll Jan 03 '19 at 20:25
  • 2
    We are using local storage where we can, but for this bit, another piece of software that we interact with looks for info stored in a Cookie. This app needs to create it on behalf of the secondary piece of software until we get round to being able to change that. – bilpor Jan 04 '19 at 07:51

5 Answers5

21

You will have to use JS interop:

        public async static Task WriteCookieAsync(string name, string value, int days)
        {
           var test = await JSRuntime.Current.InvokeAsync<object>("blazorExtensions.WriteCookie", name, value, days);
        }

Starting with ASP.NET Core 3.0.0-preview3 ([Discussion] Microsoft.Interop.JSRuntime.Current has been removed), the Current property is not available, so use the following code:

var test = await JSRuntime.InvokeAsync<string>("blazorExtensions.WriteCookie", name, value, days);

Don't forget to inject IJSRuntime at the top:

@inject IJSRuntime JSRuntime

And this JS:

window.blazorExtensions = {

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=/";
}
}
Paul
  • 110
  • 5
Flores
  • 8,226
  • 5
  • 49
  • 81
  • 15
    I know it's still early but I'm praying not to have to always use JS interop everytime I want to do little things like this, this should be an out of the box feature. But thanks, that's helpful! – Diogo Neves Jan 03 '19 at 23:11
  • 1
    I agree, eventually this will be provided by community packages. See here: https://github.com/BlazorExtensions – Flores Jan 08 '19 at 09:45
15

You can use this way without any script:
First define this service:

using Microsoft.JSInterop;

namespace MyProject.Utils
{
    public interface ICookie
    {
        public Task SetValue(string key, string value, int? days = null);
        public Task<string> GetValue(string key, string def = "");
    }

    public class Cookie : ICookie
    {
        readonly IJSRuntime JSRuntime;
        string expires = "";

        public Cookie(IJSRuntime jsRuntime)
        {
            JSRuntime = jsRuntime;
            ExpireDays = 300;
        }

        public async Task SetValue(string key, string value, int? days = null)
        {
            var curExp = (days != null) ? (days > 0 ? DateToUTC(days.Value) : "") : expires;
            await SetCookie($"{key}={value}; expires={curExp}; path=/");
        }

        public async Task<string> GetValue(string key, string def = "")
        {
            var cValue = await GetCookie();
            if (string.IsNullOrEmpty(cValue)) return def;                

            var vals = cValue.Split(';');
            foreach (var val in vals)
                if(!string.IsNullOrEmpty(val) && val.IndexOf('=') > 0)
                    if(val.Substring(0, val.IndexOf('=')).Trim().Equals(key, StringComparison.OrdinalIgnoreCase))
                        return val.Substring(val.IndexOf('=') + 1);
            return def;
        }

        private async Task SetCookie(string value)
        {
            await JSRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{value}\"");
        }

        private async Task<string> GetCookie()
        {
            return await JSRuntime.InvokeAsync<string>("eval", $"document.cookie");
        }

        public int ExpireDays
        {
            set => expires = DateToUTC(value);
        }

        private static string DateToUTC(int days) => DateTime.Now.AddDays(days).ToUniversalTime().ToString("R");
    }
}

You can set default expire by ExpireDays, and in SetValue() set null to days for use default or set to 0 for session or number of days.
Then import to _import.razor and add to service in Program.cs:

builder.Services.AddScoped<ICookie, Cookie>();

For use:

@inject ICookie cookie  
...  
await cookie.SetValue("mytest20", "Hello Mohsen!");
_message = await cookie.GetValue("mytest20");

Enjoy it.

Updated by @jaap Comment.

MohsenB
  • 1,669
  • 18
  • 29
  • 5
    `if(val.Substring(1, val.IndexOf('=') - 1).Trim().Equals(key, StringComparison.OrdinalIgnoreCase))` should be `(val.Substring(0, val.IndexOf('=')).Trim().Equals(key, StringComparison.OrdinalIgnoreCase))` I think – Jaap Nov 25 '21 at 17:10
  • Anyone using this service, please check @Jaap's comment. – Syed Rafay Sep 08 '22 at 02:21
  • Or range operator: `if (val[..val.IndexOf('=')].Trim().Equals(key, StringComparison.OrdinalIgnoreCase)) { return val[(val.IndexOf('=') + 1)..]; }` – devlife Jul 18 '23 at 23:09
5

If you want to avoid the external script dependency

await jsRuntime.InvokeVoidAsync("eval", $"document.cookie = \"{cookieValue}\"")

Remains to properly escape the cookie value.

Flores
  • 8,226
  • 5
  • 49
  • 81
Etienne Charland
  • 3,424
  • 5
  • 28
  • 58
  • Perfect and concise solution, thank you. Do you have an example for "Remains to properly escape the cookie value."? Thank you. – Fabio Pagano Jun 01 '23 at 17:31
2

You can add javaScript explained by @Flores in_Host.cshtml inside <script> tag.

<script>
window.blazorExtensions = {

    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=/";
    }
}
</script>
TheKingPinMirza
  • 7,924
  • 6
  • 51
  • 81
2

With the help of the code snippet that MohsenB provided. I have created a NuGet package for it.

NuGet install:

Install-Package AltairCA.Blazor.WebAssembly.Cookie

Program.cs

builder.Services.AddAltairCACookieService(options =>
{
   options.DefaultExpire = TimeSpan.FromMinutes(15);
});

Example of use

Inject Service

@inject IAltairCABlazorCookieUtil _cookieUtil;

Example 1

await _cookieUtil.SetValueAsync("cookieName", "value");