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
34.87k stars 9.84k forks source link

Ability to run multiple Blazor server / Web assembly apps in the same document (micro-frontends) #38128

Open javiercn opened 2 years ago

javiercn commented 2 years ago

It's interesting for us to consider running Blazor Server and WebAssembly applications in the same document as well as being able to mix them on the same apps. We've heard feedback that some people don't want to have some "proprietary business logic" on the client, and this offers a way to to do so. In the same vein, we can expect larger apps developed by several teams to run into conflicts on the versions used by their apps. Being able to run multiple blazor versions on the document, avoids this problem at the cost of increased app size.

UPDATE (from @MackinnonBuck)

We've opened https://github.com/dotnet/aspnetcore/issues/48032 to track the ability to have a single Blazor app that uses Server and WebAssembly components on the same page. This enables scenarios critical to the https://github.com/dotnet/aspnetcore/issues/46636 effort. It does not, however, include the ability to have completely separate Blazor app instances exist on the same page. That remaining work is what this issue tracks.

Source: https://github.com/dotnet/aspnetcore/issues/38128#issuecomment-1559891706

jmezach commented 2 years ago

This is also important for micro-frontend scenarios where one might have multiple "islands" of Blazor on the same page. For example, we're considering adopting single-spa to modernize our existing monolithic front-end into smaller micro-frontends and it would be great if there was a way to build some of these micro-frontends using Blazor.

timeshift92 commented 2 years ago

hello, as one of way, after download wasm binaries swap server to wasm https://github.com/jdtcn/HybridBlazor

and second for state machine between server and client using this case https://github.com/servicetitan/Stl.Fusion

nssidhu commented 2 years ago

Current work around. Currently i am able to mix them up but instead have to use MVC controller & view on the Blazor Server. So i create special end point which will render content from MVC controller on Blazor Server. But it would be nice if i could instead use Razor page in place of MVC controllers & views.

I had couple of scenarios 1) I send SMS Link. when user clicks that link , it will hit MVC controller & view which renders QR Code. In this scenario i don't want user to load my entire Blazor wasm app, because he just need to display QRCode only on his mobile device, which will be scanned from Blazor wasm app . For this simple QrCode Display, I also did not want to spin up separate Blazor Server project and Host it, hence used MVC controller & view from within Blazor Server.

2) I have some logic(financial calculation) which I don't want to be exposed on the client side and for this logic, i just need to render HTML output. For this again, i was able to invoke the MVC Controller & return view from the controller from the Blazor Server Side, but this loads the page separately out side of Blazor Wasm. In this case i would like to have it render the server content in my Blazor wasm page which is not possible for now.

nkosi23 commented 2 years ago

We are developing a large system (a consumer application) having multiple modules using the same user interface. The user can switch between modules through the user interface. The user interface project (that we call the shell) provides services such as menus, UI notifications etc... that should are consumed by module projects.

With this architecture, our goal is to ensure that a developer only has access to the source code repository related to the module he is developing (instead of having full access to the source code of a monolith) because there will be a large number of modules, some of them developed for sensitive customers. The goal also is to enable us to treat every module as a micro-service and deploy it to a difference server.

Blazor Server is exactly what we need (we love the concept, it is like having a full state desktop app running in the web for each user and this enables so many opportunities), but we are attempted to go the monolith way because we struggle to figure out a development workflow that would be practical with Blazor Server. The monolith approach would cause issues with both intellectual property protection, but also with the problems related to restarting a stateful Blazor Server applications whenever an update in one of the many modules will be required (and hereby disconnecting a very large number of users).

Being able to load a module in an Iframe-like container in the shell, and why not have some sort of built-in interprocess communication facility between the shell and the loaded modules (for example to update the menu, or to enable the modules to call UI notification services), would be great. We currently need to reference and deploy the shell project with every module (and therefore reload the user interface whenever the user moves across modules).

ghd258 commented 2 years ago

能在支持多个Area吗

ghost commented 2 years ago

Thanks for contacting us.

We're moving this issue to the .NET 7 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.

mrlife commented 2 years ago

Will this also allow combining Blazor Server and razor pages applications?

javiercn commented 2 years ago

@mrlife no, this is different.

You can already render multiple Blazor Server components in a Razor Pages application.

mrlife commented 2 years ago

@javiercn Thanks for letting me know. It's a little different... it's about combining 2 different apps that run at the same URL, e.g. example.com and example.com/app2, where the former is razor pages project and the latter (app2) is a separate Blazor project.

javiercn commented 2 years ago

it's about combining 2 different apps that run at the same URL

That's not something we are planning to invest on.

wangyjx commented 2 years ago

For me it is important to inject blazor app as WASM component to HTML page, and in the nature of things, a HTML page can contain serveral WASM components from different language (go, rust, java, c++, .net).

