-
Notifications
You must be signed in to change notification settings - Fork 10.1k
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
Comments
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. |
hello, as one of way, after download wasm binaries swap server to wasm and second for state machine between server and client using this case |
Current work around. I had couple of scenarios
|
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). |
能在支持多个Area吗 |
Thanks for contacting us. We're moving this issue to the |
Will this also allow combining Blazor Server and razor pages applications? |
@mrlife no, this is different. You can already render multiple Blazor Server components in a Razor Pages application. |
@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. |
That's not something we are planning to invest on. |
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). |
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. |
The Microfrontents use-case is a very interesting one. If each component library was hosted individually, and loaded by the browser at runtime, this would untangle a lot of issues ... If they are isolated, they are both loaded in memory per library service, if they are shared, everything needs to be eager-loaded. |
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. |
希望能尽早完善解决这个问题 |
### 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 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; 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. |
Thanks for contacting us. We're moving this issue to the |
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 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. |
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)
Source: #38128 (comment)
The text was updated successfully, but these errors were encountered: