Skip to content

Releases

v0.9.1

Choose a tag to compare

@github-actions github-actions released this 08 Nov 21:05
84ae915

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.Abstractions and
    Microsoft.Extensions.Options now 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
    (==, Equals and GetHashCode disagreed), because the collections they hold were compared by reference.
    This covers Card, Attack, DetailedVariant, Series, CardSet and TcgPlayerPricing. 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, GetHashCode and ToString cost time proportional to the card's contents, so key a cache on
      Card.Id rather than on the whole card.
    • ToString on a set of 300 cards prints all 300.

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 17:17
5f8a5d3

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) is SearchDetailedAsync(filter, cancellationToken).
      Set Page and ItemsPerPage on the CardFilter instead: new CardFilter { Name = "Furret", Page = 2, ItemsPerPage = 25 }.
    • StreamAsync(query, pageSize, cancellationToken) is StreamAsync(query, cancellationToken). The page size is the
      itemsPerPage of query.Page(1, n), and is 100 when the query sets none. The page number is still ignored.
    • StreamAsync now 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 (naming itemsPerPage) when the query is built, not from
      StreamAsync (which named pageSize). SearchDetailedAsync names the property, Page or ItemsPerPage, where it
      used to name page and itemsPerPage.
    • Apart from that, the requests sent are unchanged.

Added

  • Catalog.GetValuesAsync(name) and Catalog.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 with ArgumentException. It used to be sent as is, and the URI layer
    collapsed it, so Cards.GetAsync("..") requested /v2/en/ rather than a card.

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 19 Oct 04:03
c3d8836

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())
    threw NotSupportedException, while names[0] on a list or array worked; the string indexer is named Chars rather
    than Item, and the translator only recognised Item. Nothing that worked before changes.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 23:24
7938a69

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 gives null instead of a malformed string.
  • Breaking: CachedResponse is a class with reference equality rather than a record. It no
    longer supports with or value ==; create a new instance with an object initializer.
  • Breaking: QueryOperator is internal. No public member used it.
  • Breaking: the TcgDexApiException constructor taking only a status code is removed. It
    produced Kind = Unspecified; use the constructor that takes a TcgDexErrorKind.
  • AddTcgDexWithCaching registers TcgDexCacheOptions through the options pattern, so
    Configure<TcgDexCacheOptions>, an appsettings.json binding and IOptions<TcgDexCacheOptions>
    now work alongside the configureCache delegate. Previously only the delegate was applied.
    The merged options are validated at start-up, like TcgDexOptions: a MaxEntries below 1 or a
    negative time-to-live fails with OptionsValidationException.
  • An unreadable updated timestamp on a card or a pricing block now reads as null, as
    ReleaseDate already did, instead of failing the whole card.

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 00:16
040ee1f

