GnuHealth.Abstractions 0.1.0

dotnet add package GnuHealth.Abstractions --version 0.1.0
                    
NuGet\Install-Package GnuHealth.Abstractions -Version 0.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GnuHealth.Abstractions" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GnuHealth.Abstractions" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="GnuHealth.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GnuHealth.Abstractions --version 0.1.0
                    
#r "nuget: GnuHealth.Abstractions, 0.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GnuHealth.Abstractions@0.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GnuHealth.Abstractions&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=GnuHealth.Abstractions&version=0.1.0
                    
Install as a Cake Tool

GnuHealth.Net

CI

A .NET client for building applications on top of GNU Health (the Tryton-based HMIS/EMR).

GnuHealth.Net talks to GNU Health through the trytond JSON-RPC API β€” the same interface the official desktop and web clients use β€” rather than reaching into the PostgreSQL database directly. Every read and write runs through the real ORM, so business logic, validation, code sequences, access control and workflow transitions are enforced server-side. Your integrations get a clean, typed API and cannot leave the database in a state the application can't handle.

  • πŸ”’ Safe by construction β€” no hand-reimplemented ORM logic, no schema corruption.
  • ⌨️ Strongly typed β€” 498 POCOs covering the entire model catalog, generated from a live server.
  • 🧩 Untyped escape hatch β€” work with any model/field dynamically when you need to.
  • πŸ§ͺ Testable & DI-friendly β€” interface seams, a fakeable transport, IServiceCollection wiring.
  • 🎯 Broad reach β€” netstandard2.0 core (usable from .NET Framework 4.6.1+, .NET 5–8, MAUI, Unity).

Contents


Why JSON-RPC and not SQL?

You can write to GNU Health's PostgreSQL database directly β€” but to do it safely you would have to re-implement, per model and per version, everything the ORM does:

  • code sequences (prescription_id, evaluation/lab codes, party PUIDs, …),
  • create/write overrides and validate() rules,
  • workflow state machines (surgery draftβ†’confirmedβ†’doneβ†’signed, …),
  • computed/function fields, ir_model_data, history tables, ir.property,
  • access rules, record rules and translations.

Going through the JSON-RPC API means all of that is handled for you by trytond. The trade-off is that bulk reads are a little slower than raw SQL β€” so for heavy reporting/BI, read-only SQL is still fine; just keep all writes on the API.

Architecture

Clean, inward-pointing dependencies (the domain contracts have no dependencies of their own):

              GnuHealth.Abstractions
   (interfaces, value types, attributes, Domain, WriteCommand, exceptions)
                 β–²                    β–²
                 β”‚                    β”‚
        GnuHealth.Client        GnuHealth.Models          GnuHealth.Codegen
   transport Β· serialization    generated POCOs      introspects a live server,
   ORM Β· mapping Β· sessions      (by module ns)        emits GnuHealth.Models
   configuration Β· DI
Project Target Responsibility
GnuHealth.Abstractions netstandard2.0 Contracts and value types: IGnuHealthConnection, IModelClient, ITypedModelClient<T>, IRecordMaterializer, ITrytonRpcClient; TrytonDate, TrytonRecord, Domain, WriteCommand, [TrytonModel]/[TrytonField]
GnuHealth.Client netstandard2.0 Implementation, split by concern: Rpc/ (JSON-RPC transport), Serialization/ (Tryton __class__ value codec), Orm/ (ModelClient, TypedModelClient), Mapping/ (reflection materializer), Sessions/, Configuration/, DependencyInjection/
GnuHealth.Models netstandard2.0 498 generated POCOs grouped into GnuHealth.Models.<Module> namespaces
GnuHealth.Codegen net8.0 CLI that reads fields_get from a live server and emits the POCOs
samples/GnuHealth.Sample net8.0 Runnable example (env-based credentials)
tests/GnuHealth.Client.Tests net8.0 xUnit unit tests + env-gated live integration tests

The layering follows SOLID: one responsibility per type, dependencies expressed as interfaces (DIP), and swappable seams (ITrytonRpcClient, IRecordMaterializer) β€” which is how the ORM layer is unit-tested without a live server.

