Skip to content

DHI.Services.DS for JsonDocument — Internal Developer Guide

The DS provider is a lightweight HTTP client for consuming JSON Documents from a remote JSON Documents Web API. It implements the same repository contract as your in-process repositories, so you can plug it straight into JsonDocumentService<string> and keep the rest of your code unchanged.

Use the DS provider when:

  • Your JSON docs live in another service/environment and are exposed via the Web API.
  • You want resilient calls (retry/circuit-breaker) and bearer-token auth instead of DB credentials.
  • You prefer server-side projection to fetch only the parts of Data your clients need.


What you get

  • JsonDocumentRepository : IJsonDocumentRepository<string> — HTTP-backed repository
  • Retries via Polly (exponential backoff; default 5 attempts)
  • Bearer token support via IAccessTokenProvider
  • Optional HttpClient injection (typed client/DI friendly)
  • Familiar repo surface: Get, GetAll, GetByGroup, Add, Update, Remove, Get(Query<>), etc.

All permission checks, grouping semantics, projections, and queries are ultimately enforced by the remote Web API (and its backing provider).


Quick start

1) Choose how to construct it

using DHI.Services.Provider.DS;
using DHI.Services.JsonDocuments;
using Microsoft.Extensions.Logging;

// A) Simplest: base URL + access token provider
var repo = new JsonDocumentRepository(
    baseUrl: "https://host/api/jsondocuments/main", // include connectionId
    accessTokenProvider: new MyAccessTokenProvider(),
    retryCount: 3,                                   // optional, default 5
    logger: logger                                   // optional
);

// B) With a managed HttpClient (e.g., typed client)
var repo2 = new JsonDocumentRepository(httpClient, "https://host/api/jsondocuments/main", new MyTokenProvider());

// C) Via connection string
var repo3 = new JsonDocumentRepository(
    "BaseUrl=https://host/api/jsondocuments/main; RetryCount=3"
);

Implement your token provider:

public sealed class MyAccessTokenProvider : IAccessTokenProvider
{
    public Task<string> GetAccessToken() =>
        Task.FromResult("<jwt-or-oauth-token>");
}

2) Plug into the service

var service = new JsonDocumentService<string>(repo);

3) Fetch with server-side projection

// Only what the UI needs
var projected = service.Get("configs/sim/Sim 42", new[] { "$.params", "$.notes" }).Value;
// projected.Data == {"params":{...},"notes":"..."}

API surface (highlights)

// Discovery
bool ContainsGroup(string group);
IEnumerable<string> GetFullNames();
IEnumerable<string> GetFullNames(string group);

// Reads
Maybe<JsonDocument<string>> Get(string id, string[] dataSelectors = null);
IEnumerable<JsonDocument<string>> GetAll(string[] dataSelectors = null);
IEnumerable<JsonDocument<string>> GetByGroup(string group, string[] dataSelectors = null);
IEnumerable<JsonDocument<string>> Get(DateTime from, DateTime to);
IEnumerable<JsonDocument<string>> Get(DateTime from, DateTime to, string[] dataSelectors = null);
IEnumerable<JsonDocument<string>> Get(Query<JsonDocument<string>> query);
IEnumerable<JsonDocument<string>> Get(Query<JsonDocument<string>> query, string[] dataSelectors = null);

// Writes
void Add(JsonDocument<string> document);
void Update(JsonDocument<string> document);
void Remove(string id);

Selectors: Any method with dataSelectors forwards them to the Web API for server-side JSON projection. Both plain dotted paths (params.dt) and JSONPath-style tokens ($.params.dt) are accepted.


Base URL & routing

Set baseUrl to the Web API route including the connection id you registered in the Web API host:

https://{host}/api/jsondocuments/{connectionId}

Example: https://host/api/jsondocuments/main


Common recipes

Add / Update

var doc = new JsonDocument<string>(
    id: "configs/sim/Sim 43",
    name: "Sim 43",
    group: "configs/sim",
    data: "{\"params\":{\"dt\":120},\"notes\":\"candidate\"}"
)
{ DateTime = DateTime.UtcNow };

repo.Add(doc);

// later…
doc = repo.Get(doc.Id).Value;
doc = new JsonDocument<string>(doc.Id, doc.Name, doc.Group,
       "{\"params\":{\"dt\":180},\"notes\":\"retuned\"}",
       doc.Metadata, doc.Permissions) { DateTime = DateTime.UtcNow };
