Groundfloor Docs

.NET handler reference

Handler contract for Coderunner .NET functions, background jobs, and scheduled jobs.

Applies to the function, job, and schedule workload types — code you upload as a ZIP and Code Runner executes against a .NET bootstrap image (portal runtime slug dotnet; dotnet-script is a separate, lighter-weight catalog entry). For a service (any container, your own Dockerfile), see Deploy a container image instead — there's no handler contract there, just a container listening on a port.

Project structure

my-coderunner/
├── Handler.cs
└── CodeRunner.Function.csproj

Code Runner invokes Handler.Handler.Execute as the entry point — a static Execute method inside namespace Handler, class Handler.

Handler contract

namespace Handler;

public static class Handler
{
    public static async Task<object> Execute(
        Dictionary<string, object?> context,
        Dictionary<string, object?> payload)
    {
        // ...
    }
}
TypeSignatureReturn value
functionTask<object> Execute(context, payload)An object with statusCode, body, headers — required. A function is a request/response HTTP handler; whatever you return becomes the response.
job / scheduleTask Execute(context, payload) (no <object>)None required — the process runs to completion and exits. Log via Console.WriteLine; there's no response to return.

payload is the JSON input supplied by the caller (function) or trigger (job/schedule) as a Dictionary<string, object?>. context is runtime-provided; no properties are documented yet beyond its presence in the signature — don't depend on undocumented context fields.

Minimal example (function)

namespace Handler;

public static class Handler
{
    public static async Task<object> Execute(
        Dictionary<string, object?> context,
        Dictionary<string, object?> payload)
    {
        if (payload == null || !payload.TryGetValue("name", out var name) || name == null)
        {
            return new
            {
                statusCode = 400,
                body = new { error = "Name is required" },
                headers = new { content_type = "application/json" },
            };
        }

        return new
        {
            statusCode = 200,
            body = new { message = $"Hello {name}" },
            headers = new { content_type = "application/json" },
        };
    }
}

A job/schedule handler drops the return type to plain Task — do the work, log, and let the method end:

namespace Handler;

public static class Handler
{
    public static async Task Execute(
        Dictionary<string, object?> context,
        Dictionary<string, object?> payload)
    {
        try
        {
            Console.WriteLine("Background job started");
            await DoSomething(payload);
            Console.WriteLine("Background job completed successfully");
        }
        catch (Exception err)
        {
            Console.WriteLine($"Background job failed: {err.Message}");
        }
    }
}

Project file and NuGet packages

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OutputType>Exe</OutputType>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Newtonsoft.Json" Version="13.0.3"/>
  </ItemGroup>
</Project>

Environment variables and secrets

Environment.GetEnvironmentVariable("NAME") reads deploy-time env vars (gf deploy -e KEY=value) and injected secrets (gf deploy --secret KEY, see Secrets). Never hard-code API keys, passwords, or connection strings — and never zip a .env file.

Common status codes (function)

CodeUsage
200Successful execution
201Resource created
400Invalid or missing input
401 / 403Auth required / denied
404Not found
500Unexpected error — don't leak exception internals in the body

Best practices

  1. Structure the entry point exactly as namespace Handler / class Handler / static Execute.
  2. Use async/await; check response.IsSuccessStatusCode on outbound HTTP calls.
  3. Validate payload before using it — it's caller-supplied and untyped.
  4. Catch exceptions inside Execute so a fault doesn't crash-loop the container.
  5. Keep secrets out of the ZIP — inject them at deploy time.

On this page