Added

  • TcgDexApiException.Kind (a new TcgDexErrorKind: 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 by AddTcgDexWithCaching: 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.ReleaseDate and Serie.ReleaseDate are now DateTime? instead of string?,
    on every target framework. The value is a date of kind Unspecified with no time component.
    An absent or malformed date reads as null rather than failing the set or series.
  • Breaking: TcgDexJsonContext is no longer public. Deserialize models through the client, or
    with reflection-based JsonSerializer if you serialize them yourself (not Native AOT-safe). Your
    own source-generated JsonSerializerContext cannot be used: the models name internal converters.
  • Breaking: invalid TcgDexOptions passed to AddTcgDex or AddTcgDexWithCaching now fail
    with OptionsValidationException when the host starts (ValidateOnStart), or on first resolution
    without a host, instead of ArgumentException at registration. Validation now covers the final
    options from every source, such as appsettings.json or another Configure<TcgDexOptions> call,
    and your configure delegate no longer runs an extra time at registration.
  • Where now accepts array elements, indexers and parameterless instance methods on the value
    side, such as c.Id == ids[i] or c.Name == name.Trim(), and applies casts with C# semantics.
    It still never compiles the expression, so Native AOT is unaffected.
  • DeserializePricing = false now also skips the pricing on every VariantsDetailed entry, not
    only Card.Pricing.
  • Options are read once, when the client is created. GraphQL searches used to pick up later changes
    to GraphQlEndpoint, Timeout and MaxResponseBytes; they no longer do, matching REST.
  • Calling AddTcgDex or AddTcgDexWithCaching a 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>, IOptionsSnapshot and IOptionsMonitor now see every configured
    property. Only BaseAddress, Language and GraphQlEndpoint used 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
    IHttpClientFactory rebuilding its handlers (every two minutes by default). Both used to reset.
  • TcgDexOptions.Validate now rejects a relative GraphQlEndpoint with ArgumentException.
  • The ActivitySource now reports the SDK's version instead of 0.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
    than TargetInvocationException, and a cast that cannot succeed ((byte)300,
    (int)double.NaN) throws NotSupportedException naming the expression.
  • MemoryTcgDexResponseCache no 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 throws JsonException; the key is ignored.
  • Contains, StartsWith and EndsWith send the value as written. Before, Contains("%2A") was
    decoded to * and matched every card.
  • SearchDetailedAsync throws ArgumentOutOfRangeException for a page or itemsPerPage below
    one, as the REST query does, instead of sending it.
  • A GraphQL search result whose set is null is dropped and logged, like a null entry, instead of
    being returned with a null Set.
  • TcgDexLanguages.All can no longer be modified by casting it to a mutable list.

Documentation

  • Where documents 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

Choose a tag to compare

@github-actions github-actions released this 16 Oct 01:11
eaf7d89

Fixed

  • A connection that drops partway through a response body now throws TcgDexApiException instead
    of a raw IOException. Successful responses, GraphQL and TcgDexCachingHandler carry the
    original failure as InnerException. Error responses report their status code instead.
  • A 404 whose body cannot be read (reset or larger than MaxResponseBytes) now throws
    TcgDexApiException with StatusCode 404 instead of returning null, 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 an IOException.
  • On REST requests, a body that fails to read, or is not valid JSON, is now logged and marks the
    request's Activity as failed, like other failures.
  • TcgDexCachingHandler keeps an If-None-Match the caller set, adding its own ETag beside it,
    instead of replacing it. A 304 Not Modified is served from the cache when the caller sent no
    validator of their own, or when the 304 names the cached ETag (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 cached ETag changed carries only the current one.
  • TcgDexCachingHandler no longer adds its ETag beside a caller's If-None-Match: *, which
    produced an invalid header; a 304 answering * is returned to the caller untouched.
  • With CoalesceConcurrentRequests, a request carrying its own If-None-Match is 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
    TcgDexApiException saying so, instead of reporting that TcgDexOptions.Timeout expired. The
    "timed out" message is reserved for the SDK's own timeout.
  • When the caller cancels a request, the OperationCanceledException now carries the caller's own
    token, so catch (OperationCanceledException ex) when (ex.CancellationToken == token) matches.
  • An error response (such as a 500) whose body stalls past TcgDexOptions.Timeout now throws
    TcgDexApiException (timed out) instead of a raw cancellation exception.
  • TcgDexCachingHandler now stores the new ETag when a 304 Not Modified carries one, so the
    next revalidation uses the server's current validator.
  • TcgDexCachingHandler no longer sends If-None-Match twice when a handler above it re-sends the
    same request.
  • TcgDexCachingHandler disposes an error response if a custom cache's RemoveAsync throws.
  • A response declaring a large Content-Length with 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.Timeout may not end a response body that stalls
    after its headers arrive, because that platform's HttpClientHandler does not cancel an
    in-progress read. This is known platform behaviour, not measured by this SDK's tests.
  • TcgDexCachingHandler now throws TcgDexApiException for a body whose connection fails mid-read,
    where it previously threw a raw IOException. An oversized body already threw
    TcgDexApiException. That choice is deliberate, so retry policies do not re-download it.

v0.6.2

Choose a tag to compare

@github-actions github-actions released this 15 Oct 22:49
b96dd93

Changed

  • net10.0: Microsoft.Extensions.Http, Microsoft.Extensions.Logging.Abstractions and
    Microsoft.Extensions.Options now 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

Choose a tag to compare

@github-actions github-actions released this 12 Sep 22:36
68aadfe

Added

  • User-Agent header 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-supplied HttpClient's own headers.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 11 Sep 15:09
b43b5fc

Added

  • ThirdParty external marketplace ids. TCGdex started serving Cardmarket, TCGplayer and
    Cardtrader product ids on 2026-09. New Card.ThirdParty and DetailedVariant.ThirdParty
    properties, each independently nullable and placed the same way Pricing already 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. Cardtrader in particular is
    present only intermittently on the live API, so do not treat its absence on one fetch as
    permanent.

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 06 Sep 21:58
a2c45d0

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 the TcgDexMirror enum. The same deployment retired the
    per-node hostnames. api.na1.tcgdex.net and api.na2.tcgdex.net no longer
    route at all, so two of the six members could only ever fail; upstream now
    recommends api.tcgdex.net for everyone. Left in place they would be an
    invitation to use hosts that no longer work.

  • This also removes the 404 discrimination added after 0.4.0 but never
    released, which stopped an unrouted host's 404 being reported as a missing
    card. That defect is resolved by the feature it lived in no longer existing.
    The reasoning is kept in docs/learnings.md, because it
    is about status codes rather than about failover.

Kept

  • Pointing the SDK at a server of your own. BaseAddress and
    GraphQlEndpoint are 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 only BaseAddress
    leaves GraphQL queries going to the official host.

Added

  • BaseAddress is now validated to end with /. Paths resolve relative to
    it, so https://mine/v2 + en/cards/x gave https://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.