Skip to content

JSON Documents Providers – Guide & Recipes

Applies to PostgreSQL (authoritative DB repo) and DS (remote/Web-API client). Use this page to pick a provider quickly, and wire it. Deep dives are linked.


Quick map of the providers (the big picture)

Supported Providers Type Where data lives Auth model you pass in Highlights Deep dive
PostgreSQL – JsonDocumentRepositorySecured In-proc PostgreSQL (jsonb table) ClaimsPrincipal (row-level perms in SQL) Server-side selectors, SQL query translation, enforced permissions, soft delete JSON Documents PostgreSQL
DS – JsonDocumentRepository Remote A JSON Docs Web API Bearer token (HTTP) via IAccessTokenProvider Resilient HTTP (Polly), server-side selectors (when supported), zero DB creds in app JSON Documents DS

Both implement IJsonDocumentRepository<string> → drop into JsonDocumentService<string> unchanged.


Which one should I pick?

  • You own the DB and need strong auth + projection + queriesPostgreSQL (secured)
  • Your docs already live behind a Web API, or you need cross-env consumptionDS (HTTP)

Capabilities at a glance

Capability / API PostgreSQL DS (HTTP client to Web API)
Get/ GetAll/ GetByGroup ✓ (proxied)
Add / Update / Remove ✓ (proxied)
Selectors (project parts of data) ✓ (but see DS notes below for some overloads)
Queries (Query<T> → server) ✓ (DS /query expects POST; see notes)
Permissions enforced server-side ✓ (enforced by remote Web API/provider)
Soft delete (Deleted column) depends on remote
Retries / circuit-breaker n/a ✓ (Polly)

Registering providers (minimal wiring)

A) Programmatic

using DHI.Services.JsonDocuments;
using DHI.Services.Provider.PostgreSQL;
using DHI.Services.Provider.DS;

// PostgreSQL (authoritative)
var pgRepo = new JsonDocumentRepositorySecured(
    "Host=pg;Port=5432;Database=ds;Username=ds_user;Password=***;SearchPath=public");
var jsonSvcPg = new JsonDocumentService<string>(pgRepo);

// DS (remote Web API client)
var dsRepo = new DHI.Services.Provider.DS.JsonDocumentRepository(
    baseUrl: "https://host/api/jsondocuments/main",          // include connectionId
    accessTokenProvider: new MyAccessTokenProvider(),        // returns bearer token
    retryCount: 5);
var jsonSvcDs = new JsonDocumentService<string>(dsRepo);

B) Via connections.json (Web API hosts)

{
  "postgresql-json-documents": {
    "$type": "DHI.Services.JsonDocuments.WebApi.JsonDocumentServiceConnection, DHI.Services.JsonDocuments.WebApi",
    "ConnectionString": "Host=pg;Port=5432;Database=ds;Username=ds_user;Password=***",
    "RepositoryType": "DHI.Services.Provider.PostgreSQL.JsonDocumentRepositorySecured, DHI.Services.Provider.PostgreSQL",
    "Name": "PostgreSQL JSON Documents",
    "Id": "postgresql-json-documents"
  },
  "ds-json-documents": {
    "$type": "DHI.Services.JsonDocuments.WebApi.JsonDocumentServiceConnection, DHI.Services.JsonDocuments.WebApi",
    "ConnectionString": "baseUrl=https://remote/api/jsondocuments/main;token=[env:JSONDOCS_TOKEN];retry=5",
    "RepositoryType": "DHI.Services.Provider.DS.JsonDocumentRepository, DHI.Services.Provider.DS",
    "Name": "DS JSON Documents",
    "Id": "ds-json-documents"
  }
}

IDs, groups, permissions — mental model

  • ID / Group: full path-like IDs, e.g. configs/sim/Sim 43; groupname is hierarchical (a/b matches a/b and descendants).
  • Permissions (PostgreSQL repo enforces in SQL): store entries like:
    • ("read", ["group:Readers","user:alice"])
    • ("update", ["group:Editors"])
    • ("delete", ["group:Administrators"])
  • Always pass a ClaimsPrincipal to secured reads/writes on the PostgreSQL repo.

