From e758335cf6621648f4806069f6ef70d5fd867f74 Mon Sep 17 00:00:00 2001 From: fouad Date: Tue, 9 Jun 2026 19:30:13 +0100 Subject: [PATCH] Adds a Blazor WebAssembly (.NET 10) + Motoko Hello World template. --- blazor/hello_world/.gitignore | 20 +++ blazor/hello_world/README.md | 165 ++++++++++++++++++ blazor/hello_world/backend/backend.did | 5 + blazor/hello_world/backend/canister.yaml | 7 + blazor/hello_world/backend/mops.toml | 2 + blazor/hello_world/backend/src/main.mo | 18 ++ .../frontend/BlazorFrontend/App.razor | 12 ++ .../BlazorFrontend/BlazorFrontend.csproj | 25 +++ .../BlazorFrontend/Layout/MainLayout.razor | 5 + .../frontend/BlazorFrontend/Pages/Home.razor | 97 ++++++++++ .../frontend/BlazorFrontend/Program.cs | 9 + .../Services/IcpAgentService.cs | 26 +++ .../frontend/BlazorFrontend/_Imports.razor | 7 + .../frontend/BlazorFrontend/package.json | 16 ++ .../frontend/BlazorFrontend/src/icpAgent.ts | 59 +++++++ .../frontend/BlazorFrontend/tsconfig.json | 14 ++ .../frontend/BlazorFrontend/webpack.config.js | 21 +++ .../frontend/BlazorFrontend/wwwroot/app.css | 94 ++++++++++ .../wwwroot/icpAgent.js.LICENSE.txt | 12 ++ .../BlazorFrontend/wwwroot/index.html | 21 +++ blazor/hello_world/frontend/canister.yaml | 9 + blazor/hello_world/icp.yaml | 4 + 22 files changed, 648 insertions(+) create mode 100644 blazor/hello_world/.gitignore create mode 100644 blazor/hello_world/README.md create mode 100644 blazor/hello_world/backend/backend.did create mode 100644 blazor/hello_world/backend/canister.yaml create mode 100644 blazor/hello_world/backend/mops.toml create mode 100644 blazor/hello_world/backend/src/main.mo create mode 100644 blazor/hello_world/frontend/BlazorFrontend/App.razor create mode 100644 blazor/hello_world/frontend/BlazorFrontend/BlazorFrontend.csproj create mode 100644 blazor/hello_world/frontend/BlazorFrontend/Layout/MainLayout.razor create mode 100644 blazor/hello_world/frontend/BlazorFrontend/Pages/Home.razor create mode 100644 blazor/hello_world/frontend/BlazorFrontend/Program.cs create mode 100644 blazor/hello_world/frontend/BlazorFrontend/Services/IcpAgentService.cs create mode 100644 blazor/hello_world/frontend/BlazorFrontend/_Imports.razor create mode 100644 blazor/hello_world/frontend/BlazorFrontend/package.json create mode 100644 blazor/hello_world/frontend/BlazorFrontend/src/icpAgent.ts create mode 100644 blazor/hello_world/frontend/BlazorFrontend/tsconfig.json create mode 100644 blazor/hello_world/frontend/BlazorFrontend/webpack.config.js create mode 100644 blazor/hello_world/frontend/BlazorFrontend/wwwroot/app.css create mode 100644 blazor/hello_world/frontend/BlazorFrontend/wwwroot/icpAgent.js.LICENSE.txt create mode 100644 blazor/hello_world/frontend/BlazorFrontend/wwwroot/index.html create mode 100644 blazor/hello_world/frontend/canister.yaml create mode 100644 blazor/hello_world/icp.yaml diff --git a/blazor/hello_world/.gitignore b/blazor/hello_world/.gitignore new file mode 100644 index 000000000..88c9e4713 --- /dev/null +++ b/blazor/hello_world/.gitignore @@ -0,0 +1,20 @@ +# ICP - cache only, keep .icp/data/ for canister IDs +.icp/cache/ + +# .NET build output +bin/ +obj/ + +# Node +node_modules/ + +# webpack generated output (regenerated on build) +frontend/BlazorFrontend/wwwroot/icpAgent.js + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log +npm-debug.log* \ No newline at end of file diff --git a/blazor/hello_world/README.md b/blazor/hello_world/README.md new file mode 100644 index 000000000..d25d7b234 --- /dev/null +++ b/blazor/hello_world/README.md @@ -0,0 +1,165 @@ +# ICP Blazor Hello World + +A hello world template combining **Blazor WebAssembly (.NET 10)** on the frontend and **Motoko** on the backend, deployed fully on-chain on the **Internet Computer (ICP)**. + +> The first working template for Blazor WASM + icp-cli + Motoko. + +## Stack + +| Layer | Technology | +|----------|-------------------------------------| +| Frontend | Blazor WebAssembly (.NET 10) | +| Backend | Motoko canister | +| Bridge | @dfinity/agent (webpack bundle) | +| Platform | Internet Computer (ICP) | +| CLI | icp-cli | + +## Project Structure + +``` +icp-blazor-hello/ +├── icp.yaml # icp-cli project config +├── .gitignore +├── backend/ +│ ├── canister.yaml # backend canister config +│ ├── backend.did # Candid interface (semicolons required!) +│ └── src/ +│ └── main.mo # Motoko canister +└── frontend/ + ├── canister.yaml # frontend canister config + └── BlazorFrontend/ + ├── BlazorFrontend.csproj # .NET 10 Blazor WASM + ├── Program.cs + ├── App.razor + ├── _Imports.razor # required — Blazor namespace imports + ├── package.json # webpack + @dfinity/agent + ├── webpack.config.js # bundles icpAgent.ts → wwwroot/icpAgent.js + ├── tsconfig.json + ├── src/ + │ └── icpAgent.ts # TypeScript ICP agent bridge + ├── Layout/ + │ └── MainLayout.razor + ├── Pages/ + │ └── Home.razor # main UI with canister calls + ├── Services/ + │ └── IcpAgentService.cs # C# → JS interop service + └── wwwroot/ + ├── index.html + └── app.css +``` + +## How it works + +``` +Home.razor (C#) + → IcpAgentService.cs (IJSRuntime) + → window.IcpAgent.* (webpack bundle, defer loaded) + → @dfinity/agent + → Motoko backend canister on ICP +``` + +The key insight: use **webpack** to bundle `@dfinity/agent` into a plain JS file +loaded with `defer`, not as an ES module. This avoids the race condition between +the module loader and Blazor's JS interop system. + +## Prerequisites + +```bash +# icp-cli +npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm + +# Motoko toolchain +npm install -g ic-mops +``` + +## Prerequisites + +### icp-cli +```bash +npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm +npm install -g ic-mops +``` + +### .NET 10 SDK + +**Method 1: Ubuntu 24.04 LTS or newer (APT)** +```bash +sudo apt update +sudo apt install -y dotnet-sdk-10.0 +``` + +> **Ubuntu 22.04 LTS?** Register the backports PPA first: +> ```bash +> sudo add-apt-repository ppa:dotnet/backports +> sudo apt update +> sudo apt install -y dotnet-sdk-10.0 +> ``` + +**Method 2: Official Microsoft script (recommended for non-Ubuntu or if APT fails)** +```bash +wget https://dot.net/v1/dotnet-install.sh -O dotnet-install.sh +chmod +x dotnet-install.sh +./dotnet-install.sh --channel 10.0 +``` + +After installing, add .NET to your PATH: +```bash +echo 'export PATH="$HOME/.dotnet:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +### Blazor WASM workload (required) +```bash +dotnet workload install wasm-tools +``` + +### Remove conflicting `icp` binary (Ubuntu/Debian) +Ubuntu ships a package called `renameutils` that installs its own unrelated `icp` binary. +Remove it before installing icp-cli: +```bash +sudo apt remove renameutils -y +``` + +Verify the correct `icp` is active: +```bash +icp --version # should show icp-cli, not renameutils +``` + +## Run locally + +```bash +# 1. Start the local ICP network (make sure port 8000 is free) +icp network start -d + +# 2. Deploy both canisters +icp deploy + +# 3. Open the URL printed by icp deploy +# Format: http://.localhost:8000 +``` + +## Deploy to mainnet + +```bash +icp deploy --network ic +``` + +## Known gotchas + +### `_Imports.razor` is required +Without `_Imports.razor`, Blazor component events silently do nothing. +Always include `@using Microsoft.JSInterop` in it. + +### Port 8000 conflict +If `icp network start` exits with status 101, check the log: +```bash +cat .icp/cache/networks/local/network-launcher/stderr.log +``` +Common cause: Docker container mapped to port 8000. +```bash +docker ps | grep 8000 +docker stop +``` +## License + +MIT diff --git a/blazor/hello_world/backend/backend.did b/blazor/hello_world/backend/backend.did new file mode 100644 index 000000000..7f9052a27 --- /dev/null +++ b/blazor/hello_world/backend/backend.did @@ -0,0 +1,5 @@ +service : { + getGreeting : () -> (text) query; + setGreeting : (text) -> (text); + hello : (text) -> (text) query; +} diff --git a/blazor/hello_world/backend/canister.yaml b/blazor/hello_world/backend/canister.yaml new file mode 100644 index 000000000..2e583049d --- /dev/null +++ b/blazor/hello_world/backend/canister.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v0.1.0/docs/schemas/canister-yaml-schema.json +name: backend +recipe: + type: "@dfinity/motoko@v4.1.0" + configuration: + main: src/main.mo + candid: backend.did diff --git a/blazor/hello_world/backend/mops.toml b/blazor/hello_world/backend/mops.toml new file mode 100644 index 000000000..4363270d8 --- /dev/null +++ b/blazor/hello_world/backend/mops.toml @@ -0,0 +1,2 @@ +[toolchain] +moc = "1.3.0" diff --git a/blazor/hello_world/backend/src/main.mo b/blazor/hello_world/backend/src/main.mo new file mode 100644 index 000000000..d02270568 --- /dev/null +++ b/blazor/hello_world/backend/src/main.mo @@ -0,0 +1,18 @@ +persistent actor { + + stable var greeting : Text = "Hello from Motoko!"; + + public query func getGreeting() : async Text { + return greeting; + }; + + public func setGreeting(name : Text) : async Text { + greeting := "Hello, " # name # "! Welcome to ICP + Blazor!"; + return greeting; + }; + + public query func hello(name : Text) : async Text { + return "Hello, " # name # "!"; + }; + +}; diff --git a/blazor/hello_world/frontend/BlazorFrontend/App.razor b/blazor/hello_world/frontend/BlazorFrontend/App.razor new file mode 100644 index 000000000..d211d0776 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/App.razor @@ -0,0 +1,12 @@ +@using Microsoft.AspNetCore.Components.Routing +@using BlazorFrontend.Layout + + + + + + + +

