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
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
servicesIServiceCollection
Returns
AddDatabento(IServiceCollection, IConfigurationSection)
Registers DatabentoOptions, bound from section.
public static IServiceCollection AddDatabento(this IServiceCollection services, IConfigurationSection section)
Parameters
servicesIServiceCollectionsectionIConfigurationSection
Returns
AddDatabento(IServiceCollection, string)
Registers DatabentoOptions, bound from the configuration section at sectionPath.
public static IServiceCollection AddDatabento(this IServiceCollection services, string sectionPath)
Parameters
servicesIServiceCollectionsectionPathstring
Returns
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
servicesIServiceCollection
Returns
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
servicesIServiceCollectionconfigureAction<HistoricalOptions>
Returns
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
servicesIServiceCollection
Returns
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
servicesIServiceCollectionconfigureAction<LiveSessionOptions>
Returns
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
servicesIServiceCollectionnamestring
Returns
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
nameand 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
servicesIServiceCollectionnamestringconfigureAction<LiveSessionOptions>
Returns
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
servicesIServiceCollection
Returns
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.