# C#

> Write custom APIs, hooks, schedulers and event listeners of API Maker in C# on .NET. public static async Task<object?> Main(G g), g.Req, g.Res, the typed calls of g.Sys.Db (new GetAll(…), List<JsonObject>, GetAll<T>), AmErrors, NuGet packages, with many examples.

Source: https://docs.apimaker.dev/v4/docs/languages/csharp.html

Code in C# is a class with `public static async Task<object?> Main(G g)`. It is compiled once, in memory, by the compiler of the .NET SDK, and runs on the SDK image (`mcr.microsoft.com/dotnet/sdk:10.0` unless the [settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) name another version), with only the packages you add from NuGet.

> **In short**
>
> - Write a class with `public static async Task<object?> Main(G g)`.
> - It is compiled once, and runs on `mcr.microsoft.com/dotnet/sdk:10.0` unless the settings name another version.
> - Refuse a request with `throw new AmErrors(message, 400)`.

**Hello.cs**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        return new { hello = g.Req.Body.GetString("name", "world") };
    }
}
```

| | |
|---|---|
| Runtime | .NET 8 or later, a version of `mcr.microsoft.com/dotnet/sdk` |
| `Main` | a static method `Main(G g)` of any class : `Task<object?>`, `Task`, `object?` or `void` |
| `file` | `file static class Api` : the class is known in its file only, so every piece of code can have its own `Api` and [git](https://docs.apimaker.dev/v4/docs/Git/git.html#project-of-every-language) still builds them as one project. `public static class Api` runs as well |
| Answer | what `Main` returns, written as JSON; `null` lets the API go on |
| Calls of `g.Sys` | typed : what a call takes is a class with a property for each key (`new GetAll("mysql_8", "inventory", "customers") { Limit = 10 }`), what it answers has its type (`List<JsonObject>`, `JsonObject?`, `long`, `UpdateManyResult`, or your own class : `GetAll<Customer>`) |
| Values | `System.Text.Json.Nodes` : `JsonObject`, `JsonArray`, `JsonNode`, read by their types too (`row.GetString("name")`, `row.GetInt64("id")`, `row.To<Customer>()`) |
| Usings | `System`, `System.Collections.Generic`, `System.Linq`, `System.Threading.Tasks`, `System.Text.Json`, `System.Text.Json.Nodes` and `Am` are there already |
| Packages | NuGet : `Humanizer.Core` `2.14.1` |
| Printing | `Console.WriteLine` goes to the console of the account |

## The request and the answer

**Read the request, shape the answer**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var status = g.Req.Query.GetString("status", "open");
        var signed = g.Req.Headers["x-am-authorization"] is not null;
        g.Res.StatusCode = 201;
        g.Res.Headers["X-Request-Kind"] = "orders";
        return new { customer = g.Req.Params["id"], status, signed };
    }
}
```

- An anonymous object, a record, a list or a `JsonNode` : anything `System.Text.Json` writes is an answer. A `byte[]` is bytes : see below. `NaN` and infinities are `null`, a `DateTime` in UTC is `2026-01-02T03:04:05.678Z`.
- What you change of `g.Req` in a pre hook is what the API sees.
- A body which is a list (`[{…}, {…}]`, several rows to save) is read with `g.Req["body"] as JsonArray` ; `g.Req.Body` is an empty `JsonObject` then.

### Bytes as the answer

A file, a picture, a PDF : return its bytes (a `byte[]`, a `Memory<byte>`), or put `Json.Bytes(bytes)` in `g.Res.Output`, and name their kind of content. The answer is those bytes as they are, whatever the kind of content, with the headers of the code. (A text under a kind of bytes, `image/gif`, is read as base64.)

**A picture as the answer**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var picture = Convert.FromBase64String("R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOwA=");
        g.Res.ContentType = "image/gif";
        g.Res.Headers["Content-Disposition"] = "inline; filename=\"pixel.gif\"";
        return picture;
    }
}
```

## Calling API Maker : `g.Sys`

Each method is a call of the server, with its types. What it takes is a class with a property for each of its keys, made with what it is about (`new GetAll("mysql_8", "inventory", "customers") { Limit = 10 }`) ; what it answers is typed too : rows are a `List<JsonObject>`, a row a `JsonObject?`, a count a `long`, what an API describes a class with a property for each key (`changed.UpdatedRowsCount`).

**Database calls**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var rows = await g.Sys.Db.GetAll(new GetAll("mysql_8", "inventory", "customers") { Limit = 10, Sort = "-customer_id" });
        var two = await g.Sys.Db.GetById(new GetById("mysql_8", "inventory", "customers", 2));
        var count = await g.Sys.Db.Count(new Count("mysql_8", "inventory", "customers") { Find = new { country = "IN" } });
        return new { first = rows.FirstOrDefault(), name = two.GetString("first_name"), inIndia = count };
    }
}
```

