Table of Contents

Class DatabentoServiceCollectionExtensions

Namespace
Microsoft.Extensions.DependencyInjection
Assembly
DatabentoDotNet.Extensions.Hosting.dll

Registers Databento's historical, reference and live clients on an IServiceCollection.

public static class DatabentoServiceCollectionExtensions
Inheritance
DatabentoServiceCollectionExtensions
Inherited Members

Remarks

Namespace Microsoft.Extensions.DependencyInjection, not DatabentoDotNet.Extensions.Hosting. This is the one deliberate exception to DatabentoDotNet.* everywhere: the near-universal convention for a DI extensions class is that its Add* methods appear on IServiceCollection with no extra using, and the exception stops at this file.

Sessions are declared in code and never conjured from configuration keys. There is no scan of Databento:Live's children anywhere in this file, and there must not be one: a session that exists because somebody added a JSON key, with no handler registered anywhere, fails at startup with a cause that reads like a bug in this package rather than a missing AddDatabentoLive call in the consumer's own Program.cs.

Fields

HttpClientName

The name of the HttpClient registration the historical and reference clients share: DatabentoDotNet.Historical.

public const string HttpClientName = "DatabentoDotNet.Historical"

Field Value

string

Remarks

Public because the commonest thing a consumer does to an IHttpClientFactory registration needs it. Layering a proxy, a corporate HttpMessageHandler, or a Polly resilience policy onto this package's transport is services.AddHttpClient(DatabentoServiceCollectionExtensions.HttpClientName) followed by the ordinary builder call — and the standard way of spelling that has no form that does not name the client. Without the constant a consumer would have to guess the string, and a wrong guess is silent: it configures a second, unused client rather than failing.

The name is therefore load-bearing in the same way LiveSessionMetrics.MeterName is. Changing it detaches every consumer handler already attached to it, with nothing saying so.

Methods

AddDatabento(IServiceCollection)

Registers DatabentoOptions, bound from the conventional Databento section.

public static IServiceCollection AddDatabento(this IServiceCollection services)

Parameters

services IServiceCollection

Returns

IServiceCollection

AddDatabento(IServiceCollection, IConfigurationSection)

Registers DatabentoOptions, bound from section.

public static IServiceCollection AddDatabento(this IServiceCollection services, IConfigurationSection section)

Parameters

services IServiceCollection
section IConfigurationSection

Returns

IServiceCollection

AddDatabento(IServiceCollection, string)

Registers DatabentoOptions, bound from the configuration section at sectionPath.

public static IServiceCollection AddDatabento(this IServiceCollection services, string sectionPath)

Parameters

services IServiceCollection
sectionPath string

Returns

IServiceCollection

Remarks

Call this before the other Add* methods, and you will be told if you do not. The first registration that needs a root fixes it for the whole collection — this one when it runs first, and DefaultSectionName when something else does — so naming a second, different root afterwards throws rather than leaving the earlier registrations bound to the earlier root. Naming the root already in force is a no-op.

Exceptions

InvalidOperationException

This collection is already bound to a different configuration section.

AddDatabentoHistorical(IServiceCollection)

Registers HistoricalClient, bound from {section}:Historical.

public static IServiceCollection AddDatabentoHistorical(this IServiceCollection services)

Parameters

services IServiceCollection

Returns

IServiceCollection

Remarks

Composes with AddDatabentoReference(IServiceCollection) in either order — see that method's remarks. Everything below is a TryAdd* call or additive, so calling this twice — directly, and again through AddDatabentoReference(IServiceCollection) — yields exactly one HistoricalClient.

AddDatabentoHistorical(IServiceCollection, Action<HistoricalOptions>)

Registers HistoricalClient, then applies configure after binding.

public static IServiceCollection AddDatabentoHistorical(this IServiceCollection services, Action<HistoricalOptions> configure)

Parameters

services IServiceCollection
configure Action<HistoricalOptions>

Returns

IServiceCollection

Remarks

Registered after AddDatabentoHistorical(IServiceCollection)'s own BindConfiguration, and applied in that order — the same rule AddDatabentoLive(IServiceCollection, string, Action<LiveSessionOptions>) follows, so a lambda here overrides a bound value rather than being overridden by it.

AddDatabentoLive(IServiceCollection)

Registers a live session under DefaultSessionName.

