How to host Blazon in Razor Pages application - asp.net-core

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.

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"

Is there a way to serve a Blazor app from a specific controller action in a MVC app?

I'd like to set up a controller action in a already existing ASP.NET Core 3.0 MVC project to serve a Blazor app. I've seen something similar with other SPA frameworks in the past, the View for the action just includes contains the script tag with the bundle, there's usually some other setup like having the SPA build placed on wwwroot for the MVC project.
Looking at the Blazor app it seems similar to some extent, where the index.html file includes the wasm script. But I'm honestly not sure where to start to set it up. Any ideas?
Seems you're using Blazor Client Side. Basically, the entry for Blazor Client Side App is the <script> that loads the blazor.webassembly.js. And this script will download all the static assets( *.wasm and *.dlls) dynamically. Note that if you can make these assets available, you can host the Blazor Client Side App even without ASP.NET Core.
Since you're using ASP.NET Core MVC as the server, an easy approach would be :
Add a package reference to Microsoft.AspNetCore.Blazor.Server so that we can serve the *.wasm *.dll files with just one code.
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.0.0-preview9.19465.2" />
Add a project reference to your ClientSide Project so that we can generate the statics from source code. Let's say your client side web assembly app is MyClientSideBlazorApp:
<ProjectReference Include="..\MyClientSideBlazorApp\MyClientSideBlazorApp.csproj" />
And now you can register a middleware within the Startup.cs to serve the static files:
app.UseClientSideBlazorFiles<MyClientSideBlazorApp.Startup>();
app.UseStaticFiles();
Finally, within any page/view that you want to serve the blazor, for example, the Home/Privacy.cshtml view file, add a <script> to load the entry and also a <base> element to make sure the relative path is correct.
#{
Layout = null;
}
<base href="/">
...
<app>Loading...</app>
<script src="/_framework/blazor.webassembly.js"></script>
It should work fine now.

Blazor router, How to have multiple Client Side Blazor projects in a Net Core app?

I have a Web solution with a WebCore 3.0 razor pages that its huge!. Lets call this the WebCoreAppDefault. I want to implement 3 Blazor projects with different pages. In my Start up files I have configure as this:
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseClientSideBlazorFiles<BlazorApp1.Startup>();
app.UseClientSideBlazorFiles<BlazorApp2.Startup>();
app.UseClientSideBlazorFiles<BlazorApp3.Startup>();
app.UseRouting();
app.UseAuthorization();`enter code here`
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapFallbackToClientSideBlazor<BlazorApp1.Startup>("ReportGenerator.html");
endpoints.MapFallbackToClientSideBlazor<BlazorApp2.Startup>("DashboardConfiguration.html");
endpoints.MapFallbackToClientSideBlazor<BlazorApp3.Startup>("ClaimsManipulation.html");
I do have the startup html file in each project as this:
1.- in the BlazorApp1 I have the ReportGenerator.html file under the wwwroot
1.- in the BlazorApp2 I have the DashboardConfiguration.html file under the wwwroot
1.- in the BlazorApp3 I have the ClaimsManipulation.html file under the wwwroot
When I run my WebCoreAppDefault, I can get to each Blazor file by adding a /ReportGenerator.html, or DashboardConfiguration.html, or ClaimsManipulation.html, and Blazor will load the project, but that's it.
The router of the blazor will not load any page, It will said "Sorry there is nothing at this address" on the not found. If I can put a link tag to the /Counter page, I can run the counter page without any layouts.
I believe I have problems after the blazor loads the mono and the dll, to redirect to another page that is not the root or "/" page?
I am trying to redirect in each project to a page that is not page "/", How Can I do that?
I want to understand how I can redirect to a page that is not root? or how to configure the App.razor page to redirect to a ReportGenerator.razor page
or DashboardConfiguration.razor page or ClaimsManipulation.razor page as a landing page instead of being force to /
This is now possible. Searched all over, the thread below has a snippet which I was able to use.
https://github.com/dotnet/aspnetcore/issues/17997
2 changes you need to make for each Blazor clientside project you want to host.
Change 1
Open index.html of Blazor clientside project and change base to the url path you want
Change 2
Open Startup.cs in Blazor Serverside project and map your clientside project to a particular URL pattern.

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.

Who insert Angular scripts in ASP.NET Core Angular template project

I created a new ASP.NET Core 2.1 Angular project.
What is interesting is that there is index.html file inside ClientApp/src which is hosted as default page. I would like to know, which code triggers that index.html is served by default?
In my old application from universal template Controllers and Views are used which seems more naturally then serving index.html from client code.
When application is runned, some code magically inserts Angular scripts (inline.bundle.js, polyfills.bundle.js, vendor.bundle.js, main.bundle.js and styles.bundle.css) at the end of index.html file? Can someone share some light which code does that?
I want to understand this, because I want to serve content from RazorPage instead of static index.html, but in order to try anything I need to know a little more about magic behind this template.