I'm unable to separate the Login.razor, Register.razor, ForgotPassword.razor, and ResendEmailConfirmation.razor components from the MainLayout.razor layout properly. I am using _Imports.razor to globalize the implementation of directives to establish the bridge between the templated components and the pages I am extracting, but instead I'm stuck on a login page that loops infinitely.
Firstly, I've used dotnet (.NET CLI) and aspnet-codegenerator's Blazor Identity options to Scaffold Identity Razor components into a Blazor app with the blazor-identity generator.
Here's the complete command that I've used:
dotnet aspnet-codegenerator blazor-identity -dbProvider sqlite -dc MyProject.Data.BlazorAuthContext -fPlease see this answer for full replicated procedures to integrate this into my existing Blazor web app.
The original folders and files hierarchy it generates is:
MyProject/|├── Components/| || └── Account/| || ├── Pages/| | || | ├-- ForgotPassword.razor | | || | ├-- Login.razor| | || | ├-- Register.razor| | || | ├-- ResendEmailConfirmation.razor| | || | └-- Other files...| || └── Shared/| || ├-- AccountLayout.razor| || ├-- RedirectToLogin.razor| || └-- Other files...These specific razor files I extracted and used as reference to extract and create a new layout.
Secondly, I created a new folder hierarchy for this:
MyProject/|├── Components/| || └── External/| || ├── Pages/| | || | ├-- ForgotPassword.razor | | || | ├-- Login.razor| | || | ├-- Register.razor| | || | └-- ResendEmailConfirmation.razor| || ├── Shared/| | || | ├-- ExternalLayout.razor| | || | └-- RedirectToLogin.razor| || └── _Imports.razorI used the same code structure of AccountLayout.razor layout, and renamed it to ExternalLayout.razor. Take note that I just copied the AccountLayout.razor, then renamed it after placing it in a different folder. The AccountLayout.razor will be used as a reference and will also serve as a template for the account management upon login.
Here's the ExternalLayout.razor file:
@inherits LayoutComponentBase@inject NavigationManager NavigationManager@if (HttpContext is null){<p>Loading...</p>}else{ @Body}@code { [CascadingParameter] private HttpContext? HttpContext { get; set; } protected override void OnParametersSet() { if (HttpContext is null) { // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. // The identity pages need to set cookies, so they require an HttpContext. To achieve this we // must transition back from interactive mode to a server-rendered page. NavigationManager.Refresh(forceReload: true); } }}You may have noticed that I removed the @layout MyProject.Components.Layout.MainLayout directive, since I needed to separate this layout from the template with NavMenu.razor. However, for the AccountLayout.razor layout, I leave it as is, as I've already mentioned.
Thirdly, I configured my newly created _Imports.razor file, which is under the External folder that is separated from the other pages and components folder hierarchy. I carefully followed the article for this instance under the section: Apply a layout to a folder of components, which I'll quote:
Warning
Do not add a Razor
@layoutdirective to the root_Imports.razorfile, which results in an infinite loop of layouts. To control the default app layout, specify the layout in the Router component. For more information, see the following Apply a default layout to an app section.The same condition results when using an
_Imports.razorfile to apply a layout to a folder of components with the@layoutdirective and the layout component itself is in the same folder or folder hierarchy of the_Imports.razorfile. An infinite loop of applying the layout occurs because the@layoutdirective is also applied to the layout component. To avoid recursion problems, we recommend storing layout components in their own folder (for example,Layouts), away from where_Imports.razorfiles are applying them.
Therefore, in my root _Imports.razor file (located at MyProject/Components/), I leave the directive as is:
@using System.Net.Http@using System.Net.Http.Json@using Microsoft.AspNetCore.Components.Forms@using Microsoft.AspNetCore.Components.Authorization@using Microsoft.AspNetCore.Components.Routing@using Microsoft.AspNetCore.Components.Web@using static Microsoft.AspNetCore.Components.Web.RenderMode@using Microsoft.AspNetCore.Components.Web.Virtualization@using Microsoft.JSInterop@using MyProject@using MyProject.ComponentsAnd in my newly created _Imports.razor file located at MyProject/Components/External/, these are the directives it has:
@using MyProject.Components.Account.Shared@using MyProject.Components.External.Shared@layout ExternalLayoutNote that @using MyProject.Components.Account.Shared allows access to other Razor files that I've left there (upon generation of those templated files).
Moreover, in the Routes.razor, refactored the original configurations, from:
@using MyProject.Components.Account.Shared<Router AppAssembly="typeof(Program).Assembly"><Found Context="routeData"><AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)"><NotAuthorized><RedirectToLogin /></NotAuthorized></AuthorizeRouteView><FocusOnNavigate RouteData="routeData" Selector="h1" /></Found></Router>Into:
@using MyProject.Components.External.Shared<Router AppAssembly="typeof(Program).Assembly"><Found Context="routeData"><AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(ExternalLayout)"><NotAuthorized><RedirectToLogin /></NotAuthorized></AuthorizeRouteView><FocusOnNavigate RouteData="routeData" Selector="h1" /></Found></Router>From the original configurations with the directive @using MyProject.Components.Account.Shared, which is already defined in my newly created _Imports.razor file. I removed it in my newly configured Routes.razor file. I believe this is likely similar to this thread, but we're in different context:
In addition, here's the RedirectToLogin.razor:
@inject NavigationManager NavigationManager@code { protected override void OnInitialized() { NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true); }}I retained some of the original code structure generated by Blazor Identity options. I only modified some configurations to achieve my goal: to have a login page without a side menu. Upon successful login, I will then display the side menu and main content, which are provided by the template from this Blazor tutorial.
Since I'm currently developing this Blazor web app, I defined the "launchUrl": "account/login", within the launchSettings.json file to launch the Login.razor page upon startup.
Unfortunately, with the aforementioned procedures, the login page loads in an infinite loop.
I already applied the solution from this highly ranked answer thread, but it still doesn't resolve my issue.
Therefore, this question thread:
is different from my case.
Lastly, as I'm doing some workaround on my end, I also tried to include this @layout MyProject.Components.Layout.MainLayout directive in the ExternalLayout.razor layout. Surprisingly, it resolved the login page loading in an infinite loop, but I got the layout I was trying to change:
Since I needed to be sure that changes to the folder and file hierarchy wouldn't affect the program's expected outcome, I tested the various features and functionalities within the Login.razor page. These worked as expected.
Therefore, I can conclude that there's no problem with the hierarchy changes. My instinct suggests the issue lies solely in specifying the new layout as the DefaultLayout. This likely introduces an infinite loop on the login page due to authentication instances within the program, the origin of which I haven't been able to pinpoint.
So my question is, does anyone have an idea what causes this issue of the login page looping infinitely, for me to resolve and achieve my goal in my project for this instance?
Any help would be highly appreciated!