Page not found.

+
+
diff --git a/blazor/hello_world/frontend/BlazorFrontend/BlazorFrontend.csproj b/blazor/hello_world/frontend/BlazorFrontend/BlazorFrontend.csproj new file mode 100644 index 000000000..9a10c6ce1 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/BlazorFrontend.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + + + + + true + true + + + + + false + false + + + + + + + diff --git a/blazor/hello_world/frontend/BlazorFrontend/Layout/MainLayout.razor b/blazor/hello_world/frontend/BlazorFrontend/Layout/MainLayout.razor new file mode 100644 index 000000000..724fc91b6 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/Layout/MainLayout.razor @@ -0,0 +1,5 @@ +@inherits LayoutComponentBase + +
+ @Body +
diff --git a/blazor/hello_world/frontend/BlazorFrontend/Pages/Home.razor b/blazor/hello_world/frontend/BlazorFrontend/Pages/Home.razor new file mode 100644 index 000000000..099697bda --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/Pages/Home.razor @@ -0,0 +1,97 @@ +@page "/" +@inject IcpAgentService IcpAgent + +
+

ICP + Blazor Hello World

+

Blazor WebAssembly (.NET 10) · Motoko · Internet Computer

+ + +
+

Current Greeting

+ + @if (!string.IsNullOrEmpty(_greeting)) + { +

@_greeting

+ } +
+ + +
+