Getting started

Prerequisites

  • A reachable GNU Health / Tryton server with the JSON-RPC service enabled (default port 8000).
  • A Tryton login β€” ideally a dedicated, scoped service account rather than admin.
  • .NET SDK 8.0+ to build.

Add the libraries. They aren't on nuget.org yet; reference the projects (or the .nupkgs built by dotnet pack, see below):

<ItemGroup>
  <ProjectReference Include="path/to/GnuHealth.Client/GnuHealth.Client.csproj" />
  <ProjectReference Include="path/to/GnuHealth.Models/GnuHealth.Models.csproj" />
</ItemGroup>

Provide credentials via environment (never hard-code them):

GNUHEALTH_URL=http://your-host:8000
GNUHEALTH_DB=your-database
GNUHEALTH_USER=api_svc
GNUHEALTH_PASSWORD=β€’β€’β€’β€’β€’β€’β€’β€’

Usage

Connect

using GnuHealth.Client;
using GnuHealth.Client.Configuration;

var o = GnuHealthOptions.FromEnvironment();       // or set BaseUrl/Database/Login/Password directly
using var conn = new GnuHealthConnection(o.BaseUrl, o.Database);

Console.WriteLine(await conn.ServerVersionAsync()); // e.g. "7.0.45"
await conn.LoginAsync(o.Login, o.Password);          // opens a session; loads user context

Typed models

using GnuHealth.Abstractions;
using GnuHealth.Models.Health;   // module namespaces: .Health .Party .Product .Account .Stock .Ir ...

var patients = conn.Model<Patient>();

// read one (specify fields to keep it light; defaults to all mapped fields)
Patient? p = await patients.GetAsync(5, new[] { "rec_name", "blood_type", "rh" });

// search -> POCOs
IReadOnlyList<Patient> group = await patients.SearchAsync(
    Domain.Where("blood_type", "=", "O"),
    limit: 20,
    order: new object[] { new object[] { "id", "ASC" } },
    fields: new[] { "rec_name", "gender", "blood_type" });

Untyped model access

Any of the 500 models, no POCO required β€” values come back as CLR types (long, string, bool, decimal, TrytonDate, DateTime, byte[], nested records):

var lab = conn.Model("gnuhealth.lab");

long[] ids = await lab.SearchAsync(Domain.Where("patient", "=", 5L), limit: 10);
IReadOnlyList<TrytonRecord> rows = await lab.ReadAsync(ids, new[] { "name", "test.rec_name", "state" });

foreach (var r in rows)
    Console.WriteLine($"{r.GetString("name")}  {r.GetRelated("test")?["rec_name"]}  {r.GetString("state")}");

Creating & updating (writes)

Creates run every server-side default, validation and sequence:

long labId = await conn.Model("gnuhealth.lab").CreateOneAsync(new Dictionary<string, object?>
{
    ["test"]           = 25L,                 // many2one -> id
    ["patient"]        = 5L,
    ["date_requested"] = DateTime.UtcNow,     // encoded as a Tryton datetime
    ["state"]          = "draft",
});

await conn.Model("gnuhealth.lab").WriteAsync(new[] { labId }, new Dictionary<string, object?>
{
    ["diagnosis"] = "Reviewed",
});

// typed create/update round-trips the POCO's writable fields:
var disease = new PatientDisease { Patient = 5, Pathology = 2077, Status = "c", DiagnosedDate = new TrytonDate(2026, 1, 15) };
long id = await conn.Model<PatientDisease>().CreateAsync(disease);   // disease.Id is stamped
disease.Status = "h";
await conn.Model<PatientDisease>().UpdateAsync(disease, new[] { "status" });

Relational writes (one2many / many2many)

Use WriteCommand to build the command tuples trytond expects:

