Starting pages and starting execution flow in the Blazor Server app - blazor-server-side

Learning Blazor, and in VS 2022 started a new Blazor Server app project. I deleted some of the pages/files from the default project created, this is what I have left:
I did this because I am trying to understand the flow of execution. The key pages appear to be Pages\_Host.cshtml, Pages\_Layout.cshtml, Pages\Index.Razor, Shared\LoginDisplay.razor, Shared\MainLayout.razor, and App.razor. My questions are:
Are these pages all necessary for a Blazor Server app?
What is the difference between #Body in MainLayout.razor and #RenderBody in _Layout.cshtml? Are both needed?
In what order are the pages executed?
Why is there a #page "/" in both _Host.cshtml and Index.razor? It is my understanding that only one page could have that, also that the value for #path must be unique to a page.

Related

Only Map existing Blazor Pages(Razor Components) in the pipeline?

The usual pattern for Blazor (server) apps, is that at the end pf program.cs there is app.MapFallbackToPage("/_Host");
That will result in ALL request paths, that match no earlier endpoint, executing the Blazor App. Sure the Blazor App has a <NotFound> Renderfragment, but that will still result in establishing the SignalR and so on. What if I want to display a static file (.html) from wwwroot or a specfic RAZOR PAGE (.cshtml), which doesnt establlish a websocket for all those cases where the route does NOT EXIST IN ANY BLAZOR PAGE ???
NavigationManager.NavigateTo cannot be used directly inside the BuildRenderTree markup of the <NotFound> ... I could maybe put a component inside there that redirects OnInitialized ... but that would still first require the SignalR.
How can I completely avoid Blazor/SignalR for URLs that don't exist in the Blazor App ?
Idea: I could try to add ALL POSSIBLE Blazor-routes as fallbacks and the most general fallback to some static file or Razor Page... but that seems LIKE A LOT OF DOUBLE WORK and very error prone... is there no way?
app.MapFallbackToPage("~/Admin/{*clientroutes:nonfile}", "/_Host");
app.MapFallbackToPage("~/SomeRoute/More/{*clientroutes:nonfile}", "/_Host");
app.MapFallbackToPage("~/SomePage/{*clientroutes:nonfile}", "/_Host");
app.MapFallbackToPage("/NotValidRoute");
Edit:
#Reason:
I wanted to have a single fallback page (razor page or static) for all routes/urls that don't match any target in the app. But sadly as it seems, that conflicts with blazor'S client side routing. since the server-side routing does not know, which routes should be handled by blazor, it also cannot know, which routes would not map to anything (razor pages and blazor combined). Other please correct me here.
There's server-side routing and client-side routing. On the server side, the middleware pipeline runs first, handling static files and server defined routes, including the fallback route. Fallback routes are routes that don't match static file requests and have the lowest priority, so they are not preferred over other matching routes.
Server-side routing has no idea what routes are going to match on the client-side. That data model is separate. What's happening is, when no server route matches, the host page is rendered, which bootstraps the client. Now you've transitioned to client-side routing and cannot re-enter (without a change to refresh the browser)
What if I want to display a static file (.html) from wwwroot or a specfic RAZOR PAGE (.cshtml), which doesnt establlish a websocket for all those cases where the route does NOT EXIST IN ANY BLAZOR PAGE ???
That requires knowing what routes exists on the client.
Idea: I could try to add ALL POSSIBLE Blazor-routes as fallbacks and the most general fallback to some static file or Razor Page... but that seems LIKE A LOT OF DOUBLE WORK and very error prone... is there no way?
Possible using the <NotFound> component to run some JavaScript and redirect the browser to a fixed route on the server.
Edit:
Another would be to build that found all routable razor components and map them all as fallback routes to "_Host".
Something like this (untested):
// This should match wherever components are declared.
var types = typeof(Program).Assembly.GetTypes();
foreach (var type in types)
{
if (typeof(ComponentBase).IsAssignableFrom(type) && type.GetCustomAttribute<RouteAttribute>() is { } routeAttribute)
{
var route = routeAttribute.Template;
endpointRouteBuilder.MapFallbackToPage(route, "/_Host");
}
}
It partly depends on how your app is hosted. In IIS, you can add applications to any website, which are basically websites of any type you want with a "/path" added.
Currently, my company's main app is "ABCsite.com," and my Blazor app is "ABCsite.com/Blazor"

How to host Blazon in Razor Pages application