Set Custom Greeting

+ + + @if (!string.IsNullOrEmpty(_setResponse)) + { +

@_setResponse

+ } +
+ + +
+

Say Hello

+ + + @if (!string.IsNullOrEmpty(_helloResponse)) + { +

@_helloResponse

+ } +
+ + @if (!string.IsNullOrEmpty(_error)) + { +

Error: @_error

+ } +
+ +@code { + private string _greeting = ""; + private string _helloResponse = ""; + private string _setResponse = ""; + private string _name = ""; + private string _customName = ""; + private string _error = ""; + private bool _loading = false; + + private async Task FetchGreeting() + { + await Call(async () => _greeting = await IcpAgent.GetGreetingAsync()); + } + + private async Task SayHello() + { + if (string.IsNullOrWhiteSpace(_name)) return; + await Call(async () => _helloResponse = await IcpAgent.HelloAsync(_name)); + } + + private async Task SetGreeting() + { + if (string.IsNullOrWhiteSpace(_customName)) return; + await Call(async () => _setResponse = await IcpAgent.SetGreetingAsync(_customName)); + } + + private async Task Call(Func action) + { + _loading = true; + _error = ""; + StateHasChanged(); + try + { + await action(); + } + catch (JSException ex) + { + _error = $"JS Error: {ex.Message}"; + } + catch (Exception ex) + { + _error = $"Error: {ex.Message}"; + } + finally + { + _loading = false; + StateHasChanged(); + } + } +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/Program.cs b/blazor/hello_world/frontend/BlazorFrontend/Program.cs new file mode 100644 index 000000000..63bda100d --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/Program.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using BlazorFrontend.Services; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); + +builder.Services.AddScoped(); + +await builder.Build().RunAsync(); diff --git a/blazor/hello_world/frontend/BlazorFrontend/Services/IcpAgentService.cs b/blazor/hello_world/frontend/BlazorFrontend/Services/IcpAgentService.cs new file mode 100644 index 000000000..feeb109e4 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/Services/IcpAgentService.cs @@ -0,0 +1,26 @@ +using Microsoft.JSInterop; + +namespace BlazorFrontend.Services; + +/// +/// Calls the Motoko backend canister on ICP via JavaScript interop. +/// The IcpAgent global is defined in wwwroot/icpAgent.js (webpack bundle). +/// +public class IcpAgentService +{ + private readonly IJSRuntime _js; + + public IcpAgentService(IJSRuntime js) + { + _js = js; + } + + public async Task GetGreetingAsync() + => await _js.InvokeAsync("IcpAgent.getGreeting"); + + public async Task SetGreetingAsync(string name) + => await _js.InvokeAsync("IcpAgent.setGreeting", name); + + public async Task HelloAsync(string name) + => await _js.InvokeAsync("IcpAgent.hello", name); +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/_Imports.razor b/blazor/hello_world/frontend/BlazorFrontend/_Imports.razor new file mode 100644 index 000000000..ab61ad797 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/_Imports.razor @@ -0,0 +1,7 @@ +@using System.Net.Http +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop +@using BlazorFrontend +@using BlazorFrontend.Layout +@using BlazorFrontend.Services diff --git a/blazor/hello_world/frontend/BlazorFrontend/package.json b/blazor/hello_world/frontend/BlazorFrontend/package.json new file mode 100644 index 000000000..52368bd8b --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/package.json @@ -0,0 +1,16 @@ +{ + "name": "blazor-icp", + "version": "1.0.0", + "scripts": { + "build": "webpack" + }, + "dependencies": { + "@dfinity/agent": "^2.0.0" + }, + "devDependencies": { + "webpack": "^5.0.0", + "webpack-cli": "^5.0.0", + "ts-loader": "^9.0.0", + "typescript": "^5.0.0" + } +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/src/icpAgent.ts b/blazor/hello_world/frontend/BlazorFrontend/src/icpAgent.ts new file mode 100644 index 000000000..1424adaee --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/src/icpAgent.ts @@ -0,0 +1,59 @@ +import { Actor, HttpAgent } from "@dfinity/agent"; + +const idlFactory = ({ IDL }: any) => + IDL.Service({ + getGreeting: IDL.Func([], [IDL.Text], ["query"]), + setGreeting: IDL.Func([IDL.Text], [IDL.Text], []), + hello: IDL.Func([IDL.Text], [IDL.Text], ["query"]), + }); + +function getCanisterId(): string { + const decoded = decodeURIComponent(document.cookie); + const match = decoded + .split("; ") + .find((c) => c.startsWith("ic_env=")); + + if (match) { + const params = new URLSearchParams(match.replace("ic_env=", "")); + const id = params.get("PUBLIC_CANISTER_ID:backend"); + if (id) return id; + } + + throw new Error("Canister ID not found. Make sure the app is accessed via the canister subdomain."); +} + +let _actor: any = null; + +async function getActor() { + if (!_actor) { + const isLocal = + window.location.hostname === "localhost" || + window.location.hostname.endsWith(".localhost"); + + const agent = await HttpAgent.create({ + host: isLocal ? "http://localhost:8000" : "https://ic0.app", + }); + + if (isLocal) { + await agent.fetchRootKey(); + } + + _actor = Actor.createActor(idlFactory, { + agent, + canisterId: getCanisterId(), + }); + } + return _actor; +} + +export async function getGreeting(): Promise { + return (await getActor()).getGreeting(); +} + +export async function setGreeting(name: string): Promise { + return (await getActor()).setGreeting(name); +} + +export async function hello(name: string): Promise { + return (await getActor()).hello(name); +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/tsconfig.json b/blazor/hello_world/frontend/BlazorFrontend/tsconfig.json new file mode 100644 index 000000000..21c4dd350 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "ES6", + "moduleResolution": "node", + "strict": false, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "outDir": "./dist" + }, + "include": [ + "src/**/*" + ] +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/webpack.config.js b/blazor/hello_world/frontend/BlazorFrontend/webpack.config.js new file mode 100644 index 000000000..3246a9722 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/webpack.config.js @@ -0,0 +1,21 @@ +const path = require("path"); + +module.exports = { + entry: "./src/icpAgent.ts", + output: { + path: path.resolve(__dirname, "wwwroot"), + filename: "icpAgent.js", + library: { + name: "IcpAgent", + type: "var", + }, + globalObject: "window", + }, + resolve: { + extensions: [".ts", ".js"], + }, + module: { + rules: [{ test: /\.ts$/, use: "ts-loader" }], + }, + mode: "production", +}; diff --git a/blazor/hello_world/frontend/BlazorFrontend/wwwroot/app.css b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/app.css new file mode 100644 index 000000000..e4c14a35e --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/app.css @@ -0,0 +1,94 @@ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +body { + font-family: system-ui, -apple-system, sans-serif; + background: #0a0a1a; + color: #e0e0ff; + min-height: 100vh; + display: flex; + justify-content: center; + padding: 2rem 1rem; +} + +.container { + width: 100%; + max-width: 600px; +} + +h1 { + font-size: 2rem; + color: #a78bfa; + margin-bottom: 0.25rem; +} + +.subtitle { + color: #6b7280; + font-size: 0.9rem; + margin-bottom: 2rem; +} + +h2 { + font-size: 1.1rem; + color: #c4b5fd; + margin-bottom: 0.75rem; +} + +section { + background: #12122a; + border: 1px solid #2d2d5e; + border-radius: 10px; + padding: 1.25rem; + margin-bottom: 1.25rem; + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +input { + background: #1a1a35; + border: 1px solid #3d3d7a; + border-radius: 6px; + color: #e0e0ff; + font-size: 1rem; + padding: 0.5rem 0.75rem; + outline: none; + transition: border-color 0.2s; +} + +input:focus { border-color: #7c3aed; } + +button { + background: #7c3aed; + border: none; + border-radius: 6px; + color: white; + cursor: pointer; + font-size: 0.95rem; + font-weight: 500; + padding: 0.5rem 1.25rem; + align-self: flex-start; + transition: background 0.2s; +} + +button:hover:not(:disabled) { background: #6d28d9; } +button:disabled { opacity: 0.5; cursor: not-allowed; } + +.result { + background: #0d1f0d; + border: 1px solid #166534; + border-radius: 6px; + color: #86efac; + font-family: monospace; + font-size: 0.95rem; + padding: 0.5rem 0.75rem; +} + +.error { + background: #1f0d0d; + border: 1px solid #991b1b; + border-radius: 6px; + color: #fca5a5; + font-size: 0.9rem; + padding: 0.75rem; + margin-top: 0.5rem; +} diff --git a/blazor/hello_world/frontend/BlazorFrontend/wwwroot/icpAgent.js.LICENSE.txt b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/icpAgent.js.LICENSE.txt new file mode 100644 index 000000000..ad53c8fa5 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/icpAgent.js.LICENSE.txt @@ -0,0 +1,12 @@ +/*! + * The buffer module from node.js, for the browser. + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ + +/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */ + +/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */ diff --git a/blazor/hello_world/frontend/BlazorFrontend/wwwroot/index.html b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/index.html new file mode 100644 index 000000000..4d7ef6902 --- /dev/null +++ b/blazor/hello_world/frontend/BlazorFrontend/wwwroot/index.html @@ -0,0 +1,21 @@ + + + + + + ICP Blazor Hello World + + + + + +
Loading...
+ + + + + + diff --git a/blazor/hello_world/frontend/canister.yaml b/blazor/hello_world/frontend/canister.yaml new file mode 100644 index 000000000..c00a42d1a --- /dev/null +++ b/blazor/hello_world/frontend/canister.yaml @@ -0,0 +1,9 @@ +# yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v0.1.0/docs/schemas/canister-yaml-schema.json +name: frontend +recipe: + type: "@dfinity/asset-canister@v2.2.1" + configuration: + build: + - cd BlazorFrontend && npm install && npm run build + - dotnet publish BlazorFrontend/BlazorFrontend.csproj -c Release + dir: BlazorFrontend/bin/Release/net10.0/publish/wwwroot diff --git a/blazor/hello_world/icp.yaml b/blazor/hello_world/icp.yaml new file mode 100644 index 000000000..a506b211e --- /dev/null +++ b/blazor/hello_world/icp.yaml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v0.1.0/docs/schemas/icp-yaml-schema.json +canisters: + - backend + - frontend