Skip to content

Commit

Permalink
wip hosting models
Browse files Browse the repository at this point in the history
  • Loading branch information
Tarmil committed Nov 20, 2024
1 parent 04fecda commit f7945a9
Show file tree
Hide file tree
Showing 5 changed files with 316 additions and 232 deletions.
2 changes: 1 addition & 1 deletion ref/bolero
Submodule bolero updated 50 files
+21 −21 .build/Build.fs
+1 −1 .config/dotnet-tools.json
+17 −6 .github/workflows/build.yml
+1 −1 .github/workflows/publish.yml
+13 −0 CHANGELOG.md
+1 −1 build.ps1
+1 −1 global.json
+24 −18 paket.dependencies
+336 −366 paket.lock
+0 −1 src/Bolero.Build/Bolero.Build.targets
+2 −1 src/Bolero.Html/Bolero.Html.fsproj
+1 −1 src/Bolero.Html/Builders.fs
+1 −0 src/Bolero.Server/Bolero.Server.fsproj
+24 −1 src/Bolero.Server/Components.fs
+16 −2 src/Bolero.Server/Extensions.fs
+20 −4 src/Bolero.Server/HostConfig.fs
+19 −0 src/Bolero.Server/Html.fs
+2 −2 src/Bolero.Server/Remoting/Endpoints.fs
+7 −4 src/Bolero.Server/paket.template
+2 −1 src/Bolero.Templating.Provider/Bolero.Templating.Provider.fsproj
+35 −35 src/Bolero.Templating.Provider/CodeGen.fs
+8 −8 src/Bolero.Templating.Provider/ConvertExpr.fs
+17 −17 src/Bolero.Templating.Provider/Parsing.fs
+1 −1 src/Bolero.Templating.Provider/Path.fs
+7 −5 src/Bolero.Templating.Provider/Provider.fs
+1 −9 src/Bolero/Attr.fs
+5 −3 src/Bolero/Bolero.fsproj
+26 −17 src/Bolero/Components.fs
+10 −5 src/Bolero/Node.fs
+20 −0 src/Bolero/NodeTypes.fs
+40 −0 src/Bolero/Program.fs
+10 −1 src/Bolero/ProgramRun.fs
+1 −1 src/Bolero/Remoting.fs
+42 −0 src/Bolero/RenderMode.fs
+5 −10 src/Bolero/Router.fs
+1 −0 src/Bolero/paket.references
+12 −6 src/Bolero/paket.template
+1 −0 src/tpdummy/Microsoft.AspNetCore.Components/Library.fs
+1 −1 tests/Client/Main.fs
+16 −7 tests/Remoting.Client/Main.fs
+35 −11 tests/Remoting.Server/Startup.fs
+2 −1 tests/Remoting.Server/paket.references
+2 −1 tests/Server/Startup.fs
+1 −1 tests/Unit.Client/Html.fs
+14 −0 tests/Unit.Client/Templating.fs
+0 −1 tests/Unit/Fixture.fs
+7 −8 tests/Unit/Tests/Routing.fs
+11 −1 tests/Unit/Tests/Templating.fs
+3 −3 tests/Unit/Utility.fs
+2 −2 version.json
Empty file.
295 changes: 295 additions & 0 deletions src/Website/docs/Hosting-pre-blazor-8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
---
title: Hosting models (legacy)
subtitle: WebAssembly or server-side
---

> Note: Blazor 8 and Bolero 0.24 introduced new hosting models, [documented here](Hosting).
> This page describes the pre-Blazor 8 hosting models, which are still supported but considered legacy.
Bolero applications can run in two different modes: WebAssembly (also referred to as client-side Bolero) and server-side.