The "standard" Blazor WASM application is hosted inside a static HTML page; e.g., index.html. Due to certain requirements, I want to host Blazor inside a Razor pages application.
What I did is starting from a "standard" Blazor WASM application, removed the static files, because I do not need them, moved the content of index.html to the Wasm.cshtml, and change endpoints.MapFallbackToFile("index.html"); to endpoints.MapFallbackToPage("/Wasm");.
Everything seemed to be working as expected; I can run the application and navigate to the different pages I have in Blazor.
However, things fall apart when I try to access a page using its URL; e.g., http://mysite/counter, where /counter is a page in Blazor, and I get the following error:
An unhandled exception occurred while processing the request.
AmbiguousMatchException: The request matched multiple endpoints. Matches:
/Wasm
/Wasm
Can someone help me identify what I am doing wrong?
P.S.:
I looked at some answer here, but all that I found is people talking about Blazor Server.
I am using .NET 3.1 and Blazor 3.2.
I want to use my Razor Pages application to host/serve Blazor WASM not mixing them in a single project. They as still 2 different projects.
I am totally aware that Blazor WASM and Razor Pages are unrelated technologies. I am not trying to integrate them. I am only trying to server Blazor WASM files from a dynamic page. If it makes you think better about what I am trying to achieve, think about Razor Pages as any server-side technology; PHP, Node, or whatever, then apply this to the routing issue that I am trying to resolve.
OK, based on what you've written so far take a look at ShaunCurtis/Blazor-Experimental on Github. It's a temporary Repo for some experimental code. Ignore BlazorTest. The startup project is Blazor-Experimental.
The default page is a normal razor page. It's a mixed Razor, Blazor Server and Blazor WASM site. All the WASM routes look like wasm/fetchdata, so we have different URLs for all the Server and WASM "Pages".
Startup differentiates URLs using multiple endpoints, so any URL that is in the "scope" of the Blazor WASM application gets set to _wasm.cshtml. Anything else that can't be mapped directly is in the "scope" of the Blazor Server Application at _host.cshtml. All plan Razor pages on the site get served as is. You don't need the Blazor Server bit at all, just fallback to the default Razor page.
endpoints.MapFallbackToPage("/wasm/{**segment}", "/_wasm");
endpoints.MapFallbackToPage("/_Host");
To summarise the answer:
Create a Blazor WASM project. You can copy the one from Blazor Hosted template.
Reference the project from the Razor Pages project.
Create the page that will host Blazor WASM; e.g., Wasm.cshtml, and make sure the page route is not set; i.e., only #page at the top of the page, so that it takes the default route /wasm.
Copy the code from index.html in the Blazor WASM project into Wasm.cshtml.
Important: If you are using your own layout, it is important to have <base href="/" /> on the page or the layout <head> section.
Remove all the static files from form the Blazor WASM project; e.g., index.html.
Remove all *.razor pages from the Blazor WASM project.
Add Wasm.razor to the Blazor WASM project and set its route to /wasm; i.e., #page "/wasm".
In Startup.cs in the Razor Pages project, add app.UseBlazorFrameworkFiles(); after app.UseStaticFiles();.
Also in the same Startup.cs, add endpoints.MapFallbackToPage("/wasm/{**segment}", "/wasm"); inside app.UseEndpoints() lambda.
Now run the application and navigate to /wasm. You should see the content of your Wasm.razor in addition to whatever layout you have set. You will get the same result when you paste the URL http://whateveryoursiteis/wasm.
You give very little information, so I'll have to make a number of guesses.
I guess you based your WASM integration on the Blazor WASM ASP.NET hosted template. That template consists of three projects: The .Client project, the .Server project and some extra project with shared models (they're probably doing clean architecture). The server project is a Razor pages project and the client a WASM project.
What you must understand is that a Blazor WASM project is not comparable with a Razor pages application. A Blazor WASM, or actually any WASM file is a different kind of binary and is fully run at the client! It is a client-side application. I.e. the output binary is totally different. You cannot have one project that generates both a server (x86/arm) binary and a client (WASM) binary. You need two separate projects.
What actually happens while compiling the WASM project, is that all page routing is also converted to WASM. Just inspect your network traffic when you change a page. Even though you browser url changes, that's fake... there is no network traffic! In fact, you stay on the same page.
Now think what happens when you enter "[..]/counter" manually in the browser. The host will actually again download the same .wasm file from the root ("/" = "/Index") and then parse the routing client-side.
Going back to your problem. For some reason you copied all the contents from the Blazer WASM project wwwroot/index.html to a Razor Page project Pages/Index.cshtml. Now you're confusing the whole routing system. When you type "[...]/counter", the WASM router will tell you that the .wasm file needs to be downloaded from "/Index". At the same time the Razor Pages router will tell you the compiled Index.cshtml is available at "/Index". That will give your "AmbiguousMatchException: The request matched multiple endpoints. Matches: /Index /Index".
Just look at the Summary of UseBlazorFrameworkFiles:
Configures the application to serve Blazor WebAssembly framework files from the root path "/".
Solution is just to keep the index.html in the WASM project as is. Just look at the default Blazor WASM ASP.Net app: it hosts Blazor WASM, Razor Pages and MVC at the same time and routing is just fine.
A different solution would be to use the overload of UseBlazorFrameworkFiles, where you can give a path prefix. E.g.
app.UseBlazorFrameworkFiles("/wasm");
You will need to fix other routing.
edit:
So let's give a case. you have:
A Blazor WASM project that serves pages /, /counter, etc.
A Razor pages project that serves pages /weatherforecast, etc.
Now:
you start the app on /. This loads the WASM from the server.
Next you click on the counter icon. this doesn't change the page!: it shows the counter page and updates the navigation url, but doensn't load a new page.
Now to go to weatherforecast. This is not found in the WASM, so a new page is actually loaded from the server. (either a razor page or controller/view)
If you would go back to counter, this is not found on the server, so the server 'falls-back' to the root (/Index) and loads the WASM again. Next is look if counter is found in the WASM.
Having a dynamic /Index will disrupt this system, so you'll have to manually solve all the routing.

Where do I put the blazor.server.js script tag in a RazorPages app?

Right now I have it at the bottom of the _Layout.cshtml layout page that is used by every other page. The problem I'm having is that pages under the Identity area throw errors in the console when navigating to Identity pages:
POST https://localhost:5001/Identity/Account/_blazor/negotiate 404
and
blazor.server.js:1 Uncaught (in promise) Error: Cannot send data if the connection is not in the 'Connected' State.
These errors make me think I'm doing something wrong with how I enable Blazor Server in my RazorPages app.
So I'm wondering, should I only have the blazor.server.js scripts on pages that are actually using Blazor? Is there a way to make it available to Areas that putting it in the Layout seems to not do?
I believe this post answers my question:
How do I use blazor server-side inside a razor component library using areas?
By default, the SignalR service uses a relative path and was unable to find the _blazor/negotiate endpoint within an Area. The suggested code change in _Layout will make it use absolute paths and normalize to /_blazor/negotiate wherever you are in the page structure.
I know this is late but incase it help others.
I got this error when I was trying to add blazor to my existing app.
I added:
<base href="~/" />
at the end of the head tag of the _layout.cshtml file found in the shared folder.
enter image description here

How to call a razor page from a Blazor component in a Server Side Blazor Application without causing a page refresh

I am developing a Blazor Server Side Application which I need to include cookie authentication. We created a custom login page component, but in order to create a cookie I need to call a Razor page to create the cookie.
Is there a way to call the razor page without doing a page refresh.
I tried using the UriHelper.NavigateTo but does not work with non Blazor component routes.
Then I tried with a Javascript call to do a window.location, but the issue is that this causes a browser refresh that makes all my instances to be recreated (like AppState, HttpClient), so all the information in these instances are lost (like httpclient headers, etc).
I expect to call the razor page to create a cookie, in a way that the httpclient and appstate scoped instance keep their values.
We tried the Identity scaffolding but it has a bug in the latest Preview 8
I've seen that bug, easily corrected. You can scaffold now. And otherwise,
the only problem that we have is that we could not find a way to customize the bland Login and Register pages.
maybe you could live with those 'bland pages' until the release, end of September?
The steps, based on a Preview8 server-side project with "Individual user accounts"
NB: it is a good idea to Commit-changes in Git before and after this
temporarily disable this line in Startup: // endpoints.MapBlazorHub<App>(selector: "app");
run the scaffolding wizzard, add any pages you want
uncomment the endpoints.MapBlazorHub() line again
remove Pages/_ViewStart.cshtml
fix Pages/Shared/_Layout.cshtml, the first line is missing an #using
fix Pages/Shared/_Loginpartial.cshtml, the second line is missing #inject
Compile and Run

Blazor server-side app throws error: The SPA default page middleware could not return the default page '/index.html'

I found a client-side Blazor app that implements Identity here.
It works correctly, but when I turn it to server-side Blazor it throws an error:
"Exception thrown: 'System.InvalidOperationException' in System.Private.CoreLib.dll ("The SPA default page middleware could not return the default page '/index.html' because it was not found, and no other middleware handled the request.")
on CsrfTokenCookieMiddleware line 28.
I really don't know what the problem could be. I guessed the error occurs because when using server-side Blazor, more logic is handed over to the Server project instead of the Client project, and the server doesn't contain a wwwroot. Therefore I created a symlink from the wwwroot of the Server project to the wwwroot of the Client project. But that also didn't work.
Can anybody help me with this issue? I am completely stuck. The creator also doesn't know what the problem is.
When you do server-side rendering with blazor, that don't use the blazor.webassembly.js for bootstrap the app, instead it uses blazor.server.js.
in your index.html file which resides in wwwroot file says as below,
<script src="_framework/blazor.webassembly.js"></script>
I checked the given GitHub repo and found the above script tag.
Make it to the shown as below and try,
<script src="_framework/blazor.server.js"></script>
The server-side rendering of the blazor does not run in the web assembly it runs in the asp.net core host and makes the communication with UI thread with signal R. This is the major difference between server-side rendering and client-side rendering of blazor.