- Rows as a class of yours (a record whose properties are the columns) : `List<Customer> customers = await g.Sys.Db.GetAll<Customer>(new GetAll(…))`, `await g.Sys.Db.GetById<Customer>(…)`, or `row.To<Customer>()` on any row.
- A key the class has no property for is set by its name : `new GetAll("mysql_8", "inventory", "customers") { ["cursor"] = "abc" }`.
- What a save answers : `saved.Row`, `saved.Rows`, `saved.IsList`. The generated APIs are under `g.Sys.Db.Gen`, the whole answer of a call under `Full` : `await g.Sys.Db.Full.GetAll(…)` gives an `ApiResponse<List<JsonObject>>` (`Success`, `StatusCode`, `Data`, `TotalCount`, `Errors`).

Calls at the same time are tasks :

**Calls at the same time**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var orders = g.Sys.Db.Count(new Count("mongodb", "shop", "orders"));
        var products = g.Sys.Db.Count(new Count("mongodb", "shop", "products"));
        await Task.WhenAll(orders, products);
        return new { orders = orders.Result, products = products.Result };
    }
}
```

### Rows as they arrive

**Sum a large table**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var total = 0.0;
        await g.Sys.Db.GetAllByStream(new GetAll("mysql_8", "inventory", "orders"), row => total += row.GetDouble("amount"));
        return new { amount = total };
    }
}
```

The function of the rows can be `Action<JsonObject>` or `Func<JsonObject, Task>`.

### System APIs and cache

**Encrypt, hash, events and cache keys**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var sealedData = await g.Sys.System.Encrypt(new { card = "4111" });
        var digest = await g.Sys.System.Hash("my text");
        await g.Sys.System.EmitEvent("order_created", new { id = 7 }, new[] { "notify_shop" });
        await g.Sys.Cache.SetKey("last_order", "7", 600);
        return new { sealedData, digest, last = await g.Sys.Cache.GetKey("last_order") };
    }
}
```

## Errors

**Refuse a request**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (g.Req.Body["quantity"] is null) throw new AmErrors("'quantity' is missing.", 400);
        return new { ok = true };
    }
}
```

**Catch the errors of a call**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        try
        {
            await g.Sys.Db.RemoveById(new RemoveById("mysql_8", "inventory", "customers", 404));
            return new { removed = true };
        }
        catch (AmErrors refused)
        {
            g.Logger.Warn("not removed :", refused.Message);
            return new { removed = false, code = refused.Code };
        }
    }
}
```

Any other exception answers `500` with its type and message, the message of what it happened because of (a type initializer which failed), and the lines of your code : `at Api.Helper(Int32 x) in orders.cs:line 10`.

## Logs, printing and shared values

**Logs and shared values**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        g.Logger.Log("order", 7, "paid");               // "order 7 paid"
        g.Logger.Error(new { step = "payment" });        // objects as indented JSON
        Console.WriteLine("kept in the console of the account");
        // Lives as long as the runner, shared by every request it serves.
        return g.Shared.AddOrUpdate("calls", 1, (_, old) => (int)old! + 1);
    }
}
```

## Hooks

**Pre hook : only my rows**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        var query = g.Req.Query;
        if (query["find"] is not JsonObject find) query["find"] = find = new JsonObject();
        find["owner_id"] = g.Req.Auth.GetObject("authAMDB")["id"]?.DeepClone();
        return null;
    }
}
```

**Post hook : hide a column**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        if (g.Res.Output is JsonArray rows)
            foreach (var row in rows.OfType<JsonObject>()) row.Remove("password");
        return null;
    }
}
```

## Utility classes