public static DatabentoLiveBuilder AddDatabentoLive(this IServiceCollection services)

Parameters

services IServiceCollection

Returns

DatabentoLiveBuilder

AddDatabentoLive(IServiceCollection, Action<LiveSessionOptions>)

Registers the session named DefaultSessionName, then applies configure after binding.

public static DatabentoLiveBuilder AddDatabentoLive(this IServiceCollection services, Action<LiveSessionOptions> configure)

Parameters

services IServiceCollection
configure Action<LiveSessionOptions>

Returns

DatabentoLiveBuilder

Remarks

Exactly AddDatabentoLive(IServiceCollection, string, Action<LiveSessionOptions>) with the default name, and it exists because that name is the one in this family a caller should never have to spell. A host with a single session configured entirely in Program.cs otherwise had to write DatabentoLiveBuilder.DefaultSessionName — naming the thing whose whole purpose is not needing a name — and the binding still landed at Databento:Live:Default either way. Added by #99.

Not ambiguous with AddDatabentoLive(IServiceCollection, string). A lambda is not convertible to string and a string literal is not convertible to Action<T>, so overload resolution picks one on the argument's own type. Only a bare null is ambiguous, and it is rejected at compile time rather than silently taking a branch.

AddDatabentoLive(IServiceCollection, string)

Registers a live session named name, bound from {section}:Live:{name}.

public static DatabentoLiveBuilder AddDatabentoLive(this IServiceCollection services, string name)

Parameters

services IServiceCollection
name string

Returns

DatabentoLiveBuilder

Remarks

Idempotent per session name. Calling this twice for one name — directly, or because your code and a library you depend on each register the same session — yields one runner and one IHostedService, not two. See the comment on the guard inside.

Idempotent is not the same as tolerant. Registering your own keyed LiveSessionRunner under name and then calling this throws, because the alternative is a container that resolves a runner nothing binds options for and nothing starts. Registering one after this call is an ordinary override and is left alone — that is how a test double gets in, and it leaves nothing half-configured.

Exceptions

InvalidOperationException

A keyed LiveSessionRunner is already registered under name and this package did not register it.

AddDatabentoLive(IServiceCollection, string, Action<LiveSessionOptions>)

Registers a live session named name, then applies configure after binding.

public static DatabentoLiveBuilder AddDatabentoLive(this IServiceCollection services, string name, Action<LiveSessionOptions> configure)

Parameters

services IServiceCollection
name string
configure Action<LiveSessionOptions>

Returns

DatabentoLiveBuilder

Remarks

The registration itself is idempotent — see AddDatabentoLive(IServiceCollection, string) — but configure is not swallowed with it: a caller who passes a lambda is asking for it to run, and options configuration is additive by design. Two calls with two lambdas apply both, in order.

AddDatabentoReference(IServiceCollection)

Registers ReferenceClient, sharing HistoricalClient's transport.

public static IServiceCollection AddDatabentoReference(this IServiceCollection services)

Parameters

services IServiceCollection

Returns

IServiceCollection

Remarks

The spec's §1 promise, in four lines. Registers the transport itself if AddDatabentoHistorical(IServiceCollection) has not already done so, and reuses it if it has — TryAddSingleton is what makes both orders equivalent. One HistoricalClient, one HttpClient, one connection pool to hist.databento.com.

ReferenceClient(HistoricalClient) does not dispose the transport it was handed, and the container disposes the HistoricalClient singleton directly, so nothing is disposed twice.

There is deliberately no lambda overload, and #99 is where that was decided rather than left unfinished. Every other Add* in this family has one because each owns an options type — HistoricalOptions, LiveSessionOptions per session name. This one owns none: the client it registers is ReferenceClient(HistoricalClient) over the transport AddDatabentoHistorical(IServiceCollection) configures, so every knob that shapes it is already bound from {section}:Historical. Configure it through AddDatabentoHistorical(IServiceCollection, Action<HistoricalOptions>).

The two shapes an overload could take were both rejected. An AddDatabentoReference(Action<HistoricalOptions>) would be a second name for a method that already exists, over the same options instance — so a consumer calling both would write what reads as two independent configurations and get one, last-writer-wins on any property they both set. A ReferenceOptions of its own would be an empty class, advertising a configuration surface with nothing in it. If reference data ever gains a setting that is genuinely its own, that setting gets an issue and brings the overload with it.