Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/frontend/config/sidebar/integrations.topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,7 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = {
{ label: 'Launch profiles', slug: 'integrations/dotnet/launch-profiles' },
{ label: '.NET tool resources', slug: 'integrations/dotnet/dotnet-tool-resources' },
{ label: '.NET MAUI', slug: 'integrations/dotnet/maui' },
{ label: 'Blazor hosting', slug: 'integrations/dotnet/blazor-hosting' },
{ label: 'WPF and Windows Forms', slug: 'integrations/frameworks/wpf-winforms' },
{ label: 'Orleans', slug: 'integrations/frameworks/orleans' },
],
Expand Down
99 changes: 98 additions & 1 deletion src/frontend/src/content/docs/app-host/withdockerfile.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ These two methods serve different purposes:
- **`AddDockerfile`**: Creates a new container resource from an existing Dockerfile. Use this when you want to add a custom containerized service to your app model.
- **`WithDockerfile`**: Customizes an existing container resource (like a database or cache) to use a different Dockerfile. Use this when you want to modify the default container image for an Aspire component.

Both methods expect an existing Dockerfile in the specified context path—neither method creates a Dockerfile for you. To generate a Dockerfile from AppHost code instead, use the [Dockerfile builder APIs](#generate-a-dockerfile-programmatically).
Both methods expect an existing Dockerfile in the specified context path—neither method creates a Dockerfile for you. To generate a Dockerfile from AppHost code instead, use the [Dockerfile builder APIs](#generate-a-dockerfile-programmatically) or the [Dockerfile factory APIs](#generate-a-dockerfile-with-a-factory-function).

## When to use AddDockerfile vs WithDockerfile

Expand Down Expand Up @@ -247,6 +247,103 @@ await builder.build().run();
</TabItem>
</Tabs>

## Generate a Dockerfile with a factory function

Use `AddDockerfileFactory` or `WithDockerfileFactory` when you need to generate a Dockerfile as a string from AppHost code. Unlike the [Dockerfile builder APIs](#generate-a-dockerfile-programmatically) that use a fluent API to compose Dockerfile instructions, the factory APIs let you return Dockerfile content directly as a string — useful when you already have string-based Dockerfile generation logic or want to construct content conditionally.

The factory callback receives a `DockerfileFactoryContext` parameter that provides access to the resource and DI services when needed.

`AddDockerfileFactory` creates a new container resource and configures the generated Dockerfile in one step:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var container = builder.AddDockerfileFactory("myapp", "../myapp", async context =>
{
// Return Dockerfile content as a string.
return """
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
EXPOSE 3000
CMD ["node", "server.js"]
""";
});

builder.Build().Run();
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';
import type { DockerfileFactoryContext } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const container = await builder.addDockerfileFactory(
"myapp",
"../myapp",
async (factoryContext: DockerfileFactoryContext) => {
// Return Dockerfile content as a string.
return `FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
EXPOSE 3000
CMD ["node", "server.js"]`;
}
);

await builder.build().run();
```
</TabItem>
</Tabs>

`WithDockerfileFactory` applies a factory-generated Dockerfile to an existing container resource:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

builder.AddContainer("myapp", "placeholder")
.WithDockerfileFactory("../myapp", async context =>
{
return """
FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
EXPOSE 80
""";
});

builder.Build().Run();
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.mts"
import { createBuilder } from './.aspire/modules/aspire.mjs';
import type { DockerfileFactoryContext } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const container = await builder.addContainer("myapp", "placeholder");
await container.withDockerfileFactory(
"../myapp",
async (factoryContext: DockerfileFactoryContext) => {
return `FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
EXPOSE 80`;
}
);

await builder.build().run();
```
</TabItem>
</Tabs>

## Pass build arguments

The `WithBuildArg` method can be used to pass arguments into the container image build.
Expand Down
176 changes: 176 additions & 0 deletions src/frontend/src/content/docs/integrations/dotnet/blazor-hosting.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
---
title: Blazor hosting integration
description: Learn how to orchestrate Blazor WebAssembly and Blazor Gateway resources in your Aspire app model using AddBlazorGateway, WithBlazorClientApp, and AddBlazorWasmProject.
---

import { Aside, Tabs, TabItem } from '@astrojs/starlight/components';
import LearnMore from '@components/LearnMore.astro';

The `Aspire.Hosting.Blazor` package provides extension methods for adding Blazor WebAssembly apps and a Blazor Gateway to your Aspire app model. These APIs are available in both C# and polyglot AppHosts (TypeScript, Go, Java, Python).

## Install the package

Add the `Aspire.Hosting.Blazor` package to your AppHost project:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```xml title="AppHost.csproj"
<PackageReference Include="Aspire.Hosting.Blazor" Version="13.4.0" />
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```bash title="Add the integration"
aspire add Aspire.Hosting.Blazor
```

Or add it directly to `aspire.config.json`:

```json title="aspire.config.json" ins={3}
{
"packages": {
"Aspire.Hosting.Blazor": "13.4.0"
}
}
```
</TabItem>
</Tabs>

## Add a Blazor WebAssembly project

Use `AddBlazorWasmProject` to add a Blazor WebAssembly app to the app model. The method references the `.csproj` for the client project:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var api = builder.AddProject<Projects.MyApi>("api");

var blazorApp = builder.AddBlazorWasmApp("app", "../MyBlazorApp/MyBlazorApp.csproj");
blazorApp.WithReference(api);

builder.Build().Run();
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const api = await builder.addProject('api', '../MyApi');

const blazorApp = await builder.addBlazorWasmProject('app', '../MyBlazorApp/MyBlazorApp.csproj');
await blazorApp.withReference(await api.getEndpoint('http'));

await builder.build().run();
```
</TabItem>
</Tabs>

## Add a Blazor Gateway

The Blazor Gateway (`AddBlazorGateway`) is a reverse proxy shipped with `Aspire.Hosting.Blazor`. It routes requests between a Blazor WebAssembly frontend and the backing APIs, enabling a unified entry point for the application.

Use `WithBlazorClientApp` to associate a WASM app with the gateway:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var weatherApi = builder.AddProject<Projects.WeatherApi>("weatherapi");

var blazorApp = builder.AddBlazorWasmApp("app", "../MyBlazorApp/MyBlazorApp.csproj");
blazorApp.WithReference(weatherApi);

var gateway = builder.AddBlazorGateway("gateway")
.WithExternalHttpEndpoints();

gateway.WithBlazorClientApp(blazorApp);

builder.Build().Run();
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const weatherApi = await builder.addProject('weatherapi', '../WeatherApi');

const blazorApp = await builder.addBlazorWasmProject('app', '../MyBlazorApp/MyBlazorApp.csproj');
await blazorApp.withReference(await weatherApi.getEndpoint('http'));

const gateway = await builder.addBlazorGateway('gateway');
await gateway.withExternalHttpEndpoints();
await gateway.withBlazorClientApp(blazorApp);

await builder.build().run();
```
</TabItem>
</Tabs>

:::note
`WithExternalHttpEndpoints()` marks the gateway's HTTP endpoint as externally accessible. This is required when the gateway is the entry point for browser traffic.
:::

## Full example

The following example wires together a Blazor WebAssembly frontend, a Weather API backend, and a Blazor Gateway:

<Tabs syncKey='aspire-lang'>
<TabItem id='csharp' label='C#'>
```csharp title="AppHost.cs"
var builder = DistributedApplication.CreateBuilder(args);

var weatherApi = builder.AddProject<Projects.WeatherApi>("weatherapi")
.WithHttpEndpoint(name: "http");

var timeApi = builder.AddProject<Projects.TimeApi>("timeapi")
.WithHttpsEndpoint(name: "api");

var blazorApp = builder.AddBlazorWasmApp("app", "../Client/Client.csproj");
blazorApp.WithReference(weatherApi.GetEndpoint("http"));
blazorApp.WithReference(timeApi.GetEndpoint("api"));

var gateway = builder.AddBlazorGateway("gateway")
.WithExternalHttpEndpoints();

gateway.WithBlazorClientApp(blazorApp);

builder.Build().Run();
```
</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.mts" twoslash
import { createBuilder } from './.aspire/modules/aspire.mjs';

const builder = await createBuilder();

const weatherApi = await builder.addProject('weatherapi', '../WeatherApi', {
launchProfileOrOptions: 'http',
});

const timeApi = await builder.addProject('timeapi', '../TimeApi', {
launchProfileOrOptions: 'https',
});
await timeApi.withHttpsEndpoint({ name: 'api' });

const blazorApp = await builder.addBlazorWasmProject('app', '../Client/Client.csproj');
await blazorApp.withReference(await weatherApi.getEndpoint('http'));
await blazorApp.withReference(await timeApi.getEndpoint('api'));

const gateway = await builder.addBlazorGateway('gateway');
await gateway.withExternalHttpEndpoints();
await gateway.withBlazorClientApp(blazorApp);

await builder.build().run();
```
</TabItem>
</Tabs>

<LearnMore>
For more on how polyglot AppHosts work and how to write integrations that export to TypeScript, see [Multi-language integrations](/extensibility/multi-language-integration-authoring/).
</LearnMore>
Loading