An experimental SDK for building Spin application components using .NET.
- Handle HTTP requests using the Spin executor
 - Make outbound HTTP requests
 - Access Postgres databases
 - Make outbound Redis calls
 - Fast startup by preparing the .NET runtime during Wasm compilation (via Wizer)
 
You'll need the following to build Spin applications using this SDK:
- Spin v0.5.0 or above
 - .NET 8+
 - Wizer - download and place it on your PATH
- If you have Rust installed, you can install Wizer by running 
make bootstrapin the root of the SDK repo 
 - If you have Rust installed, you can install Wizer by running 
 
You can get the SDK itself from NuGet via dotnet add package Fermyon.Spin.Sdk --prerelease, or use
the provided template - see below for details.
To build and run the hello-world sample, clone this repo and run:
$ cd samples/hello-world
$ spin build --up
If everything worked, you should see a Spin "serving routes" message:
Serving http://127.0.0.1:3000
Available Routes:
  hello: http://127.0.0.1:3000 (wildcard)
You should be able to curl the address and get a response along the lines of:
$ curl -v 127.0.0.1:3000
// outbound trace omitted
< HTTP/1.1 200 OK
< content-type: text/plain
< x-testheader: this is a test
< content-length: 451
< date: Thu, 21 Jul 2022 03:11:15 GMT
<
Called with method Get on URL /
Header 'host' had value '127.0.0.1:3000'
// ... more headers info ...
Header 'spin-component-route' had value ''
The body was empty
The SDK includes a Spin template for C# projects. To install it, run:
spin templates install --git https://github.com/fermyon/spin-dotnet-sdk --branch main --update
You can then run spin new -t http-csharp <project-name> to create a new Spin C# application.
If you're creating a project without using the Spin template, add a reference the Spin SDK with the command
dotnet add package Fermyon.Spin.Sdk --prerelease
Your .NET project should contain a method with the Fermyon.Spin.Sdk.HttpHandler attribute.
This method must be static, and must take one argument of type Fermyon.Spin.Sdk.HttpRequest
and return a Fermyon.Spin.Sdk.HttpResponse.
using Fermyon.Spin.Sdk;
public static class MyHandler
{
    [HttpHandler]
    public static HttpResponse HandleHttpRequest(HttpRequest request)
    {
        // ...
    }
}Your spin.toml file should reference the compiled Wasm file built from the project.
[component.test]
source = "bin/Release/net8.0/MyApplication.wasm"To make outbound HTTP requests, use the HttpOutbound.Send() method.
For an example of constructing an outbound request, see the UseOutboundHttp method
in the hello-world sample.
To make outbound Redis requests, use the methods of the RedisOutbound class -
Get, Set and Publish.
For examples of making Redis requests, see the UseRedis method
in the hello-world sample.
To access Postgres databases, use the methods of the PostgresOutbound class -
Query for statements that return database values (SELECT), and Execute
for statements that modify the database (INSERT, UPDATE, DELETE).
For examples of making Postgres requests, see the UsePostgresQuery and
UsePostgresExec methods in the hello-world sample, or see the
Fermyon.PetStore sample.
To access Spin configuration, use the SpinConfig.Get() method.
It is not expected that a Spin component will try to access config entries that don't exist. At the moment, the only way to detect if a config setting is missing is to catch the exception from
Get.
For examples of accessing configuration, see the samples.
Both HTTP and Redis represent payload blobs using the Buffer type, and Postgres also
uses Buffer for values of binary (aka 'blob') type. Buffer represents
an unmanaged span of Wasm linear memory.  The SDK provides several convenience methods
to make it easier to work with.  For example:
- Use 
HttpRequest.Body.AsString()andHttpRequest.Body.AsBytes()to read a request body as text or a binary blob. - Use 
Buffer.ToUTF8String()to read an arbitrary Buffer as string. - Use 
HttpResponse.BodyAsStringandHttpResponse.BodyAsBytesto set the body of a HTTP response. - Use 
Buffer.FromString()to write a string into a buffer using the UTF-8 encoding. - Use 
Buffer.FromBytes()to write any sequence of bytes into a buffer. 
If your project file (.csproj) contains <UseWizer>true</UseWizer>, then dotnet build
will run Wizer to pre-initialise your
Wasm file.  Wizer will run a request through your application at compile time and snapshot
the state of your Wasm module at the end of the request.  This means that the resulting
Wasm module contains the .NET runtime in a state where it is already loaded (and the
interpreter has already seen your code), saving startup time when a request comes in at runtime.
You must run install Wizer and place it on your path (or run
make bootstrap).
Using Wizer has certain observable impacts:
- You should not (and in some cases cannot) call external services from the warmup request handler. If your handler talks to HTTP or Redis, you must skip those calls at warmup time. If the warmup code fails, then the build will fail.
 - Static constructors and static member initialisation happens at warmup time (at least for
any type used on the warmup path).  For example, if your handler type has a static
Randommember, and this gets initialised during warmup, then the same state of the random number generator will be used in all requests! 
You can identify if a request is the warmup request because the URL will be /warmupz.
You can override this in the HttpHandler attribute.  However, it is not currently possible
to have Wizer initialise the runtime but omit calling your handler.
If your handler logic doesn't require any external services then you don't need any special warmup handling. Otherwise, you'll need to guard those calls, or skip your real handler altogether, e.g.:
[HttpHandler]
public static HttpResponse HandleHttpRequest(HttpRequest request)
{
    if (request.Url == Warmup.DefaultWarmupUrl)
    {
        return new HttpResponse
        {
            StatusCode = HttpStatusCode.OK,
            Headers = new Dictionary<string, string>
            {
                { "Content-Type", "text/plain" },
            },
            BodyAsString = "warmup",
        };
    }
    // ... real handler goes here ...
}The Spin .NET SDK is a preview, built on an implementation of .NET that is currently experimental. There are several known issues, of which the most severe are:
- Some static methods and properties cause a "indirect call type mismatch" error when Wizer is turned
on - we have seen this on numeric parse methods and 
StringComparerproperties. You can work around this by turning Wizer off for affected modules. To do this, change<UseWizer>true</UseWizer>in the.csprojto<UseWizer>false</UseWizer>. - In some cases, unhandled exceptions also cause "indirect call type mismatch" instead of being returned as 500 Internal Server Error responses. You can work around this by catching problematic exceptions and returning error responses manually.
 
You can track issues or report problems at https://github.com/fermyon/spin-dotnet-sdk/issues.
The initial version of the SDK closely mirrors the underlying low-level Spin interop interfaces. This maximises performance but doesn't provide an idiomatic experience for .NET developers. We'll be aiming to improve that over future releases, and welcome contributions or suggestions!