repo.Update(doc);

Query on the server

var q = new Query<JsonDocument<string>>(
    new QueryCondition("groupname", "^configs/sim(/|$)", QueryOperator.Like),
    new QueryCondition("$.params.dt", 180, QueryOperator.Equal)
);

var hits = repo.Get(q);

Read by time window

// server-side or client-side depending on overload (see notes below)
var recent = repo.Get(DateTime.UtcNow.AddDays(-7), DateTime.UtcNow);

How retries & errors work

The DS provider (via DS Core):

  • Uses Polly exponential backoff (1s, 2s, 4s, 8s, … up to retryCount).
  • 4xx are not retried (except 404, which may occur during rolling restarts).
  • Non-success responses are logged and surfaced as HttpRequestException once retries are exhausted.

Known behaviors & workarounds (current implementation)

These are good to be aware of when choosing the exact overloads:

  • GetFullNames(string group) expects DTOs but the Web API returns string[]. Workaround: repo.GetByGroup(group).Select(d => d.FullName).

  • Get(DateTime from, DateTime to, string[] selectors, …) builds the query with from/to flipped in the URL. Workaround: use repo.Get(from, to) (no selectors) and, if you need projection, call document.Filter(selectors) client-side.

  • Get(Query<>, string[] selectors, …) issues a GET to /query, while the Web API expects POST with a body. Workaround: use repo.Get(query) (no selectors), then Filter(selectors) client-side.

  • GetAll(string[] selectors, …) deserializes directly to domain entities (the Web API returns DTOs). Workaround: use repo.GetAll() and then Filter(selectors) client-side.

  • Get(DateTime from, DateTime to) (no selectors) fetches all from the server and filters client-side by DateTime. This is correct but can be inefficient on large sets.

If in doubt, prefer: fetch without selectors → doc.Filter(selectors) (client-side). For the best performance where supported, use the selector-aware overloads that forward selectors to the Web API.


Using with JsonDocumentService<string>

var dsRepo = new DHI.Services.Provider.DS.JsonDocumentRepository(
    "https://host/api/jsondocuments/main",
    new MyAccessTokenProvider());

var jsonDocService = new DHI.Services.JsonDocuments.JsonDocumentService<string>(dsRepo);

// Now use the service as usual (guards, events, optional notifications)
var one = jsonDocService.Get("configs/sim/Sim 42", new[] { "$.params.dt", "$.notes" });

Connections module (optional)

If you use the Connections module, you can register a DS-backed JSON Documents connection like this:

{
  "$type": "System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[DHI.Services.IConnection, DHI.Services]], mscorlib",
  "ds-json-documents": {
    "$type": "DHI.Services.JsonDocuments.WebApi.JsonDocumentServiceConnection, DHI.Services.JsonDocuments.WebApi",
    "ConnectionString": "baseUrl=https://remote.host/api/scalars/prod;token=[env:SCALARS_API_TOKEN];retry=5",
    "RepositoryType": "DHI.Services.Provider.DS.JsonDocumentRepository, DHI.Services.Provider.DS",
    "Name": "DS JSON Documents",
    "Id": "ds-json-documents"
  }
}
  • baseUrl should include the correct connectionId segment of the remote Web API.
  • token can be resolved from environment (e.g., [env:SCALARS_API_TOKEN]).
  • retry controls the Polly retry count (defaults to 5 if omitted).

Endpoint mapping (for reference)

The DS provider wraps these Web API routes:

  • GET / – list all (or filtered by time window in some overloads)
  • POST /query – query body (DTO)
  • GET /{fullName} – get one
  • GET /group/{group} – list by group
  • GET /fullnames (+ optional ?group=...) – discovery
  • POST / – add
  • PUT / – update
  • DELETE /{fullName} – delete (hard)

Base route is your baseUrl (e.g., https://host/api/jsondocuments/main). For selector-aware reads, the provider forwards dataSelectors as a comma-separated query parameter.


Tips

  • Always include the connection id in baseUrl: /api/jsondocuments/{connectionId}.
  • Prefer server-side selectors for payload efficiency; otherwise use JsonDocument.Filter(...) locally.
  • For production, consider a PG-backed Web API as the source of truth and consume it via this DS provider in downstream services.

That’s it — drop in JsonDocumentRepository, point it at your Web API, and you’ve got a resilient, token-protected client for JSON Documents.