Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
javiercn opened this issue Nov 5, 2021 · 43 comments
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one Needs: Design This issue requires design work before implementating. needs-prototype Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without

Comments

@javiercn
Copy link
Member

javiercn commented Nov 5, 2021

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 #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 #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: #38128 (comment)

@javiercn javiercn added jcn-p0 area-blazor Includes: Blazor, Razor Components feature-blazor-server feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-blazor-deployment Issues related to deploying Blazor labels Nov 5, 2021
@javiercn javiercn added this to the .NET 7 Planning milestone Nov 5, 2021
@mkArtakMSFT mkArtakMSFT added Priority:1 Work that is critical for the release, but we could probably ship without triaged labels Nov 5, 2021
@mkArtakMSFT mkArtakMSFT added the Needs: Design This issue requires design work before implementating. label Jan 19, 2022
@jmezach
Copy link

jmezach commented Feb 9, 2022

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
Copy link

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
Copy link

nssidhu commented Mar 15, 2022

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
Copy link

nkosi23 commented Mar 15, 2022

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
Copy link

ghd258 commented Apr 22, 2022

能在支持多个Area吗

@ghost
Copy link

ghost commented May 24, 2022

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
Copy link
Contributor

mrlife commented May 26, 2022

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

@javiercn
Copy link
Member Author

@mrlife no, this is different.

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

@mrlife
Copy link
Contributor

mrlife commented May 26, 2022

@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
Copy link
Member Author

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

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

@wangyjx
Copy link

wangyjx commented Jun 14, 2022

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
Copy link
Member

mattleibow commented Jun 17, 2022

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.

@mkArtakMSFT
Copy link
Member

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 #48032. There is a little bit of work left to complete it, which is tracked by #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.

@SteveSandersonMS SteveSandersonMS removed feature-full-stack-web-ui Full stack web UI with Blazor feature-blazor-wasm This issue is related to and / or impacts Blazor WebAssembly feature-blazor-server Priority:1 Work that is critical for the release, but we could probably ship without feature-blazor-deployment Issues related to deploying Blazor webui-P1-JC labels Jun 13, 2023
@SGStino
Copy link

SGStino commented Aug 22, 2023

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
Copy link

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
Copy link

ghd258 commented Nov 17, 2023

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

@carlornd
Copy link

carlornd commented Dec 8, 2023

### 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
Copy link

ghost commented Dec 19, 2023

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.

@mkArtakMSFT mkArtakMSFT added cost: L Will take from 5 - 10 days to complete and removed cost: L Will take from 5 - 10 days to complete labels Dec 19, 2023
@mkArtakMSFT mkArtakMSFT added the Priority:1 Work that is critical for the release, but we could probably ship without label Jan 17, 2024
@mvromer
Copy link

mvromer commented Feb 14, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components enhancement This issue represents an ask for new feature or an enhancement to an existing one Needs: Design This issue requires design work before implementating. needs-prototype Pillar: Complete Blazor Web Priority:1 Work that is critical for the release, but we could probably ship without
Projects
None yet
Development

No branches or pull requests