Blazor WASM Prerendered with MVC. Controller is appending /[controller]/... to the action path and does not load _framework/blazor.boot.json

Viewed 157

I have a bit of a pickle. I'm not sure how standard my setup is, so I'll give it a quick overview.

We had a project in which we've set up both MVC and API serving endpoint. It works great. I've read recently that you can mix Blazor components into the MVC project using something like this:

@(await Html.RenderComponentAsync<BlazorComponents.Components.SelectExmpleComponent>(RenderMode.WebAssemblyPrerendered, new {Options = operatorRoles}))

This actually works great! I had to set up a Blazor client project and mess around with the Startup configurations, but I was successful at getting Blazor to load pre-rendered almost instantly. It then downloads required components in the background and all bindings work. I'm very happy with this and I can see this becoming a huge boost in productivity.

Just as I was ready to call it a roaring success, we found a pickle. Apparently, all my initial tests were done on the default controller. As soon as I move it to a different controller, the browser appends the controller path to all the outgoing requests. I can get around it for the initial JavaScript import:

<script src="@Url.Content("~/_framework/blazor.webassembly.js")"></script>

But this script internally loads other components, the first one being _framework/blazor.boot.json for example and I get:

Failed to load resource: the server responded with a status of 404 () _framework/blazor.boot.json

On further inspection, it turns out that the browsers is asking for [Root Domain]/[Controller]/_framework/blazor.boot.json , which obviously is incorrect.

I'm unsure how to fix this. One workaround that we currently employ is to not use the controller in the routes and ensure that each route is unique... But that's a little hacky to my taste, I liked the separation into controllers. I could also put all the dependant files manually and put them into a static resources folder and reference them through a @Url.Content() directive, but that also feels a little hacky.

Is there a cleaner way? For reference, I'll include my current Configure method from the Startup:

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseWebAssemblyDebugging();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }

        app.UseSession();
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseBlazorFrameworkFiles();

        app.UseRouting();

        app.UseAuthorization();
        app.UseAuthentication();
        
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
            endpoints.MapControllers();
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}");
        });
    }
1 Answers

I had the same issue and didn't find a lot of info out there on solutions so posting this in the hope it may help someone.

In .Net 6 you can manipulate the Blazor startup process to make adjustments as needed. In this case to change the path of requests. Docs here https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-6.0

Implementation details:

To do this, first you need to disable autostart when the blazor.webassembly.js file is loaded with autostart="false" property on the script tag to prevent Blazor starting automatically.

<script src="/_framework/blazor.webassembly.js" autostart="false"></script>

Next you need to add some java script to make adjustments to the load paths. This will start Blazor with an override for the loadBootResourse. Do this in the same file as the blazor.webassemly.js reference. I my case it is the index.cshtml file for the view using the Blazor component.

<script>
    Blazor.start({
        // manually start blazor and override boot load to redirect http requests to /_framework/ instead of /{controller}/_framework
        loadBootResource: function (type, name, defaultUri, integrity) {
            console.log(`Loading: '${type}', '${name}', '${defaultUri}', '${integrity}'`);

            //https://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/startup?view=aspnetcore-6.0
            // change framework uri's to be absolute instead of relative.
            if (defaultUri.startsWith("_framework"))
            {
                    return  `/${defaultUri}`;
            }

            // over ride dotnet js location to correct locaction.
            if (type == 'dotnetjs') {
                return `/_framework/${name}`;
            }
        }
    });
</script>

This will change any reference to "_framework/* " to be "/_framework/*".

I also found that the dotnet.js file used the full path ie https://domain/something/_framework/dotnet.xx.x.x.js so that needs to be handled as well.

Now Blazor should start and load its resources.

Related