A [utility class](https://docs.apimaker.dev/v4/docs/utility-class/utility-class.html) written in C# is compiled with the other C# code of the account, which names what it declares.

**The utility class Tax**

```csharp
public static class Tax
{
    public const double Rate = 0.2;

    public static double Add(double amount) => Round.Cents(amount * (1 + Rate));   // Round : another utility class
}
```

**A custom API which uses it**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        return new { total = Tax.Add(g.Req.Body.GetDouble("amount")) };
    }
}
```

- Every class of the account is compiled with every piece of code which runs : `internal` and `public` types are both there, and what a class keeps in a static field is kept per piece of code.
- A `Main(G g)` of a utility class is never taken for the `Main` of the code.
- In the Git repository the class is `src/utility_classes/<folder>---<name>/<name>.cs` : `ApiMaker.csproj` compiles it with everything else.

## Test cases

A [test case](https://docs.apimaker.dev/v4/docs/test-cases/test-cases.html#other-languages) written in C# has a method per test : every static method whose name starts with `Test` and which takes `G g`, in any class. What it throws is its failure.

**A test case**

```csharp
public static class ShippingTests
{
    public static async Task TestTheCostOfAParcel(G g)
    {
        await g.Sys.Test.Mock(new Mock("db.getAll") { When = new { collection = "rates" }, Return = new[] { new { per_kg = 2.5 } } });
        var output = await g.Sys.Test.RunCustomApi(new RunCustomApi("shippingCost") { Body = new { weight = 4 } });
        if (output.GetDouble("cost") != 10) throw new Exception("the cost is " + output?["cost"]);
    }
}
```

- `g.Sys.Test` : `RunCustomApi(new RunCustomApi(…))`, `RunCustomApiFull(…)` for the whole answer (`Data`, `StatusCode`, `Headers`, `Logs`), `Mock(new Mock(…))`, `Mocks(…)`, `ClearMocks(method)`, `Calls(method)`.
- The tests run in the order they are written. The custom API under test can be written in any language.

## Packages

Add `Humanizer.Core` in [Sandbox Settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html#packages), then :

**A package of NuGet**

```csharp
using Humanizer;

