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 intoJsonDocumentService<string>unchanged.
Which one should I pick?¶
- You own the DB and need strong auth + projection + queries → PostgreSQL (secured)
- Your docs already live behind a Web API, or you need cross-env consumption → DS (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;groupnameis hierarchical (a/bmatchesa/band descendants). - Permissions (PostgreSQL repo enforces in SQL): store entries like:
("read", ["group:Readers","user:alice"])("update", ["group:Editors"])("delete", ["group:Administrators"])
- Always pass a
ClaimsPrincipalto 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: callGet(from,to)thendoc.Filter(selectors)client-side.Get(query, selectors)→ DS uses GET to/query, but Web API expects POST. Do: callGet(query)thenFilter(selectors)client-side.GetAll(selectors)→ DS deserializes to domain while Web API returns DTO. Do: callGetAll()thenFilter(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. Passuserto:Get*,Contains*,Count,Update,Remove.Addvalidates 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.