Selectors & queries (cheat-sheet)

Selectors (project only parts of data)

  • PostgreSQL: dotted paths only → "params", "params.dt", "notes".
  • DS: forwards selectors to the remote API (where supported).
// PG: server-side projection
var one = pgRepo.Get("configs/sim/Sim 43", new[] { "params.dt", "notes" }, user).Value;

// DS: projection (works best with Get(id, selectors))
var projected = dsRepo.Get("configs/sim/Sim 42", new[] { "$.params", "$.notes" }).Value;

Queries

var q = new Query<JsonDocument<string>>(
  new("groupname", "^configs/sim(/|$)", QueryOperator.Like),
  new("datetime", DateTime.UtcNow.AddDays(-7), QueryOperator.GreaterThanOrEqual),
  new("params.dt", 180, QueryOperator.Equal));   // JSON condition

var hits = pgRepo.Get(q, new[] { "params", "notes" }, user);

Common tasks (copy-paste)

Add / Update (PG shown; DS is identical surface)

var doc = new JsonDocument<string>("configs/sim/Sim 43", "Sim 43", "configs/sim",
    "{\"params\":{\"dt\":180},\"notes\":\"retuned\"}")
{
  DateTime = DateTime.UtcNow,
  Permissions = new() {
    new("read",   new[] { "group:Readers", "user:alice" }),
    new("update", new[] { "group:Editors" }),
    new("delete", new[] { "group:Administrators" })
  },
  Metadata = new() { ["owner"] = "ops" }
};

pgRepo.Add(doc);

var user = BuildUser("alice", new[] { "Readers","Editors" });
doc = pgRepo.Get(doc.Id, user).Value;
doc.Data = "{\"params\":{\"dt\":240},\"notes\":\"final\"}";
pgRepo.Update(doc, user);

Soft delete (PG)

doc.Deleted = DateTime.UtcNow;
pgRepo.Update(doc, user);   // callers decide whether to filter deleted docs

Time window

var recentPg = pgRepo.Get(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow, new[] { "params" }, user);
var recentDs = dsRepo.Get(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow); // server/client mix per remote

DS provider foot-guns (and safe patterns)

  • Get(id, selectors)good (server-side projection).
  • Get(from,to, selectors) → URL from/to flipped in current build. Do: call Get(from,to) then doc.Filter(selectors) client-side.
  • Get(query, selectors) → DS uses GET to /query, but Web API expects POST. Do: call Get(query) then Filter(selectors) client-side.
  • GetAll(selectors) → DS deserializes to domain while Web API returns DTO. Do: call GetAll() then Filter(selectors) client-side.

Bottom line for DS: Prefer selector-aware Get(id, selectors); otherwise fetch without selectors and project locally.


Security tips

  • PostgreSQL: permissions are enforced in SQL using your ClaimsPrincipal. Pass user to: Get*, Contains*, Count, Update, Remove. Add validates that the entity contains at least one "read" permission.
  • DS: security is delegated to the remote API; provide a valid bearer token via IAccessTokenProvider.

Using over HTTP (what your Web API typically exposes)

If your app hosts the JSON Documents Web API, clients (including the DS repo) will hit:

GET    /api/jsondocuments/{connectionId}
GET    /api/jsondocuments/{connectionId}/{fullName}
GET    /api/jsondocuments/{connectionId}/group/{group}
GET    /api/jsondocuments/{connectionId}/fullnames[?group=...]
POST   /api/jsondocuments/{connectionId}            // add
PUT    /api/jsondocuments/{connectionId}            // update
POST   /api/jsondocuments/{connectionId}/query      // query body (DTO)
DELETE /api/jsondocuments/{connectionId}/{fullName}

TL;DR

  • Use PostgreSQL when you own storage + need SQL-enforced perms and projection.
  • Use DS when consuming another service’s JSON docs over HTTP with tokens + retries.
  • Both plug into the same JsonDocumentService<string> — swap without touching callers.