dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.19k stars 9.93k forks source link

RazorComponentResult returned component cannot execute clientside code #50762

Closed michaelongithub closed 8 months ago

michaelongithub commented 12 months ago

Hi I want to return a Razor Component from a controller. (.net 8 RC1, newest Blazor WebApp Template)

        public IResult CRT()
        {
            return new  RazorComponentResult<Components.Components.Counter>();
        }

I have a simple test component (the infamous Counter.razor) with @attribute [RenderModeServer] Returns correct rendered component But counter event is not triggered:

BTW: The same behaviour if I use the component inside a Razor page (cshtml) with @await Component.InvokeAsync("Counter"); So has this maybe to do with that these calls cannot handle the new @attribute [RenderModeServer] ?

Thanks for information, Michael

marinasundstrom commented 12 months ago

You can't serve an interactive component without a static page in which to host it. That is why the new SSR model requires you to have the root App.razor (equivalent to index.html). It has to do with the Blazor JS script having to load once.

And I'm not sure that interactive components are supported for component endpoints anyway.

But theoretically:

To enable interactivity, you need to serve the blazor.web.js script, from the _framework path, and there is no easy way to expose that file without activating the entire thing by automatically mapping the components.

But you could check the source code, and look for the extension method with code that adds those static files to be hosted. I think it is in MapRazorComponents.

michaelongithub commented 12 months ago

Hi thanks for reacting so quickly. in .net 7 going via an enveloping .cshtml and having a layout file used in that view with and @(await Html.RenderComponentAsync(RenderMode.Server)) works with events. Haven't found out jet, how to make this work in .net 8 RC1. But expect that to work again analogously somehow latest at RTM. Is not as ideal as it would be directy via RazorComponentResult. My main motivation for that was to be able to have an MVC pattern style architecture again for may Blazor apps.

marinasundstrom commented 12 months ago

@michaelongithub I see. You try to create an MVC style app with Razor Components for views in custom endpoints.

I don't think that it is a supported case. Component endpoints are not meant to allow you do create MVC-style apps like with "Razor Views" in MVC.

Btw. Another thing to consider is the use of layouts for shared UI. They will not work unless you have the Router and the RouteView. Because the latter applies that layout.

I do however agree that there are some things with component endpoints that aren't clear.

michaelongithub commented 12 months ago

@marinasundstrom Thanks again. So my understanding of the current situation is as follows:

marinasundstrom commented 12 months ago

@michaelongithub Btw. I now see that you are using Component.InvokeAsync. Isn't that just for Razor View components? (Not Razor/Blazor components)

https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-7.0

Have you tried using the component tag helper to render a Blazor component in a Razor View?

https://learn.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/built-in/component-tag-helper?view=aspnetcore-7.0

marinasundstrom commented 12 months ago

@danroth27 Are interactive components supported through Minimal API endpoints returning ComponentResult?

(Apart from the limitation that you would have to render them in a SSR component)

michaelongithub commented 12 months ago

@marinasundstrom Thanks again for the hint I had already tried @(await Html.RenderComponentAsync<BlazorWebAppVS1782Empty.Components.Components.Counter>(RenderMode.Server))

And now per your advice

@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
<component type="typeof(Counter)" render-mode="Server" />

both render but make no clientside code running.

marinasundstrom commented 12 months ago

@michaelongithub

Have you included the script? (Provided that you run .NET 8)

<script src="_framework/blazor.web.js" suppress-error="BL9992"></script>

I checked, and the Blazor Server template uses:

 <script src="_framework/blazor.server.js"></script>

That is required to activate the interactivity. So it should be either directly in your view (.cshtml), or in a layout/master file.

marinasundstrom commented 12 months ago

@michaelongithub I have created a functioning MVC app that has a view that renders an interactive Razor component. To me it seem like it is working in .NET 8.

https://github.com/marinasundstrom/BlazorMvcViewTest

The route is /Test.

Edit:

I also tested creating a component endpoint. /Test/foo that maps to the MyPage component which declares a Counter that has been marked as having RenderModeServer.

It is all using the normal Blazor Server stuff.

michaelongithub commented 12 months ago

@marinasundstrom Tested Your Example:

marinasundstrom commented 12 months ago

@michaelongithub Yes. I created a Blazor Server .NET 7 project, and upgraded it. And true, I did not use the new script.