mattleibow commented 2 years ago

This is also useful for cases where I want to mix a blazor server app in with wasm app or run a server app but have a wasm graphics library

A practical example is running skia in the browser. But this could be a 3D engine too. I want the full app to be a server app because of reasons, but the rendering logic needs to happen on the client as it is part of webgl and in a render loop. I am expecting to send batches of drawing commands to the client and then the client must update and handle cases like panning and zooming without requiring server interactions.

A more concrete example is maybe an image editing app. The server needs to handle all the page, auth, feature sets, storage and the client just makes editing happen. For example, the user could load a project and that all gets downloaded to the server one time. The user can pan, zoom, crop, recolor. Then hitting the save or export, the new state gets sent to the server for rendering and persistence.

In fact, think of me treating the wasm part as a js library, but written in c++ and .net.

Interactions between the server and client would be on the basis of either sending json or byte arrays via a method call... Or somehow. And then the client would parse or deserialize it. Just like I would if I was doing a web API, but more magical.

Webreaper commented 2 years ago

Yep, that's similar to my use case; server based code for the majority of the work, but a wasm component that runs entirely client-side that allows the user to modify images (changing hue, saturation, etc) without requiring the image to refresh server side.

RChrisCoble commented 2 years ago

This is also useful for cases where I want to mix a blazor server app in with wasm app or run a server app but have a wasm graphics library

A practical example is running skia in the browser. But this could be a 3D engine too. I want the full app to be a server app because of reasons, but the rendering logic needs to happen on the client as it is part of webgl and in a render loop. I am expecting to send batches of drawing commands to the client and then the client must update and handle cases like panning and zooming without requiring server interactions.

A more concrete example is maybe an image editing app. The server needs to handle all the page, auth, feature sets, storage and the client just makes editing happen. For example, the user could load a project and that all gets downloaded to the server one time. The user can pan, zoom, crop, recolor. Then hitting the save or export, the new state gets sent to the server for rendering and persistence.

In fact, think of me treating the wasm part as a js library, but written in c++ and .net.

Interactions between the server and client would be on the basis of either sending json or byte arrays via a method call... Or somehow. And then the client would parse or deserialize it. Just like I would if I was doing a web API, but more magical.

Thanks @mattleibow, this is what we'd be looking to do. Some other minor requirements from our side based on your great foundational description (and perhaps these are unrelated):

  1. Support for processing clicks in the client and sending them back to the server for processing.
  2. Support for drawing Blazor controls on top of the canvas to create a mixed view of graphics and controls. Ideally that's all managed in the DOM with a Blazor control cavnas on top of the Skia canvas.
danroth27 commented 1 year ago

Related: https://github.com/dotnet/aspnetcore/issues/29577

Webreaper commented 1 year ago

For anyone else subscribed to this issue and awaiting it after the discussion of it earlier in the year, it's been moved to a .Net 8 milestone. Commenting here explicitly because that move/postponement doesn't result in a notification.

jmezach commented 1 year ago

This is also important for micro-frontend scenarios where one might have multiple "islands" of Blazor on the same page. For example, we're considering adopting single-spa to modernize our existing monolithic front-end into smaller micro-frontends and it would be great if there was a way to build some of these micro-frontends using Blazor.

As I mentioned earlier we're looking to single-spa as a way to modernize our current monolithic frontend by using a micro frontend approach so that each team can build and maintain their own features independently of each other. Obviously we're also looking at Blazor to build some of these micro frontends in the future. I recently had the chance to do some experiments with running a Blazor WebAssembly app in single-spa. While I was able to get something running in the end, I did have some observations that I thought were worth sharing here:

I've also had a look at the new .NET 7 features where you could use .NET from any JavaScript application. That seems to provide at least some of the things described above, so could but a better fit. But as soon as I try to use Blazor with that things start to break apart as that requires blazor.webassembly.js to be loaded which probably sets up a few things in the JavaScript world for Blazor. That does however beg the question of whether we might see Blazor being "replatformed" on top of the low-level API's that are now available in .NET 7?

T-mp commented 1 year ago

In our scenario, there are several manufacturers of web components who also want to host them separately and (partially) use Blazor. Embedded in only one web application / page. Here it is necessary that the individual Blazor WebComponents are independent of each other (hosts, versions, and so on). As described in #44588, a complete encapsulation in WebComponents is required. Therefore, the integration of Blazor, for this use, must be done within the WebComponent Islands.

Sketch / Example:

Host html-document is from manufacturer A
<script type="module" src="https://vendor-b/path/to-module-file.js"></script>
<script type="module" src="https://vendor-c/path/to-module-file.js"></script>
[...]
<component-from-vendor-b>Blazor V8.0</component-from-vendor-b>
<component-from-vendor-c>Blazor V9.0</component-from-vendor-c>
danroth27 commented 1 year ago

