[Introduction to Blazor] Blazor beginners tried to deploy with login to chat function ~ Part 4 ~
Last time
[Introduction to Blazor] Blazor beginners tried to deploy with login to chat function ~ Part 1 ~
[Introduction to Blazor] Blazor beginners tried to deploy with login and chat functions-Part 2-
[Introduction to Blazor] Blazor beginners tried to deploy with login to chat function ~ Part 3 ~
It is a continuation of.
Last time, I made it possible to log in only to users who are registered using Cognito
.
I still have a problem with this, but before that, I would like to keep the login information.
Retain login information
I think that many services retain login information by the following processing.
This time, I will add functions according to this method.
Reading and writing cookies with Blazor
I did a lot of research, but so far I haven’t found any information about cookies.
Blazor can’t handle HTTPContext (?)
In .NET MVC Core
etc., it was possible to handle sessions and cookies by passing through HTTPContext
, but it can only be handled from Controller class
. It’s the same with Blazor
, but I thought it was a bit strange to add the Controller class
to the current solution (or what does that mean for Blazor
?), So it’s different. I thought about the method.
Handle cookies with JavaScript
Another way I thought was to handle cookies with JavaScript
. It feels like it spoils the goodness of Blazor
, but it can’t be helped here. Let’s implement it.
Add the process of JS you want to call
Add the WriteCookie method
and ReadCookie method
to the _Host.cshtml file
.
_Host.cshtml
<script>
window.blazorExtensions = {
WriteCookie: function(name, value) {
var maxAge = "max-age=3600; ";
var path = "path=/; ";
document.cookie = name + "=" + value + "; " + maxAge + path;
},
ReadCookie: function () {
return document.cookie;
},
}
</script>
Addictive point
Here, I wanted to return the ReadCookie method
as an associative array instead of the entire cookie, but I was crying because I got a ʻUnHundled Error` just by generating the array. Please let me know if there is a way to return an array.
Call JavaScript processing from C
Newly added Service folder
and CookieService class
.
Because I thought it might not be just a particular page that I wanted to do with cookies. It’s not a class that instantiates multiple times, and it’s a smart idea to let DI
use it on any page.
The code is below.
CookieService.cs
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.JSInterop;
namespace LoginTest.Service
{
public class CookieService
{
#region field
private readonly IJSRuntime _jsRuntime;
#endregion
#region constructor
public CookieService(IJSRuntime jsRuntime)
{
_jsRuntime = jsRuntime;
}
#endregion
#region method
/// <summary>
///Write to cookie
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public async Task WriteCookieAsync(string key, string value)
{
await _jsRuntime.InvokeVoidAsync("blazorExtensions.WriteCookie", key, value).ConfigureAwait(false);
}
/// <summary>
///Reading cookies
/// </summary>
/// <returns></returns>
public async Task<Dictionary<string, string>> ReadCookieAsync()
{
var cookieDictionary = new Dictionary<string, string>();
var cookie = await _jsRuntime.InvokeAsync<string>("blazorExtensions.ReadCookie").ConfigureAwait(false);
var cookieSplit = cookie.Split(";");
for (var i = 0; i < cookieSplit.Length; ++i)
{
var cookieKeyValue = cookie.Split("=");
cookieDictionary.TryAdd(cookieKeyValue[0], cookieKeyValue[1]);
}
return cookieDictionary;
}
#endregion
}
}
It can be executed simply by passing the method name and arguments of the JavaScript
declared earlier to the ʻInvokeAsync method of the ʻIJSRuntime class
. Easy and easy.
All you have to do is add the CookieService class
to the ConfigureServices method
of the Startup class
with Scoped
so that you can DI
.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddResponseCompression(opts =>
{
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
services.AddBlazoredLocalStorage();
services.AddScoped<CookieService>(); //★ Add this line ★
}
Addictive point
The implementation looks very easy, but it took me a long time to get to this process. There are two addictive points.
** ① DI to service class can only be constructor injection **
Until now, if you somehow added the ʻInject attribute, you could
DI for the service, but it seems that it was because it was a
razor file or a
razor.cs file`.
You can read about it on the following page.
Use DI for services
So this CookieService class
does constructor injection and injects into ʻIJSRuntime class. Even if you declare the ʻIJSRuntime class
in the property and add the ʻInject attribute, it will remain
null` and no value will be entered.
** ② If you want to DI the default service in the service class, add the service according to the validity period of the default service **
It seems that many people don’t understand what they are saying, so I will explain it carefully.
The service class is the CookieService class
that we are trying to create this time.
The default service refers to the following three services. These are services that can be used from the beginning.
This time I’m creating a solution on the server side.
I also want to use the default service ʻIJSRuntime class.
As you can see from the image above, the validity period of the ʻIJSRuntime class
is “scope” on the server side.
So, as shown below, CookieService class
is added by” scope “.
Startup.cs
services.AddScoped<CookieService>(); //★ Add this line ★
I didn’t know I needed to fit this, so I initially added it as a singleton. Then, the following error was output.
InvalidOperationException: Cannot consume scoped service ‘Microsoft.JSInterop.IJSRuntime’ from singleton ‘LoginTest.Service.CookieService’.
(InvalidOperationException: The scope service “Microsoft.JSInterop.IJSRuntime” cannot be used from the singleton “LoginTest.Service.CookieService”.)
Here’s an explanation of how long the service is valid. The scope is valid only on the server side, isn’t it?
Reuse login information
We use cookies and local storage to retain your login information. Also. If your login information is saved, let’s automatically route it to the chat page.
First, install Blazored.LocalStorage
from NuGet
.
Using this library makes it very easy to use local storage. If it is local storage, you can close the browser or take over the saved information in another tab, so this time we will use local storage.
Source: Active engineers explain how to use JavaScript sessionStorage [for beginners]
Let’s recall the figure below again.
Here’s what to do when a user visits a page:
- Access the login page
- Get the session ID stored in the user’s cookie
- Find the session ID held in local storage that matches the session ID of the cookie
- Get the last login information held in the local storage
- Route to chat page
If the session ID is not saved in the cookie, create it and let the local storage hold the login information using the session ID as a key when login is successful.
Since HTTPContext
cannot be used, Session ID
is replaced by New Guid
.
The above processes 1 to 5 could be implemented by the following processes.
C#:Index.razor.cs
protected override async Task OnAfterRenderAsync(bool firstRender)
{
//Feels like the IsPostback property?
if (firstRender)
{
//If you don't have a session ID, embed it in a cookie
var newSessionID = Guid.NewGuid().ToString();
var cookie = await CookieService.ReadCookieAsync().ConfigureAwait(false);
if (cookie == null)
{
await CookieService.WriteCookieAsync(SessinID, newSessionID).ConfigureAwait(false);
}
else
{
var sessionID = cookie.FirstOrDefault(x => x.Key == SessinID);
if (sessionID.Key == null)
{
//If you don't have a session ID, embed it in a cookie
await CookieService.WriteCookieAsync(SessinID, newSessionID).ConfigureAwait(false);
}
else
{
//If you have a session ID and you also have login information in your local storage
var loginData = await LocalStorage.GetItemAsync<LoginData>(sessionID.Value).ConfigureAwait(false);
if (loginData != null)
{
Navigation.NavigateTo("Chat", false);
}
}
}
}
}
Also, if you can log in as the correct user without checking Validation
, add the login information to the local storage and you’re done. Since the cookie may have been deleted during this time, we have added a process to write to the cookie just in case.
C#:Index.razor.cs
public async Task OnValidSubmit(EditContext context)
{
Console.WriteLine($"OnValidSubmit()");
var errorMessage = await SignInUserAsync().ConfigureAwait(false);
if (string.IsNullOrEmpty(errorMessage))
{
var cookie = await CookieService.ReadCookieAsync();
var sessionID = cookie.FirstOrDefault(x => x.Key == SessinID);
if (sessionID.Key == null)
{
//If you don't have a session ID, embed it in a cookie
var newSessionID = Guid.NewGuid().ToString();
await CookieService.WriteCookieAsync(SessinID, newSessionID).ConfigureAwait(false);
sessionID = cookie.Single(x => x.Key == SessinID);
}
await LocalStorage.SetItemAsync(sessionID.Value, LoginData).ConfigureAwait(false);
Navigation.NavigateTo("Chat", false);
}
else
{
LoginErrorMessage = errorMessage;
}
}
Try to run
You can glimpse the login screen, but you can see that it will be routed to the chat page immediately.
I think the login screen is probably due to routing with the ʻOnAfterRenderAsync method. However, there is no ʻOnBeforeRender method
.
However, I received a pull request, so it will be available in the future.
OnBeforeRender #1716
Summary
In the previous articles, it was Full C #
, but I ended up using JavaScript
to handle cookies. Probably, in the future, I think that libraries will come out for parts other than C #
, so I would like to expect it.
Next time, I would like to solve the problem that I can route to the chat page without authentication.
** Referenced page **
-Is security measures perfect? Introducing the difference between sessions and cookies and how to use them in PHP!
-Insert ASP.NET Core Blazor Dependency
-SPA with Blazor! (7) –DI (Dependency Injection) –Official version supported
- InvalidOperationException: Cannot consume scoped service ‘Microsoft.JSInterop.IJSRuntime’ from singleton ‘…IAuthentication’ in Blazor
-Call JavaScript from C # code in Blazor application
-Call a JavaScript function from a .NET method in ASP.NET Core Blazor
–Easy way to set, get and delete cookies with javascript
-ASP.NET Core Blazor State Management
-Difference between “cookie”, “session” and “session cookie”