await conn.Model("gnuhealth.lab").WriteAsync(new[] { labId }, new Dictionary<string, object?>
{
    ["critearea"] = new object[]
    {
        WriteCommand.Create(new Dictionary<string, object?> { ["name"] = "Glucose", ["result"] = 92m }),
        WriteCommand.Add(5, 6),      // link existing rows
        WriteCommand.Remove(9),      // unlink (does not delete)
    },
});

Buttons & workflow

await conn.Model("gnuhealth.surgery").ButtonAsync("confirmed", new[] { surgeryId });

Dependency injection

using GnuHealth.Client.DependencyInjection;

services.AddGnuHealthClient(o =>
{
    o.BaseUrl = cfg["GNUHEALTH_URL"]!;
    o.Database = cfg["GNUHEALTH_DB"]!;
    o.Login = cfg["GNUHEALTH_USER"]!;
    o.Password = cfg["GNUHEALTH_PASSWORD"]!;
});

// later:
var factory = provider.GetRequiredService<IGnuHealthConnectionFactory>();
using IGnuHealthConnection conn = await factory.ConnectAsync();   // creates + logs in

More capabilities

// Resolve many2one relations to POCOs (single or batched, avoiding N+1):
var doctor = await conn.LoadAsync<Healthprofessional>(patient.PrimaryCareDoctor);
var doctors = await conn.LoadManyAsync<Healthprofessional>(patients.Select(p => p.PrimaryCareDoctor));

// Page through every match:
var all = await conn.Model<Patient>().SearchAllAsync(Domain.Where("blood_type", "=", "O"), pageSize: 500);

// Run a report -> bytes:
ReportResult label = await conn.ExecuteReportAsync("party.label", new long[] { partyId });
File.WriteAllBytes($"{label.Name}.{label.Type}", label.Content);

// Drive a wizard (low-level, stateful):
var wiz = await conn.CreateWizardAsync("gnuhealth.lab.test.create");
await wiz.ExecuteAsync(wiz.StartState, data);
await wiz.DeleteAsync();

Core concepts

  • Domain β€” a search filter. Domain.All, Domain.Where("f","=",v).And("g",">",1), or combine: Domain.Combine("OR", Domain.Where(...), Domain.Where(...)).
  • WriteCommand β€” Create / Write / Add / Remove / Delete tuples for relational fields.
  • TrytonDate β€” a date without time, for Tryton date fields (DateTime maps to datetime).
  • TrytonRecord β€” an untyped row with typed accessors (GetString, GetId, GetDate, GetRelated for dotted reads like "test.rec_name").
  • Context β€” the user's language/company context is loaded at login and merged into every call; pass a per-call context dictionary to override.
  • Value marshalling β€” trytond encodes date/datetime/Decimal/bytes/timedelta as tagged objects; the client converts them to/from native CLR types transparently.
  • Typed errors β€” failures surface as TrytonUserError (validation/business rules, safe to show users), TrytonUserWarning, TrytonConcurrencyException, TrytonAuthenticationException and TrytonTransportException β€” all deriving from TrytonRpcException.
  • Resilient sessions β€” an expired session (HTTP 401) is transparently re-authenticated and the call retried once. Transient transport failures (network / 502 / 503 / 504 / 429) are retried with exponential backoff (GnuHealthOptions.MaxRetries). Via DI, connections use a pooled HttpClient (IHttpClientFactory), so creating many connections won't exhaust sockets. Pass an ILogger (or an ILoggerFactory via DI) to log calls, retries and re-auth.
  • Selection enums β€” Tryton selection fields with a static option list are generated as typed enums (e.g. Patient.BloodTypeKind.O, Patient.GenderKind.Female) mapped to their wire values via [TrytonValue]. (Symbol-only options such as Rh +/- get generic member names β€” the value is still preserved on the attribute.)

Model coverage & code generation

GnuHealth.Models is generated from a live server, so it always matches that instance's version and customizations. This build covers 498 models / ~7,000 fields, grouped by module:

Namespace Models Namespace Models
GnuHealth.Models.Health 201 GnuHealth.Models.Stock 40
GnuHealth.Models.Account 94 GnuHealth.Models.Product 22
GnuHealth.Models.Ir 82 GnuHealth.Models.Party 20
GnuHealth.Models.Calendar 13 GnuHealth.Models.Res 11
Country Β· Currency Β· Company Β· Webdav 15

