Skip to main content
Most of an application is better described than coded, which is what the definition is for. A few things are not. Rounding rules. A checksum. A rule that holds across two fields rather than in one. A side effect on write that belongs to your business and nobody else’s. Put the code in custom/dotnet/ beside the rest of the app and call it by name.
Available for the dotnet-vue target. Custom TypeScript for node-vue is not built yet, and an application carrying custom code cannot run on the Cordango platform. See Where it runs.

Set it up

That writes the folder, a sample, and a project file your editor reads so Invoice and RecordContext resolve while you type.
The .csproj exists so an editor has something to load. The generated application compiles its own copy of these files, so nothing here is what ships. On a fresh clone .generated/ is not there yet and your editor will show errors until you run cordango build once.

Functions

A function is callable from any computed field.
The name in the attribute is the name the expression uses. It is deliberately separate from the C# method name, so renaming a method does not rewrite every formula that calls it.

The rules

A function must answer immediately and answer the same thing every time. A computed field is worked out when the row is written and stored beside it, and the recompute cascade may work it out many times over, so one that reads a clock or a random number is right once and wrong afterwards. cordango check refuses a function that is async, or that reads DateTime.UtcNow, Guid.NewGuid, Random or anything else whose answer moves. It warns about HttpClient, File and friends. It cannot see through a helper you call, so this is a promise you keep rather than one the compiler keeps for you. Parameters and returns are decimal?, bool? or string?, and nothing else: Every one is nullable because a computed value can be unknown, and a non-nullable parameter has nowhere to put that. Numbers are decimal throughout, since an average of integers is not an integer. Dates are not offered yet.

Hooks

A hook runs around a write. It has none of the restrictions above: it may be async and may do I/O.
The class is resolved from the container, so it can take a logger or a DbContext through its constructor like any other service. Each attribute has one signature. Update hooks are handed both the incoming record and the row as it was, which is what makes “did this field actually change” answerable:

Refusing a write

Throw RecordException from a before-hook and the write stops with a message the caller sees. This is the thing a definition cannot do: it describes fields, not the arithmetic that has to hold between them.
An after-hook runs once the write is already decided and cannot veto it.

Ordering

A before-hook runs before the computed fields, so it can set a value the formulas then read. If you want the opposite, say so:
Two hooks on the same entity run in the order they appear in the file.

What happens at build

cordango build reads the folder, works out what it declares, and records the contract in the App Definition along with a hash of the sources. The bodies are never part of the definition. The generator checks the hash before it writes anything, so an application can never carry code its own definition does not vouch for. The sources are copied into the generated application under api/Custom/ and compiled with it. A wrong signature is a build error in your own project, with a file and a line. cordango doctor tells you when the code has changed since the last build.

Where it runs

The platform runs an application by interpreting its definition. There is no compiler in the runtime and no sandbox, so a method you wrote has nothing there to execute it. cordango check --target platform and cordango publish both refuse rather than accepting an application whose figures would silently come out blank. If you need an app on the platform, express the calculation with what the language already has. Computed fields covers what that is.