Repository navigation
Releases
v0.9.1
NuGet: https://www.nuget.org/packages/TcgDex.CSharpSdk/0.9.1
dotnet add package TcgDex.CSharpSdk --version 0.9.1
Changed
- net10.0:
Microsoft.Extensions.Http,Microsoft.Extensions.Logging.Abstractionsand
Microsoft.Extensions.Optionsnow require 10.0.14, up from 10.0.13. Each target tracks the
latest patch of its own line, for the security, bug and performance fixes. The net8.0 and
netstandard2.0 builds are unchanged and still depend on the 8.0.x line, which is at its newest patch.
Fixed
- Models now compare by value, collections included. Two
Cards deserialized from the same JSON were never equal
(==,EqualsandGetHashCodedisagreed), because the collections they hold were compared by reference.
This coversCard,Attack,DetailedVariant,Series,CardSetandTcgPlayerPricing. Order matters for
a list and not for the printings of a price, whose keys compare ordinally whatever comparer your dictionary used.
A model's text (ToString) now shows a collection's items instead of its type name. No signature changes.
Three things you can now observe:- A model still does not copy a list you give it, so editing that list afterwards changes the model, and now its
equality and hash as well. Treat a list as given away. (A price's printings are copied.) Equals,GetHashCodeandToStringcost time proportional to the card's contents, so key a cache on
Card.Idrather than on the whole card.ToStringon a set of 300 cards prints all 300.
- A model still does not copy a list you give it, so editing that list afterwards changes the model, and now its
v0.9.0
NuGet: https://www.nuget.org/packages/TcgDex.CSharpSdk/0.9.0
dotnet add package TcgDex.CSharpSdk --version 0.9.0
The last interface change before 1.0 (DECISIONS #36): public interfaces cannot gain parameters or members after
1.0 without a major version, so paging and the catalog were reshaped now.
Changed
- Breaking, source level: paging moved off the method signatures, ahead of 1.0. Public interfaces cannot gain
parameters after 1.0 without a breaking release, so growth now goes onto the types they take.SearchDetailedAsync(filter, page, itemsPerPage, cancellationToken)isSearchDetailedAsync(filter, cancellationToken).
SetPageandItemsPerPageon theCardFilterinstead:new CardFilter { Name = "Furret", Page = 2, ItemsPerPage = 25 }.StreamAsync(query, pageSize, cancellationToken)isStreamAsync(query, cancellationToken). The page size is the
itemsPerPageofquery.Page(1, n), and is 100 when the query sets none. The page number is still ignored.StreamAsyncnow honours the query's page size. Before,query.Page(3, 10)was ignored and the stream fetched
100 per request; now it fetches 10. A query that never set a page size still fetches 100. This changes the request
count with no compile error for anyone who set one.- An out-of-range page size now throws from
CardQuery.Page(namingitemsPerPage) when the query is built, not from
StreamAsync(which namedpageSize).SearchDetailedAsyncnames the property,PageorItemsPerPage, where it
used to namepageanditemsPerPage. - Apart from that, the requests sent are unchanged.
Added
Catalog.GetValuesAsync(name)andCatalog.GetNumberValuesAsync(name)read an enumeration endpoint by name, so one
the SDK has no named method for yet is reachable without waiting for a release. The name is escaped as a single path
segment.
Fixed
- An id or endpoint name of
.or..is rejected withArgumentException. It used to be sent as is, and the URI layer
collapsed it, soCards.GetAsync("..")requested/v2/en/rather than a card.
v0.8.1
NuGet: https://www.nuget.org/packages/TcgDex.CSharpSdk/0.8.1
dotnet add package TcgDex.CSharpSdk --version 0.8.1
Fixed
- A string indexer is now accepted as a filter value, like every other indexer.
Where(c => c.Name == text[0].ToString())
threwNotSupportedException, whilenames[0]on a list or array worked; the string indexer is namedCharsrather
thanItem, and the translator only recognisedItem. Nothing that worked before changes.
v0.8.0
NuGet: https://www.nuget.org/packages/TcgDex.CSharpSdk/0.8.0
dotnet add package TcgDex.CSharpSdk --version 0.8.0
The 1.0 naming pass: every public name now follows the .NET Framework Design Guidelines. Almost
every change is breaking at compile time and none changes what goes over the wire — JSON keys,
request paths and query strings are the same as in 0.7.0. Most upgrades are a find-and-replace from
the table below.
Migration
| 0.7.0 | 0.8.0 |
|---|---|
Serie |
Series |
SerieBrief |
SeriesBrief |
ISerieResource |
ISeriesResource |
ISetResource |
ICardSetResource |
Set (the model) |
CardSet |
SetBrief |
CardSetBrief |
SetAbbreviation |
CardSetAbbreviation |
Set.Serie |
CardSet.Series |
Card.Hp |
Card.HitPoints |
CardFilter.Hp |
CardFilter.HitPoints |
Card.DexId |
Card.DexIds (CardFilter.DexId stays singular: it filters by one id) |
Card.Retreat |
Card.RetreatCost |
CardFilter.Retreat |
CardFilter.RetreatCost |
Card.EvolveFrom |
Card.EvolvesFrom |
CardFilter.EvolveFrom |
CardFilter.EvolvesFrom |
Card.VariantsDetailed |
Card.DetailedVariants |
Card.Legal / CardSet.Legal |
Card.Legality / CardSet.Legality |
DetailedVariant.Stamp |
DetailedVariant.Stamps |
CardCount.FirstEd |
CardCount.FirstEdition |
CardmarketPricing.IdProduct |
CardmarketPricing.ProductId |
CardmarketPricing.Avg |
CardmarketPricing.Average |
CardmarketPricing.Avg1 / Avg7 / Avg30 |
Average1Day / Average7Days / Average30Days |
CardmarketPricing.AvgHolo |
CardmarketPricing.AverageHolo |
CardmarketPricing.Avg1Holo / Avg7Holo / Avg30Holo |
Average1DayHolo / Average7DaysHolo / Average30DaysHolo |
Pricing.Tcgplayer |
Pricing.TcgPlayer |
ThirdParty.Tcgplayer |
ThirdParty.TcgPlayer |
ThirdParty.Cardtrader |
ThirdParty.CardTrader |
TcgDexProblem.Lang |
TcgDexProblem.Language |
TcgDexOptions.GraphQlEndpoint |
TcgDexOptions.GraphQLEndpoint |
TcgDexErrorKind.GraphQlError |
TcgDexErrorKind.GraphQLError (same value, 7; its ToString() changes) |
Random.CardAsync / SetAsync / SerieAsync |
Random.GetCardAsync / GetSetAsync / GetSeriesAsync |
Catalog.CategoriesAsync (and the other twelve) |
Catalog.GetCategoriesAsync (each gains Get) |
Catalog.HitPointsAsync |
Catalog.GetHitPointsAsync |
TcgDexCachingHandler.FreshHits / Revalidations / Misses |
TcgDexCachingHandler.Statistics.FreshHits / .Revalidations / .Misses |
GetImageUrl / GetLogoUrl / GetSymbolUrl returning string? |
the same methods, returning Uri? |
ImageUrl.Build / BuildAsset returning string? |
the same methods, returning Uri? |
new TcgDexApiException(message, statusCode, problem, inner) |
new TcgDexApiException(message, kind, requestUri, statusCode, problem, inner) |
QueryOperator |
removed from the public API (internal) |
Changed
- Breaking: the renames in the table above.
- Breaking: the image URL helpers return
Uri?. A base value that does not form an absolute
http(s) URL now givesnullinstead of a malformed string. - Breaking:
CachedResponseis a class with reference equality rather than a record. It no
longer supportswithor value==; create a new instance with an object initializer. - Breaking:
QueryOperatoris internal. No public member used it. - Breaking: the
TcgDexApiExceptionconstructor taking only a status code is removed. It
producedKind = Unspecified; use the constructor that takes aTcgDexErrorKind. AddTcgDexWithCachingregistersTcgDexCacheOptionsthrough the options pattern, so
Configure<TcgDexCacheOptions>, anappsettings.jsonbinding andIOptions<TcgDexCacheOptions>
now work alongside theconfigureCachedelegate. Previously only the delegate was applied.
The merged options are validated at start-up, likeTcgDexOptions: aMaxEntriesbelow 1 or a
negative time-to-live fails withOptionsValidationException.- An unreadable
updatedtimestamp on a card or a pricing block now reads asnull, as
ReleaseDatealready did, instead of failing the whole card.
v0.7.0
Added
TcgDexApiException.Kind(a newTcgDexErrorKind:HttpStatus,Network,Timeout,
Canceled,ResponseTooLarge,InvalidResponse,GraphQlError) and
TcgDexApiException.RequestUri, set on every exception the SDK throws, so a caller can branch on
the failure without reading the message.TcgDexCacheStatistics, registered byAddTcgDexWithCaching: inject it to read the cache's
fresh hits, revalidations and misses.- The cache log events 1100–1104 (hit, revalidated, miss, coalesced, evicted) are now emitted. They
were documented but never written.
Changed
- Breaking:
Set.ReleaseDateandSerie.ReleaseDateare nowDateTime?instead ofstring?,
on every target framework. The value is a date of kindUnspecifiedwith no time component.
An absent or malformed date reads asnullrather than failing the set or series. - Breaking:
TcgDexJsonContextis no longer public. Deserialize models through the client, or
with reflection-basedJsonSerializerif you serialize them yourself (not Native AOT-safe). Your
own source-generatedJsonSerializerContextcannot be used: the models name internal converters. - Breaking: invalid
TcgDexOptionspassed toAddTcgDexorAddTcgDexWithCachingnow fail
withOptionsValidationExceptionwhen the host starts (ValidateOnStart), or on first resolution
without a host, instead ofArgumentExceptionat registration. Validation now covers the final
options from every source, such asappsettings.jsonor anotherConfigure<TcgDexOptions>call,
and your configure delegate no longer runs an extra time at registration. Wherenow accepts array elements, indexers and parameterless instance methods on the value
side, such asc.Id == ids[i]orc.Name == name.Trim(), and applies casts with C# semantics.
It still never compiles the expression, so Native AOT is unaffected.DeserializePricing = falsenow also skips the pricing on everyVariantsDetailedentry, not
onlyCard.Pricing.- Options are read once, when the client is created. GraphQL searches used to pick up later changes
toGraphQlEndpoint,TimeoutandMaxResponseBytes; they no longer do, matching REST. - Calling
AddTcgDexorAddTcgDexWithCachinga second time on one service collection now throws
InvalidOperationException. The second call's options used to be silently ignored.
Fixed
- The in-memory caches could throw
ArgumentException("IComparer.Compare() method returns
inconsistent results") from a cache write when entries were evicted while other threads read
them. IOptions<TcgDexOptions>,IOptionsSnapshotandIOptionsMonitornow see every configured
property. OnlyBaseAddress,LanguageandGraphQlEndpointused to reach them.- Under
AddTcgDex, the parsed-model cache (MaxDeserializedCacheEntries) is now shared by every
resolved client. Each resolution used to get an empty cache, so it never hit. - Under
AddTcgDexWithCaching, the cache counters and request coalescing now survive
IHttpClientFactoryrebuilding its handlers (every two minutes by default). Both used to reset. TcgDexOptions.Validatenow rejects a relativeGraphQlEndpointwithArgumentException.- The
ActivitySourcenow reports the SDK's version instead of0.1.0. - In
Where, a user-defined conversion operator on the value side is now applied; its operand's
ToString()used to be sent. A throwing property getter now surfaces its own exception rather
thanTargetInvocationException, and a cast that cannot succeed ((byte)300,
(int)double.NaN) throwsNotSupportedExceptionnaming the expression. MemoryTcgDexResponseCacheno longer throws, or stores an entry already expired, for a very large
time-to-live.- A card whose TCGplayer pricing carries a key with a non-object value (for example
"currency": "USD") no longer throwsJsonException; the key is ignored. Contains,StartsWithandEndsWithsend the value as written. Before,Contains("%2A")was
decoded to*and matched every card.SearchDetailedAsyncthrowsArgumentOutOfRangeExceptionfor apageoritemsPerPagebelow
one, as the REST query does, instead of sending it.- A GraphQL search result whose
setis null is dropped and logged, like a null entry, instead of
being returned with a nullSet. TcgDexLanguages.Allcan no longer be modified by casting it to a mutable list.
Documentation
Wheredocuments that a*at the start or end of a value, and any|, are interpreted by the
server as a wildcard and an OR, and cannot be matched literally.
v0.6.3
Fixed
- A connection that drops partway through a response body now throws
TcgDexApiExceptioninstead
of a rawIOException. Successful responses, GraphQL andTcgDexCachingHandlercarry the
original failure asInnerException. Error responses report their status code instead. - A
404whose body cannot be read (reset or larger thanMaxResponseBytes) now throws
TcgDexApiExceptionwithStatusCode404instead of returningnull, with the read failure as
InnerException. Only the body tells a missing resource apart from an unsupported language. - A GraphQL error response now reports its status code even when its body resets or is too large.
- Cancelling a request now always surfaces as
OperationCanceledException, including on a
platform that reports the cancelled body read as anIOException. - On REST requests, a body that fails to read, or is not valid JSON, is now logged and marks the
request'sActivityas failed, like other failures. TcgDexCachingHandlerkeeps anIf-None-Matchthe caller set, adding its ownETagbeside it,
instead of replacing it. A304 Not Modifiedis served from the cache when the caller sent no
validator of their own, or when the 304 names the cachedETag(weak or strong form); otherwise
it is returned to the caller untouched, rather than serving the stale cached body as current. A
request message sent again after the cachedETagchanged carries only the current one.TcgDexCachingHandlerno longer adds itsETagbeside a caller'sIf-None-Match: *, which
produced an invalid header; a304answering*is returned to the caller untouched.- With
CoalesceConcurrentRequests, a request carrying its ownIf-None-Matchis no longer
merged with a concurrent request for the same URL, so it gets the answer to its own validator
rather than the other request's. - A request cancelled by the HTTP pipeline, such as a shorter
HttpClient.Timeout, now throws
TcgDexApiExceptionsaying so, instead of reporting thatTcgDexOptions.Timeoutexpired. The
"timed out" message is reserved for the SDK's own timeout. - When the caller cancels a request, the
OperationCanceledExceptionnow carries the caller's own
token, socatch (OperationCanceledException ex) when (ex.CancellationToken == token)matches. - An error response (such as a 500) whose body stalls past
TcgDexOptions.Timeoutnow throws
TcgDexApiException(timed out) instead of a raw cancellation exception. TcgDexCachingHandlernow stores the newETagwhen a304 Not Modifiedcarries one, so the
next revalidation uses the server's current validator.TcgDexCachingHandlerno longer sendsIf-None-Matchtwice when a handler above it re-sends the
same request.TcgDexCachingHandlerdisposes an error response if a custom cache'sRemoveAsyncthrows.- A response declaring a large
Content-Lengthwith a small body no longer pre-allocates the
declared size. The initial buffer is capped at 1 MiB and grows as needed.
Changed
- Documented: on .NET Framework,
TcgDexOptions.Timeoutmay not end a response body that stalls
after its headers arrive, because that platform'sHttpClientHandlerdoes not cancel an
in-progress read. This is known platform behaviour, not measured by this SDK's tests. TcgDexCachingHandlernow throwsTcgDexApiExceptionfor a body whose connection fails mid-read,
where it previously threw a rawIOException. An oversized body already threw
TcgDexApiException. That choice is deliberate, so retry policies do not re-download it.
v0.6.2
Changed
- net10.0:
Microsoft.Extensions.Http,Microsoft.Extensions.Logging.Abstractionsand
Microsoft.Extensions.Optionsnow require 10.0.13, up from 10.0.10. Each target tracks the
latest patch of its own line, for the security, bug and performance fixes. The net8.0 and
netstandard2.0 builds are unchanged and still depend on the 8.0.x line.
v0.6.1
Added
User-Agentheader on every outbound request,TcgDex.CSharpSdk/{version}— requested by
TCGdex directly so their logs (which store no IP address, for GDPR) can identify SDK traffic.
Applied per-request; does not touch a caller-suppliedHttpClient's own headers.
v0.6.0
Added
ThirdPartyexternal marketplace ids. TCGdex started serving Cardmarket, TCGplayer and
Cardtrader product ids on 2026-09. NewCard.ThirdPartyandDetailedVariant.ThirdParty
properties, each independently nullable and placed the same wayPricingalready is — a card
with several distinctly-priced printings carries it per printing; a card with one
undifferentiated printing carries it at the card root instead.Cardtraderin particular is
present only intermittently on the live API, so do not treat its absence on one fetch as
permanent.
v0.5.0
Breaking. Client-side failover and mirror selection are gone. If you called
UseFailover or UseMirror, delete the call — the default configuration is now
the recommended one. To target a different server, set BaseAddress and
GraphQlEndpoint.
Removed
-
UseFailover()and its two overloads,FailoverEndpoints,
FailoverAttemptTimeout,FailoverCooldown. TCGdex deployed new
infrastructure in September 2026, and its main route now routes around a node
that is down. Rotating between official nodes client-side was always meant to
be removed once that existed — it was built on the understanding that it would
be — and keeping it would mean a second, slower retry layer duplicating one the
service already does better. -
UseMirror()and theTcgDexMirrorenum. The same deployment retired the
per-node hostnames.api.na1.tcgdex.netandapi.na2.tcgdex.netno longer
route at all, so two of the six members could only ever fail; upstream now
recommendsapi.tcgdex.netfor everyone. Left in place they would be an
invitation to use hosts that no longer work. -
This also removes the
404discrimination added after 0.4.0 but never
released, which stopped an unrouted host's404being reported as a missing
card. That defect is resolved by the feature it lived in no longer existing.
The reasoning is kept indocs/learnings.md, because it
is about status codes rather than about failover.
Kept
- Pointing the SDK at a server of your own.
BaseAddressand
GraphQlEndpointare unchanged, and remain the supported way to use an
unofficial mirror, a self-hosted instance, or a local test server. Set both —
GraphQL sits outside the language segment, so changing onlyBaseAddress
leaves GraphQL queries going to the official host.
Added
BaseAddressis now validated to end with/. Paths resolve relative to
it, sohttps://mine/v2+en/cards/xgavehttps://mine/en/cards/x— the
API root silently dropped, every lookup returning "not found", and nothing
pointing at the cause. The check previously existed only for failover
endpoints; removing those left the identical hazard on the one address that
still matters.