Just a quick announcement to document some upcoming changes for anyone using the v5.9.3 pre-release NuGet packages on MyGet.
A large part of this release was focused on adding Async APIs to many of ServiceStack’s providers most of which now have both Sync & Async APIs starting with ICacheClientAsync
which all ServiceStack Caching Providers now support in addition to ICacheClient
.
Which you can access from inside your ServiceStack Services (and other MVC/Razor base classes) with the CacheAsync
property, e.g:
public Task<object> Any(MyRequest request)
{
var item = await CacheAsync.GetAsync<Item>("key");
//....
}
Outside of ServiceStack you can the AppHost.GetCacheClientAsync()
API to access the ICacheClientAsync
provider:
var cache = HostContext.AppHost.GetCacheClientAsync();
var item = await cache.GetAsync<Item>("key");
Nothing different is needed to register ICacheClientAsync
which utilizes the existing ICacheClient
dependency since all built-in caching providers implements both. In addition you can even use the ICacheClientAsync
APIs even when using your own ICacheClient
providers as it will return an Async ICacheClientAsync
wrapper over Sync ICacheClient
APIs.
In order to implement true async caching providers all underlying clients also needed to implement Async APIs.
Redis Async
The biggest client library implemented in this release by sheer API surface area is ServiceStack.Redis. All Redis Client Managers also implement both IRedisClientsManager
and IRedisClientsManagerAsync
so you can use your existing Redis configuration to access Redis in your Services with GetRedisAsync()
, e.g:
public async Task<object> Any(AsyncRedis request)
{
await using var redis = await GetRedisAsync();
await redis.IncrementAsync(nameof(AsyncRedis), request.Value);
return new AsyncRedisResponse {
Value = await redis.GetAsync<long>(nameof(AsyncRedis))
};
}
Or should you wish you can use both Sync/Async APIs in the same project, e.g:
public async object Any(SyncRedis request)
{
Redis.Increment(nameof(SyncRedis), request.Value);
return new SyncRedisResponse {
Value = Redis.Get<long>(nameof(SyncRedis))
};
}
The async support in ServiceStack.Redis differs from other async APIs in that it aimed for maximum efficiency so uses ValueTask
& other modern Async APIs so it requires a minimum v4.7.2+ .NET Framework or .NET Standard 2.0 (i.e. .NET Core) project. All other Async APIs return the more interoperable Task
responses for their async APIs.
If you’re using ServiceStack.Redis in your own (i.e. non ServiceStack) projects you could just register IRedisClientsManagerAsync
to force usage of async APIs as it only lets you resolve async only IRedisClientAsync
and ICacheClientAsync
clients, e.g:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRedisClientsManagerAsync>(c => new RedisManagerPool());
}
//...
public class MyDep
{
private IRedisClientsManagerAsync manager;
public MyDep(IRedisClientsManagerAsync manager) => this.manager = manager;
public async Task<long> Incr(string key, uint value)
{
await using var redis = await manager.GetClientAsync();
return await redis.IncrementAsync(key, value);
}
}
PocoDynamo Async
Just like ServiceStack.Redis the ServiceStack.Aws PocoDynamo Dynamo DB client now also supports both sync & async APIs where the IPocoDynamo
interface inherits IPocoDynamoAsync
interface so you can use the same client to make sync & async API calls whilst continue to use the
same PocoDynamo registration.
Azure Table Storage Async
Likewise AzureTableCacheClient
also implements ICacheClientAsync
which
container.Register<ICacheClient>(new AzureTableCacheClient(cacheConnStr));
Async Auth Repositories
All built-in ServiceStack Auth Repositories now also implement IUserAuthRepositoryAsync
which you can use inside ServiceStack Services with the AuthRepositoryAsync
property, e.g:
public async Task<object> Post(GetUserAuth request)
{
var userAuth = await AuthRepositoryAsync.GetUserAuthByUserNameAsync(request.UserName);
if (userAuth == null)
throw HttpError.NotFound(request.UserName);
return userAuth;
}
Outside of ServiceStack you can access it from the AppHost.GetAuthRepositoryAsync()
API, e.g:
var authRepo = HostContext.AppHost.GetAuthRepositoryAsync();
await using (authRepo as IAsyncDisposable)
{
//...
}
Like the caching providers the async Auth Repositories makes use of the existing IAuthRepository
registration so no additional configuration is needed. Also your Services can use the IAuthRepositoryAsync
APIs above even for your own sync IAuthRepository
providers as it will return a IAuthRepositoryAsync
wrapper API in its place.
Async Auth Providers
To make usage of the new async API functionality all of ServiceStack’s built-in Auth Providers were rewritten to use the new Async APIs. If you’re only using the existing Auth Providers this will be a transparent detail, however your own Custom Auth Providers will need to change as all existing Sync I/O base class APIs have been refactored into Async APIs.
JWT UseTokenCookie
When JWT is enabled if you wanted to return your Authenticated UserSession into a stateless JWT Token Cookie your clients would’ve needed to request it with UseTokenCookie on the Authenticate Request or in a hidden FORM Input. This capability was only available for Authenticate
requests as OAuth requests would’ve needed a separate call to Convert their Server Session into a JWT Cookie.
You can now configure this behavior on the server with the new UseTokenCookie
on the JWT Auth Provider which now works for both successful Authenticate
Requests & OAuth Sign Ins:
new JwtAuthProvider(appSettings) {
UseTokenCookie = true,
},
Breaking Changes
Async Auth Providers
The recommendation would be change your existing Auth Providers to use the new Async APIs which all follow the same async method convention, i.e:
- Has an
*Async
suffix - Takes an optional
CancellationToken
as its last parameter - Returns a
Task
Here’s an example of all these changes to convert a sync into an async method:
int Add(int value);
Task<int> AddAsync(int value, CancellationToken token = default);
So if your custom Auth Provider inherits from CredentialsAuthProvider
it would now need to implement:
//Async
public class CustomCredentialsAuthProvider : CredentialsAuthProvider
{
public virtual async Task<bool> TryAuthenticateAsync(IServiceBase authService,
string userName, string password, CancellationToken token=default)
{
//Add here your custom auth logic (database calls etc)
//Return true if credentials are valid, otherwise false
}
public override async Task<object> AuthenticateAsync(IServiceBase authService,
IAuthSession session, Authenticate request, CancellationToken token = default)
{
//Fill IAuthSession with data you want to retrieve in the app eg:
session.FirstName = "some_firstname_from_db";
//Call base method to Save Session and fire Auth/Session callbacks:
return await base.AuthenticateAsync(authService, session, tokens, authInfo, token);
}
}
To simplify migration of existing Auth Providers when upgrading ServiceStack, the popular Auth Providers below used to implement Custom Auth Providers now have a Sync
suffix:
CredentialsAuthProviderSync
BasicAuthProviderSync
AuthProviderSync
OAuthProviderSync
OAuth2ProviderSync
So the easiest way to migrate would be to just add a *Sync
suffix to your base class, e.g:
//Sync
public class CustomCredentialsAuthProvider : CredentialsAuthProviderSync
{
public override bool TryAuthenticate(IServiceBase authService,
string userName, string password)
{
}
public override IHttpResult OnAuthenticated(IServiceBase authService,
IAuthSession session, IAuthTokens tokens,
Dictionary<string, string> authInfo)
{
return base.OnAuthenticated(authService, session, tokens, authInfo);
}
}
Note that all sync providers use continue to use the sync ICacheClient
and IAuthRepository
and Session APIs whilst the async providers only use the new async providers. Since there are shims to support when no async APIs are available, you can continue to use either async or sync APIs without issue.
Auth Response & URL Redirect Filters
If your AuthProvider implements IAuthResponseFilter
to implement for intercepting successful Authenticate Request DTO requests, the interface gains an additional ResultFilterAsync
method
for intercepting successful OAuth redirect responses:
public interface IAuthResponseFilter
{
// Intercept successful Authenticate Request DTO requests
void Execute(AuthFilterContext authContext);
// Intercept successful OAuth redirect requests
Task ResultFilterAsync(AuthResultContext context, CancellationToken token);
}
Which you can provide an empty implementation by returning a completed task, e.g:
public Task ResultFilterAsync(AuthResultContext context, CancellationToken token)
=> Task.CompletedTask;
The OAuth URL Filters have changed from being passed an AuthProvider
to an AuthContext
, if your URL filter made use of the AuthProvider
it can be accessed from AuthContext.AuthProvider
, e.g:
SuccessRedirectUrlFilter = (authProvider,url) => ...;
SuccessRedirectUrlFilter = (authContext,url) => authContext.AuthProvider ...;
Together these features is used to implement the new JwtAuthProvider.UseTokenCookie
feature.
Session Save APIs
Whilst ServiceStack has been changed to use async APIs it will only fire OnSaveSessionAsync()
AppHost callback to save the session. So if you previously have overridden OnSaveSession
in your AppHost to intercept when sessions are saved you’ll need to change it to override OnSaveSessionAsync
instead, e.g:
[Obsolete("Use OnSaveSessionAsync")]
public override void OnSaveSession(IRequest httpReq, IAuthSession session, TimeSpan? expiresIn = null)
{
}
public override Task OnSaveSessionAsync(IRequest httpReq, IAuthSession session, TimeSpan? expiresIn = null, CancellationToken token=default)
{
}
Note that if you have code that calls the sync SaveSession()
to save a Users Session you would either need to change it to use SaveSessionAsync()
or override both APIs above if you’re intercepting session saves.
That’s the main gotcha’s from the refactor to use async APIs, most of the time the Async APIs are just additive so it shouldn’t effect existing code.
Authenticate & Register Services
If you’re using HostContext.ResolveService<T>
to call either the AuthenticateService
or RegisterService
APIs, e.g. to Authenticate or impersonate a user:
using var authService = HostContext.ResolveService<AuthenticateService>(req);
var response = authService.Post(new Authenticate
{
provider = Name,
UserName = userName,
Password = password
});
The Post()
API implementation is now a deprecated “sync over async” implementation which although will continue to work, you’re encouraged to change it to use the async version, e.g:
var response = await authService.PostAsync(new Authenticate { ... });
If you do run into any other issues after upgrading to v5.9.3+ please let me know and I’ll rectify them ASAP.