file static class Api
{
    public static object? Main(G g) => new { words = 3.ToWords() };
}
```

- Versions are NuGet versions : `13.0.3`, floating `13.*`, ranges `[1.0,2.0)`.
- The packages are restored with the runner when the image is built; the compiler sees them at once.

## How it runs

- The code is compiled once per version, in memory, with its symbols : stack traces say its lines. The first save of code starts that compilation in the background, so the first request does not wait for it.
- Every request is a task of the runner; requests run at the same time. A thread of the pool which has nothing left to do waits at once, it does not spin first (`DOTNET_ThreadPool_UnfairSemaphoreSpinLimit=0`) : a request which arrives by itself then costs less than half the processor time. A variable of that name in the environment of your sandboxes is kept as you set it.
- `nullable` is on : the compiler warns about `null`, it does not refuse.
- A `JsonNode` belongs to one parent : to put a node of the request into another object, `DeepClone()` it.

## In the code editor

The editor completes `g` as C# writes it, the names of your instances, databases and tables, and the columns of a table on the rows a call answers. More in [the code editor in every language](https://docs.apimaker.dev/v4/docs/languages/index.html#editor).

**Where the editor helps**

```csharp
file static class Api
{
    public static async Task<object?> Main(G g)
    {
        // g.  →  Req, Res, Sys, Logger, Shared      g.Sys.Db.  →  GetAll, GetById, Query, Gen, Full…
        g.Logger.Log(g.Req.Body.GetString("name"));  // g.Req.  →  Body, Query, Headers, Auth…      g.Res.  →  StatusCode, Headers…
        var rows = await g.Sys.Db.GetAll(new GetAll("mysql_8", "inventory", "customers") { Limit = 10 });
        //         new GetAll("  →  your instances, then its databases, then their tables      { L  →  Limit = , Find = , Sort = …
        foreach (var row in rows)
            g.Logger.Log(row.GetString("first_name"), row["last_name"]);  // row.GetString("  and  row["  →  the columns of customers
        return rows[0]["first_name"];
    }
}
```

- What a call answers has its type : on the rows of a `GetAll` the columns are offered inside the quotes of `GetString`, `GetInt64`, `GetDouble`, `GetObject`… and of an index (`rows[0]["`, `row["`), in a `foreach`, in a lambda (`rows.Select(row => row.GetString("…"))`) and in the function given to `GetAllByStream`. `changed.` (an `UpdateMany`) offers `UpdatedRowsCount`, `saved.` offers `Row`, `Rows` and `IsList`, the whole answer of `g.Sys.Db.Full` offers `Success`, `Data`, `Errors`…
- Between the braces of what a call takes the editor offers its properties as names, each with what it is for : after `new GetAll("mysql_8", "inventory", "customers") { ` it offers `Limit`, `Find`, `Sort`, `Select`… Inside `Find = new { ` it offers the columns of the table, inside `Select = "` and `Sort = "` too.
- `g.Req.` and `g.Res.` offer a property for each key of the request and of the answer ; `g.Req.Auth.` the opened tokens, written as C# reads a key (`g.Req.Auth["authAMUser"]`, and `?["name"]` after `?.`).
- The columns follow a row the same way : `rows[0].` then `first_name` writes `rows[0]["first_name"]`.

## Reference

| `g.` | C# |
|---|---|
| `Req` | `Body`, `Query`, `Params`, `Headers`, `Auth`, `ReqInfo` (each a `JsonObject`), `EventData`, `IsApiRequestFromUser` |
| `Res` | `StatusCode`, `ContentType`, `Headers`, `Shared`, `Output`, `Errors`, `Warnings` |
| `Sys.Db` | `GetAll(GetAll)`, `GetById(GetById)`, `Query(Query)`, `SaveSingleOrMultiple(Save)`, `MasterSave(Save)`, `ArrayOperations(ArrayOperations)`, `UpdateById(UpdateById)`, `UpdateMany(UpdateMany)`, `ReplaceById(UpdateById)`, `RemoveById(RemoveById)`, `RemoveByQuery(RemoveByQuery)`, `Aggregate(Aggregate)`, `Count(Count)`, `Distinct(Distinct)`, `DistinctQuery(Distinct)`, `GetAllByStream`, `QueryByStream` ; `GetAll<T>`, `GetById<T>`… for rows as your own class ; `Sys.Db.Gen.GetAllGen`… |
| `Sys.Db.Full`, `Sys.Db.Gen.Full`, `Sys.System.Full`, `Sys.Cache.Full` | the same calls, each with its whole answer : `ApiResponse<T>` (`Success`, `StatusCode`, `Data`, `TotalCount`, `Errors`, `Warnings`) |
| `Sys.System` | `Encrypt`, `EncryptWith`, `Decrypt`, `DecryptWith`, `Hash`, `GetSecret`, `GetSecrets`, `CallExternalApi`, `CallExternalApis`, `GetToken`, `GetTokens`, `ExecuteQuery`, `GetTableMeta`, `GetTableMetas`, `CreateIndexes`, `DropIndexes`, `DropIndexesOfMany`, `GetIndexes`, `GetIndexesOfMany`, `EmitEvent`, `EmitEventWS`, `IsValidDataForTable(s)`, `IsValidDataForCustomAPI(s)`, `MultiTenantInstance(s)Updated`, `IsValidConnectionString(s)`, `GetLock`, `ReleaseLock` |
| `Sys.Cache` | `GetKey`, `GetKeys`, `SetKey`, `SetKeys`, `RemoveKey`, `RemoveKeys`, `ResetCacheDB`, `ResetCacheDBs`, `ResetCacheCustomApis`, `ResetCacheCustomApisOfTenant`, `ResetCacheSystemApis`, `ResetCacheSystemApisOfTenant` |
| `Sys.Call("db.getAll", …)` | any API by its name in JavaScript, with its arguments as they are |
| a `JsonObject`, a `JsonNode` | `GetString`, `GetInt64`, `GetInt32`, `GetDouble`, `GetBoolean`, `GetObject`, `GetArray`, `GetObjects`, `GetStrings`, `To<T>()` |
| `Logger` | `Log`, `Debug`, `Info`, `Warn`, `Error` |
| `Shared` | `ConcurrentDictionary<string, object?>` of the runner |
| errors | `throw new AmErrors(message, code)` or `new AmErrors(JsonArray)`; `.Errors` (a `List<ResponseError>`), `.Code` |

## Debugging

- The debug runner of C# compiles your code with its symbols, under its file of the repository : netcoredbg attaches to it for the **Debug** tab of the API testing page.
- Learn more [Debugging](https://docs.apimaker.dev/v4/docs/features/debugging.html).

## Related

- [Languages and runtimes](https://docs.apimaker.dev/v4/docs/languages/index.html) · [Sandbox settings](https://docs.apimaker.dev/v4/docs/settings/sandboxSettings.html) · [The global object g](https://docs.apimaker.dev/v4/docs/pre-defined-terms/global-object-g.html)
