Skip to content

Commit 044144b

Browse files
ericgregoryricochet
authored andcommitted
blog: add blog on componentize-dotnet
Signed-off-by: Eric Gregory <eric@cosmonic.com>
1 parent e1b604f commit 044144b

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
---
2+
title: "Simplifying components for .NET/C# developers with componentize-dotnet"
3+
author: "Eric Gregory"
4+
date: "2024-09-03"
5+
github_name: "ericgregory"
6+
excerpt_separator: <!--end_excerpt-->
7+
---
8+
If you're a .NET/C# developer, `componentize-dotnet` makes it easy to compile your code to WebAssembly components using a single tool. This Bytecode Alliance project is a NuGet package that can be used to create a fully AOT-compiled component from a .NET application&mdash;giving .NET developers a component experience comparable to those in Rust and TinyGo.
9+
<!--end_excerpt-->
10+
11+
`componentize-dotnet` serves as a one-stop shop for .NET developers, wrapping several tools into one:
12+
13+
* [`NativeAOT-LLVM`](https://github.com/dotnet/runtimelab/tree/feature/NativeAOT-LLVM) (compilation)
14+
* [`wit-bindgen`](https://github.com/bytecodealliance/wit-bindgen) (WIT imports and exports)
15+
* [`wasm-tools`](https://github.com/bytecodealliance/wasm-tools) (component conversion)
16+
* [WASI SDK](https://github.com/WebAssembly/wasi-sdk) (SDK used by NativeAOT-LLVM)
17+
18+
In addition to everyone who worked on those projects, `componentize-dotnet` exists thanks to the work of [James Sturtevant](https://github.com/jsturtevant), [Steve Sanderson](https://github.com/SteveSandersonMS), [Scott Waye](https://github.com/yowl), [Joel Dice](https://github.com/dicej), [Timmy Silesmo](https://github.com/silesmo), and many other contributors.
19+
20+
In this blog, we'll explore how .NET/C# developers can start building components today using .NET 9 Preview 7 and `componentize-dotnet`.
21+
22+
## Getting started
23+
24+
For this walkthrough, we'll use the [.NET 9 SDK Preview 7](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). You should also have the [`wasmtime` WebAssembly runtime](https://wasmtime.dev/) installed so you can run the Wasm binary that you produce. Optionally, you may wish to use the [C# Dev Kit extension](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit) for Visual Studio Code.
25+
26+
**Note:** While the .NET SDK is available on macOS and Linux and Wasm components can run on any OS, [the NativeAOT-LLVM compiler is limited to Windows at this time](https://github.com/dotnet/runtimelab/issues/1890#issuecomment-1221602595). Maintainers expect Linux and macOS support to arrive soon.
27+
28+
Once you have the .NET SDK installed, create a new project:
29+
30+
```sh
31+
dotnet new console -o hello
32+
```
33+
```sh
34+
cd hello
35+
```
36+
37+
The `componentize-dotnet` package depends on the `NativeAOT-LLVM` package, which resides at the `dotnet-experimental` package source, so you will need to make sure that NuGet is configured to refer to experimental packages. You can create a project-scoped NuGet configuration by running:
38+
39+
```sh
40+
dotnet new nugetconfig
41+
```
42+
43+
Edit your new `nuget.config` file to look like this:
44+
45+
```xml
46+
<?xml version="1.0" encoding="utf-8"?>
47+
<configuration>
48+
<packageSources>
49+
<!--To inherit the global NuGet package sources remove the <clear/> line below -->
50+
<clear />
51+
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
52+
<add key="nuget" value="https://api.nuget.org/v3/index.json" />
53+
</packageSources>
54+
</configuration>
55+
```
56+
57+
Now back in the console we'll add the `BytecodeAlliance.Componentize.DotNet.Wasm.SDK` package:
58+
59+
```sh
60+
dotnet add package BytecodeAlliance.Componentize.DotNet.Wasm.SDK --prerelease
61+
```
62+
63+
In the `.csproj` project file, add the following to the `<PropertyGroup>`:
64+
65+
```xml
66+
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
67+
<UseAppHost>false</UseAppHost>
68+
<PublishTrimmed>true</PublishTrimmed>
69+
<InvariantGlobalization>true</InvariantGlobalization>
70+
<SelfContained>true</SelfContained>
71+
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
72+
```
73+
74+
Now you're all set to build your console app, compiling to a `.wasm` component:
75+
76+
```sh
77+
dotnet build
78+
```
79+
80+
You can use [wasmtime](https://wasmtime.dev/) to run the component:
81+
82+
```sh
83+
wasmtime bin\Debug\net9.0\wasi-wasm\native\hello.wasm
84+
```
85+
```text
86+
Hello, World!
87+
```
88+
89+
## Streamlining the component workflow
90+
91+
In real-world applications, you will frequently want to use WebAssembly Interface Type (WIT) definitions so your components can interoperate over a common interface.
92+
93+
`componentize-dotnet` simplifies the process of fetching and using WIT interfaces, making it easy for your project file to reference a WIT artifact in an OCI registry. James Sturtevant created an excellent [demo](https://github.com/jsturtevant/wasi-http-oci/tree/master) that showcases this ability&mdash;we'll lightly adapt that demo here to use .NET 9.
94+
95+
First replace the contents of `hello/Program.cs` with this:
96+
97+
```c#
98+
using System.Text;
99+
using ProxyWorld.wit.imports.wasi.http.v0_2_0;
100+
101+
namespace ProxyWorld.wit.exports.wasi.http.v0_2_0;
102+
103+
public class IncomingHandlerImpl: IIncomingHandler {
104+
public static void Handle(ITypes.IncomingRequest request, ITypes.ResponseOutparam responseOut) {
105+
var content = Encoding.ASCII.GetBytes("Hello, World!");
106+
var headers = new List<(string, byte[])> {
107+
("content-type", Encoding.ASCII.GetBytes("text/plain")),
108+
("content-length", Encoding.ASCII.GetBytes(content.Count().ToString()))
109+
};
110+
var response = new ITypes.OutgoingResponse(ITypes.Fields.FromList(headers));
111+
var body = response.Body();
112+
ITypes.ResponseOutparam.Set(responseOut, Result<ITypes.OutgoingResponse, ITypes.ErrorCode>.ok(response));
113+
using (var stream = body.Write()) {
114+
stream.BlockingWriteAndFlush(content);
115+
}
116+
ITypes.OutgoingBody.Finish(body, null);
117+
}
118+
}
119+
```
120+
If you're familiar with `wasi-http`, you'll recognize some of the types here, but your editor may give you error squigglies for the moment.
121+
122+
Now replace the contents of `hello.csproj` with the following:
123+
124+
```xml
125+
<Project Sdk="Microsoft.NET.Sdk">
126+
127+
<PropertyGroup>
128+
<TargetFramework>net9.0</TargetFramework>
129+
<ImplicitUsings>enable</ImplicitUsings>
130+
<Nullable>enable</Nullable>
131+
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
132+
<UseAppHost>false</UseAppHost>
133+
<PublishTrimmed>true</PublishTrimmed>
134+
<InvariantGlobalization>true</InvariantGlobalization>
135+
<SelfContained>true</SelfContained>
136+
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
137+
</PropertyGroup>
138+
139+
<ItemGroup>
140+
<PackageReference Include="BytecodeAlliance.Componentize.DotNet.Wasm.SDK" Version="0.2.0-preview00004" />
141+
</ItemGroup>
142+
143+
<ItemGroup>
144+
<Wit Include="wit/wit.wasm" World="proxy" Registry="ghcr.io/webassembly/wasi/http:0.2.0" />
145+
</ItemGroup>
146+
</Project>
147+
```
148+
The project file is mostly the same&mdash;the most significant change is that we've added a new `ItemGroup` for our WIT reference, which refers to an OCI registry.
149+
150+
If you're using the C# Dev Kit with Visual Studio Code, saving the project file will create WIT bindings for you to reference at `.\obj\Debug\net8.0\wasi-wasm\wit_bindgen\`, making it easier to use the `wasi-http` interface that you've just imported. (Your code should no longer show error squigglies, as well.) Regardless of your editor, now we can build a component that uses the interface:
151+
152+
```sh
153+
dotnet build
154+
```
155+
```sh
156+
wasmtime serve -S cli .\bin\Debug\net9.0\wasi-wasm\native\hello.wasm --addr 127.0.0.1:3000
157+
```
158+
Check `localhost:3000` with your browser or `curl`:
159+
160+
```text
161+
Hello, World!
162+
```
163+
164+
## Looking ahead
165+
166+
When the final release of .NET 9 drops (scheduled for November 2024), it is expected to have support for generating components through the [Mono](https://github.com/dotnet/runtime/tree/main/src/mono) compiler, giving .NET/C# developers multiple approaches to building native WASI P2 components from the .NET SDK. The `componentize-dotnet` project will soon give users an easy way to choose between the `NativeAOT-LLVM` or Mono compilers, and either way, `componentize-dotnet` will be an essential tool for building components with WIT files. Maintainers anticipate Linux and macOS support soon.
167+
168+
For more information on `componentize-dotnet`, including instructions on using WIT interfaces with .NET 9, composition, and exporting functionality, [see the `componentize-dotnet` readme](https://github.com/bytecodealliance/componentize-dotnet/blob/main/README.md).
169+
170+
## Want to contribute?
171+
172+
Join the [Bytecode Alliance community on Zulip](https://bytecodealliance.zulipchat.com/), and explore the [meetings repository](https://github.com/bytecodealliance/meetings/tree/main) to find a SIG or community meeting that matches your interests.
173+
174+
If you'd like to get involved with `componentize-dotnet`, check out the [issues](https://github.com/bytecodealliance/componentize-dotnet/issues) and join the conversation in the [C# channel of the Bytecode Alliance Zulip](https://bytecodealliance.zulipchat.com/#narrow/stream/407028-C.23.2F.2Enet-collaboration).

0 commit comments

Comments
 (0)