In the case of the TestController and its action and endpoint, I am not using _Host.cshtml at all. They are separate Razor views and Components. And the Razor view is just for demonstrating that you can embedd an interactive Razor component.

The issue is that Blazor only exposes blazor.web.js when invoking the MapRazorComponents<TRootComponent>. You only need to invoke those methods when you are using the stuff related to the integration with the router.

And I reckon that Blazor Server stuff is doing similar stuff as those extension methods under the hood.

Because the script is not being exposed, I asked @danroth27 for answers whether interactive components are supported in components from plain endpoints.

I have update the test app, and added a custom UseBlazorWebJS() method that maps the blazor.web.js file. It is based on this.

That way you can reference and use the newer script. But you will not be able to use Blazor WASM components without adding them to the container first - I think.

@SteveSandersonMS Perhaps you know the details of this? How to approach this scenario.

ghost commented 12 months ago

Thanks for contacting us.

We're moving this issue to the .NET 9 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s). If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues. To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

marinasundstrom commented 12 months ago

I have tested Minimal API endpoint with interactive components. It works.

https://github.com/marinasundstrom/BlazorMinimalApiTest

Created components:

Register Razor component services:

builder.Services.AddRazorComponents()
    .AddServerComponents();

Serve the Blazor JS script:

app.UseBlazorWebJS()

Map endpoint:

app.MapGet("/test", () => new RazorComponentResult<BlazorMinimalApi.Components.MyPage>());

Map Blazor Hub:

app.MapBlazorHub();

I don't know how well this is documented. But then, the script is not exposed other than through the offical API.

michaelongithub commented 12 months ago

@marinasundstrom Amazing. Admirable. Shows that it is basically possible in .net 8.

marinasundstrom commented 12 months ago

@michaelongithub I'm just a humble enthusiast and developer who is happy to help. And it was much needed distraction to me! 🙂

I guess it is about priorities. Now when the final release is near.

When I started experimenting with this, a month ago, I noticed that the blazor.web.js wasn't served unless you called MapRazorComponents(). So I dug into the code and extracted that bit into my own extension method.

The key to make interactive server components work is the .MapBlazorHub(), as well as serving the script.

I think that the Blazor team needs time to think about how to design these APIs.

In the meantime, you have this workaround. Which I documented about in my repository for others to see.

But I have not tried out WebAssembly. I guess that I need to expose more static files. I will update once I have explored that.

michaelongithub commented 12 months ago

@marinasundstrom Thanks for letting me know. Currently we have not used and short term foreseeably don't intent to use WASM. So I will wait and see, what the team will come up with in RC2 and RTM. I hope they will clarify and document the state of affairs with regard the topics we discussed and You researched. And your distraction from whatever was at any rate very helpful for me, to see what the current state is with regard to my initial motivation.

marinasundstrom commented 11 months ago

FYI. I got WebAssembly working as well. So added endpoint for that.

https://github.com/marinasundstrom/BlazorMinimalApiTest/blob/main/README.md

michaelongithub commented 11 months ago

@marinasundstrom Indefatigable ! From the viewpoint of a wide perspective of a "Blazor Fullstack" I would expect the possibility to return a fully functional Blazor Component programmatically in a very straightforward way. My main argument being: You can return Razor Views and Pages (from Controllers). So please make that possible for Blazor Components. For me personally the primary concern is not minimal API's but a mvc application architecture.

marinasundstrom commented 11 months ago

@michaelongithub You have to know that endpoints that serve Razor Components aren't a replacement for Razor Views in MVC. You will not get the same with Razor components.

You won't get things like layouts since they require you to have a Router component, and a RouteView component that applies the layout.

And I don't know if enhanced navigation, and such will work in your MVC case.

So in normal cases, when you are building full on interactive Blazor apps, you would rather use the other way of letting ASP.NET Core registering the pages to be used by the router.

But I think that since ASP.NET now supports rendering Blazor components through endpoints, and that you can add interactivity, then the functionality should be exposed so that developers can use it.

marinasundstrom commented 11 months ago

@mkArtakMSFT What should we do with this?

javiercn commented 11 months ago

I am going to clarify the state of things for .NET 8.0

With this in mind, very likely our focus moving forward will be to continue expanding on the Blazor Web experience (MapRazorComponents), as that is the mainline scenario for us.

michaelongithub commented 11 months ago

@javiercn: Thanks a lot for this clarification