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
Datayour clients need.
Quick links¶
What you get¶
JsonDocumentRepository : IJsonDocumentRepository<string>— HTTP-backed repository- Retries via Polly (exponential backoff; default 5 attempts)
- Bearer token support via
IAccessTokenProvider - Optional
HttpClientinjection (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
dataSelectorsforwards 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 toretryCount). - 4xx are not retried (except
404, which may occur during rolling restarts). - Non-success responses are logged and surfaced as
HttpRequestExceptiononce 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 returnsstring[]. Workaround:repo.GetByGroup(group).Select(d => d.FullName). -
Get(DateTime from, DateTime to, string[] selectors, …)builds the query withfrom/toflipped in the URL. Workaround: userepo.Get(from, to)(no selectors) and, if you need projection, calldocument.Filter(selectors)client-side. -
Get(Query<>, string[] selectors, …)issues a GET to/query, while the Web API expects POST with a body. Workaround: userepo.Get(query)(no selectors), thenFilter(selectors)client-side. -
GetAll(string[] selectors, …)deserializes directly to domain entities (the Web API returns DTOs). Workaround: userepo.GetAll()and thenFilter(selectors)client-side. -
Get(DateTime from, DateTime to)(no selectors) fetches all from the server and filters client-side byDateTime. 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"
}
}
baseUrlshould include the correct connectionId segment of the remote Web API.tokencan be resolved from environment (e.g.,[env:SCALARS_API_TOKEN]).retrycontrols 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 oneGET /group/{group}– list by groupGET /fullnames(+ optional?group=...) – discoveryPOST /– addPUT /– updateDELETE /{fullName}– delete (hard)
Base route is your
baseUrl(e.g.,https://host/api/jsondocuments/main). For selector-aware reads, the provider forwardsdataSelectorsas 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.