Regenerate against your own server β€” either from source, or install the published tool:

# as a global dotnet tool:
dotnet tool install -g GnuHealth.Codegen
gnuhealth-codegen http://host:8000 db admin PASSWORD --all --out ./Generated

# or from source (whole catalog; run as admin so admin-only models introspect fully):
dotnet run --project src/GnuHealth.Codegen -- http://host:8000 db admin PASSWORD \
    --all --out src/GnuHealth.Models/Generated

# or a subset:
dotnet run --project src/GnuHealth.Codegen -- http://host:8000 db api_svc PASSWORD \
    --models gnuhealth.patient,gnuhealth.lab --out src/GnuHealth.Models/Generated

Generating a POCO doesn't grant access β€” a record is still only readable/writable if the connected user's groups permit it.

Build, test, pack

dotnet build

# Unit tests always run. Live integration tests run only when GNUHEALTH_* env vars are set:
dotnet test

# Produce NuGet packages into ./artifacts:
dotnet pack -c Release -o artifacts

Good to know (GNU Health specifics)

  • common.server.version is served at the server root and is unauthenticated; the client routes it there automatically (all model calls go to the per-database endpoint).
  • Functional fields may not be searchable. For example gnuhealth.patient.gender is computed and has no searcher β€” you can read it but not filter on it; filter on stored fields (e.g. blood_type) or the underlying party.party.gender.
  • Empty many2one comes back as false/null; typed properties are nullable and map it to null.

Security

  • Use a dedicated, scoped service account (not admin). Grant it only the groups your product needs; audit trails then attribute changes to that account.
  • Keep credentials in environment variables / user-secrets / a secret store β€” never in source.

Publishing

Two independent release streams, driven by tags (see .github/workflows/ci.yml):

Tag Publishes Versioning
v1.2.3 GnuHealth.Abstractions, GnuHealth.Client, GnuHealth.Codegen the client libraries' own semver
models-v5.0.1 GnuHealth.Models tracks the GNU Health release it was generated from

So consumers can pin GnuHealth.Models to their server's GNU Health version, e.g. <PackageReference Include="GnuHealth.Models" Version="5.0.*" />.

Publishing uses NuGet Trusted Publishing (OIDC) β€” no stored API key. One-time setup:

  1. nuget.org β†’ your account β†’ Trusted Publishing β†’ add a policy: repository owner + repo, workflow file ci.yml (filename only).
  2. Add a repo secret NUGET_USER = your nuget.org account/profile name.

The workflow requests id-token: write, runs NuGet/login@v1 to mint a 1-hour key, and pushes.

GnuHealth.Models is generated from a stock GNU Health install, so it's an accurate baseline for standard deployments. If your server runs extra/fewer modules or has custom fields, regenerate with the gnuhealth-codegen tool to match it exactly β€” an unused generated POCO is harmless, but custom fields won't be present until you regenerate. Cut a new models-v* release whenever GNU Health releases a new version (regenerate against a stock server of that version, commit, tag).

Roadmap

  • Change-tracking for partial typed updates; IAsyncEnumerable streaming for large reads.
  • Richer enum member naming for symbol-only selections; typed wizard state models.
  • A published NuGet feed (CI already builds/tests/packs and publishes on tags).
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on GnuHealth.Abstractions:

Package Downloads
GnuHealth.Models

Strongly-typed POCOs for the GNU Health (Tryton) model catalog, generated from a stock GNU Health 5.0 server. Covers clinical, party, product, accounting, stock and Tryton-core models. For customized installs (extra modules or custom fields), regenerate with the GnuHealth.Codegen tool to match your server exactly.

GnuHealth.Client

A .NET client for GNU Health / Tryton over the JSON-RPC API. All access runs through the trytond ORM, so business logic, validation, sequences and workflow are enforced server-side and integrations cannot corrupt the database.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 212 7/13/2026