HTTP API Toolkit HTTP API Toolkit
HTTP API Toolkit HTTP API Toolkit
DocFX + Singulink = ♥

Search Results for

    Upgrading to 4.0

    This guide covers upgrading an app from 3.x to 4.0. Session tokens and stores change the most; features new in 4.0, such as sign-in codes, passkeys, chunked uploads and rate limiting, have guides of their own.

    Before Upgrading

    • The session cookie has a new format, so users sign in once after upgrading. Services that read the same session cookie are upgraded together.
    • The default session cookie name is __Host-session-token (SessionCookieName) rather than session-token, in the service and the client. While 3.x clients or services are in use, set the name to session-token on both sides. A __Host- name can't be used with CookieDomain or another CookiePath: an app that sets either chooses its own name.
    • A 4.0 hub connection doesn't start against a 3.x service, since it waits for a confirmation that only 4.0 sends (see Confirming connections).
    • Session handling registers ASP.NET Core data protection unless an IDataProtectionProvider is registered first (see Session Handling), and requires the allowed origins (AddAllowedOrigins) at startup.
    • UseApiResponseHandling and services.AddApiResponseHandling() require each other, and an app that calls UseEndpoints after mapping endpoints on the app fails to start (see Streaming Responses).
    • The client package no longer depends on Microsoft.Extensions.Http: an app that used it through the toolkit references it itself.
    • The shared and client packages also target .NET Standard 2.0, for .NET Framework 4.6.2 and later. Apps on other runtimes that load it, such as .NET 8 and 9, get a build warning: they are not supported.
    • Assemblies built against 3.x are rebuilt: several signatures change in ways that keep calls compiling, such as the client's path parameters (see Clients).

    Session Tokens and Stores

    • ISessionToken only has UserId. Remove RefreshedUtc, ValidFor, Generation, IsPersistent and RefreshAfter from your token type; the refresh interval is SessionHandlingOptions.RefreshAfter (see The Session Token).
    • ISessionStoreContextFactory<T> and ISessionStoreContext<T> are replaced by the scoped ISessionStore<TSessionToken>, the second type argument of AddHttpSessionHandling. Its methods take a cancellation token.
    • CreateTokenAsync and SessionLookupResult.IsTokenStale are replaced by the replacement token of SessionLookupResult<TSessionToken>.
    • SignInInfo derives from SessionRecord, and SessionExpiry (a time span) is replaced by ExpiresUtc (a time). SessionRecord has required properties instead of a constructor, and a new CreatedUtc that the store saves when a session is created and returns when it loads it (see Signing In and Out).
    • The device that a session records is at most SessionHandlingOptions.MaxDeviceLength characters, 512 by default.
    • SetToken throws for a request without a session.
    • The AddHttpSessionHandling overload without JSON type info is not trimming- or AOT-safe; use the overload that takes a JsonTypeInfo<T>.

    Existing sessions need values for the new columns, or can simply be deleted, since users sign in again anyway. In SQL Server, where the 3.x SessionExpiry column holds ticks (a bigint):

    ALTER TABLE Sessions ADD ExpiresUtc datetime2 NULL;
    UPDATE Sessions SET ExpiresUtc = DATEADD(second, SessionExpiry / 10000000, RefreshedUtc);
    ALTER TABLE Sessions ALTER COLUMN ExpiresUtc datetime2 NOT NULL;
    ALTER TABLE Sessions DROP COLUMN SessionExpiry;
    
    -- Existing sessions count from the migration:
    ALTER TABLE Sessions ADD CreatedUtc datetime2 NOT NULL DEFAULT SYSUTCDATETIME();
    
    -- Or from their last refresh:
    ALTER TABLE Sessions ADD CreatedUtc datetime2 NULL;
    UPDATE Sessions SET CreatedUtc = RefreshedUtc;
    ALTER TABLE Sessions ALTER COLUMN CreatedUtc datetime2 NOT NULL;
    

    Session Behavior

    • SessionHandlingOptions are validated at startup: an expiry that is not longer than RefreshAfter fails it.
    • SignInAsync rejects requests from origins that are not allowed, as retrieving the session does.
    • SignInAsync ends the request's previous session, so an app that signs in on a request that has a session, such as to switch accounts, loses the previous session's record.
    • A store can end a session by throwing an UnauthorizedApiException from GetSessionAsync (see Ending Sessions).
    • In an app that registers SignalR, hub connections whose sessions have ended are closed, unless SessionHandlingOptions.RevalidateHubConnections is false (see Rechecking the sessions of open connections).

    Origins

    • An allowed origin given as a host, such as example.com, matches https only, and http too for loopback hosts. List other http origins with their scheme, such as http://intranet.example.com (see OriginValidator). Origins with a scheme or a port now match as given.
    • An allowed origin that is not a valid host pattern throws an ArgumentException when AddAllowedOrigins is called.
    • UseAllowedOrigins exposes the Retry-After, Reference-Id and Location headers. An app with its own CORS policy exposes the ones its clients read.

    Errors

    • With AddApiExceptionHandler, requests that minimal API endpoints can't read are reported as API errors in every environment (see Requests That Cannot Be Read).
    • HubOptionsExtensions is renamed to ApiHubOptionsExtensions.
    • The toolkit's own errors carry error codes (see Error Codes). Clients that read the response body themselves see the code in brackets before the message.
    • The JSON null handling also rejects a null in a property that doesn't allow one when writing: such a response fails with a 500, and a client throws a JsonException for such a command (see JSON Serialization).
    • Route templates without a leading slash gain one in endpoints mapped after UseApiResponseHandling, which changes the http.route tag of metrics and traces.

    Clients

    • Request paths are an InterpolatedApiPathHandler instead of a string in CreateRequest, GetApiUrl and CreateHubConnection. Calls compile as before; overrides change their parameter type.
    • Values in interpolated paths are escaped, so a / in a value no longer starts a segment: use the raw format, as in $"items?{queryString:raw}", for values that hold several segments or a query string. Paths that lead outside the base address throw (see Paths), and alignment, as in {value,8}, doesn't compile.
    • A hub outside the client's base address is connected through a child client whose GetBaseAddress returns the hubs' address:
    // Created with new HubsApiClient(this) by the client whose base address is https://api.example.com/business/v1/.
    public sealed class HubsApiClient(ApiClientBase parent) : SignalRApiClientBase(parent)
    {
        protected override Uri GetBaseAddress() => new("https://api.example.com/hubs/");
    
        public ApiHubConnection CreateChatConnection() => CreateHubConnection("chat");
    }
    
    • The query string parameters of CreateRequest, GetApiUrl and CreateHubConnection are one parameter, query, passed as a collection expression: CreateRequest(HttpMethod.Get, "items", [("page", 2), ("filter", filter)]).
    • GetDefaultQueryParams and the DefaultUserIdPreconditionQueryName and DefaultSessionCookieName constants are gone: child clients take the user ID precondition as a constructor argument, and UserIdPreconditionQueryName and SessionCookieName hold the names.
    • A base address whose path has no trailing slash is used as if it had one.
    • IsPersistentSession is read-only: the app passes its "remember me" choice to its sign-in endpoint.
    • The constructors that take a type info resolver apply the JSON null handling, their parameter is named typeInfoResolver rather than serializerContext, and their configuration callback runs after the resolver is set (see JSON Serialization).
    • Redirects are not followed, except on browser platforms: a 3xx response throws an ApiException.
    • The session cookie is only taken from responses from the base address's host.
    • Hub connections skip the negotiate request, so ConnectionId is null. A service that needs negotiation, such as Azure SignalR Service, turns it back on in the configureOptions callback of CreateHubConnection.
    • A 401 with the sign-in-required code ends the session on the device (see Session Persistence).
    • ApiHubConnection.StartAsync completes once the hub accepted the connection, so an error from the hub's OnConnectedAsync fails the start (see Using the Contract on the Client).
    • An error response that is not in the toolkit's format keeps its status instead of being reported as a 500.
    • RequestTimeout also covers reading the response body.
    • An IOException while reading a JSON response is thrown as an HttpRequestException.
    • The new protected UploadAsync and UploadStreamingAsync can clash with methods of the same name in a derived client (see Chunked Uploads in Client Apps).
    • VoidApiResponse is internal.
    • New types such as ConflictApiException, UploadSource and UploadOptions can clash with an app's own types of the same name.

    Next Steps

    Continue with these related articles:

    • Session Handling - The session token, the session store and signing in.
    • Building API Clients - Paths, session persistence and child clients.
    • JSON Serialization - The null handling on the service and in clients.
    © Singulink. All rights reserved.