That does however beg the question of whether we might see Blazor being "replatformed" on top of the low-level API's that are now available in .NET 7?

@jmezach We weren't able to get this done for .NET 7, but we do plan to do this for .NET 8.

joshmiller1FEI commented 1 year ago

CustomElement wouldn't work in a Blazor app, if it's hosted as a seperate app (on same site). Same basic issue yes? Also using CustomElement in MVC on same host is complicated (is it possible?). (Steve's conf Wordpress talk didn't show specifics on what doesn't work)

chassq commented 1 year ago

Probably already mentioned but this would be great for issues like #27592 where an initial page hit runs server side but then once all wasm components are downloaded it has the option to run client side. Not sure if tis possible but might help with issues with auth integration and eliminate separate Host server app maybe.

Webreaper commented 1 year ago

For those that haven't seen it, this is likely of interest and is relevant to this issue: https://www.youtube.com/watch?v=48G_CEGXZZM (cc @SteveSandersonMS)

thomashauser commented 1 year ago

This is also important for micro-frontend scenarios where one might have multiple "islands" of Blazor on the same page. For example, we're considering adopting single-spa to modernize our existing monolithic front-end into smaller micro-frontends and it would be great if there was a way to build some of these micro-frontends using Blazor.

We have done a PoC, too, about micro frontends with Blazor. This feature is very important for the future of Blazor on our side. Will the feature definitely be a part of the .NET 8 release?

javiercn commented 1 year ago

@thomashauser this is scheduled for .NET 8.0 and is part of our roadmap.

Unless we discover something, we think this will be part of .NET 8.0. That's said, many things can still happen until the release is out, so take my words with a grain of salt.

ghd258 commented 1 year ago

很关注这个功能,到底什么时候能解决呢,希望能实现

timeshift92 commented 1 year ago

already in net8 preview there is blazor united

thomashauser commented 1 year ago

Unfortunately, again not what one would have expected from an architectural point of view. As already clearly stated in other comments, it is about the possibility to use microfrontends in Blazor. Since it is apparently not clear what is meant by this, here is a list of requirements:

Blazor united does not seem to be the answer to all these requirements. Although here on this page was also repeatedly described by microfrontends, this requirement seems not to have been understood. Blazor united is good for applications that are again in danger of becoming a monolith. Blazor united will certainly have a positive impact on start-up performance. But Blazor united does not help me one step further regarding microfrontends.

If I have misunderstood anything, let me know.

mvromer commented 1 year ago

I'll chime in and say I'm very interested in this being a supported use case for Blazor. This has been hashed a few times already, but to put my spin on it: we would like the ability to load and run multiple, independently built and independently released Blazor WASM apps on the same page. In theory each app could be mounted to different parts of the DOM, or they could mount and unmount from a common mount node in the DOM based on the current client-side route.

The latter scenario is one I have been actively investigating off and on over the past year. In that case I use single-spa to lazily load and mount microfrontends (MFEs) in our app based on the current client-side route. Ideally in my case I would support traditional JS-based MFEs as well as Blazor-based ones.

In my learnings, one of the challenges in supporting Blazor MFEs is the reliance on global Blazor and DotNet objects and global navigation event handlers attached to the window by the Blazor startup script. There's also the aspect that a Blazor-based MFE's resources would typically load from an origin that differs from the origin of the page loading and running the MFEs.

Last year I hacked together a set of patches that I could apply to the .NET 6 version of the aspnetcore repo and build a custom blazor.webassembly.js script that gave enough control over managing global Blazor/DotNet objects, nav event handlers, and the resource URIs used to fetch the resources for a Blazor app. This allowed me to make Blazor-based MFEs to work, but there were some caveats and restrictions that one had to know about lest you shot yourself in the foot.

If MFEs were a properly supported use case for Blazor, my hope would be that not only would these foot guns be a non-issue, but it would also mean I wouldn't have to internally maintain a set of patches for my custom Blazor WASM startup script that makes this even feasible in my end.

joshmiller1FEI commented 1 year ago

Michael , did you achieve this via changing hosting ports for each micro in the respective.js file ?


From: Michael Romer @.> Sent: Thursday, April 13, 2023 3:43:02 PM To: dotnet/aspnetcore @.> Cc: Josh Miller @.>; Comment @.> Subject: Re: [dotnet/aspnetcore] Ability to run multiple Blazor server / Web assembly apps in the same document (Issue #38128)

I'll chime in and say I'm very interested in this being a supported use case for Blazor. This has been hashed a few times already, but to put my spin on it: we would like the ability to load and run multiple, independently built and independently released Blazor WASM apps on the same page. In theory each app could be mounted to different parts of the DOM, or they could mount and unmount from a common mount node in the DOM based on the current client-side route.

The latter scenario is one I have been actively investigating off and on over the past year. In that case I use single-spahttps://single-spa.js.org/ to lazily load and mount microfrontends (MFEs) in our app based on the current client-side route. Ideally in my case I would support traditional JS-based MFEs as well as Blazor-based ones.

In my learnings, one of the challenges in supporting Blazor MFEs is the reliance on global Blazor and DotNet objects and global navigation event handlers attached to the window by the Blazor startup script. There's also the aspect that a Blazor-based MFE's resources would typically load from an origin that differs from the origin of the page loading and running the MFEs.

Last year I hacked together a set of patches that I could apply to the .NET 6 version of the aspnetcore repo and build a custom blazor.webassembly.js script that gave enough control over managing global Blazor/DotNet objects, nav event handlers, and the resource URIs used to fetch the resources for a Blazor app. This allowed me to make Blazor-based MFEs to work, but there were some caveats and restrictions that one had to know about lest you shot yourself in the foot.

If MFEs were a properly supported use case for Blazor, my hope would be that not only would these foot guns be a non-issue, but it would also mean I wouldn't have to internally maintain a set of patches for my custom Blazor WASM startup script that makes this even feasible in my end.

— Reply to this email directly, view it on GitHubhttps://github.com/dotnet/aspnetcore/issues/38128#issuecomment-1507644530, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AWYSXXFPICZU7UV6U3ZX4JLXBBXONANCNFSM5HOJAXOA. You are receiving this because you commented.Message ID: @.***>

mvromer commented 1 year ago

@joshmiller1FEI , no, at least in the working proof of concept I put together, I did not have to use alternate ports. In my case, the Blazor MFEs were Blazor WASM projects that I prepared via dotnet publish. I then uploaded each MFE's static assets (JS, WASM, CSS, etc.) to a separate location within a publicly accessible Azure Storage account. Then, I configured single-spa to just load each MFE's assets from its respective location in that storage account. At no point did I have to try loading assets from an alternate port.

The crux of what I had to do was to add a number of properties to WebAssemblyStartOptions that let me independently configure things like the base URI Blazor/.NET would use for fetching resources and the base URI Blazor uses for its internal navigation and routing purposes. These options would get set when Blazor.start gets called inside things like the single-spa bootstrap/mount lifecycles for an MFE. Most of the changes were then around plumbing these values to the right spots in the Blazor WASM JS glue code where it was instead just using things like document.baseURI (again, not valid if MFEs are loaded from different origins or if the MFE's base client-side URI doesn't match the document's base URI).

There were some additional methods I added to IBlazor to manage when a particular Blazor app's nav event handlers were active. Essentially, they allowed for adding the handlers when single-spa mounts the Blazor MFE and removing them when the MFE is unmounted. This hack was basically to work around the limitation brought up by @jmezach above in his second point about Blazor not having the ability to start/stop/pause the app during these key lifecycle events.

I did this all a year ago, so there may have been changes made to Blazor either later in .NET 6 or as part of .NET 7 that make some of this easier. Though judging by the discussion above, I doubt it, so I've started picking this back up again. This was/is all done as part of an ongoing project at work, so while it has been my intention to get a working demo publicly on GitHub and even though there's nothing proprietary in the work I've done, I would need to clear things with my company's legal department first.

gerneio commented 1 year ago

@mvromer would def be interested in seeing the code that you put together to make this work.

carlornd commented 1 year ago

Reiterating and emphasizing what has already been written by @thomashauser... I integrated in a "shell" (frame/"master page"/"microfrontemd"...) Blazor Server a series of web components (also implemented in Blazor server) provided by different ASP.NET Core web apps, and running on different servers (or Pods on AKS). This to support a logic of separation of responsibilities between different teams, and also to distribute the "server" load among multiple processing nodes (or Pods). I used .NET 7. But to do this I had to implement a sort of "trick"... For example, consider the need to "visualize" (with a kind of "Microfrontend" implementation...) in a "Shell" Web App "ClnBlz" a server Web Component "Counter" exposed (with the RegisterCustomElement) as "my-blazor-counter1", hosted by a server ASP.NET Core Blazor Server Web App "Blazor1"; so the MF web app "ClnBlz" is hosted on a different server/Pod than the "Blazor1" "component" app. In addition to the canonical registration as a web component on the "Blazor1" web app side, I have also implemented these two changes in the web component "Blazor1" web app:

<script> Blazor.start({ configureSignalR: builder => { builder.withUrl("_custom1"); } }); </script>

Ad here's what I implemented on the "consumer" / frame / shell / MF Web App "ClnBlz" side (the "microfrontend portal"):

<iframe id="server1" name="server1" src="blazor1.html" scrolling="yes"></iframe>

<my-blazor-counter1></my-blazor-counter1>

followed by a "custom load" of the Bazor server javascript library:

"_framework_custom1/_framework/blazor.server.js"

with endpoint withUrl "_custom1".

Note the "_flamework_custom1" path prefix... In fact, the microfrontend web app "ClnBlz" contain an implementation of a local proxy (for example YARP, https://microsoft.github.io/reverse-proxy/) able (with a set of specified RULES based on paths) to "re-route" the requests to Blazor server to the "right" server (that in this case is not the "local" one hosted by "ClnBlz", but the one hosted by the DIFFERENT web component web app "Blazor1"...).

I know this is a set of "tricks"... and hopefully with .NET 8 I hope that it will support the "native" integration into one Blazor server web app of multiple other Blazor server (web) components, provided by several and additional Blazor server web apps.

Update Dec 8, 2023:

Recently I did some further tests, with a more "simple" approach than the one described above.

In summary, I have implemented some pages as ASP.NET Core Blazor Server Web App pages; call one of these pages here "PageB1", as part of an ASP.NET Core Blazor Server Web App "B", and I hosted this page in an IFRAME in another different ASP.NET Core Blazor Server Web App called here "A", specifically in a page that we can call "PageAShell". So, the "PageAShell" of the ASP.NET Core Blazor Server web app "A" can show in a simple IFRAME the PageB1 exposed by the different ASP.NET Core Blazor Server Web App B (hosted in a different server and process). With some configuration I'm also able to send events from the page PageAShell to the page PageB1, and vice versa. I do not have used in this implementation special configuration as described before in the first part of this post.

Overall this simple setup looks promising (it just works...), but there is an issue related to the SSO (Single-Sign On) when the web apps involved are secured on Azure AD (Entra). Both the ASP.NET Core Blazor Server web apps involved (A and B) are registered (in my case) in Azure AD (Entra), specifically with the same App Registration (because are "de facto" part of a single "platform" in business terms). And both the web apps A and B use the Azure AD OpenID Connect authentication mechanism, as officially supported and described by Microsoft. But when the page PageB1 of the second Web App (B) is "opened" in the IFRAME from the Web app A page PageAShell, there is the well known situation related to the X-FRAME OPTIONS SET TO DENY (with the related login error appearing in the IFRAME). At the moment the "first workaround" that I've found (decidedly crude to be honest) is to open (temporarily) in Javascript from the web app A a window (with a simple window.open()) in a popup (so outside the IFRAME) with the url of the PageB1 (or any other "authenticated"/"secure" page exposed by the Web App B...) in Web App B. This approach enable an authentication roundtrip/refresh in the Web App B (flow that is "silent" from the user point of view...), and - later - an hosting in an IFRAME in PageAShell of the PageB1 works well, with the full (good) SSO experience. To support at the best this appoach I have also did an override of the OIDC authentication sequence in Web App B defining an

options.Events = new OpenIdConnectEvents { OnRedirectToIdentityProvider = async ctxt => ... }

to be able to pass to Web App B a couple of previously valued (by Web App A) cookies with the IdTokenHint and LoginHint, so that I can set this hint in the loading of Web App B OnRedirectToIdentityProvider event:

context.ProtocolMessage.IdTokenHint = token; ... context.ProtocolMessage.LoginHint = name; . Passing the hints (by cookies in my case) from the Web App A to the Web App B and set these hints in the OnRedirectToIdentityProvider of the Web App B increases the chances of an effective (AND "SILENT"!) SSO - without any user interaction - on the web app B.

The main problem of this approach is that from the user point of view there is a temporary open (and close) of an authentication popup window, and only later the user can see (in SSO) the PageB1 correctly hosted in the IFRAME of the PageAShell page.

It would be interesting to define a "cleaner" approach to implement the same experience, without the need to implement a "temporary" opening of a popup window aimed only to support the right SSO of web app B in the context already defined by Web App A; this could perhaps be implemented using an ad-hoc implementation based on MSAL.js ssoSilent (in Web App B probably), or based on the invocation by the web app A (or B...) of a login API exposed by Azure AD (calling something like "https://login.microsoftonline.com/common/oauth2/v2.0/authorize..."). So, the solution I experimented with the window.open (popup) approach seems to work, but the user experience is actually "dirtied" by this temporary popup, which would be best avoided (obviously).

What would be interesting could be an indication or guideline by Microsoft on how to implement a pattern of this type (SSO of a Entra secured Blazor server web app hosted in an IFrame in another Entra secured Blazor server web app) using a SSO authentication based on Azure AD (Entra) APIs calls and/or on a specific MSAL.JS ssoSilent adoption, tailored to this specific context. All withouth (hopefully) the need to use a temporary window open/close.

The investigation continues... share your impressions and experiences on the matter... ;-)

PS: Personally I believe that if it will be possible to manage the SSO problem (as described in the terms I summarized above), then the possibility of integrating (also via IFRAME) multiple Blazor Server "microfrontends" in an ASP.NET Core Blazor Server shell will be correctly and completely addressed.

MackinnonBuck commented 1 year ago

We've opened #48032 to track the ability to have a single Blazor app that uses Server and WebAssembly components on the same page. This enables scenarios critical to the full-stack Blazor effort. It does not, however, include the ability to have completely separate Blazor app instances exist on the same page. That remaining work is what this issue tracks.

ghd258 commented 1 year ago

@mkArtakMSFT 请问这个问题到底什么时候能解决

mvromer commented 1 year ago

I managed to get approval from my employer to upload the patches I've made for creating a custom Blazor WebAssembly start script. Late last week I cleaned them up and got them to cleanly apply to latest versions of .NET 6 and .NET 7 tagged in the dotnet/aspnetcore repository (so v6.0.16 and v7.0.5, respectively, at the time of writing).

The patches along with scripts that apply them are on GitHub located at mvromer/Blazor.WebAssembly.SingleSpa. I also setup a GitHub workflow to build a NuGet that includes the patched versions of blazor.webassembly.js for both .NET 6 and .NET 7 as well as corresponding MSBuild props files that ensure the patched script is included in the Blazor WebAssembly build process. The NuGet package is also named Blazor.WebAssembly.SingleSpa. Installing the package in a Blazor WASM project should be sufficient for picking up the patched script. If you do nothing else, by default the patched script should preserve the behavior of the original script.

This script really only exposes just enough hooks for managing the load and lifecycle of a Blazor WASM application. It doesn't do anything related to defining any of the single-spa lifecycle callbacks a micro frontend has to implement. How you do that really depends on your project's requirements.

That being said, the last couple of days I built and deployed a demo application that uses single-spa to integrate both a Blazor-based micro frontend (using the above NuGet) and a Lit (Web Component)-based micro frontend. It's really just a proof of concept as opposed to showing anything necessarily production-grade. That said, it does show how you can use Blazor's router for internal navigation when the Blazor MFE is mounted as well as how to dynamically load CSS styles provided by the Blazor MFE on top of what the app shell provides.

The demo repository is located at mvromer/blazing-lit-mfe-demo. I've also deployed the app shell and the two micro frontends each to their own free instance of Azure Static Web Apps so others can see what this would look like "for reals". The live instance demo app can be accessed at https://gentle-river-0e21e6610.3.azurestaticapps.net/.

Hopefully this can spur some sort of discussion around more officially supporting the micro frontend use case with Blazor. This would be really beneficial in my case where roughly half my team is well-versed in .NET and the other half is well-versed in JavaScript/TypeScript. They're all building independent modules (micro frontends) that will eventually plug into a single portal/web app built on single-spa. As the above shows, it's certainly possible to pull this off for the Blazor-based modules, but it's also not the most ideal way of supporting this in the long run IMO.

ghost commented 1 year ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

mkArtakMSFT commented 1 year ago

Hello everyone. I just wanted to drop a comment to explain the reason for moving this issue to the backlog. This issue wasn't super clear from the beginning as it covered two separate asks:

  1. Support for hosting components with mixed render modes on a single page, where all components are part of the same application.
  2. Support for hosting multiple Blazor applications on the same page. That includes something like having one component provided / hosted by App 1 and another from App 2 to coexist on the same page.

The #1 above is very much aligned with supporting the Full Stack Web Development with Blazor work that we're doing in .NET 8, hence we've prioritized completing that work in .NET 8 as part of https://github.com/dotnet/aspnetcore/issues/48032. There is a little bit of work left to complete it, which is tracked by https://github.com/dotnet/aspnetcore/issues/48396

This particular issue we will use for tracking support for the #2 scenario. That will require more work which we won't be able to tackle in .NET 8 timeframe. Hence, I've moved it to the Backlog so that we revisit it after .NET 8.

SGStino commented 10 months ago

The Microfrontents use-case is a very interesting one. Right now, one would develop the components as Razor Libraries, and reference them together on the hosting application. But any update to the component libraries would need to trigger a rebuild of the host, with multiple different components each going their own pace, that leads to a lot of fragmentation, even if they are totally separate.

If each component library was hosted individually, and loaded by the browser at runtime, this would untangle a lot of issues ... But also create a few more ... What about shared assemblies (libraries)? With one component already upgraded to ASP.NET 7.0.10, another still at 7.0.8 ... the browser should be able to know what assembly to load, similar to how nuget resolves versions.

If they are isolated, they are both loaded in memory per library service, if they are shared, everything needs to be eager-loaded. Because when it is lazy-loaded, the order in which dependencies are loaded can potentially reverse. Loading first 7.0.8 and then requesting to load 7.0.10 won't work?

tma-lawo commented 8 months ago

I hope this also includes the ability to have multiple, independent projects to be able to contribute HTML custom elements to a single page. Blazor HTML custom elements currently have a rather limited use case because there can only be one source.

ghd258 commented 8 months ago

希望能尽早完善解决这个问题

carlornd commented 7 months ago

### An Update (Dec 8, 2023) to my previous post dated May 8, 2023:

In the context of an ASP.NET Core Blazor Server web app able to "host" (in this case by IFRAME) some pages/components exposed by another different ASP.NET Core Blazor Server Web App, recently I did some further tests, with a more "simple" approach than the one described in my previous post.

In summary, I have implemented some pages as ASP.NET Core Blazor Server Web App pages; call one of these pages here "PageB1", as part of an ASP.NET Core Blazor Server Web App "B", and I hosted this page in an IFRAME in another different ASP.NET Core Blazor Server Web App called here "A", specifically in a page that we can call "PageAShell". So, the "PageAShell" of the ASP.NET Core Blazor Server web app "A" can show in a simple IFRAME the PageB1 exposed by the different ASP.NET Core Blazor Server Web App B (hosted in a different server and process). With some configuration I'm also able to send events from the page PageAShell to the page PageB1, and vice versa. I do not have used in this implementation special configuration as described before in my previous post.

Overall, this simple setup looks promising (it just works...), but there is an issue related to the SSO (Single-Sign On) when the web apps involved are secured on Azure AD (Entra). Both the ASP.NET Core Blazor Server web apps involved (A and B) are registered (in my case) in Azure AD (Entra), specifically with the same App Registration (because are "de facto" part of a single "platform" in business terms). And both the web apps A and B use the Azure AD OpenID Connect authentication mechanism, as officially supported and described by Microsoft. But when the page PageB1 of the second Web App (B) is "opened" in the IFRAME from the Web app A page PageAShell, there is the well known situation related to the X-FRAME OPTIONS SET TO DENY (with the related login error appearing in the IFRAME). At the moment the "first workaround" that I've found (decidedly crude to be honest) is to open (temporarily) in Javascript from the web app A a window (with a simple window.open()) in a popup (so outside the IFRAME) with the url of the PageB1 (or any other "authenticated"/"secure" page exposed by the Web App B...) in Web App B. This approach enable an authentication roundtrip/refresh in the Web App B (flow that is "silent" from the user point of view...), and - later - an hosting in an IFRAME in PageAShell of the PageB1 works well, with the full (good) SSO experience. To support at the best this appoach I have also did an override of the OIDC authentication sequence in Web App B defining an

options.Events = new OpenIdConnectEvents { OnRedirectToIdentityProvider = async ctxt => ... }

to be able to pass to Web App B a couple of previously valued (by Web App A) cookies with the IdTokenHint and LoginHint, so that I can set this hint in the loading of Web App B OnRedirectToIdentityProvider event:

context.ProtocolMessage.IdTokenHint = token; ... context.ProtocolMessage.LoginHint = name; . Passing the hints (by cookies in my case) from the Web App A to the Web App B and set these hints in the OnRedirectToIdentityProvider of the Web App B increases the chances of an effective (AND "SILENT"!) SSO - without any user interaction - on the web app B.

The main problem of this approach is that from the user point of view there is a temporary open (and close) of an authentication popup window, and only later the user can see (in SSO) the PageB1 correctly hosted in the IFRAME of the PageAShell page.

It would be interesting to define a "cleaner" approach to implement the same experience, without the need to implement a "temporary" opening of a popup window aimed only to support the right SSO of web app B in the context already defined by Web App A; this could perhaps be implemented using an ad-hoc implementation based on MSAL.js ssoSilent (in Web App B probably), or based on the invocation by the web app A (or B...) of a login API exposed by Azure AD (calling something like "https://login.microsoftonline.com/common/oauth2/v2.0/authorize..."). So, the solution I experimented with the window.open (popup) approach seems to work, but the user experience is actually "dirtied" by this temporary popup, which would be best avoided (obviously).

What would be interesting could be an indication or guideline by Microsoft on how to implement a pattern of this type (SSO of a Entra secured Blazor server web app hosted in an IFrame in another Entra secured Blazor server web app) using a SSO authentication based on Azure AD (Entra) APIs calls and/or on a specific MSAL.JS ssoSilent adoption, tailored to this specific context. All without (hopefully) the need to use a temporary window open/close.

The investigation continues... share your impressions and experiences on the matter... ;-)

PS: Personally I believe that if it will be possible to manage - in a "clean" way ... - the SSO problem (as described in the terms I summarized above), then the possibility of integrating (even simply via IFRAME) multiple Blazor Server "microfrontends" in an ASP.NET Core Blazor Server shell will be correctly and completely addressed.

ghost commented 6 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.

mvromer commented 5 months ago

For what it's worth, I've updated my Blazor.WebAssembly.SingleSpa NuGet package to provide experimental support for Blazor WebAssembly micro-frontends targeting .NET 8 and only .NET 8. Previously I had some .NET 6 and .NET 7 support, but .NET 8 brought a number of significant changes.

By and large the changes in .NET 8 were a net positive -- they simplified a lot of the things my package has to do in order to ensure a Blazor WASM app can be mounted/unmounted via single-spa. It did move some functionality to the .NET browser runtime, such as the code the selects the name of the in-browser .NET resource cache or the code that actually dynamic imports the hot reload script. That led to some more ... creative ... solutions, but nothing that really breaks the intent or semantics of the original framework code.

Since cleanly and safely mounting/unmounting a Blazor WASM app from the DOM and cleaning up the global state left by Blazor on the window object requires a bit of a fine dance, I also put together an experimental single-spa framework helper for Blazor WASM micro-frontends called blazor-wasm-single-spa. The whole point of this package is to make it easy to define the bootstrap, mount, and unmount lifecycle hooks that single-spa expects from each micro-frontend.

Getting these right is especially important if another independent Blazor WASM micro-frontend might subsequently load itself (along with its own version of the .NET runtime) and mount itself to the DOM. If you don't properly dispose and cleanup the first micro-frontend, it's easy to get into a situation where one micro-frontend is calling into the runtime of a different micro-frontend, with all the bad stuff that comes from that.

The following is a bare minimum example of what it takes to define the single-spa integration using this framework helper. This is pulled from my Blazing Lit project which is deployed live here:

import singleSpaBlazor from 'blazor-wasm-single-spa';

// Build the asset base URL from this JavaScript module's URL. The asset base URL must have a
// trailing slash for Blazor to apply it correctly.
const iLastSlash = import.meta.url.lastIndexOf('/');
const assetBaseUrl = import.meta.url.substring(0, iLastSlash + 1);

export const { bootstrap, mount, unmount } = singleSpaBlazor({
  appTagName: 'mfe-catalog-app',
  stylePaths: ['CatalogApp.styles.css'],
  assetBaseUrl,
});

I've tried to keep things relatively simple with a lot of the magic tucked away inside the framework helper. To also demonstrate the ability to load multiple, independent Blazor micro-frontends and be able to cleanly switch between them, I added a second Blazor WASM micro-frontend to my Blazing Lit demo. This second one also incorporates MudBlazor and uses another (experimental) extension package to blazor-wasm-single-spa. This basically ensures the global state MudBlazor puts on the window object is cleared/restored when the micro-frontend is unmounted from and then later re-mounted to the DOM.

It's worth noting that while all of this is possible with Blazor WebAssembly, developing an application using a micro-frontend architecture definitely requires some thought also with regards to deployment and tooling. When developing, running, and testing a micro-frontend locally, you ideally shouldn't be forced to also locally run things like the app shell or any independent backend services needed just to have a fully running app. Otherwise, developer experience will be just super painful.

I'm using the above integration packages for some projects at work, and one of the things I developed to make locally running and testing micro-frontends easy is a local dev proxy based on YARP. When I run it, by default it simply proxies all requests to a dev instance of our app running remotely. However, if I specify a frontend override, then requests for that particular micro-frontend get routed to the localhost endpoint I specified. In practice, this means I run my dev proxy in one terminal in the background and then dotnet run or dotnet watch my micro-frontend. This gives me the benefit of being able to load and run my micro-frontend updates within the context of the entire application without having to deploy my updates first remotely.

The proxy also does a lot of magic to ensure things like WebAssembly debugging and hot reload work properly. There are lot of things that need to line up just right for these things to work in a micro-frontend context, some of which get complicated because the hot reload and debug endpoints are served up through "special" routes that get installed on your dev server via things like startup filters and startup hooks, a lot of which are described here. More often than not, the framework will reference these endpoints by absolute URL paths, which makes actually routing the requests to the micro-frontend's dev server tricky.

In the end I was able to get hot reload and client-side debugging working in a Blazor WASM micro-frontend, so I know it can be done. It would nicer though if things like the .NET SDK tooling (like dotnet-watch and the browser refresh scripts), the .NET browser runtime, and Blazor itself supported this micro-frontend scenario in a more first-class manner to minimize the software acrobatics that otherwise need to be done here.

However, until such a time when/if that occurs, if I'm able to find time to put together a minimal dev proxy that shows to support things like locally running a Blazor WASM micro-frontend as well as enabling debugging and hot reload, I'll be sure to post the link here. I think without something to fill in those pieces, it would be truly hard to effectively develop and maintain any micro-frontend solution.