|
| 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—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—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—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