In WebAssembly mode, dynamic content runs on the client side using [WebAssembly](https://webassembly.org/).
It is still a full .NET implementation, but running directly in the user's browser.

In server-side mode, dynamic content runs on the server side and is shared with the user's browser via a [SignalR](https://dotnet.microsoft.com/apps/aspnet/signalr) connection.

You can learn more about these hosting models in [the official Blazor documentation](https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-models?view=aspnetcore-5.0).

## Hosting models

### Plain WebAssembly mode

To create a plain WebAssembly application, use the [dotnet project template](index#creating-a-project) with the following arguments:

```sh
dotnet new bolero-app --render=WebAssembly
```

Or with the Bolero project template older than v0.24:

```sh
dotnet new bolero-app --server=false
```

In plain WebAssembly mode, the static content of the application is a simple `.html` file, and the dynamic Bolero content is rendered in a tag of this file.
This file must contain the following:

```html
<script src="_framework/blazor.webassembly.js"></script>
```

as well as a container element for the dynamic content, for example `<div id="main"></div>`.
This container is selected in `Startup.fs`:

```fsharp
builder.RootComponents.Add<Main.MyApp>("#main")
```

Note that prerendering is not possible in plain WebAssembly mode.

### Hosted WebAssembly mode

To create a hosted WebAssembly application, use the [dotnet project template](index#creating-a-project) with the following arguments:

```sh
dotnet new bolero-app --render=LegacyWebAssembly
```

Or with the Bolero project template older than v0.24:

```sh
dotnet new bolero-app
```

In hosted WebAssembly mode, the static content of the application is rendered on the ASP.NET Core server side.
[See below](#static-content-generation) for the different ways it can be generated.

Like in plain WebAssembly mode, the static content must include a container element matching the selector used in `Startup.fs`.

Unlike in plain WebAssembly mode, prerendering is possible based on the [configuration](#configuring-hosted-modes).

### Server-side mode

To create a server-side Bolero app, use the [dotnet project template](index#creating-a-project) with the following arguments:

```sh
dotnet new bolero-app --render=LegacyServer
```

Or with the Bolero project template older than v0.24:

```sh
dotnet new bolero-app
```

and in `src/AppName.Server/Startup.fs`, replace the line:

```fsharp
.AddBoleroHost()
```

with:

<!-- TODO: .AddServerSideBlazor()? if we remove it from template -->
```fsharp
.AddBoleroHost(server = true)
```

The static content is rendered exactly like hosted WebAssembly mode, [see below](#static-content-generation).

## Configuring hosted modes

Hosted WebAssembly and server-side are known collectively as hosted modes, and they share a lot of configuration and features.

### Configuration

Hosted modes are configured by using `AddBoleroHost` in the (server-side) dependency injection.

```fsharp
type Startup() =
member this.ConfigureServices(services: IServiceCollection) =
services.AddBoleroHost() |> ignore
// ... other dependencies ...
```

This method takes several optional arguments:

* `server: bool` determines whether this is a hosted WebAssembly (`server = false`) or server-side application (`server = true`).

The default is `false`.

Note: when setting to `true`, make sure that `services.AddServerSideBlazor() |> ignore` is also called.

* `prerendered: bool` determines whether the dynamic Bolero content is prerendered.

If true, then the content is rendered on the server side (even in WebAssembly mode) and the resulting HTML is included in the static content served by ASP.NET Core.
This prevents the "pop-in" effect where the user first sees an empty container, and once the page is ready (ie WebAssembly has started or SignalR has connected), the content suddenly appears.

The default is `true`.

* `devToggle: bool` determines whether the user can choose between hosted WebAssembly and server-side mode by passing `?server=false` or `?server=true`, respectively, in the URL.

This feature is intended for development only.
For example, when writing a hosted WebAssembly application, it can be convenient to temporarily switch to server-side mode for debugging.

This setting is only active when the ASP.NET Core application runs in the `Development` environment; in any other environment, it has no effect.

The default is `true`.

### Static content generation

There are multiple ways available to generate the static HTML content that contains a hosted Bolero app.

#### Bolero HTML

> Introduced in v0.17.
The recommended way to generate static content for Bolero is to use the same [HTML functions](HTML) that are used by dynamic content.
It is the default method used by the dotnet project template, or can be explicitly used with `--hostpage=bolero`.

A few additional functions are available in the module `Bolero.Server.Html`:

* `doctypeHtml` creates a `<html>` tag preceded by a standard doctype declaration.
Like other element functions, it takes as arguments a list of attributes and a list of child elements.

* `comp<T>` inserts a component of type `T` as dynamic content.

This may include prerendered content, depending on the `prerendered` value passed to `AddBoleroHost`.

In WebAssembly mode, this must be inserted inside a container element matched by the selector used in `Startup.fs`.

In WebAssembly mode without prerendering, this actually doesn't insert any content.
Indeed, in this mode, all that is needed is the container element and the `boleroScript` (see below).
So if you know that you will always run your app in WebAssembly mode without prerendering, then you can simply not insert the `comp`.
This way, you won't need a reference from the server project to the client project.

Note that other functions in the `comp` family, such a `ecomp` and `lazyComp`, will not work correctly here.

* `boleroScript` inserts a `<script>` tag pointing to the JavaScript file that starts the application.
It knows which script to insert (either `blazor.webassembly.js` or `blazor.server.js`) based on the `server` and `devToggle` values passed to `AddBoleroHost`.

Here is an example page using all of the above:

```fsharp
open Bolero.Html
open Bolero.Server.Html
let myPage = doctypeHtml {
head {
title { "Hello, world!" }
}
body {
div { "This is the body of the page" }
div {
attr.id "main"
comp<Client.Main.MyApp>
}
boleroScript
}
}
```

Such a page can be served as follows in the server-side Startup:

```fsharp
type Startup() =
member this.Configure(app: IApplicationBuilder) =
app.UseStaticFiles()
.UseRouting()
.UseBlazorFrameworkFiles() // Necessary in hosted WebAssembly mode
.UseEndpoints(fun endpoints ->
endpoints.MapBlazorHub() |> ignore // Necessary in server-side mode
endpoints.MapFallbackToBolero(myPage) |> ignore
)
|> ignore
```

Alternatively, it can be used as the content of an ASP.NET Core MVC controller:

```fsharp
open Microsoft.AspNetCore.Mvc
open Bolero.Server
type MyController() =
inherit Controller()
member this.Index() =
this.BoleroPage(myPage)
```

#### Razor

Another option is to use a Razor page.
This is particularly convenient when integrating the application in an ASP.NET Core application written in C#.
It can also be used with an F# application with [Razor runtime compilation](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-compilation?view=aspnetcore-5.0&tabs=visual-studio#enable-runtime-compilation-in-an-existing-project); this was the mode used by the dotnet template until Bolero 0.16.
Starting with 0.17, it can be used with `--hostpage=razor`.

Bolero provides the type `IBoleroHostConfig` and a few extension methods on Razor's `Html` to help render components and Blazor JavaScript tags using the configuration from `AddBoleroHost`:

* `RenderComponentAsync<T>(IBoleroHostConfig)` inserts a component of type `T`.

This may include prerendered content, depending on the `prerendered` value passed to `AddBoleroHost`.

In WebAssembly mode, this must be inserted inside a container element matched by the selector used in `Startup.fs`.

* `RenderBoleroScript(IBoleroHostConfig)` inserts a `<script>` tag pointing to the JavaScript file that starts the application.
It knows which script to insert based on the `server` and `devToggle` values passed to `AddBoleroHost`.

Here is an example page using all of the above:

```razor
@page "/"
@namespace MyApp.Server
@using Bolero.Server
@inject IBoleroHostConfig BoleroHostConfig
<!DOCTYPE html>
<html>
<head>
<title>Hello, world!</title>
</head>
<body>
<div>This is the body of the page</div>
<div id="main">
@(await Html.RenderComponentAsync<MyApp.Client.Main.MyApp>(BoleroHostConfig))
</div>
@Html.RenderBoleroScript(BoleroHostConfig)
</body>
</html>
```

Such a page saved as `Pages/_Host.cshtml` can be served as follows in the server-side Startup:

```fsharp
type Startup() =
member this.Configure(app: IApplicationBuilder) =
app.UseStaticFiles()
.UseRouting()
.UseBlazorFrameworkFiles() // Necessary in hosted WebAssembly mode
.UseEndpoints(fun endpoints ->
endpoints.MapBlazorHub() |> ignore // Necessary in server-side mode
endpoints.MapFallbackToPage("/_Host") |> ignore
)
|> ignore
```

Or in a C# application:

```csharp
public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles()
.UseRouting()
.UseBlazorFrameworkFiles() // Necessary in hosted WebAssembly mode
.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub(); // Necessary in server-side mode
endpoints.MapFallbackToPage("/_Host");
});
}
}
```
Loading

0 comments on commit f7945a9

Please sign in to comment.