diff --git a/.vscode/tasks.json b/.vscode/tasks.json index c549ce77..d5b6d366 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,17 +1,20 @@ { // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format - "version": "0.1.0", + "version": "2.0.0", "command": "dotnet", - "isShellCommand": true, "args": [], "tasks": [ { - "taskName": "build", - "args": [ "tests/ServiceStack.Redis.Tests"], - "isBuildCommand": true, - "showOutput": "silent", - "problemMatcher": "$msCompile" + "label": "build", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "tests/ServiceStack.Redis.Tests" + ], + "problemMatcher": "$msCompile", + "group": "build" } ] } \ No newline at end of file diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 00000000..42daf5f4 --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/NuGet/NuGetPack.cmd b/NuGet/NuGetPack.cmd deleted file mode 100644 index b010c460..00000000 --- a/NuGet/NuGetPack.cmd +++ /dev/null @@ -1,2 +0,0 @@ -SET NUGET=..\src\.nuget\nuget -%NUGET% pack ServiceStack.Redis\servicestack.redis.nuspec -symbols diff --git a/NuGet/NuGetPush.cmd b/NuGet/NuGetPush.cmd deleted file mode 100644 index 0b9fda08..00000000 --- a/NuGet/NuGetPush.cmd +++ /dev/null @@ -1,3 +0,0 @@ -SET NUGET=..\src\.nuget\nuget -%NUGET% push ServiceStack.Redis.3.9.60.nupkg -%NUGET% push ServiceStack.Redis.3.9.60.symbols.nupkg diff --git a/NuGet/ServiceStack.Redis.Core/servicestack.redis.core.nuspec b/NuGet/ServiceStack.Redis.Core/servicestack.redis.core.nuspec deleted file mode 100644 index 541f9ea1..00000000 --- a/NuGet/ServiceStack.Redis.Core/servicestack.redis.core.nuspec +++ /dev/null @@ -1,35 +0,0 @@ - - - - ServiceStack.Redis.Core - ServiceStack.Redis .NET Standard 2.0 - 5.0.0 - ServiceStack - ServiceStack - - .NET Standard 2.0 version of ServiceStack.Common - - https://github.com/ServiceStack/ServiceStack.Redis - https://servicestack.net/terms - true - https://servicestack.net/img/logo-32.png - Redis NoSQL Client Distributed Cache PubSub Messaging Transactions - en-US - ServiceStack and contributors - - - - - - - - - - - - - - - - - diff --git a/NuGet/ServiceStack.Redis/servicestack.redis.nuspec b/NuGet/ServiceStack.Redis/servicestack.redis.nuspec deleted file mode 100644 index 803a65d2..00000000 --- a/NuGet/ServiceStack.Redis/servicestack.redis.nuspec +++ /dev/null @@ -1,40 +0,0 @@ - - - - ServiceStack.Redis - C# Redis client for the Redis NoSQL DB - 5.0.0 - ServiceStack - ServiceStack - - C# Redis Client for the worlds fastest distributed NoSQL datastore. - Byte[], String and POCO Typed clients. - Thread-Safe Basic and Pooled client managers included. - - https://github.com/ServiceStack/ServiceStack.Redis - https://servicestack.net/terms - true - https://servicestack.net/img/logo-32.png - Redis NoSQL Client Distributed Cache PubSub Messaging Transactions - en-US - ServiceStack and contributors - - - - - - - - - - - - - - - - - - - - diff --git a/README.md b/README.md index b2497965..cfbbe837 100644 --- a/README.md +++ b/README.md @@ -1,998 +1,5 @@ -Follow [@ServiceStack](https://twitter.com/servicestack) or join the [Google+ Community](https://plus.google.com/communities/112445368900682590445) -for updates, or [StackOverflow](http://stackoverflow.com/questions/ask) or the [Customer Forums](https://forums.servicestack.net/) for support. +Follow [@ServiceStack](https://twitter.com/servicestack), [view the docs](https://docs.servicestack.net), use [StackOverflow](https://stackoverflow.com/questions/ask?tags=servicestack,servicestack.redis) or [Customer Forums](https://forums.servicestack.net/) for support. -# C#/.NET Client for Redis +# Read ServiceStack.Redis Docs at [docs.servicestack.net/redis](https://docs.servicestack.net/redis/) -[ServiceStack's C# Redis Client](https://github.com/ServiceStack/ServiceStack.Redis) is a simple, high-performance and feature-rich C# Client for Redis with native support and [high-level abstractions](https://github.com/ServiceStack/ServiceStack.Redis/wiki/DesigningNoSqlDatabase) for serializing POCOs and Complex Types. - -There are a number of different APIs available with the `RedisClient` implementing the following interfaces: - - * [ICacheClient](http://docs.servicestack.net/caching) - If you are using Redis solely as a cache, you should bind to the ServiceStack's common interface as there already are In-Memory an Memcached implementations available in ServiceStack, allowing you to easily switch providers - * [IRedisNativeClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisNativeClient.cs) - For those wanting a low-level raw byte access (where you can control your own serialization/deserialization) that map 1:1 with Redis operations of the same name. - -For most cases if you require access to Redis specific functionality you would want to bind to the interface below: - - * [IRedisClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisClient.cs) - Provides a friendlier, more descriptive API that lets you store values as strings (UTF8 encoding). - * [IRedisTypedClient](https://github.com/ServiceStack/ServiceStack/tree/master/src/ServiceStack.Interfaces/Redis/Generic) - created with `IRedisClient.As()` - it returns a 'strongly-typed client' that provides a typed-interface for all redis value operations that works against any C#/.NET POCO type. - -The interfaces works cleanly with any IOC and allows your app logic to bind to implementation-free interfaces which can easily be mocked and substituted. - -An overview of class hierarchy for the C# Redis clients looks like: - - RedisTypedClient (POCO) > RedisClient (string) > RedisNativeClient (raw byte[]) - -With each client providing different layers of abstraction: - - * The RedisNativeClient exposes raw `byte[]` apis and does no marshalling and passes all values directly to redis. - * The RedisClient assumes `string` values and simply converts strings to UTF8 bytes before sending to Redis - * The RedisTypedClient provides a generic interface allowing you to add POCO values. The POCO types are serialized using [.NETs fastest JSON Serializer](http://www.servicestack.net/mythz_blog/?p=344) which is then converted to UTF8 bytes and sent to Redis. - -### API Overview - -[![Redis Client API](http://mono.servicestack.net/img/Redis-annotated-preview.png)](http://mono.servicestack.net/img/Redis-annotated.png) - -## Redis Connection Strings - -Redis Connection strings have been expanded to support the more versatile URI format which is now able to capture most of Redis Client -settings in a single connection string (akin to DB Connection strings). - -Redis Connection Strings supports multiple URI-like formats, from a simple **hostname** or **IP Address and port** pair to a -fully-qualified **URI** with multiple options specified on the QueryString. - -Some examples of supported formats: - - localhost - 127.0.0.1:6379 - redis://localhost:6379 - password@localhost:6379 - clientid:password@localhost:6379 - redis://clientid:password@localhost:6380?ssl=true&db=1 - -> More examples can be seen in -[ConfigTests.cs](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/ConfigTests.cs) - -Any additional configuration can be specified as QueryString parameters. The full list of options that can be specified include: - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SslboolIf this is an SSL connection
DbintThe Redis DB this connection should be set to
ClientstringA text alias to specify for this connection for analytic purposes
PasswordstringUrlEncoded version of the Password for this connection
ConnectTimeoutintTimeout in ms for making a TCP Socket connection
SendTimeoutintTimeout in ms for making a synchronous TCP Socket Send
ReceiveTimeoutintTimeout in ms for waiting for a synchronous TCP Socket Receive
IdleTimeOutSecsintTimeout in Seconds for an Idle connection to be considered active
NamespacePrefixstringUse a custom prefix for ServiceStack.Redis internal index colletions
- -## Download - - PM> Install-Package ServiceStack.Redis - -_Latest v4+ on NuGet is a [commercial release](https://servicestack.net/redis) with [free quotas](https://servicestack.net/download#free-quotas)._ - -#### [Getting Started with AWS ElastiCache Redis and ServiceStack](https://github.com/ServiceStackApps/AwsGettingStarted) - -ServiceStack.Redis has great support AWS's ElastiCache Redis solution, follow this guide to help getting up and running quickly: - -- [ElastiCache Redis](https://github.com/ServiceStackApps/AwsGettingStarted/blob/master/docs/redis-guide.md) - -### Try out [ServiceStack.Redis Live](http://gistlyn.com/redis-todo) - -A great way to try out ServiceStack.Redis is on [gistlyn.com](http://gistlyn.com) which lets you immediately -run and explore Redis features from the comfort of your browser with zero software install: - -[![](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/redis/gistlyn-redis.png)](http://gistlyn.com/redis-todo) - -## Redis Client Managers - -The recommended way to access `RedisClient` instances is to use one of the available Thread-Safe Client Managers below. Client Managers are connection factories which is ideally registered as a Singleton either in your IOC or static classes. - -### RedisManagerPool - -With the enhanced Redis URI Connection Strings we've been able to simplify and streamline the existing `PooledRedisClientManager` implementation and have extracted it out into a new clients manager called `RedisManagerPool`. - -In addition to removing all above options on the Client Manager itself, readonly connection strings have also been removed so the configuration ends up much simpler and more aligned with the common use-case: - -```csharp -container.Register(c => - new RedisManagerPool(redisConnectionString)); -``` - -**Pooling Behavior** - -Any connections required after the maximum Pool size has been reached will be created and disposed outside of the Pool. By not being restricted to a maximum pool size, the pooling behavior in `RedisManagerPool` can maintain a smaller connection pool size at the cost of potentially having a higher opened/closed connection count. - -### PooledRedisClientManager - -If you prefer to define options on the Client Manager itself or you want to provide separate Read/Write and ReadOnly -(i.e. Master and Slave) redis-servers, use the `PooledRedisClientManager` instead: - -```csharp -container.Register(c => - new PooledRedisClientManager(redisReadWriteHosts, redisReadOnlyHosts) { - ConnectTimeout = 100, - //... - }); -``` - -**Pooling Behavior** - -The `PooledRedisClientManager` imposes a maximum connection limit and when its maximum pool size has been reached will instead block on any new connection requests until the next `RedisClient` is released back into the pool. If no client became available within `PoolTimeout`, a Pool `TimeoutException` will be thrown. - -### BasicRedisClientManager - -If don't want to use connection pooling (i.e. your accessing a local redis-server instance) you can use a basic (non-pooled) Clients Manager which creates a new `RedisClient` instance each time: - -```csharp -container.Register(c => - new BasicRedisClientManager(redisConnectionString)); -``` - -### Accessing the Redis Client - -Once registered, accessing the RedisClient is the same in all Client Managers, e.g: - -```csharp -var clientsManager = container.Resolve(); -using (IRedisClient redis = clientsManager.GetClient()) -{ - redis.IncrementValue("counter"); - List days = redis.GetAllItemsFromList("days"); - - //Access Typed API - var redisTodos = redis.As(); - - redisTodos.Store(new Todo { - Id = redisTodos.GetNextSequence(), - Content = "Learn Redis", - }); - - var todo = redisTodos.GetById(1); - - //Access Native Client - var redisNative = (IRedisNativeClient)redis; - - redisNative.Incr("counter"); - List days = redisNative.LRange("days", 0, -1); -} -``` - -A more detailed list of the available RedisClient APIs used in the example can be seen in the C# interfaces below: - - - [IRedisClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisClient.cs) - - [IRedisTypedClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/Generic/IRedisTypedClient.cs) - - [IRedisNativeClient](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisNativeClient.cs) - -## [Redis React Browser](https://servicestack.net/redis-react) - -Redis React is a simple user-friendly UI for browsing data in Redis servers which takes advantages of the complex -type conventions built in the ServiceStack.Redis Client to provide a rich, human-friendly UI for navigating related datasets, enabling a fast and fluid browsing experience for your Redis servers. - -#### [Live Demo](http://redisreact.servicestack.net/#/) - -[![](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/livedemos/redis-react/home.png)](http://redisreact.servicestack.net/#/) - -#### Downloads available from [Redis React Home Page](https://servicestack.net/redis-react) - -## [Redis Sentinel](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Redis-Sentinel) - -To use the new Sentinel support, instead of populating the Redis Client Managers with the -connection string of the master and slave instances you would create a single RedisSentinel -instance configured with the connection string of the running Redis Sentinels: - -```csharp -var sentinelHosts = new[]{ "sentinel1", "sentinel2:6390", "sentinel3" }; -var sentinel = new RedisSentinel(sentinelHosts, masterName: "mymaster"); -``` - -This configues a `RedisSentinel` with 3 sentinel hosts looking at **mymaster** group. -As the default port for sentinels when unspecified is **26379** and how RedisSentinel is able to -auto-discover other sentinels, the minimum configuration required is with a single Sentinel host: - -```csharp -var sentinel = new RedisSentinel("sentinel1"); -``` - -### Custom Redis Connection String - -The host the RedisSentinel is configured with only applies to that Sentinel Host, to use the -flexibility of [Redis Connection Strings](#redis-connection-strings) to apply configuration on -individual Redis Clients you need to register a custom `HostFilter`: - -```csharp -sentinel.HostFilter = host => "{0}?db=1&RetryTimeout=5000".Fmt(host); -``` - -An alternative to using connection strings for configuring clients is to modify -[default configuration on RedisConfig](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Redis-Config). - -### Change to use RedisManagerPool - -By default RedisSentinel uses a `PooledRedisClientManager`, this can be changed to use the -newer `RedisManagerPool` with: - -```csharp -sentinel.RedisManagerFactory = (master,slaves) => new RedisManagerPool(master); -``` - -### Start monitoring Sentinels - -Once configured, you can start monitoring the Redis Sentinel servers and access the pre-configured -client manager with: - -```csharp -IRedisClientsManager redisManager = sentinel.Start(); -``` - -Which as before, can be registered in your preferred IOC as a **singleton** instance: - -```csharp -container.Register(c => sentinel.Start()); -``` - -## [Configure Redis Sentinel Servers](https://github.com/ServiceStack/redis-config) - -[![Instant Redis Setup](https://raw.githubusercontent.com/ServiceStack/Assets/master/img/redis/instant-sentinel-setup.png)](https://github.com/ServiceStack/redis-config) - -See the -[redis config project](https://github.com/ServiceStack/redis-config) for a quick way to setup up -the minimal -[highly available Redis Sentinel configuration](https://github.com/ServiceStack/redis-config/blob/master/README.md#3x-sentinels-monitoring-1x-master-and-2x-slaves) -including start/stop scripts for instantly running multiple redis instances on a single (or multiple) -Windows, OSX or Linux servers. - -### [Redis Stats](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Redis-Stats) - -You can use the `RedisStats` class for visibility and introspection into your running instances. -The [Redis Stats wiki](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Redis-Stats) lists the stats available. - -## [Automatic Retries](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Automatic-Retries) - -To improve the resilience of client connections, `RedisClient` will transparently retry failed -Redis operations due to Socket and I/O Exceptions in an exponential backoff starting from -**10ms** up until the `RetryTimeout` of **10000ms**. These defaults can be tweaked with: - -```csharp -RedisConfig.DefaultRetryTimeout = 10000; -RedisConfig.BackOffMultiplier = 10; -``` - -## [ServiceStack.Redis SSL Support](https://github.com/ServiceStack/ServiceStack/wiki/Secure-SSL-Redis-connections-to-Azure-Redis) - -ServiceStack.Redis now supporting **SSL connections** making it suitable for accessing remote Redis server instances over a -**secure SSL connection**. - -![Azure Redis Cache](https://github.com/ServiceStack/Assets/raw/master/img/wikis/redis/azure-redis-instance.png) - -### [Connecting to Azure Redis](https://github.com/ServiceStack/ServiceStack/wiki/Secure-SSL-Redis-connections-to-Azure-Redis) - -As connecting to [Azure Redis Cache](http://azure.microsoft.com/en-us/services/cache/) via SSL was the primary use-case for this feature, -we've added a new -[Getting connected to Azure Redis via SSL](https://github.com/ServiceStack/ServiceStack/wiki/Secure-SSL-Redis-connections-to-Azure-Redis) -to help you get started. - -## [Redis GEO](https://github.com/ServiceStackApps/redis-geo) - -The [release of Redis 3.2.0](http://antirez.com/news/104) brings it exciting new -[GEO capabilities](http://redis.io/commands/geoadd) which will let you store Lat/Long coordinates in Redis -and query locations within a specified radius. To demonstrate this functionality we've created a new -[Redis GEO Live Demo](https://github.com/ServiceStackApps/redis-geo) which lets you click on anywhere in -the U.S. to find the list of nearest cities within a given radius, Live Demo at: http://redisgeo.servicestack.net - - -## Generic APIs for calling Custom Redis commands - -Most of the time when waiting to use a new [Redis Command](http://redis.io/commands) you'll need to wait for an updated version of -**ServiceStack.Redis** to add support for the new commands likewise there are times when the Redis Client doesn't offer every permutation -that redis-server supports. - -With the new `Custom` and `RawCommand` APIs on `IRedisClient` and `IRedisNativeClient` you can now use the RedisClient to send your own -custom commands that can call adhoc Redis commands: - -```csharp -public interface IRedisClient -{ - ... - RedisText Custom(params object[] cmdWithArgs); -} - -public interface IRedisNativeClient -{ - ... - RedisData RawCommand(params object[] cmdWithArgs); - RedisData RawCommand(params byte[][] cmdWithBinaryArgs); -} -``` - -These Custom APIs take a flexible `object[]` arguments which accepts any serializable value e.g. -`byte[]`, `string`, `int` as well as any user-defined Complex Types which are transparently serialized -as JSON and send across the wire as UTF-8 bytes. - -```csharp -var ret = Redis.Custom("SET", "foo", 1); // ret.Text = "OK" - -byte[] cmdSet = Commands.Set; -ret = Redis.Custom(cmdSet, "bar", "b"); // ret.Text = "OK" - -ret = Redis.Custom("GET", "foo"); // ret.Text = "1" -``` - -There are also -[convenient extension methods](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/src/ServiceStack.Redis/RedisDataExtensions.cs) -on `RedisData` and `RedisText` that make it easy to access structured data, e.g: - -```csharp -var ret = Redis.Custom(Commands.Keys, "*"); -var keys = ret.GetResults(); // keys = ["foo", "bar"] - -ret = Redis.Custom(Commands.MGet, "foo", "bar"); -var values = ret.GetResults(); // values = ["1", "b"] - -Enum.GetNames(typeof(DayOfWeek)).ToList() - .ForEach(x => Redis.Custom(Commands.RPush, "DaysOfWeek", x)); -ret = Redis.Custom(Commands.LRange, "DaysOfWeek", 1, -2); -var weekDays = ret.GetResults(); - -weekDays.PrintDump(); // ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"] -``` - -and some more examples using Complex Types with the Custom APIs: - -```csharp -var ret = Redis.Custom(Commands.Set, "foo", new Poco { Name = "Bar" }); // ret.Text = "OK" - -ret = Redis.Custom(Commands.Get, "foo"); // ret.Text = {"Name":"Bar"} -Poco dto = ret.GetResult(); - -dto.Name.Print(); // Bar -``` - -This API is used in most of Redis React UI's -[redis.js](https://github.com/ServiceStackApps/RedisReact/blob/master/src/RedisReact/RedisReact/js/redis.js) -JavaScript client library where Redis server commands are made available via the -[single ServiceStack Service](https://github.com/ServiceStackApps/RedisReact/blob/a1b66603d52d2f18b96227fc455ecb5323e424c8/src/RedisReact/RedisReact.ServiceInterface/RedisServices.cs#L73): - -```csharp -public object Any(CallRedis request) -{ - var args = request.Args.ToArray(); - var response = new CallRedisResponse { Result = Redis.Custom(args) }; - return response; -} -``` - -## Managed Pub/Sub Server - -The Pub/Sub engine powering -[Redis ServerEvents](https://github.com/ServiceStack/ServiceStack/wiki/Redis-Server-Events) and -[Redis MQ](https://github.com/ServiceStack/ServiceStack/wiki/Messaging-and-Redis) has been extracted -and encapsulated it into a re-usable class that can be used independently for handling messages -published to specific [Redis Pub/Sub](http://redis.io/commands#pubsub) channels. - -`RedisPubSubServer` processes messages in a managed background thread that **automatically reconnects** -when the redis-server connection fails and works like an independent background Service that can be -stopped and started on command. - -The public API is captured in the -[IRedisPubSubServer](https://github.com/ServiceStack/ServiceStack/blob/master/src/ServiceStack.Interfaces/Redis/IRedisPubSubServer.cs) interface: - -```csharp -public interface IRedisPubSubServer : IDisposable -{ - IRedisClientsManager ClientsManager { get; } - // What Channels it's subscribed to - string[] Channels { get; } - - // Run once on initial StartUp - Action OnInit { get; set; } - // Called each time a new Connection is Started - Action OnStart { get; set; } - // Invoked when Connection is broken or Stopped - Action OnStop { get; set; } - // Invoked after Dispose() - Action OnDispose { get; set; } - - // Fired when each message is received - Action OnMessage { get; set; } - // Fired after successfully subscribing to the specified channels - Action OnUnSubscribe { get; set; } - // Called when an exception occurs - Action OnError { get; set; } - // Called before attempting to Failover to a new redis master - Action OnFailover { get; set; } - - int? KeepAliveRetryAfterMs { get; set; } - // The Current Time for RedisServer - DateTime CurrentServerTime { get; } - - // Current Status: Starting, Started, Stopping, Stopped, Disposed - string GetStatus(); - // Different life-cycle stats - string GetStatsDescription(); - - // Subscribe to specified Channels and listening for new messages - IRedisPubSubServer Start(); - // Close active Connection and stop running background thread - void Stop(); - // Stop than Start - void Restart(); -} -``` -### Usage - -To use `RedisPubSubServer`, initialize it with the channels you want to subscribe to and assign handlers -for each of the events you want to handle. At a minimum you'll want to handle `OnMessage`: - -```csharp -var clientsManager = new PooledRedisClientManager(); -var redisPubSub = new RedisPubSubServer(clientsManager, "channel-1", "channel-2") { - OnMessage = (channel, msg) => "Received '{0}' from '{1}'".Print(msg, channel) - }.Start(); -``` - -Calling `Start()` after it's initialized will get it to start listening and processing any messages -published to the subscribed channels. - -### Lex Operations - -The new [ZRANGEBYLEX](http://redis.io/commands/zrangebylex) sorted set operations allowing you to query a sorted set lexically have been added. -A good showcase for this is available on [autocomplete.redis.io](http://autocomplete.redis.io/). - -These new operations are available as a 1:1 mapping with redis-server on `IRedisNativeClient`: - -```csharp -public interface IRedisNativeClient -{ - ... - byte[][] ZRangeByLex(string setId, string min, string max, int? skip, int? take); - long ZLexCount(string setId, string min, string max); - long ZRemRangeByLex(string setId, string min, string max); -} -``` - -And the more user-friendly APIs under `IRedisClient`: - -```csharp -public interface IRedisClient -{ - ... - List SearchSortedSet(string setId, string start=null, string end=null); - long SearchSortedSetCount(string setId, string start=null, string end=null); - long RemoveRangeFromSortedSetBySearch(string setId, string start=null, string end=null); -} -``` - -Just like NuGet version matchers, Redis uses `[` char to express inclusiveness and `(` char for exclusiveness. -Since the `IRedisClient` APIs defaults to inclusive searches, these two APIs are the same: - -```csharp -Redis.SearchSortedSetCount("zset", "a", "c") -Redis.SearchSortedSetCount("zset", "[a", "[c") -``` - -Alternatively you can specify one or both bounds to be exclusive by using the `(` prefix, e.g: - -```csharp -Redis.SearchSortedSetCount("zset", "a", "(c") -Redis.SearchSortedSetCount("zset", "(a", "(c") -``` - -More API examples are available in [LexTests.cs](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/LexTests.cs). - -### HyperLog API - -The development branch of Redis server (available when v3.0 is released) includes an ingenious algorithm to approximate the unique elements in a set with maximum space and time efficiency. For details about how it works see Redis's creator Salvatore's blog who [explains it in great detail](http://antirez.com/news/75). Essentially it lets you maintain an efficient way to count and merge unique elements in a set without having to store its elements. -A Simple example of it in action: - -```csharp -redis.AddToHyperLog("set1", "a", "b", "c"); -redis.AddToHyperLog("set1", "c", "d"); -var count = redis.CountHyperLog("set1"); //4 - -redis.AddToHyperLog("set2", "c", "d", "e", "f"); - -redis.MergeHyperLogs("mergedset", "set1", "set2"); - -var mergeCount = redis.CountHyperLog("mergedset"); //6 -``` - -### Scan APIs - -Redis v2.8 introduced a beautiful new [SCAN](http://redis.io/commands/scan) operation that provides an optimal strategy for traversing a redis instance entire keyset in managable-size chunks utilizing only a client-side cursor and without introducing any server state. It's a higher performance alternative and should be used instead of [KEYS](http://redis.io/commands/keys) in application code. SCAN and its related operations for traversing members of Sets, Sorted Sets and Hashes are now available in the Redis Client in the following APIs: - -```csharp -public interface IRedisClient -{ - ... - IEnumerable ScanAllKeys(string pattern = null, int pageSize = 1000); - IEnumerable ScanAllSetItems(string setId, string pattern = null, int pageSize = 1000); - IEnumerable> ScanAllSortedSetItems(string setId, string pattern = null, int pageSize = 1000); - IEnumerable> ScanAllHashEntries(string hashId, string pattern = null, int pageSize = 1000); -} - -//Low-level API -public interface IRedisNativeClient -{ - ... - ScanResult Scan(ulong cursor, int count = 10, string match = null); - ScanResult SScan(string setId, ulong cursor, int count = 10, string match = null); - ScanResult ZScan(string setId, ulong cursor, int count = 10, string match = null); - ScanResult HScan(string hashId, ulong cursor, int count = 10, string match = null); -} -``` - -The `IRedisClient` provides a higher-level API that abstracts away the client cursor to expose a lazy Enumerable sequence to provide an optimal way to stream scanned results that integrates nicely with LINQ, e.g: - -```csharp -var scanUsers = Redis.ScanAllKeys("urn:User:*"); -var sampleUsers = scanUsers.Take(10000).ToList(); //Stop after retrieving 10000 user keys -``` - -### Efficient SCAN in LUA - -The C# API below returns the first 10 results matching the `key:*` pattern: - -```csharp -var keys = Redis.ScanAllKeys(pattern: "key:*", pageSize: 10) - .Take(10).ToList(); -``` - -However the C# Streaming API above requires an unknown number of Redis Operations (bounded to the number of keys in Redis) -to complete the request. The number of SCAN calls can be reduced by choosing a higher `pageSize` to tell Redis to scan more keys -each time the SCAN operation is called. - -As the number of API calls has the potential to result in a large number of Redis Operations, it can end up yielding an unacceptable -delay due to the latency of multiple dependent remote network calls. An easy solution is to instead have the multiple SCAN calls -performed in-process on the Redis Server, eliminating the network latency of multiple SCAN calls, e.g: - -```csharp -const string FastScanScript = @" -local limit = tonumber(ARGV[2]) -local pattern = ARGV[1] -local cursor = 0 -local len = 0 -local results = {} -repeat - local r = redis.call('scan', cursor, 'MATCH', pattern, 'COUNT', limit) - cursor = tonumber(r[1]) - for k,v in ipairs(r[2]) do - table.insert(results, v) - len = len + 1 - if len == limit then break end - end -until cursor == 0 or len == limit -return results"; - -RedisText r = redis.ExecLua(FastScanScript, "key:*", "10"); -r.Children.Count.Print() //= 10 -``` - -The `ExecLua` API returns this complex LUA table response in the `Children` collection of the `RedisText` Response. - -#### Alternative Complex API Response - -Another way to return complex data structures in a LUA operation is to serialize the result as JSON - - return cjson.encode(results) - -Which you can access as raw JSON by parsing the response as a String with: - -```csharp -string json = redis.ExecLuaAsString(FastScanScript, "key:*", "10"); -``` - -> This is also the approach used in Redis React's -[RedisServices](https://github.com/ServiceStackApps/RedisReact/blob/a1b66603d52d2f18b96227fc455ecb5323e424c8/src/RedisReact/RedisReact.ServiceInterface/RedisServices.cs#L60). - -### ExecCachedLua - -ExecCachedLua is a convenient high-level API that eliminates the bookkeeping required for executing high-performance server LUA -Scripts which suffers from many of the problems that RDBMS stored procedures have which depends on pre-existing state in the RDBMS -that needs to be updated with the latest version of the Stored Procedure. - -With Redis LUA you either have the option to send, parse, load then execute the entire LUA script each time it's called or -alternatively you could pre-load the LUA Script into Redis once on StartUp and then execute it using the Script's SHA1 hash. -The issue with this is that if the Redis server is accidentally flushed you're left with a broken application relying on a -pre-existing script that's no longer there. The new `ExecCachedLua` API provides the best of both worlds where it will always -execute the compiled SHA1 script, saving bandwidth and CPU but will also re-create the LUA Script if it no longer exists. - -You can instead execute the compiled LUA script above by its SHA1 identifier, which continues to work regardless if it never existed -or was removed at runtime, e.g: - -```csharp -// #1: Loads LUA script and caches SHA1 hash in Redis Client -r = redis.ExecCachedLua(FastScanScript, sha1 => - redis.ExecLuaSha(sha1, "key:*", "10")); - -// #2: Executes using cached SHA1 hash -r = redis.ExecCachedLua(FastScanScript, sha1 => - redis.ExecLuaSha(sha1, "key:*", "10")); - -// Deletes all existing compiled LUA scripts -redis.ScriptFlush(); - -// #3: Executes using cached SHA1 hash, gets NOSCRIPT Error, -// re-creates then re-executes the LUA script using its SHA1 hash -r = redis.ExecCachedLua(FastScanScript, sha1 => - redis.ExecLuaSha(sha1, "key:*", "10")); -``` - -### IRedisClient LUA APIs - -The `IRedisClient` APIs for [redis server-side LUA support](http://redis.io/commands/eval) have been re-factored into the more user-friendly APIs below: - -```csharp -public interface IRedisClient -{ - //Eval/Lua operations - T ExecCachedLua(string scriptBody, Func scriptSha1); - - RedisText ExecLua(string body, params string[] args); - RedisText ExecLua(string luaBody, string[] keys, string[] args); - RedisText ExecLuaSha(string sha1, params string[] args); - RedisText ExecLuaSha(string sha1, string[] keys, string[] args); - - string ExecLuaAsString(string luaBody, params string[] args); - string ExecLuaAsString(string luaBody, string[] keys, string[] args); - string ExecLuaShaAsString(string sha1, params string[] args); - string ExecLuaShaAsString(string sha1, string[] keys, string[] args); - - int ExecLuaAsInt(string luaBody, params string[] args); - int ExecLuaAsInt(string luaBody, string[] keys, string[] args); - int ExecLuaShaAsInt(string sha1, params string[] args); - int ExecLuaShaAsInt(string sha1, string[] keys, string[] args); - - List ExecLuaAsList(string luaBody, params string[] args); - List ExecLuaAsList(string luaBody, string[] keys, string[] args); - List ExecLuaShaAsList(string sha1, params string[] args); - List ExecLuaShaAsList(string sha1, string[] keys, string[] args); - - string CalculateSha1(string luaBody); - - bool HasLuaScript(string sha1Ref); - Dictionary WhichLuaScriptsExists(params string[] sha1Refs); - void RemoveAllLuaScripts(); - void KillRunningLuaScript(); - string LoadLuaScript(string body); -} -``` - -### Usage Examples - -Here's how you can implement a ZPOP in Lua to remove the items with the lowest rank from a sorted set: - -```csharp -var luaBody = @" - local val = redis.call('zrange', KEYS[1], 0, ARGV[1]-1) - if val then redis.call('zremrangebyrank', KEYS[1], 0, ARGV[1]-1) end - return val"; - -var i = 0; -var alphabet = 26.Times(c => ((char)('A' + c)).ToString()); -alphabet.ForEach(x => Redis.AddItemToSortedSet("zalphabet", x, i++)); - -//Remove the letters with the lowest rank from the sorted set 'zalphabet' -var letters = Redis.ExecLuaAsList(luaBody, keys: new[] { "zalphabet" }, args: new[] { "3" }); -letters.PrintDump(); //[A, B, C] -``` - -And how to implement ZREVPOP to remove items with the highest rank from a sorted set: - -```csharp -var luaBody = @" - local val = redis.call('zrange', KEYS[1], -ARGV[1], -1) - if val then redis.call('zremrangebyrank', KEYS[1], -ARGV[1], -1) end - return val"; - -var i = 0; -var alphabet = 26.Times(c => ((char)('A' + c)).ToString()); -alphabet.ForEach(x => Redis.AddItemToSortedSet("zalphabet", x, i++)); - -//Remove the letters with the highest rank from the sorted set 'zalphabet' -List letters = Redis.ExecLuaAsList(luaBody, - keys: new[] { "zalphabet" }, args: new[] { "3" }); - -letters.PrintDump(); //[X, Y, Z] -``` - -### Other examples - -Returning an int: - -```csharp -int intVal = Redis.ExecLuaAsInt("return 123"); //123 -int intVal = Redis.ExecLuaAsInt("return ARGV[1] + ARGV[2]", "10", "20"); //30 -``` - -Returning an string: - -```csharp -//Hello, Redis Lua! -var strVal = Redis.ExecLuaAsString(@"return 'Hello, ' .. ARGV[1] .. '!'", "Redis Lua"); -``` - -Returning a List of strings: - -```csharp -Enum.GetNames(typeof(DayOfWeek)).ToList() - .ForEach(x => Redis.AddItemToList("DaysOfWeek", x)); - -var daysOfWeek = Redis.ExecLuaAsList("return redis.call('LRANGE', 'DaysOfWeek', 0, -1)"); -daysOfWeek.PrintDump(); //[Sunday, Monday, Tuesday, ...] -``` - -More examples can be found in the [Redis Eval Lua tests](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/RedisClientEvalTests.cs -) - -### Debugging Data Corruption Issues - -An issue that can be hard to debug is if the same `RedisClient` instance is shared across multiple threads which can result in returning corrupted data. -Typically this is a result of using `IRedisClient` field in a singleton instance or sharing it as a static instance. To prevent this, each Thread that -uses Redis should retrieve the redis client within a using statement, e.g: - -```csharp -using (var redis = redisManager.GetClient()) -{ - //.. -} -``` - -Unfortunately the call-site which returns the corrupted response or runtime Exception doesn't identify where else the Redis client instance was being used. -To help identify where client instances are being used you can assert that the client is only used in the Thread that resolved it from the pool with: - -```csharp -RedisConfig.AssertAccessOnlyOnSameThread = true; -``` - -This captures the Thread's StackTrace each time the client is resolved from the pool which as it adds a lot of overhead, should only be enabled when debugging connection issues. - -If it does detect the client is being accessed from a different thread it will throw a `InvalidAccessException` with the message containing the different **Thread Ids** and the **original StackTrace** where the client was resolved from the pool. You can compare this with the StackTrace of the Exception to hopefully identify where the client is being improperly used. - -## Copying - -Since September 2013, ServiceStack source code is available under GNU Affero General Public License/FOSS License Exception, see license.txt in the source. Alternative commercial licensing is also available, see https://servicestack.net/pricing for details. - -### [Docs and Downloads for older v3 BSD releases](https://github.com/ServiceStackV3/ServiceStackV3) - -## Contributing - -Contributors need to approve the [Contributor License Agreement](https://docs.google.com/forms/d/16Op0fmKaqYtxGL4sg7w_g-cXXyCoWjzppgkuqzOeKyk/viewform) before any code will be reviewed, see the [Contributing wiki](https://github.com/ServiceStack/ServiceStack/wiki/Contributing) for more details. - -### Redis Server builds for Windows - - * [Redis on Windows](https://github.com/ServiceStack/redis-windows) - * [MS Open Tech - Redis on Windows](https://github.com/MSOpenTech/Redis) - * [Downloads for Cygwin 32bit Redis Server Windows builds](http://code.google.com/p/servicestack/wiki/RedisWindowsDownload). - * [Project that lets you run Redis as a Windows Service](https://github.com/rgl/redis) - * [Another Redis as a Windows Service project, which allows you to run separate service for each Redis instance](https://github.com/kcherenkov/redis-windows-service) - * [Downloads for MinGW 32bit and 64bit Redis Server Windows builds](http://github.com/dmajkic/redis/downloads) - -### Redis Virtual Machines - - * [Run Redis in a Vagrant virtual machine](https://github.com/JasonPunyon/redishobo) - -# Getting Started with the C# Redis client - -###[C# Redis Client wiki](https://github.com/ServiceStack/ServiceStack.Redis/wiki) -Contains all the examples, tutorials and resources you need to get you up to speed with common operations and the latest features. - -[Useful Links on Redis server](https://github.com/ServiceStack/ServiceStack.Redis/wiki/Useful-Redis-Links) - -### Specific Examples - * [Using Transactions in Redis (i.e. MULTI/EXEC/DISCARD)](https://github.com/ServiceStack/ServiceStack.Redis/wiki/RedisTransactions) - * [Using Redis's built-in Publish/Subscribe pattern for high performance network notifications](https://github.com/ServiceStack/ServiceStack.Redis/wiki/RedisPubSub) - * [Using Redis to create high performance *distributed locks* spannable across multiple app servers](https://github.com/ServiceStack/ServiceStack.Redis/wiki/RedisLocks) - -# Simple example using Redis Lists - -Below is a simple example to give you a flavour of how easy it is to use some of Redis's advanced data structures - in this case Redis Lists: -_Full source code of this example is [viewable online](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/ShippersExample.cs)_ - - using (var redisClient = new RedisClient()) - { - //Create a 'strongly-typed' API that makes all Redis Value operations to apply against Shippers - IRedisTypedClient redis = redisClient.As(); - - //Redis lists implement IList while Redis sets implement ICollection - var currentShippers = redis.Lists["urn:shippers:current"]; - var prospectiveShippers = redis.Lists["urn:shippers:prospective"]; - - currentShippers.Add( - new Shipper { - Id = redis.GetNextSequence(), - CompanyName = "Trains R Us", - DateCreated = DateTime.UtcNow, - ShipperType = ShipperType.Trains, - UniqueRef = Guid.NewGuid() - }); - - currentShippers.Add( - new Shipper { - Id = redis.GetNextSequence(), - CompanyName = "Planes R Us", - DateCreated = DateTime.UtcNow, - ShipperType = ShipperType.Planes, - UniqueRef = Guid.NewGuid() - }); - - var lameShipper = new Shipper { - Id = redis.GetNextSequence(), - CompanyName = "We do everything!", - DateCreated = DateTime.UtcNow, - ShipperType = ShipperType.All, - UniqueRef = Guid.NewGuid() - }; - - currentShippers.Add(lameShipper); - - Dump("ADDED 3 SHIPPERS:", currentShippers); - - currentShippers.Remove(lameShipper); - - Dump("REMOVED 1:", currentShippers); - - prospectiveShippers.Add( - new Shipper { - Id = redis.GetNextSequence(), - CompanyName = "Trucks R Us", - DateCreated = DateTime.UtcNow, - ShipperType = ShipperType.Automobiles, - UniqueRef = Guid.NewGuid() - }); - - Dump("ADDED A PROSPECTIVE SHIPPER:", prospectiveShippers); - - redis.PopAndPushBetweenLists(prospectiveShippers, currentShippers); - - Dump("CURRENT SHIPPERS AFTER POP n' PUSH:", currentShippers); - Dump("PROSPECTIVE SHIPPERS AFTER POP n' PUSH:", prospectiveShippers); - - var poppedShipper = redis.PopFromList(currentShippers); - Dump("POPPED a SHIPPER:", poppedShipper); - Dump("CURRENT SHIPPERS AFTER POP:", currentShippers); - - //reset sequence and delete all lists - redis.SetSequence(0); - redis.Remove(currentShippers, prospectiveShippers); - Dump("DELETING CURRENT AND PROSPECTIVE SHIPPERS:", currentShippers); - } - - /* - == EXAMPLE OUTPUT == - - ADDED 3 SHIPPERS: - Id:1,CompanyName:Trains R Us,ShipperType:Trains,DateCreated:2010-01-31T11:53:37.7169323Z,UniqueRef:d17c5db0415b44b2ac5da7b6ebd780f5 - Id:2,CompanyName:Planes R Us,ShipperType:Planes,DateCreated:2010-01-31T11:53:37.799937Z,UniqueRef:e02a73191f4b4e7a9c44eef5b5965d06 - Id:3,CompanyName:We do everything!,ShipperType:All,DateCreated:2010-01-31T11:53:37.8009371Z,UniqueRef:d0c249bbbaf84da39fc4afde1b34e332 - - REMOVED 1: - Id:1,CompanyName:Trains R Us,ShipperType:Trains,DateCreated:2010-01-31T11:53:37.7169323Z,UniqueRef:d17c5db0415b44b2ac5da7b6ebd780f5 - Id:2,CompanyName:Planes R Us,ShipperType:Planes,DateCreated:2010-01-31T11:53:37.799937Z,UniqueRef:e02a73191f4b4e7a9c44eef5b5965d06 - - ADDED A PROSPECTIVE SHIPPER: - Id:4,CompanyName:Trucks R Us,ShipperType:Automobiles,DateCreated:2010-01-31T11:53:37.8539401Z,UniqueRef:67d7d4947ebc4b0ba5c4d42f5d903bec - - CURRENT SHIPPERS AFTER POP n' PUSH: - Id:4,CompanyName:Trucks R Us,ShipperType:Automobiles,DateCreated:2010-01-31T11:53:37.8539401Z,UniqueRef:67d7d4947ebc4b0ba5c4d42f5d903bec - Id:1,CompanyName:Trains R Us,ShipperType:Trains,DateCreated:2010-01-31T11:53:37.7169323Z,UniqueRef:d17c5db0415b44b2ac5da7b6ebd780f5 - Id:2,CompanyName:Planes R Us,ShipperType:Planes,DateCreated:2010-01-31T11:53:37.799937Z,UniqueRef:e02a73191f4b4e7a9c44eef5b5965d06 - - PROSPECTIVE SHIPPERS AFTER POP n' PUSH: - - POPPED a SHIPPER: - Id:2,CompanyName:Planes R Us,ShipperType:Planes,DateCreated:2010-01-31T11:53:37.799937Z,UniqueRef:e02a73191f4b4e7a9c44eef5b5965d06 - - CURRENT SHIPPERS AFTER POP: - Id:4,CompanyName:Trucks R Us,ShipperType:Automobiles,DateCreated:2010-01-31T11:53:37.8539401Z,UniqueRef:67d7d4947ebc4b0ba5c4d42f5d903bec - Id:1,CompanyName:Trains R Us,ShipperType:Trains,DateCreated:2010-01-31T11:53:37.7169323Z,UniqueRef:d17c5db0415b44b2ac5da7b6ebd780f5 - - DELETING CURRENT AND PROSPECTIVE SHIPPERS: - */ - -More examples are available in the [RedisExamples Redis examples page] and in the comprehensive -[test suite](https://github.com/ServiceStack/ServiceStack.Redis/tree/master/tests/ServiceStack.Redis.Tests) - - -## Speed -One of the best things about Redis is the speed - it is quick. - -[This example](https://github.com/ServiceStack/ServiceStack.Redis/blob/master/tests/ServiceStack.Redis.Tests/RedisClientTests.cs) -below stores and gets the entire [Northwind database](http://code.google.com/p/servicestack/source/browse/trunk/Common/Northwind.Benchmarks/Northwind.Common/DataModel/NorthwindData.cs) (3202 records) in less *1.2 secs* - we've never had it so quick! - -_(Running inside a VS.NET/R# unit test on a 3 year old iMac)_ - - using (var client = new RedisClient()) - { - var before = DateTime.Now; - client.StoreAll(NorthwindData.Categories); - client.StoreAll(NorthwindData.Customers); - client.StoreAll(NorthwindData.Employees); - client.StoreAll(NorthwindData.Shippers); - client.StoreAll(NorthwindData.Orders); - client.StoreAll(NorthwindData.Products); - client.StoreAll(NorthwindData.OrderDetails); - client.StoreAll(NorthwindData.CustomerCustomerDemos); - client.StoreAll(NorthwindData.Regions); - client.StoreAll(NorthwindData.Territories); - client.StoreAll(NorthwindData.EmployeeTerritories); - - Console.WriteLine("Took {0}ms to store the entire Northwind database ({1} records)", - (DateTime.Now - before).TotalMilliseconds, totalRecords); - - - before = DateTime.Now; - var categories = client.GetAll(); - var customers = client.GetAll(); - var employees = client.GetAll(); - var shippers = client.GetAll(); - var orders = client.GetAll(); - var products = client.GetAll(); - var orderDetails = client.GetAll(); - var customerCustomerDemos = client.GetAll(); - var regions = client.GetAll(); - var territories = client.GetAll(); - var employeeTerritories = client.GetAll(); - - Console.WriteLine("Took {0}ms to get the entire Northwind database ({1} records)", - (DateTime.Now - before).TotalMilliseconds, totalRecords); - } - /* - == EXAMPLE OUTPUT == - - Took 1020.0583ms to store the entire Northwind database (3202 records) - Took 132.0076ms to get the entire Northwind database (3202 records) - */ - - -Note: The total time taken includes an extra Redis operation for each record to store the id in a Redis set for each -type as well as serializing and de-serializing each record using Service Stack's TypeSerializer. - - -# Community Resources - - - [Synchronizing Redis local caches for distributed multi-subscriber scenarios](http://toreaurstad.blogspot.no/2015/09/synchronizing-redis-local-caches-for.html) by [@Tore_Aurstad](https://twitter.com/Tore_Aurstad) - - [Distributed Caching using Redis Server with .NET/C# Client](http://www.codeproject.com/Articles/636730/Distributed-Caching-using-Redis) by [Sem.Shekhovtsov](http://www.codeproject.com/script/Membership/View.aspx?mid=6495187) - - [Fan Messaging with ServiceStack.Redis](http://cornishdev.wordpress.com/2013/04/04/fan-messaging-with-servicestack-redis/) by [miket](http://stackoverflow.com/users/1804544/miket) - - [Redis and VB.Net](http://blogs.lessthandot.com/index.php/DataMgmt/DBProgramming/redis-and-vb-net) by [@chrissie1](https://twitter.com/chrissie1) - - [Using ServiceStack.Redis Part 2: Sets and Hashes](http://michaelsarchet.com/using-servicestack-redis-part-2-sets-and-hashes/) by [@msarchet](http://twitter.com/msarchet) - - [Using the ServiceStack.Redis Client](http://michaelsarchet.com/using-the-servicestack-redis-client/) by [@msarchet](http://twitter.com/msarchet) - - [Implementing ServiceStack.Redis.RedisClient (.NET Client for Redis)](http://www.narizwallace.com/2012/10/implementing-servicestack-redis-redisclient-net-client-for-redis/) by [@NarizWallace](https://twitter.com/NarizWallace) - - [Getting started with Redis in ASP.NET under Windows](http://maxivak.com/getting-started-with-redis-and-asp-net-mvc-under-windows/) by [@maxivak](https://twitter.com/maxivak) - +### This repository [has moved](https://docs.servicestack.net/mono-repo) to [github.com/ServiceStack/ServiceStack/ServiceStack.Redis](https://github.com/ServiceStack/ServiceStack/tree/main/ServiceStack.Redis) diff --git a/build/build-core.proj b/build/build-core.proj new file mode 100644 index 00000000..3ac34b8b --- /dev/null +++ b/build/build-core.proj @@ -0,0 +1,80 @@ + + + + + + 6 + 0 + $(BUILD_NUMBER) + + + + $(MSBuildProjectDirectory)/.. + $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests + Release + $(BuildSolutionDir)/NuGet/ + $(MajorVersion).$(MinorVersion).$(PatchVersion) + + + + + BeforeBuildSolutions; + BuildSolutions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <Version>[^<]* + <Version>$(PackageVersion) + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/build.bat b/build/build.bat index de9128d7..c51cb92d 100644 --- a/build/build.bat +++ b/build/build.bat @@ -1,5 +1,3 @@ SET MSBUILD="C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe" -%MSBUILD% build.proj /target:Default;NuGetPack /property:Configuration=Release;MinorVersion=1;PatchVersion=0 - -msbuild /p:Configuration=Release ..\src\ServiceStack.Redis.sln +%MSBUILD% build.proj /property:Configuration=Release;MinorVersion=4;PatchVersion=1 diff --git a/build/build.proj b/build/build.proj index b7f4a100..7899a808 100644 --- a/build/build.proj +++ b/build/build.proj @@ -1,10 +1,10 @@ + xmlns='http://schemas.microsoft.com/developer/msbuild/2003' ToolsVersion="4.0"> - 5 + 6 0 $(BUILD_NUMBER) @@ -12,17 +12,12 @@ $(MSBuildProjectDirectory)/.. $(BuildSolutionDir)/src + $(BuildSolutionDir)/tests Release - $(BuildSolutionDir)/src/.nuget/nuget.exe $(BuildSolutionDir)/NuGet/ - $(MajorVersion).$(MinorVersion).$(PatchVersion).0 $(MajorVersion).$(MinorVersion).$(PatchVersion) - - - - BeforeBuildSolutions; @@ -30,67 +25,55 @@ - - - - - - - + + + Properties="Configuration=$(Configuration)" /> + + + + + + + + + + - - AssemblyFileVersion\(\"\d+\.\d+\.\d+\.\d+\"\) - AssemblyFileVersion("$(Version)") - - - - version="5\.[^"]*" - version="$(PackageVersion)" + + <Version>[^<]* + <Version>$(PackageVersion) - - - - - - - + + - - - - - - + Targets="Build;Pack" + Properties="Configuration=$(Configuration)" /> - - - - - + + + + + - + + \ No newline at end of file diff --git a/build/copy.bat b/build/copy.bat deleted file mode 100644 index 1d455614..00000000 --- a/build/copy.bat +++ /dev/null @@ -1,6 +0,0 @@ -REM SET BUILD=Debug -SET BUILD=Release - -COPY ..\src\ServiceStack.Redis\bin\%BUILD%\net45\ServiceStack.Redis.* ..\..\ServiceStack\lib\net45 -COPY ..\src\ServiceStack.Redis\bin\%BUILD%\netstandard2.0\ServiceStack.Redis.* ..\..\ServiceStack\lib\netstandard2.0 -COPY ..\src\ServiceStack.Redis\bin\Signed\net45\ServiceStack.Redis.* ..\..\ServiceStack\lib\signed diff --git a/lib/copy.bat b/lib/copy.bat deleted file mode 100644 index edfee53b..00000000 --- a/lib/copy.bat +++ /dev/null @@ -1,22 +0,0 @@ -REM SET BUILD=Debug -SET BUILD=Release - -COPY ..\..\ServiceStack\src\ServiceStack.Interfaces\bin\%BUILD%\net45\ServiceStack.Interfaces.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Interfaces\bin\%BUILD%\netstandard2.0\ServiceStack.Interfaces.* netstandard2.0 - -COPY ..\..\ServiceStack.Text\src\ServiceStack.Text\bin\%BUILD%\net45\ServiceStack.Text.* net45 -COPY ..\..\ServiceStack.Text\src\ServiceStack.Text\bin\%BUILD%\netstandard2.0\ServiceStack.Text.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack.Client\bin\%BUILD%\net45\ServiceStack.Client.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Client\bin\%BUILD%\netstandard2.0\ServiceStack.Client.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack.Common\bin\%BUILD%\net45\ServiceStack.Common.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Common\bin\%BUILD%\netstandard2.0\ServiceStack.Common.* netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\net45\ServiceStack.dll net45 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\net45\ServiceStack.xml net45 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\netstandard2.0\ServiceStack.dll netstandard2.0 -COPY ..\..\ServiceStack\src\ServiceStack\bin\%BUILD%\netstandard2.0\ServiceStack.xml netstandard2.0 - -COPY ..\..\ServiceStack\src\ServiceStack.Server\bin\%BUILD%\net45\ServiceStack.Server.* net45 -COPY ..\..\ServiceStack\src\ServiceStack.Server\bin\%BUILD%\netstandard2.0\ServiceStack.Server.* netstandard2.0 diff --git a/lib/net45/Northwind.Common.dll b/lib/net45/Northwind.Common.dll deleted file mode 100644 index b33ffb10..00000000 Binary files a/lib/net45/Northwind.Common.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Client.dll b/lib/net45/ServiceStack.Client.dll deleted file mode 100644 index 444e9c5e..00000000 Binary files a/lib/net45/ServiceStack.Client.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Client.xml b/lib/net45/ServiceStack.Client.xml deleted file mode 100644 index 5eea6ded..00000000 --- a/lib/net45/ServiceStack.Client.xml +++ /dev/null @@ -1,499 +0,0 @@ - - - - ServiceStack.Client - - - - Need to provide async request options - http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx - - - - The request filter is called before any request. - This request filter is executed globally. - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - This response action is executed globally. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Called before request resend, when the initial request required authentication - - - - - The request filter is called before any request. - This request filter only works with the instance where it was set (not global). - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - The ResultsFilter is called before the Request is sent allowing you to return a cached response. - - - - - The ResultsFilterResponse is called before returning the response allowing responses to be cached. - - - - - Called with requestUri, ResponseType when server returns 304 NotModified - - - - - Useful .NET Encryption Utils from: - https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider(v=vs.110).aspx - - - - - Generic Proxy for service calls. - - The service Contract - - - - Returns the transparent proxy for the service call - - - - - Returns the next message from queueName or null if no message - - - - - - - Creates the error response from the values provided. - - If the errorCode is empty it will use the first validation error code, - if there is none it will throw an error. - - The error code. - The error message. - The validation errors. - - - - - Default MaxStringContentLength is 8k, and throws an exception when reached - - - - - Serializer cache of delegates required to create a type from a string map (e.g. for REST urls) - - - - - Gets the namespace from an attribute marked on the type's definition - - - Namespace of type - - - - Removes all registered Handlers, Named Receivers and Listeners - - - - Need to provide async request options - http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx - - - - The request filter is called before any request. - This request filter is executed globally. - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - This response action is executed globally. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Gets the collection of headers to be added to outgoing requests. - - - - - Sets all baseUri properties, using the Format property for the SyncReplyBaseUri and AsyncOneWayBaseUri - - Base URI of the service - - - - Whether to Accept Gzip,Deflate Content-Encoding and to auto decompress responses - - - - - The user name for basic authentication - - - - - The password for basic authentication - - - - - Sets the username and the password for basic authentication. - - - - - The Authorization Bearer Token to send with this request - - - - - Gets or sets authentication information for the request. - Warning: It's recommened to use and for basic auth. - This property is only used for IIS level authentication. - - - - - Determines if the basic auth header should be sent with every request. - By default, the basic auth header is only sent when "401 Unauthorized" is returned. - - - - - Specifies if cookies should be stored - - - - - Called before request resend, when the initial request required authentication - - - - - If a request fails with a 401 Unauthorized and a BearerToken is present the client - will automatically fetch a new AccessToken using this RefreshToken and retry the request - - - - - Send the Request to get the AccessToken with the RefreshToken at a non-default location - - - - - The request filter is called before any request. - This request filter only works with the instance where it was set (not global). - - - - - The ResultsFilter is called before the Request is sent allowing you to return a cached response. - - - - - The ResultsFilterResponse is called before returning the response allowing responses to be cached. - - - - - Called with requestUri, ResponseType when server returns 304 NotModified - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Called by Send method if an exception occurs, for instance a System.Net.WebException because the server - returned an HTTP error code. Override if you want to handle specific exceptions or always want to parse the - response to a custom ErrorResponse DTO type instead of ServiceStack's ErrorResponse class. In case ex is a - System.Net.WebException, do not use - createWebRequest/getResponse/HandleResponse<TResponse> to parse the response - because that will result in the same exception again. Use - ThrowWebServiceException<YourErrorResponseType> to parse the response and to throw a - WebServiceException containing the parsed DTO. Then override Send to handle that exception. - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Delete(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Delete(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Post(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Put(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.CustomMethod(method,dto)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.CustomMethod(method,dto)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - Compresses the specified text using the default compression method: Deflate - - The text. - Type of the compression. - - - - - Compresses the specified text using the default compression method: Deflate - - - - - Decompresses the specified gz buffer using the default compression method: Inflate - - The gz buffer. - Type of the compression. - - - - - Decompresses the specified gz buffer using inflate or gzip method - - Compressed stream - Type of the compression. Can be "gzip" or "deflate" - Decompressed stream - - - - Decompresses the specified gz buffer using the default compression method: Inflate - - - - - Donated by Ivan Korneliuk from his post: - http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html - - Modified to only allow using routes matching the supplied HTTP Verb - - - - - Generate a url from a Request DTO. Pretty URL generation require Routes to be defined using `[Route]` on the Request DTO - - - - - The exception which is thrown when a validation error occurred. - This validation is serialized in a extra clean and human-readable way by ServiceStack. - - - - - Returns the first error code - - The error code. - - - - Used if we need to serialize this exception to XML - - - - - - Encapsulates a validation result. - - - - - Gets or sets the success code. - - The success code. - - - - Gets or sets the error code. - - The error code. - - - - Gets or sets the success message. - - The success message. - - - - Gets or sets the error message. - - The error message. - - - - The errors generated by the validation. - - - - - Returns True if the validation was successful (errors list is empty). - - - - - Constructs a new ValidationResult - - - - - Constructs a new ValidationResult - - A list of validation results - - - - Initializes a new instance of the class. - - The errors. - The success code. - The error code. - - - - Merge errors from another - - - - - - Adds the singleton instance of to an endpoint on the client. - - - Based on http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/ - - - - - Adds the singleton of the class to the client endpoint's message inspectors. - - The endpoint that is to be customized. - The client runtime to be customized. - - - - Maintains a copy of the cookies contained in the incoming HTTP response received from any service - and appends it to all outgoing HTTP requests. - - - This class effectively allows to send any received HTTP cookies to different services, - reproducing the same functionality available in ASMX Web Services proxies with the class. - Based on http://megakemp.wordpress.com/2009/02/06/managing-shared-cookies-in-wcf/ - - - - - Initializes a new instance of the class. - - - - - Gets the singleton instance. - - - - - Inspects a message after a reply message is received but prior to passing it back to the client application. - - The message to be transformed into types and handed back to the client application. - Correlation state data. - - - - Inspects a message before a request message is sent to a service. - - The message to be sent to the service. - The client object channel. - - Null since no message correlation is used. - - - - - Specifies if cookies should be stored - - - - - Naming convention for the request's Response DTO - - - - - Shortcut to get the ResponseStatus whether it's bare or inside a IHttpResult - - - - - - diff --git a/lib/net45/ServiceStack.Common.dll b/lib/net45/ServiceStack.Common.dll deleted file mode 100644 index a48c379f..00000000 Binary files a/lib/net45/ServiceStack.Common.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Common.xml b/lib/net45/ServiceStack.Common.xml deleted file mode 100644 index 1ea784a0..00000000 --- a/lib/net45/ServiceStack.Common.xml +++ /dev/null @@ -1,693 +0,0 @@ - - - - ServiceStack.Common - - - - - - Provide the an option for the callee to block until all commands are executed - - - - - - - Return T[0] when enumerable is null, safe to use in enumerations like foreach - - - - - Gets the textual description of the enum if it has one. e.g. - - - enum UserColors - { - [Description("Bright Red")] - BrightRed - } - UserColors.BrightRed.ToDescription(); - - - - - - - - Default base sleep time (milliseconds). - - - - - Default maximum back-off time before retrying a request - - - - - Maximum retry limit. Avoids integer overflow issues. - - - - - How long to sleep before next retry using Exponential BackOff delay with Full Jitter. - - - - - - Exponential BackOff Delay with Full Jitter - - - - - - - Exponential BackOff Delay with Full Jitter from: - https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedBackoffStrategies.java - - - - - - - - - Calculate exponential retry back-off. - - - - - - - Calculate exponential retry back-off. - - - - - - - - - Invokes the action provided and returns true if no excpetion was thrown. - Otherwise logs the exception and returns false if an exception was thrown. - - The action. - - - - - Useful IPAddressExtensions from: - http://blogs.msdn.com/knom/archive/2008/12/31/ip-address-calculations-with-c-subnetmasks-networks.aspx - - - - - - Gets the ipv4 addresses from all Network Interfaces that have Subnet masks. - - - - - - Gets the ipv6 addresses from all Network Interfaces. - - - - - - Configure ServiceStack.Text JSON Serializer to use Templates JS parsing - - - - - Creates a Console Logger, that logs all messages to: System.Console - - Made public so its testable - - - - - Default logger is to Console.WriteLine - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Creates a Debug Logger, that logs all messages to: System.Diagnostics.Debug - - Made public so its testable - - - - - Default logger is to System.Diagnostics.Debug.WriteLine - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Categories of sql statements. - - - - - Unknown - - - - - DML statements that alter database state, e.g. INSERT, UPDATE - - - - - Statements that return a single record - - - - - Statements that iterate over a result set - - - - - A callback for ProfiledDbConnection and family - - - - - Called when a command starts executing - - - - - - - Called when a reader finishes executing - - - - - - - - Called when a reader is done iterating through the data - - - - - - Called when an error happens during execution of a command - - - - - - - - True if the profiler instance is active - - - - - Wraps a database connection, allowing sql execution timings to be collected when a session is started. - - - - - Returns a new that wraps , - providing query execution profiling. If profiler is null, no profiling will occur. - - Your provider-specific flavor of connection, e.g. SqlConnection, OracleConnection - The currently started or null. - Determines whether the ProfiledDbConnection will dispose the underlying connection. - - - - The underlying, real database connection to your db provider. - - - - - The current profiler instance; could be null. - - - - - The raw connection this is wrapping - - - - - Wrapper for a db provider factory to enable profiling - - - - - Every provider factory must have an Instance public field - - - - - Allow to re-init the provider factory. - - - - - - - proxy - - - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - Runs an action for a minimum of runForMs - - What to run - Minimum ms to run for - time elapsed in micro seconds - - - - Returns average microseconds an action takes when run for the specified runForMs - - What to run - How many times to run for each iteration - Minimum ms to run for - - - - - - - - Multiple string replacements - - Even number of old and new value pairs - - - - Replace string contents outside of string quotes - - - - - Protect against XSS by cleaning non-standared User Input - - - - - Common functionality when creating adapters - - - - - Executes the specified expression. - - - The action. - - - - - Executes the specified action (for void methods). - - The action. - - - - Note: InMemoryLog keeps all logs in memory, so don't use it long running exceptions - - Returns a thread-safe InMemoryLog which you can use while *TESTING* - to provide a detailed analysis of your logs. - - - - - Captures the output and assigns it to the specified variable. - Accepts an optional Object Dictionary as scope arguments when evaluating body. - - Usages: {{#capture output}} {{#each args}} - [{{it}}](/path?arg={{it}}) {{/each}} {{/capture}} - {{#capture output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}} - {{#capture appendTo output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}} - - - - - Handlebars.js like each block - Usages: {{#each customers}} {{Name}} {{/each}} - {{#each customers}} {{it.Name}} {{/each}} - {{#each num in numbers}} {{num}} {{/each}} - {{#each num in [1,2,3]}} {{num}} {{/each}} - {{#each numbers}} {{it}} {{else}} no numbers {{/each}} - {{#each numbers}} {{it}} {{else if letters != null}} has letters {{else}} no numbers {{/each}} - {{#each n in numbers where n > 5}} {{it}} {{else}} no numbers > 5 {{/each}} - {{#each n in numbers where n > 5 orderby n skip 1 take 2}} {{it}} {{else}} no numbers > 5 {{/each}} - - - - - Special block which evaluates the rendered body as a ServiceStack Template - - Usages: {{#eval}}emit {{evaluateBodyOfArg}} at {{now}} {{/eval}} - {{#eval {scopeArg:1} }}emit {{evaluateBodyOfArg}} at {{now}} with {{scopeArg}} {{/eval}} - {{#eval {htmlDecode:true} }}emit htmldecoded {{evaluateBodyOfArg}} at {{now}} {{/eval}} - {{#eval {use:{filters:'TemplateServiceStackFilters',plugins:['MarkdownTemplatePlugin'],context:true} }} - emit {{evaluateBodyOfArg}} at {{now}} in new context - {{/eval}} - - - - - Usages: {{#ul {each:items, class:'nav'} }}
  • {{it}}
  • {{/ul}} -
    -
    - - - Handlebars.js like if block - Usages: {{#if a > b}} max {{a}} {{/if}} - {{#if a > b}} max {{a}} {{else}} max {{b}} {{/if}} - {{#if a > b}} max {{a}} {{else if b > c}} max {{b}} {{else}} max {{c}} {{/if}} - - - - - Handlebars.js like noop block - Usage: Remove {{#noop}} contents in here {{/noop}} - - - - - Partial Block doesn't emit anything it only creates and saves a partial in the PageResult - - Usages: {{#partial mypartial}} contents {{/partial}} - {{#partial mypartial {format:'html'} }} contents {{/partial}} - {{#partial mypartial {format:'html', pageArg:1} }} contents {{/partial}} - - - - - Special block which captures the raw body as a string fragment - - Usages: {{#raw}}emit {{ verbatim }} body{{/raw}} - {{#raw varname}}assigned to varname{{/raw}} - {{#raw appendTo varname}}appended to varname{{/raw}} - - - - - Handlebars.js like with block - Usages: {{#with person}} Hi {{name}}, I'm {{age}} years old{{/with}} - {{#with person}} Hi {{name}}, I'm {{age}} years old {{else}} no person {{/with}} - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaluate then set asyncResult if Result was async, otherwise set result. - - - - - true if result was synchronous otherwise false - - - - The Template Page to Render - - - - - The Code Page to Render - - - - - Use specified Layout - - - - - Use Layout with specified name - - - - - Render without any Layout - - - - - Extract Model Properties into Scope Args - - - - - Add additional Args available to all pages - - - - - Add additional template filters available to all pages - - - - - Add additional template blocks available to all pages - - - - - Add additional partials available to all pages - - - - - Return additional HTTP Headers in HTTP Requests - - - - - Specify the Content-Type of the Response - - - - - Transform the Page output using a chain of stream transformers - - - - - Transform the entire output using a chain of stream transformers - - - - - Available transformers that can transform context filter stream outputs - - - - - Don't allow access to specified filters - - - - - The last error thrown by a filter - - - - - The StackTrace where the Last Error Occured - - - - - What argument errors should be binded to - - - - - Whether to skip execution of all page filters and just write template string fragments - - - - - Overrides Context to specify whether to Ignore or Continue executing filters on error - - - - - Whether to always rethrow Exceptions - - - - - Immediately halt execution of the page - - - - - Rethrow fatal exceptions thrown on incorrect API usage - - - - - Available transformers that can transform context filter stream outputs - - - - - Whether to check for modified pages by default when not in DebugMode - - - - - How long in between checking for modified pages - - - - - Existing caches and pages created prior to specified date should be invalidated - - - - - Render render filter exceptions in-line where filter is located - - - - - What argument to assign Filter Exceptions to - - - - - Whether to skip executing Filters if an Exception was thrown - - - - - Creates a Unified Resource Name (URN) with the following formats: - - - urn:{TypeName}:{IdFieldValue} e.g. urn:UserSession:1 - - urn:{TypeName}:{IdFieldName}:{IdFieldValue} e.g. urn:UserSession:UserId:1 - - - -
    -
    diff --git a/lib/net45/ServiceStack.Interfaces.dll b/lib/net45/ServiceStack.Interfaces.dll deleted file mode 100644 index aa81d891..00000000 Binary files a/lib/net45/ServiceStack.Interfaces.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Interfaces.xml b/lib/net45/ServiceStack.Interfaces.xml deleted file mode 100644 index a5e2aae1..00000000 --- a/lib/net45/ServiceStack.Interfaces.xml +++ /dev/null @@ -1,2419 +0,0 @@ - - - - ServiceStack.Interfaces - - - - - Gets or sets parameter name with which allowable values will be associated. - - - - - Generates body DTO parameter only if `DisableAutoDtoInBodyParam = false` - - - - - Always generate body DTO for request - - - - - Never generate body DTO for request - - - - - The overall description of an API. Used by Swagger. - - - - - Create or not body param for request type when verb is POST or PUT. - Value can be one of the constants of `GenerateBodyParam` class: - `GenerateBodyParam.IfNotDisabled` (default value), `GenerateBodyParam.Always`, `GenerateBodyParam.Never` - - - - - Tells if body param is required - - - - - Gets or sets verb to which applies attribute. By default applies to all verbs. - - - - - Gets or sets parameter type: It can be only one of the following: path, query, body, form, or header. - - - - - Gets or sets unique name for the parameter. Each name must be unique, even if they are associated with different paramType values. - - - - Other notes on the name field: - If paramType is body, the name is used only for UI and codegeneration. - If paramType is path, the name field must correspond to the associated path segment from the path field in the api object. - If paramType is query, the name field corresponds to the query param name. - - - - - - Gets or sets the human-readable description for the parameter. - - - - - For path, query, and header paramTypes, this field must be a primitive. For body, this can be a complex or container datatype. - - - - - Fine-tuned primitive type definition. - - - - - For path, this is always true. Otherwise, this field tells the client whether or not the field must be supplied. - - - - - For query params, this specifies that a comma-separated list of values can be passed to the API. For path and body types, this field cannot be true. - - - - - Gets or sets route to which applies attribute, matches using StartsWith. By default applies to all routes. - - - - - Whether to exclude this property from being included in the ModelSchema - - - - - The status code of a response - - - - - The description of a response status code - - - - - HTTP status code of response - - - - - End-user description of the data which is returned by response - - - - - If set to true, the response is default for all non-explicity defined status codes - - - - - Open API schema definition type for response - - - - - The Password Hasher provider used to hash users passwords, by default uses the same algorithm used by ASP.NET Identity v3: - PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - - - - - The first byte marker used to specify the format used. The default implementation uses the following format: - { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } - - - - - Returns a boolean indicating whether the matches the . - The out parameter indicates whether the password should be re-hashed. - - The hash value for a user's stored password. - The password supplied for comparison. - Implementations of this method should be time consistent. - - - - Returns a hashed representation of the supplied . - - The password to hash. - A hashed representation of the supplied . - - - - A common interface implementation that is implemented by most cache providers - - - - - Removes the specified item from the cache. - - The identifier for the item to delete. - - true if the item was successfully removed from the cache; false otherwise. - - - - - Removes the cache for all the keys provided. - - The keys. - - - - Retrieves the specified item from the cache. - - - The identifier for the item to retrieve. - - The retrieved item, or null if the key was not found. - - - - - Increments the value of the specified key by the given amount. - The operation is atomic and happens on the server. - A non existent value at key starts at 0 - - The identifier for the item to increment. - The amount by which the client wants to increase the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Increments the value of the specified key by the given amount. - The operation is atomic and happens on the server. - A non existent value at key starts at 0 - - The identifier for the item to increment. - The amount by which the client wants to decrease the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Adds a new item into the cache at the specified cache key only if the cache is empty. - - The key used to reference the item. - The object to be inserted into the cache. - - true if the item was successfully stored in the cache; false otherwise. - - The item does not expire unless it is removed due memory pressure. - - - - Sets an item into the cache at the cache key specified regardless if it already exists or not. - - - - - Replaces the item at the cachekey specified only if an items exists at the location already. - - - - - Invalidates all data on the cache. - - - - - Retrieves multiple items from the cache. - The default value of T is set for all keys that do not exist. - - The list of identifiers for the items to retrieve. - - a Dictionary holding all items indexed by their key. - - - - - Sets multiple items to the cache. - - - The values. - - - - Extend ICacheClient API with shared, non-core features - - - - - A light interface over a cache client. - This interface was inspired by Enyim.Caching.MemcachedClient - - Only the methods that are intended to be used are required, if you require - extra functionality you can uncomment the unused methods below as they have been - implemented in DdnMemcachedClient - - - - - Removes the specified item from the cache. - - The identifier for the item to delete. - - true if the item was successfully removed from the cache; false otherwise. - - - - - Removes the cache for all the keys provided. - - The keys. - - - - Retrieves the specified item from the cache. - - The identifier for the item to retrieve. - - The retrieved item, or null if the key was not found. - - - - - Increments the value of the specified key by the given amount. The operation is atomic and happens on the server. - - The identifier for the item to increment. - The amount by which the client wants to increase the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Increments the value of the specified key by the given amount. The operation is atomic and happens on the server. - - The identifier for the item to increment. - The amount by which the client wants to decrease the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Inserts an item into the cache with a cache key to reference its location. - - The key used to reference the item. - The object to be inserted into the cache. - - true if the item was successfully stored in the cache; false otherwise. - - The item does not expire unless it is removed due memory pressure. - - - - Inserts an item into the cache with a cache key to reference its location. - - The key used to reference the item. - The object to be inserted into the cache. - The time when the item is invalidated in the cache. - true if the item was successfully stored in the cache; false otherwise. - - - - Removes all data from the cache. - - - - - Retrieves multiple items from the cache. - - The list of identifiers for the items to retrieve. - - a Dictionary holding all items indexed by their key. - - - - - Removes items from cache that have keys matching the specified wildcard pattern - - The wildcard, where "*" means any sequence of characters and "?" means any single character. - - - - Removes items from the cache based on the specified regular expression pattern - - Regular expression pattern to search cache keys - - - - A Users Session - - - - - Store any object at key - - - - - - - Set a typed value at key - - - - - - - - Get a typed value at key - - - - - - - - Remove the value at key - - - - - - - Delete all Cache Entries (requires ICacheClient that implements IRemoveByPattern) - - - - - Retrieves a User Session - - - - - Gets the Session Bag for this request, creates one if it doesn't exist. - - - - - - - - Gets the Session Bag for this request, creates one if it doesn't exist. - Only for ASP.NET apps. Uses the HttpContext.Current singleton. - - - - - Create a Session Bag using a custom sessionId - - - - - - - Allow delegation of dependencies to other IOC's - - - - - Resolve Constructor Dependency - - - - - - - Resolve a dependency from the AppHost's IOC - - - - - - - BelongToAttribute - Use to indicate that a join column belongs to another table. - - - - - Compute attribute. - Use to indicate that a property is a Calculated Field - - - - - Decimal length attribute. - - - - - Mark types that are to be excluded from specified features - - - - - Explicit foreign key name. If it's null, or empty, the FK name will be autogenerated as before. - - - - - Hash Key Attribute used to specify which property is the HashKey, e.g. in DynamoDb. - - - - - IgnoreAttribute - Use to indicate that a property is not a field in the table - properties with this attribute are ignored when building sql sentences - - - - - Ignore this property in SELECT statements - - - - - Ignore this property in UPDATE statements - - - - - Ignore this property in INSERT statements - - - - - Decorate any type or property with adhoc info - - - - - Primary key attribute. - use to indicate that property is part of the pk - - - - - Range Key Attribute used to specify which property is the RangeKey, e.g. in DynamoDb. - - - - - ReturnAttribute - Use to indicate that a property should be included in the - returning/output clause of INSERT sql sentences - - - - - Used to indicate that property is a row version incremented automatically by the database - - - - - Used to annotate an Entity with its DB schema - - - - - - For providers that want a cleaner API with a little more perf - - - - - - Generic ResponseStatus for when Response Type can't be inferred. - In schemaless formats like JSON, JSV it has the same shape as a typed Response DTO - - - - - Contract indication that the Response DTO has a ResponseStatus - - - - - Refresh file stats for this node if supported - - - - - How many results to skip - - - - - How many results to return - - - - - List of fields to sort by, can order by multiple fields and inverse order, e.g: Id,-Amount - - - - - List of fields to sort by descending, can order by multiple fields and inverse order, e.g: -Id,Amount - - - - - Include aggregate data like Total, COUNT(*), COUNT(DISTINCT Field), Sum(Amount), etc - - - - - The fields to return - - - - - Populate with Include=Total or if registered with: AutoQueryFeature { IncludeTotal = true } - - - - - Unifed API to create any missing Tables, Data Structure Schema - or perform any other tasks dependencies require to run at Startup. - - - - - Provide unique, incrementing sequences. Used in PocoDynamo. - - - - - Marker interfaces - - - - - The minimal API Surface to capture the most common SYNC requests. - Convenience extensions over these core API's available in ServiceGatewayExtensions - - - - - Normal Request/Reply Services - - - - - Auto Batched Request/Reply Requests - - - - - OneWay Service - - - - - Auto Batched OneWay Requests - - - - - The minimal API Surface to capture the most common ASYNC requests. - Convenience extensions over these core API's available in ServiceGatewayExtensions - - - - - Normal Request/Reply Services - - - - - Auto Batched Request/Reply Requests - - - - - OneWay Service - - - - - Auto Batched OneWay Requests - - - - - Helper ILog implementation that reduces effort to extend or use without needing to impl each API - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Logs a message in a running application - - - - - Gets or sets a value indicating whether this instance is debug enabled. - - - true if this instance is debug enabled; otherwise, false. - - - - - Logs a Debug message. - - The message. - - - - Logs a Debug message and exception. - - The message. - The exception. - - - - Logs a Debug format message. - - The format. - The args. - - - - Logs a Error message. - - The message. - - - - Logs a Error message and exception. - - The message. - The exception. - - - - Logs a Error format message. - - The format. - The args. - - - - Logs a Fatal message. - - The message. - - - - Logs a Fatal message and exception. - - The message. - The exception. - - - - Logs a Error format message. - - The format. - The args. - - - - Logs an Info message and exception. - - The message. - - - - Logs an Info message and exception. - - The message. - The exception. - - - - Logs an Info format message. - - The format. - The args. - - - - Logs a Warning message. - - The message. - - - - Logs a Warning message and exception. - - The message. - The exception. - - - - Logs a Warning format message. - - The format. - The args. - - - - Factory to create ILog instances - - - - - Gets the logger. - - - - - Gets the logger. - - - - - Pushes a property on the current log context, returning an - to remove the property again from the async context. - - Property Name - Property Value - Interface for popping the property off the stack - - - - Pushes a property on the current log context, returning an - to remove the property again from the async context. - - The logger - Property Name - Property Value - Interface for popping the property off the stack - - - - Logs a Debug format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs an Info format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Warn format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs an Error format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Fatal format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Debug format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs an Info format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs a Warn format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs an Error format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs a Fatal format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logging API for this library. You can inject your own implementation otherwise - will use the DebugLogFactory to write to System.Diagnostics.Debug - - - - - Gets or sets the log factory. - Use this to override the factory that is used to create loggers - - - - - Gets the logger. - - - - - Gets the logger. - - - - - Default logger is to System.Diagnostics.Debug.Print - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Creates a Debug Logger, that logs all messages to: System.Diagnostics.Debug - - Made public so its testable - - - - - StringBuilderLog writes to shared StringBuffer. - Made public so its testable - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - The message. - - - - Creates a test Logger, that stores all log messages in a member list - - - - - Tests logger which stores all log messages in a member list which can be examined later - - Made public so its testable - - - - - Initializes a new instance of the class. - - The type. - - - - Initializes a new instance of the class. - - The type. - - - - Logs the specified message. - - The message. - The exception. - - - - Logs the format. - - The message. - The args. - - - - Logs the specified message. - - The message. - - - - Single threaded message handler that can process all messages - of a particular message type. - - - - - The type of the message this handler processes - - - - - The MqClient processing the message - - - - - Process all messages pending - - - - - - Process messages from a single queue. - - - The queue to process - A predicate on whether to continue processing the next message if any - - - - - Process a single message - - - - - Get Current Stats for this Message Handler - - - - - - Publish the specified message into the durable queue @queueName - - - - - Publish the specified message into the transient queue @queueName - - - - - Synchronous blocking get. - - - - - Non blocking get message - - - - - Acknowledge the message has been successfully received or processed - - - - - Negative acknowledgement the message was not processed correctly - - - - - Create a typed message from a raw MQ Response artefact - - - - - Create a temporary Queue for Request / Reply - - - - - - Simple definition of an MQ Host - - - - - Factory to create consumers and producers that work with this service - - - - - Register DTOs and hanlders the MQ Server will process - - - - - - - Register DTOs and hanlders the MQ Server will process using specified number of threads - - - - - - - - Register DTOs and hanlders the MQ Server will process - - - - - - - - Register DTOs and hanlders the MQ Server will process using specified number of threads - - - - - - - - - Get Total Current Stats for all Message Handlers - - - - - - Get a list of all message types registered on this MQ Host - - - - - Get the status of the service. Potential Statuses: Disposed, Stopped, Stopping, Starting, Started - - - - - - Get a Stats dump - - - - - - Start the MQ Host if not already started. - - - - - Stop the MQ Host if not already stopped. - - - - - Basic implementation of IMessage[T] - - - - - - Util static generic class to create unique queue names for types - - - - - - Util class to create unique queue names for runtime types - - - - - For messaging exceptions that should by-pass the messaging service's configured - retry attempts and store the message straight into the DLQ - - - - - Specify a VirtualPath or Layout for a Code Page - - - - - Specify static page arguments - - - - - Wrap the common redis list operations under a IList[string] interface. - - - - - Redis transaction for typed client - - - - - - Interface to redis typed pipeline - - - - - interface to queueable operation using typed redis client - - - - - - Returns a high-level typed client API - - - - - - Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write ICacheClient (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly ICacheClient using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write ICacheClient (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly ICacheClient using the hosts defined in ReadOnlyHosts. - - - - - - The number of active subscriptions this client has - - - - - Registered handler called after client *Subscribes* to each new channel - - - - - Registered handler called when each message is received - - - - - Registered handler called when each channel is unsubscribed - - - - - Subscribe to channels by name - - - - - - Subscribe to channels matching the supplied patterns - - - - - - Interface to redis transaction - - - - - Base transaction interface, shared by typed and non-typed transactions - - - - - Interface to redis pipeline - - - - - Pipeline interface shared by typed and non-typed pipelines - - - - - interface to operation that can queue commands - - - - - Interface to operations that allow queued commands to be completed - - - - - A log entry added by the IRequestLogger - - - - - Error information pertaining to a particular named field. - Used for returning multiple field validation errors.s - - - - - Common ResponseStatus class that should be present on all response DTO's - - - - - Initializes a new instance of the class. - - A response status without an errorcode == success - - - - - Initializes a new instance of the class. - A response status with an errorcode == failure - - - - - Initializes a new instance of the class. - A response status with an errorcode == failure - - - - - Holds the custom ErrorCode enum if provided in ValidationException - otherwise will hold the name of the Exception type, e.g. typeof(Exception).Name - - A value of non-null means the service encountered an error while processing the request. - - - - - A human friendly error message - - - - - The Server StackTrace when DebugMode is enabled - - - - - For multiple detailed validation errors. - Can hold a specific error message for each named field. - - - - - For additional custom metadata about the error - - - - - Decorate on Request DTO's to alter the accessibility of a service and its visibility on /metadata pages - - - - - Allow access but hide from metadata to requests from Localhost only - - - - - Allow access but hide from metadata to requests from Localhost and Local Intranet only - - - - - Restrict access and hide from metadata to requests from Localhost only - - - - - Restrict access and hide from metadata to requests from Localhost and Local Intranet only - - - - - Restrict access and hide from metadata to requests from External only - - - - - Sets a single access restriction - - Restrict Access to. - - - - Restrict access to any of the specified access scenarios - - Access restrictions - - - - Sets a single metadata Visibility restriction - - Restrict metadata Visibility to. - - - - Restrict metadata visibility to any of the specified access scenarios - - Visibility restrictions - - - - Restrict access and metadata visibility to any of the specified access scenarios - - The restrict access to scenarios. - - - - Restrict access and metadata visibility to any of the specified access scenarios - - The restrict access to scenarios. - - - - Returns the allowed set of scenarios based on the user-specified restrictions - - - - - - - Converts from a User intended restriction to a flag with all the allowed attribute flags set, e.g: - - If No Network restrictions were specified all Network access types are allowed, e.g: - restrict EndpointAttributes.None => ... 111 - - If a Network restriction was specified, only it will be allowed, e.g: - restrict EndpointAttributes.LocalSubnet => ... 010 - - The returned Enum will have a flag with all the allowed attributes set - - - - - - - Used to decorate Request DTO's to associate a RESTful request - path mapping with a service. Multiple attributes can be applied to - each request DTO, to map multiple paths to the service. - - - - - Initializes an instance of the class. - - - The path template to map to the request. See - RouteAttribute.Path - for details on the correct format. - - - - - Initializes an instance of the class. - - - The path template to map to the request. See - RouteAttribute.Path - for details on the correct format. - - A comma-delimited list of HTTP verbs supported by the - service. If unspecified, all verbs are assumed to be supported. - - - - Gets or sets the path template to be mapped to the request. - - - A value providing the path mapped to - the request. Never . - - - Some examples of valid paths are: - - - "/Inventory" - "/Inventory/{Category}/{ItemId}" - "/Inventory/{ItemPath*}" - - - Variables are specified within "{}" - brackets. Each variable in the path is mapped to the same-named property - on the request DTO. At runtime, ServiceStack will parse the - request URL, extract the variable values, instantiate the request DTO, - and assign the variable values into the corresponding request properties, - prior to passing the request DTO to the service object for processing. - - It is not necessary to specify all request properties as - variables in the path. For unspecified properties, callers may provide - values in the query string. For example: the URL - "http://services/Inventory?Category=Books&ItemId=12345" causes the same - request DTO to be processed as "http://services/Inventory/Books/12345", - provided that the paths "/Inventory" (which supports the first URL) and - "/Inventory/{Category}/{ItemId}" (which supports the second URL) - are both mapped to the request DTO. - - Please note that while it is possible to specify property values - in the query string, it is generally considered to be less RESTful and - less desirable than to specify them as variables in the path. Using the - query string to specify property values may also interfere with HTTP - caching. - - The final variable in the path may contain a "*" suffix - to grab all remaining segments in the path portion of the request URL and assign - them to a single property on the request DTO. - For example, if the path "/Inventory/{ItemPath*}" is mapped to the request DTO, - then the request URL "http://services/Inventory/Books/12345" will result - in a request DTO whose ItemPath property contains "Books/12345". - You may only specify one such variable in the path, and it must be positioned at - the end of the path. - - - - - Gets or sets short summary of what the route does. - - - - - Gets or sets longer text to explain the behaviour of the route. - - - - - Gets or sets a comma-delimited list of HTTP verbs supported by the service, such as - "GET,PUT,POST,DELETE". - - - A providing a comma-delimited list of HTTP verbs supported - by the service, or empty if all verbs are supported. - - - - - Used to rank the precedences of route definitions in reverse routing. - i.e. Priorities below 0 are auto-generated have less precedence. - - - - - Must match rule defined in Config.RequestRules or Regex expression with format: - "{IHttpRequest.Field} =~ {pattern}", e.g "PathInfo =~ \/[0-9]+$" - - - - - Fallback routes have the lowest precedence, i.e. after normal Routes, static files or any matching Catch All Handlers. - - - - - Additional checks to notify of invalid state, configuration or use of ServiceStack libraries. - Can disable StrictMode checks with Config.StrictMode = false; - - - - - Get or sets tag name - - - - - Get or sets operation verbs for which the attribute be applied - - - - - Adds an expired Set-Cookie instruction for clients to delete this Cookie - - - - - Adds a new Set-Cookie instruction for ss-pid - - - - - Adds a new Set-Cookie instruction for ss-id - - - - - Order in which Request Filters are executed. - <0 Executed before global request filters - >0 Executed after global request filters - - - - - A new shallow copy of this filter is used on every request. - - - - - - This interface can be implemented by an attribute - which adds an request filter for the specific request DTO the attribute marked. - - - - - The request filter is executed before the service. - - The http request wrapper - The http response wrapper - The request DTO - - - - This interface can be implemented by an attribute - which adds an request filter for the specific request DTO the attribute marked. - - - - - The request filter is executed before the service. - - The http request wrapper - The http response wrapper - The request DTO - - - - Order in which Response Filters are executed. - <0 Executed before global response filters - >0 Executed after global response filters - - - - - A new shallow copy of this filter is used on every request. - - - - - - This interface can be implemented by an attribute - which adds an response filter for the specific response DTO the attribute marked. - - - - - The response filter is executed after the service - - The http request wrapper - The http response wrapper - - - - This interface can be implemented by an attribute - which adds an response filter for the specific response DTO the attribute marked. - - - - - The response filter is executed after the service - - The http request wrapper - The http response wrapper - - - - A thin wrapper around ASP.NET or HttpListener's HttpRequest - - - - - The HttpResponse - - - - - The HTTP Verb - - - - - The IP Address of the X-Forwarded-For header, null if null or empty - - - - - The Port number of the X-Forwarded-Port header, null if null or empty - - - - - The http or https scheme of the X-Forwarded-Proto header, null if null or empty - - - - - The value of the X-Real-IP header, null if null or empty - - - - - The value of the Accept HTTP Request Header - - - - - A thin wrapper around ASP.NET or HttpListener's HttpResponse - - - - - Adds a new Set-Cookie instruction to Response - - - - - - Removes all pending Set-Cookie instructions - - - - - The HTTP Response Status - - - - - The HTTP Response Status Code - - - - - The HTTP Status Description - - - - - The HTTP Response ContentType - - - - - Additional HTTP Headers - - - - - Additional HTTP Cookies - - - - - Response DTO - - - - - if not provided, get's injected by ServiceStack - - - - - Holds the request call context - - - - - The padding length written with the body, to be added to ContentLength of body - - - - - Serialize the Response within the specified scope - - - - - Whether this HttpResult allows Partial Response - - - - - Write a partial content result - - - - - Whether this HttpResult allows Partial Response - - - - - Write a partial content result - - - - - A thin wrapper around each host's Request e.g: ASP.NET, HttpListener, MQ, etc - - - - - The underlying ASP.NET or HttpListener HttpRequest - - - - - The Response API for this Request - - - - - The name of the service being called (e.g. Request DTO Name) - - - - - The Verb / HttpMethod or Action for this request - - - - - Different Attribute Enum flags classifying this Request - - - - - Optional preferences for the processing of this Request - - - - - The Request DTO, after it has been deserialized. - - - - - The request ContentType - - - - - Whether this was an Internal Request - - - - - The UserAgent for the request - - - - - A Dictionary of HTTP Cookies sent with this Request - - - - - The expected Response ContentType for this request - - - - - Whether the ResponseContentType has been explicitly overrided or whether it was just the default - - - - - Attach any data to this request that all filters and services can access. - - - - - The HTTP Headers in a NameValueCollection - - - - - The ?query=string in a NameValueCollection - - - - - The HTTP POST'ed Form Data in a NameValueCollection - - - - - Buffer the Request InputStream so it can be re-read - - - - - The entire string contents of Request.InputStream - - - - - - Relative URL containing /path/info?query=string - - - - - The Absolute URL for the request - - - - - The Remote IP as reported by Request.UserHostAddress - - - - - The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress - - - - - The value of the Authorization Header used to send the Api Key, null if not available - - - - - e.g. is https or not - - - - - Array of different Content-Types accepted by the client - - - - - The normalized /path/info for the request - - - - - The original /path/info as sent - - - - - The Request Body Input Stream - - - - - The size of the Request Body if provided - - - - - Access to the multi-part/formdata files posted on this request - - - - - The value of the Referrer, null if not available - - - - - Log every service request - - - - - Turn On/Off Session Tracking - - - - - Turn On/Off Raw Request Body Tracking - - - - - Turn On/Off Tracking of Responses - - - - - Turn On/Off Tracking of Exceptions - - - - - Limit logging to only Service Requests - - - - - Limit access to /requestlogs service to role - - - - - Don't log matching requests - - - - - Don't log requests of these types. - - - - - Don't log request bodys for services with sensitive information. - By default Auth and Registration requests are hidden. - - - - - Log a request - - The RequestContext - Request DTO - Response DTO or Exception - How long did the Request take - - - - View the most recent logs - - - - - - - Implement on services that need access to the RequestContext - - - - - Implement on Request DTOs that need access to the raw Request Stream - - - - - The raw Http Request Input Stream - - - - - A thin wrapper around each host's Response e.g: ASP.NET, HttpListener, MQ, etc - - - - - The underlying ASP.NET, .NET Core or HttpListener HttpResponse - - - - - The corresponding IRequest API for this Response - - - - - The Response Status Code - - - - - The Response Status Description - - - - - The Content-Type for this Response - - - - - Add a Header to this Response - - - - - - - Remove an existing Header added on this Response - - - - - - Get an existing Header added to this Response - - - - - - - Return a Redirect Response to the URL specified - - - - - - The Response Body Output Stream - - - - - The Response DTO - - - - - Buffer the Response OutputStream so it can be written in 1 batch - - - - - Signal that this response has been handled and no more processing should be done. - When used in a request or response filter, no more filters or processing is done on this request. - - - - - Close this Response Output Stream Async - - - - - Calls Response.End() on ASP.NET HttpResponse otherwise is an alias for Close(). - Useful when you want to prevent ASP.NET to provide it's own custom error page. - - - - - Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET - - - - - Flush this Response Output Stream Async - - - - - Gets a value indicating whether this instance is closed. - - - - - Set the Content Length in Bytes for this Response - - - - - - Whether the underlying TCP Connection for this Response should remain open - - - - - Whether the HTTP Response Headers have already been written. - - - - - Responsible for executing the operation within the specified context. - - The operation types. - - - - Returns the first matching RestPath - - - - - Executes the MQ DTO request. - - - - - Executes the MQ DTO request with the supplied requestContext - - - - - Executes the DTO request under the supplied requestContext. - - - - - Executes the DTO request under supplied context and option to Execute Request/Response Filters. - - - - - Executes the DTO request with an empty RequestContext. - - - - - Executes the DTO request with the current HttpRequest and option to Execute Request/Response Filters. - - - - - Allow the registration of user-defined routes for services - - - - - Maps the specified REST path to the specified request DTO. - - The type of request DTO to map - the path to. - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, and - specifies the HTTP verbs supported by the path. - - The type of request DTO to map - the path to. - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". Specify empty or - to indicate that all verbs are supported. - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - Used to rank the precedences of route definitions in reverse routing. - i.e. Priorities below 0 are auto-generated have less precedence. - - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - - The short summary of what the REST does. - - - The longer text to explain the behaviour of the REST. - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - - The short summary of what the REST does. - - - The longer text to explain the behaviour of the REST. - - - Must match rule defined in Config.RequestRules or Regex expression with format: - "{IHttpRequest.Field} =~ {pattern}", e.g "PathInfo =~ \/[0-9]+$" - - The same instance; - never . - - - diff --git a/lib/net45/ServiceStack.Server.dll b/lib/net45/ServiceStack.Server.dll deleted file mode 100644 index c47ecd03..00000000 Binary files a/lib/net45/ServiceStack.Server.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Server.xml b/lib/net45/ServiceStack.Server.xml deleted file mode 100644 index 47a8964a..00000000 --- a/lib/net45/ServiceStack.Server.xml +++ /dev/null @@ -1,305 +0,0 @@ - - - - ServiceStack.Server - - - - - Base rcon class. - - - - - Event delegate when disconnected from the server. - - - - - - Delegate for async callbacks. - - - - - - - Disconnected event. - - - - - Rcon connection socket. Always set to null when not connected. - - - - - Unique ID for each message. - - - - - Registered callbacks. - - - - - Create a new instance of rcon. - - Endpoint to connect to, usually the game server with query port. - - - - Attempts to connect to the game server for rcon operations. - - True if connection established, false otherwise. - - - - Processes a received packet. - - The packet. - - - - Disconnects from rcon. - - - - - Sends message to the server. - - Words to send. - - - - Game server endpoint. - - - - - Last exception that occured during operation. - - - - - Connected? - - - - - Gets the next unique ID to be used for transmisson. Read this before sending to pair responses to sent messages. - - - - - Exception thrown when attempting to send on a non-connected service client. - - - - - True if the packet originated on the server. - - - - - True if the packet is a response from a sent packet. - - - - - Sequence identifier. Unique to the connection. - - - - - Words. - - - - - Contains methods required for encoding and decoding rcon packets. - - - - - Decodes a packet. - - The packet. - A packet object. - - - - Decodes the packet header. - - - - - - - Decodes words in a packet. - - - - - - - Encodes a packet for transmission to the server. - - - - - - - - - - Encodes a packet header. - - - - - - - - - Encodes words. - - - - - - - Processing client used to interface with ServiceStack and allow a message to be processed. - Not an actual client. - - - - - Publish the specified message into the durable queue @queueName - - - - - Publish the specified message into the transient queue @queueName - - - - - Synchronous blocking get. - - - - - Non blocking get message - - - - - Hosting services via a binary-safe TCP-based protocol. - - - - - Factory to create consumers and producers that work with this service - - - - - Register DTOs and hanlders the MQ Host will process - - - - - - - Get Total Current Stats for all Message Handlers - - - - - - Get a Stats dump - - - - - - Start the MQ Host. Stops the server and restarts if already started. - - - - - Stop the MQ Host if not already stopped. - - - - - Processes a received packet. - - The packet. - - - - Creates a Redis MQ Server that processes each message on its own background thread. - i.e. if you register 3 handlers it will create 7 background threads: - - 1 listening to the Redis MQ Subscription, getting notified of each new message - - 3x1 Normal InQ for each message handler - - 3x1 PriorityQ for each message handler (Turn off with DisablePriorityQueues) - - When RedisMqServer Starts it creates a background thread subscribed to the Redis MQ Topic that - listens for new incoming messages. It also starts 2 background threads for each message type: - - 1 for processing the services Priority Queue and 1 processing the services normal Inbox Queue. - - Priority Queue's can be enabled on a message-per-message basis by specifying types in the - OnlyEnablePriortyQueuesForTypes property. The DisableAllPriorityQueues property disables all Queues. - - The Start/Stop methods are idempotent i.e. It's safe to call them repeatedly on multiple threads - and the Redis MQ Server will only have Started or Stopped once. - - - - - Execute global transformation or custom logic before a request is processed. - Must be thread-safe. - - - - - Execute global transformation or custom logic on the response. - Must be thread-safe. - - - - - Execute global error handler logic. Must be thread-safe. - - - - - If you only want to enable priority queue handlers (and threads) for specific msg types - - - - - Don't listen on any Priority Queues - - - - - Opt-in to only publish responses on this white list. - Publishes all responses by default. - - - - - Transient message queues are a one-pass message queue service that starts - processing messages when Start() is called. Any subsequent Start() calls - while the service is running is ignored. - - The transient service will continue to run until all messages have been - processed after which time it will shutdown all processing until Start() is called again. - - - - diff --git a/lib/net45/ServiceStack.Text.dll b/lib/net45/ServiceStack.Text.dll deleted file mode 100644 index ca0367cd..00000000 Binary files a/lib/net45/ServiceStack.Text.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.Text.xml b/lib/net45/ServiceStack.Text.xml deleted file mode 100644 index 069b6bf7..00000000 --- a/lib/net45/ServiceStack.Text.xml +++ /dev/null @@ -1,1457 +0,0 @@ - - - - ServiceStack.Text - - - - - Utils to load types - - - - - Find the type from the name supplied - - [typeName] or [typeName, assemblyName] - - - - - The top-most interface of the given type, if any. - - - - - Find type if it exists - - - - The type if it exists - - - - If AlwaysUseUtc is set to true then convert all DateTime to UTC. If PreserveUtc is set to true then UTC dates will not convert to local - - - - - - - Repairs an out-of-spec XML date/time string which incorrectly uses a space instead of a 'T' to separate the date from the time. - These string are occasionally generated by SQLite and can cause errors in OrmLite when reading these columns from the DB. - - The XML date/time string to repair - The repaired string. If no repairs were made, the original string is returned. - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - Get the type(string) constructor if exists - - The type. - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - Class to hold - - - - - - A fast, standards-based, serialization-issue free DateTime serailizer. - - - - - Determines whether this serializer can create the specified type from a string. - - The type. - - true if this instance [can create from string] the specified type; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Deserializes from reader. - - The reader. - - - - - Serializes to string. - - The value. - - - - - Serializes to writer. - - The value. - The writer. - - - - Sets which format to use when serializing TimeSpans - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - if the is configured - to support web-friendly serialized formats, ie emitting lowercase_underscore_casing for JSON - - - - - Define how property names are mapped during deserialization - - - - - Gets or sets a value indicating if the framework should throw serialization exceptions - or continue regardless of serialization errors. If the framework - will throw; otherwise, it will parse as many fields as possible. The default is . - - - - - Gets or sets a value indicating if the framework should always convert to UTC format instead of local time. - - - - - Gets or sets a value indicating if the framework should skip automatic conversions. - Dates will be handled literally, any included timezone encoding will be lost and the date will be treaded as DateTimeKind.Local - Utc formatted input will result in DateTimeKind.Utc output. Any input without TZ data will be set DateTimeKind.Unspecified - This will take precedence over other flags like AlwaysUseUtc - JsConfig.DateHandler = DateHandler.ISO8601 should be used when set true for consistent de/serialization. - - - - - Gets or sets a value indicating if the framework should always assume is in UTC format if Kind is Unspecified. - - - - - Gets or sets whether we should append the Utc offset when we serialize Utc dates. Defaults to no. - Only supported for when the JsConfig.DateHandler == JsonDateHandler.TimestampOffset - - - - - Gets or sets a value indicating if unicode symbols should be serialized as "\uXXXX". - - - - - Gets or sets a value indicating if HTML entity chars [> < & = '] should be escaped as "\uXXXX". - - - - - Gets or sets a value indicating if the framework should call an error handler when - an exception happens during the deserialization. - - Parameters have following meaning in order: deserialized entity, property name, parsed value, property type, caught exception. - - - - If set to true, Interface types will be prefered over concrete types when serializing. - - - - - If set to true, Interface types will be prefered over concrete types when serializing. - - - - - Sets the maximum depth to avoid circular dependencies - - - - - Set this to enable your own type construction provider. - This is helpful for integration with IoC containers where you need to call the container constructor. - Return null if you don't know how to construct the type and the parameterless constructor will be used. - - - - - Always emit type info for this type. Takes precedence over ExcludeTypeInfo - - - - - Never emit type info for this type - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Define custom serialization fn for BCL Structs - - - - - Opt-in flag to set some Value Types to be treated as a Ref Type - - - - - Whether there is a fn (raw or otherwise) - - - - - Define custom raw serialization fn - - - - - Define custom serialization hook - - - - - Define custom after serialization hook - - - - - Define custom deserialization fn for BCL Structs - - - - - Define custom raw deserialization fn for objects - - - - - Exclude specific properties of this type from being serialized - - - - - The property names on target types must match property names in the JSON source - - - - - The property names on target types may not match the property names in the JSON source - - - - - Uses the xsd format like PT15H10M20S - - - - - Uses the standard .net ToString method of the TimeSpan class - - - - - Get JSON string value converted to T - - - - - Get JSON string value - - - - - Get JSON string value - - - - - Get unescaped string value - - - - - Get unescaped string value - - - - - Write JSON Array, Object, bool or number values as raw string - - - - - Creates an instance of a Type from a string value - - - - - Parses the specified value. - - The value. - - - - - Shortcut escape when we're sure value doesn't contain any escaped chars - - - - - - - Given a character as utf32, returns the equivalent string provided that the character - is legal json. - - - - - - - Micro-optimization keep pre-built char arrays saving a .ToCharArray() + function call (see .net implementation of .Write(string)) - - - - - Searches the string for one or more non-printable characters. - - The string to search. - - True if there are any characters that require escaping. False if the value can be written verbatim. - - Micro optimizations: since quote and backslash are the only printable characters requiring escaping, removed previous optimization - (using flags instead of value.IndexOfAny(EscapeChars)) in favor of two equality operations saving both memory and CPU time. - Also slightly reduced code size by re-arranging conditions. - TODO: Possible Linq-only solution requires profiling: return value.Any(c => !c.IsPrintable() || c == QuoteChar || c == EscapeChar); - - - - - Implement the serializer using a more static approach - - - - - - Implement the serializer using a more static approach - - - - - - Pretty Thread-Safe cache class from: - http://code.google.com/p/dapper-dot-net/source/browse/Dapper/SqlMapper.cs - - This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), - and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** - equality. The type is fully thread-safe. - - - - - Represents an individual object, allowing access to members by-name - - - - - Get or Set the value of a named member for the underlying object - - - - - The object represented by this instance - - - - - Use the target types definition of equality - - - - - Obtain the hash of the target object - - - - - Use the target's definition of a string representation - - - - - Wraps an individual object, allowing by-name access to that instance - - - - - Provides by-name member-access to objects of a given type - - - - - Does this type support new instances via a parameterless constructor? - - - - - Create a new instance of this type - - - - - Provides a type-specific accessor, allowing by-name access for all objects of that type - - The accessor is cached internally; a pre-existing accessor may be returned - - - - Get or set the value of a named member on the target instance - - - - - Courtesy of @marcgravell - https://github.com/mgravell/protobuf-net/blob/master/src/protobuf-net/BufferPool.cs - - - - - https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element - - - - - https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element - - - - - Generic implementation of object pooling pattern with predefined pool size limit. The main - purpose is that limited number of frequently used objects can be kept in the pool for - further recycling. - - Notes: - 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there - is no space in the pool, extra returned objects will be dropped. - - 2) it is implied that if object was obtained from a pool, the caller will return it back in - a relatively short time. Keeping checked out objects for long durations is ok, but - reduces usefulness of pooling. Just new up your own. - - Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. - Rationale: - If there is no intent for reusing the object, do not use pool - just use "new". - - - - - Not using System.Func{T} because this file is linked into the (debugger) Formatter, - which does not have that type (since it compiles against .NET 2.0). - - - - - Produces an instance. - - - Search strategy is a simple linear probing which is chosen for it cache-friendliness. - Note that Free will try to store recycled objects close to the start thus statistically - reducing how far we will typically search. - - - - - Returns objects to the pool. - - - Search strategy is a simple linear probing which is chosen for it cache-friendliness. - Note that Free will try to store recycled objects close to the start thus statistically - reducing how far we will typically search in Allocate. - - - - - Removes an object from leak tracking. - - This is called when an object is returned to the pool. It may also be explicitly - called if an object allocated from the pool is intentionally not being returned - to the pool. This can be of use with pooled arrays if the consumer wants to - return a larger array to the pool than was originally allocated. - - - - - this is RAII object to automatically release pooled object when its owning pool - - - - - Shared object pool for roslyn - - Use this shared pool if only concern is reducing object allocations. - if perf of an object pool itself is also a concern, use ObjectPool directly. - - For example, if you want to create a million of small objects within a second, - use the ObjectPool directly. it should have much less overhead than using this. - - - - - pool that uses default constructor with 100 elements pooled - - - - - pool that uses default constructor with 20 elements pooled - - - - - pool that uses string as key with StringComparer.OrdinalIgnoreCase as key comparer - - - - - pool that uses string as element with StringComparer.OrdinalIgnoreCase as element comparer - - - - - pool that uses string as element with StringComparer.Ordinal as element comparer - - - - - Used to reduce the # of temporary byte[]s created to satisfy serialization and - other I/O requests - - - - pooled memory : 4K * 512 = 4MB - - - - Manages pools of RecyclableMemoryStream objects. - - - There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams - as they write more data. - - For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all - multiples of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer - usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. - - - - - Generic delegate for handling events without any arguments. - - - - - Delegate for handling large buffer discard reports. - - Reason the buffer was discarded. - - - - Delegate for handling reports of stream size when streams are allocated - - Bytes allocated. - - - - Delegate for handling periodic reporting of memory use statistics. - - Bytes currently in use in the small pool. - Bytes currently free in the small pool. - Bytes currently in use in the large pool. - Bytes currently free in the large pool. - - - - pools[0] = 1x largeBufferMultiple buffers - pools[1] = 2x largeBufferMultiple buffers - etc., up to maximumBufferSize - - - - - Initializes the memory manager with the default block/buffer specifications. - - - - - Initializes the memory manager with the given block requiredSize. - - Size of each block that is pooled. Must be > 0. - Each large buffer will be a multiple of this value. - Buffers larger than this are not pooled - blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. - maximumBufferSize is not a multiple of largeBufferMultiple - - - - The size of each block. It must be set at creation and cannot be changed. - - - - - All buffers are multiples of this number. It must be set at creation and cannot be changed. - - - - - Gets or sets the maximum buffer size. - - Any buffer that is returned to the pool that is larger than this will be - discarded and garbage collected. - - - - Number of bytes in small pool not currently in use - - - - - Number of bytes currently in use by stream from the small pool - - - - - Number of bytes in large pool not currently in use - - - - - Number of bytes currently in use by streams from the large pool - - - - - How many blocks are in the small pool - - - - - How many buffers are in the large pool - - - - - How many bytes of small free blocks to allow before we start dropping - those returned to us. - - - - - How many bytes of large free buffers to allow before we start dropping - those returned to us. - - - - - Maximum stream capacity in bytes. Attempts to set a larger capacity will - result in an exception. - - A value of 0 indicates no limit. - - - - Whether to save callstacks for stream allocations. This can help in debugging. - It should NEVER be turned on generally in production. - - - - - Whether dirty buffers can be immediately returned to the buffer pool. E.g. when GetBuffer() is called on - a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned - to the buffer pool immediately. - Note when enabling this setting that the user is responsible for ensuring that any buffer previously - retrieved from a stream which is subsequently modified is not used after modification (as it may no longer - be valid). - - - - - Removes and returns a single block from the pool. - - A byte[] array - - - - Returns a buffer of arbitrary size from the large buffer pool. This buffer - will be at least the requiredSize and always be a multiple of largeBufferMultiple. - - The minimum length of the buffer - The tag of the stream returning this buffer, for logging if necessary. - A buffer of at least the required size. - - - - Returns the buffer to the large pool - - The buffer to return. - The tag of the stream returning this buffer, for logging if necessary. - buffer is null - buffer.Length is not a multiple of LargeBufferMultiple (it did not originate from this pool) - - - - Returns the blocks to the pool - - Collection of blocks to return to the pool - The tag of the stream returning these blocks, for logging if necessary. - blocks is null - blocks contains buffers that are the wrong size (or null) for this memory manager - - - - Retrieve a new MemoryStream object with no tag and a default initial capacity. - - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and a default initial capacity. - - A tag which can be used to track the source of the stream. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and at least the given capacity. - - A tag which can be used to track the source of the stream. - The minimum desired capacity for the stream. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using - a single continugous underlying buffer. - - Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations - where the initial size is known and it is desirable to avoid copying data between the smaller underlying - buffers to a single large one. This is most helpful when you know that you will always call GetBuffer - on the underlying stream. - A tag which can be used to track the source of the stream. - The minimum desired capacity for the stream. - Whether to attempt to use a single contiguous buffer. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and with contents copied from the provided - buffer. The provided buffer is not wrapped or used after construction. - - The new stream's position is set to the beginning of the stream when returned. - A tag which can be used to track the source of the stream. - The byte buffer to copy data from. - The offset from the start of the buffer to copy from. - The number of bytes to copy from the buffer. - A MemoryStream. - - - - Triggered when a new block is created. - - - - - Triggered when a new block is created. - - - - - Triggered when a new large buffer is created. - - - - - Triggered when a new stream is created. - - - - - Triggered when a stream is disposed. - - - - - Triggered when a stream is finalized. - - - - - Triggered when a stream is finalized. - - - - - Triggered when a user converts a stream to array. - - - - - Triggered when a large buffer is discarded, along with the reason for the discard. - - - - - Periodically triggered to report usage statistics. - - - - - MemoryStream implementation that deals with pooling and managing memory streams which use potentially large - buffers. - - - This class works in tandem with the RecylableMemoryStreamManager to supply MemoryStream - objects to callers, while avoiding these specific problems: - 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC - 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This - leads to continual memory growth as each stream approaches the maximum allowed size. - 3. Memory copying - Each time a MemoryStream grows, all the bytes are copied into new buffers. - This implementation only copies the bytes when GetBuffer is called. - 4. Memory fragmentation - By using homogeneous buffer sizes, it ensures that blocks of memory - can be easily reused. - - The stream is implemented on top of a series of uniformly-sized blocks. As the stream's length grows, - additional blocks are retrieved from the memory manager. It is these blocks that are pooled, not the stream - object itself. - - The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single - contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks - are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, - split by size--they are multiples of a chunk size (1 MB by default). - - Once a large buffer is assigned to the stream the blocks are NEVER again used for this stream. All operations take place on the - large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers - are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). - - - - - - All of these blocks must be the same size - - - - - This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on - construction if the caller immediately requests a single large buffer. - - If this field is non-null, it contains the concatenation of the bytes found in the individual - blocks. Once it is created, this (or a larger) largeBuffer will be used for the life of the stream. - - - - - This list is used to store buffers once they're replaced by something larger. - This is for the cases where you have users of this class that may hold onto the buffers longer - than they should and you want to prevent race conditions which could corrupt the data. - - - - - Unique identifier for this stream across it's entire lifetime - - Object has been disposed - - - - A temporary identifier for the current usage of this stream. - - Object has been disposed - - - - Gets the memory manager being used by this stream. - - Object has been disposed - - - - Callstack of the constructor. It is only set if MemoryManager.GenerateCallStacks is true, - which should only be in debugging situations. - - - - - Callstack of the Dispose call. It is only set if MemoryManager.GenerateCallStacks is true, - which should only be in debugging situations. - - - - - This buffer exists so that WriteByte can forward all of its calls to Write - without creating a new byte[] buffer on every call. - - - - - Allocate a new RecyclableMemoryStream object. - - The memory manager - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - The initial requested size to prevent future allocations - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - The initial requested size to prevent future allocations - An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. - - - - Returns the memory used by this stream back to the pool. - - Whether we're disposing (true), or being called by the finalizer (false) - This method is not thread safe and it may not be called more than once. - - - - Equivalent to Dispose - - - - - Gets or sets the capacity - - Capacity is always in multiples of the memory manager's block size, unless - the large buffer is in use. Capacity never decreases during a stream's lifetime. - Explicitly setting the capacity to a lower value than the current value will have no effect. - This is because the buffers are all pooled by chunks and there's little reason to - allow stream truncation. - - Object has been disposed - - - - Gets the number of bytes written to this stream. - - Object has been disposed - - - - Gets the current position in the stream - - Object has been disposed - - - - Whether the stream can currently read - - - - - Whether the stream can currently seek - - - - - Always false - - - - - Whether the stream can currently write - - - - - Returns a single buffer containing the contents of the stream. - The buffer may be longer than the stream length. - - A byte[] buffer - IMPORTANT: Doing a Write() after calling GetBuffer() invalidates the buffer. The old buffer is held onto - until Dispose is called, but the next time GetBuffer() is called, a new buffer from the pool will be required. - Object has been disposed - - - - Returns a new array with a copy of the buffer's contents. You should almost certainly be using GetBuffer combined with the Length to - access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included - for the sake of completeness. - - Object has been disposed - - - - Reads from the current position into the provided buffer - - Destination buffer - Offset into buffer at which to start placing the read bytes. - Number of bytes to read. - The number of bytes read - buffer is null - offset or count is less than 0 - offset subtracted from the buffer length is less than count - Object has been disposed - - - - Writes the buffer to the stream - - Source buffer - Start position - Number of bytes to write - buffer is null - offset or count is negative - buffer.Length - offset is not less than count - Object has been disposed - - - - Returns a useful string for debugging. This should not normally be called in actual production code. - - - - - Writes a single byte to the current position in the stream. - - byte value to write - Object has been disposed - - - - Reads a single byte from the current position in the stream. - - The byte at the current position, or -1 if the position is at the end of the stream. - Object has been disposed - - - - Sets the length of the stream - - value is negative or larger than MaxStreamLength - Object has been disposed - - - - Sets the position to the offset from the seek location - - How many bytes to move - From where - The new position - Object has been disposed - offset is larger than MaxStreamLength - Invalid seek origin - Attempt to set negative position - - - - Synchronously writes this stream's bytes to the parameter stream. - - Destination stream - Important: This does a synchronous write, which may not be desired in some situations - - - - Release the large buffer (either stores it for eventual release or returns it immediately). - - - - - Allow Type to be deserialized into late-bould object Types using __type info - - - - - Allow Type to be deserialized into late-bould object Types using __type info - - - - - Reusable StringBuilder ThreadStatic Cache - - - - - Alternative Reusable StringBuilder ThreadStatic Cache - - - - - Helpful extensions on ReadOnlySpan<char> - Previous extensions on StringSegment available from: https://gist.github.com/mythz/9825689f0db7464d1d541cb62954614c - - - - - Returns null if Length == 0, string.Empty if value[0] == NonWidthWhitespace, otherise returns value.ToString() - - - - - Reusable StringWriter ThreadStatic Cache - - - - - Alternative Reusable StringWriter ThreadStatic Cache - - - - - A class to allow the conversion of doubles to string representations of - their exact decimal values. The implementation aims for readability over - efficiency. - - Courtesy of @JonSkeet - http://www.yoda.arachsys.com/csharp/DoubleConverter.cs - - - - - - - - How many digits are *after* the decimal point - - - - - Constructs an arbitrary decimal expansion from the given long. - The long must not be negative. - - - - - Multiplies the current expansion by the given amount, which should - only be 2 or 5. - - - - - Shifts the decimal point; a negative value makes - the decimal expansion bigger (as fewer digits come after the - decimal place) and a positive value makes the decimal - expansion smaller. - - - - - Removes leading/trailing zeroes from the expansion. - - - - - Converts the value to a proper decimal string representation. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether the specified type is convertible from string. - - The type. - - true if the specified type is convertible from string; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Parses the specified type. - - The type. - The value. - - - - - Useful extension method to get the Dictionary[string,string] representation of any POCO type. - - - - - - Recursively prints the contents of any POCO object in a human-friendly, readable format - - - - - - Print Dump to Console.WriteLine - - - - - Print string.Format to Console.WriteLine - - - - - Parses the specified value. - - The value. - - - - - Populate an object with Example data. - - - - - - - Populates the object with example data. - - - Tracks how deeply nested we are - - - - - Public Code API to register commercial license for ServiceStack. - - - - - Internal Utilities to verify licensing - - - - - Maps the path of a file in the context of a VS project in a Console App - - the relative path - the absolute path - Assumes static content is two directories above the /bin/ directory, - eg. in a unit test scenario the assembly would be in /bin/Debug/. - - - - Maps the path of a file in the context of a VS 2017+ multi-platform project in a Console App - - the relative path - the absolute path - Assumes static content is two directories above the /bin/ directory, - eg. in a unit test scenario the assembly would be in /bin/Debug/net45 - - - - Maps the path of a file in the bin\ folder of a self-hosted scenario - - the relative path - the absolute path - Assumes static content is copied to /bin/ folder with the assemblies - - - - Maps the path of a file in an ASP.NET hosted scenario - - the relative path - the absolute path - Assumes static content is in the parent folder of the /bin/ directory - - - - Add a Property attribute at runtime. - Not threadsafe, should only add attributes on Startup. - - - - - Add a Property attribute at runtime. - Not threadsafe, should only add attributes on Startup. - - - - - Implement the serializer using a more static approach - - - - - - Creates a new instance of type. - First looks at JsConfig.ModelFactory before falling back to CreateInstance - - - - - Creates a new instance of type. - First looks at JsConfig.ModelFactory before falling back to CreateInstance - - - - - Creates a new instance from the default constructor of type - - - - - @jonskeet: Collection of utility methods which operate on streams. - r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - - - - - Reads the given stream up to the end, returning the data as a byte - array. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer size. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer for transferring data. Note that the - current contents of the buffer is ignored, so the buffer needn't - be cleared beforehand. - - - - - Copies all the data from one stream into another. - - - - - Copies all the data from one stream into another, using a buffer - of the given size. - - - - - Copies all the data from one stream into another, using the given - buffer for transferring data. Note that the current contents of - the buffer is ignored, so the buffer needn't be cleared beforehand. - - - - - Reads exactly the given number of bytes from the specified stream. - If the end of the stream is reached before the specified amount - of data is read, an exception is thrown. - - - - - Reads into a buffer, filling it completely. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Same as ReadExactly, but without the argument checks. - - - - - Returns bytes in publiclyVisible MemoryStream - - - - - Converts from base: 0 - 62 - - The source. - From. - To. - - - - - Skip the encoding process for 'safe strings' - - - - - - diff --git a/lib/net45/ServiceStack.dll b/lib/net45/ServiceStack.dll deleted file mode 100644 index 7e686471..00000000 Binary files a/lib/net45/ServiceStack.dll and /dev/null differ diff --git a/lib/net45/ServiceStack.xml b/lib/net45/ServiceStack.xml deleted file mode 100644 index 6710a4c1..00000000 --- a/lib/net45/ServiceStack.xml +++ /dev/null @@ -1,7165 +0,0 @@ - - - - ServiceStack - - - - - Inherit from this class if you want to host your web services inside an - ASP.NET application. - - - - - Get an IAppHost container. - Note: Registering dependencies should only be done during setup/configuration - stage and remain immutable there after for thread-safety. - - - - - - - Inherit from this class if you want to host your web services inside a - Console Application, Windows Service, etc. - - Usage of HttpListener allows you to host webservices on the same port (:80) as IIS - however it requires admin user privillages. - - - - - Exécute les tâches définies par l'application associées à la libération ou à la redéfinition des ressources non managées. - - 2 - - - - Default redirct URL if [Authenticate] attribute doesn't permit access. - - - - - Default redirct URL if Required Role or Permission attributes doesn't permit access. - - - - - Indicates that the request dto, which is associated with this attribute, - requires authentication. - - - - - Restrict authentication to a specific . - For example, if this attribute should only permit access - if the user is authenticated with , - you should set this property to . - - - - - Redirect the client to a specific URL if authentication failed. - If this property is null, simply `401 Unauthorized` is returned. - - - - - Enable the authentication feature and configure the AuthService. - - - - - Whether to Create Digest Auth MD5 Hash when Creating/Updating Users. - Defaults to only creating Digest Auth when DigestAuthProvider is registered. - - - - - Should UserName or Emails be saved in AuthRepository in LowerCase - - - - - Use a plugin to register authProvider dynamically. Your plugin can implement `IPreInitPlugin` interface - to call `appHost.GetPlugin<AuthFeature>().RegisterAuthProvider()` before the AuthFeature is registered. - - - - - The Interface Auth Repositories need to implement to support API Keys - - - - - The POCO Table used to persist API Keys - - - - - Enable access to protected Services using API Keys - - - - - Modify the registration of GetApiKeys and RegenerateApiKeys Services - - - - - How much entropy should the generated keys have. (default 24) - - - - - Generate different keys for different environments. (default live,test) - - - - - Different types of Keys each user can have. (default secret) - - - - - Whether to automatically expire keys. (default no expiry) - - - - - Automatically create the ApiKey Table for AuthRepositories which need it. (default true) - - - - - Whether to only allow access via API Key from a secure connection. (default true) - - - - - Change how API Key is generated - - - - - Run custom filter after API Key is created - - - - - Cache the User Session so it can be reused between subsequent API Key Requests - - - - - Whether to allow API Keys in 'apikey' QueryString or FormData - - - - - Specify all roles to be used by this application - - - - - Only allow access to users in specified roles - - - - - Inject logic into existing services by introspecting the request and injecting your own - validation logic. Exceptions thrown will have the same behaviour as if the service threw it. - - If a non-null object is returned the request will short-circuit and return that response. - - The instance of the service - GET,POST,PUT,DELETE - - Response DTO; non-null will short-circuit execution and return that response - - - - Get AuthProviders Registered in AuthFeature Plugin. - - specific provider, or null for all providers - - - - - - Get specific AuthProvider - - - - - - - Public API entry point to authenticate via code - - - null; if already autenticated otherwise a populated instance of AuthResponse - - - - The specified may change as a side-effect of this method. If - subsequent code relies on current data be sure to reload - the session istance via . - - - - - Conveneint base class with empty virtual methods so subclasses only need to override the hooks they need. - - - - - Allows specifying a global fallback config that if exists is formatted with the Provider as the first arg. - E.g. this appSetting with the TwitterAuthProvider: - oauth.CallbackUrl="http://localhost:11001/auth/{0}" - Would result in: - oauth.CallbackUrl="http://localhost:11001/auth/twitter" - - - - - - Remove the Users Session - - - - - - - - Create a Facebook App at: https://developers.facebook.com/apps - The Callback URL for your app should match the CallbackUrl provided. - - - - - Create an App at: https://github.com/settings/applications/new - The Callback URL for your app should match the CallbackUrl provided. - - - - - Download Yammer User Info given its ID. - - - The Yammer User ID. - - - The User info in JSON format. - - - - Yammer provides a method to retrieve current user information via - "https://www.yammer.com/api/v1/users/current.json". - - - However, to ensure consistency with the rest of the Auth codebase, - the explicit URL will be used, where [:id] denotes the User ID: - "https://www.yammer.com/api/v1/users/[:id].json" - - - Refer to: https://developer.yammer.com/restapi/ for full documentation. - - - - - - Remove the Users Session - - - - - - - - The entry point for all AuthProvider providers. Runs inside the AuthService so exceptions are treated normally. - Overridable so you can provide your own Auth implementation. - - - - - Determine if the current session is already authenticated with this AuthProvider - - - - - Called when the user is registered or on the first OAuth login - - - - - Called after the user has successfully authenticated - - - - - Fired before the session is removed after the /auth/logout Service is called - - - - - Fired when a new Session is created - - - - - - Fired before Session is resolved - - - - - Override with Custom Validation logic to Assert if User is allowed to Authenticate. - Returning a non-null response invalidates Authentication with IHttpResult response returned to client. - - - - - Thread-safe In memory UserAuth data store so it can be used without a dependency on Redis. - - - - - Used to Issue and process JWT Tokens and registers ConvertSessionToToken Service to convert Sessions to JWT Tokens - - - - - Whether to populate the Bearer Token in the AuthenticateResponse - - - - - Enable access to protected Services using JWT Tokens - - - - - Different HMAC Algorithms supported - - - - - Different RSA Signing Algorithms supported - - - - - Whether to only allow access via API Key from a secure connection. (default true) - - - - - Run custom filter after JWT Header is created - - - - - Run custom filter after JWT Payload is created - - - - - Run custom filter after session is restored from a JWT Token - - - - - Whether to encrypt JWE Payload (default false). - Uses RSA-OAEP for Key Encryption and AES/128/CBC HMAC SHA256 for Conent Encryption - - - - - Which Hash Algorithm should be used to sign the JWT Token. (default HS256) - - - - - Whether to only allow processing of JWT Tokens using the configured HashAlgorithm. (default true) - - - - - The Issuer to embed in the token. (default ssjwt) - - - - - The Audience to embed in the token. (default null) - - - - - Embed Multiple Audiences in the token. (default none) - A JWT is valid if it contains ANY audience in this List - - - - - What Id to use to identify the Key used to sign the token. (default First 3 chars of Base64 Key) - - - - - The AuthKey used to sign the JWT Token - - - - - Allow verification using multiple Auth keys - - - - - The RSA Private Key used to Sign the JWT Token when RSA is used - - - - - Convenient overload to intialize the Private Key via exported XML - - - - - The RSA Public Key used to Verify the JWT Token when RSA is used - - - - - Convenient overload to intialize the Public Key via exported XML - - - - - Allow verification using multiple public keys - - - - - How long should JWT Tokens be valid for. (default 14 days) - - - - - How long should JWT Refresh Tokens be valid for. (default 365 days) - - - - - Convenient overload to initialize ExpireTokensIn with an Integer - - - - - Allow custom logic to invalidate JWT Tokens - - - - - Allow custom logic to invalidate Refresh Tokens - - - - - Whether to invalidate all JWT Tokens issued before a specified date. - - - - - Modify the registration of ConvertSessionToToken Service - - - - - Allow JWT in ?ss-tok=jwt QueryString. (default false) - - - - - Allow JWT in ss-tok=jwt HTML POST FormData. (default false) - - - - - Whether to automatically remove expired or invalid cookies - - - - - Whether to also Include Token in ConvertSessionToTokenResponse - - - - - Return token payload which is both verified and still valid - - - - - Return token payload which has been verified to be created using the configured encryption key. - - - - - The entry point for all AuthProvider providers. Runs inside the AuthService so exceptions are treated normally. - Overridable so you can provide your own Auth implementation. - - - - - - - - - Sets the CallbackUrl and session.ReferrerUrl if not set and initializes the session tokens for this AuthProvider - - - - - - - - - Create Odnoklassniki App at: http://www.odnoklassniki.ru/devaccess - The Callback URL for your app should match the CallbackUrl provided. - - NB: They claim they use OAuth 2.0, but they in fact don't. - http://apiok.ru/wiki/display/api/Authorization+OAuth+2.0 - - - - - Allow utilizing an alternative PBKDF2 implementation. - - - - - The PBKDF2 strategy PasswordHasher implementation that's used for hashing PBKDF2 passwords. - - - - - The Password Hasher provider used to hash users passwords which uses the same algorithm used by ASP.NET Identity v3: - PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - - - - - Gets the number of iterations used when hashing passwords using PBKDF2. Default is 10,000. - - - - - The number of iterations used when hashing passwords using PBKDF2. Default is 10,000. - - - - - The Format Version specifier for this PasswordHasher embedded as the first byte in password hashes. - - - - - Returns a hashed representation of the supplied for the specified . - - The user whose password is to be hashed. - The password to hash. - A hashed representation of the supplied for the specified . - - - - Specifies the PRF which should be used for the key derivation algorithm. - - - - - The HMAC algorithm (RFC 2104) using the SHA-1 hash function (FIPS 180-4). - - - - - The HMAC algorithm (RFC 2104) using the SHA-256 hash function (FIPS 180-4). - - - - - The HMAC algorithm (RFC 2104) using the SHA-512 hash function (FIPS 180-4). - - - - - Provides algorithms for performing key derivation. - - - - - Performs key derivation using the PBKDF2 algorithm. - - The password from which to derive the key. - The salt to be used during the key derivation process. - The pseudo-random function to be used in the key derivation process. - The number of iterations of the pseudo-random function to apply - during the key derivation process. - The desired length (in bytes) of the derived key. - The derived key. - - The PBKDF2 algorithm is specified in RFC 2898. - - - - - Internal interface used for abstracting away the PBKDF2 implementation since the implementation is OS-specific. - - - - - A PBKDF2 provider which utilizes the managed hash algorithm classes as PRFs. - This isn't the preferred provider since the implementation is slow, but it is provided as a fallback. - - - - - Update an existing registraiton - - - - - Create new Registration - - - - - Logic to update UserAuth from Registration info, not enabled on PUT because of security. - - - - - Thank you Martijn - http://www.dijksterhuis.org/creating-salted-hash-values-in-c/ - - Stronger/Slower Alternative: - https://github.com/defuse/password-hashing/blob/master/PasswordStorage.cs - - - - - Create an app at https://dev.twitter.com/apps to get your ConsumerKey and ConsumerSecret for your app. - The Callback URL for your app should match the CallbackUrl provided. - - - - - Creates the required missing tables or DB schema - - - - - Create VK App at: http://vk.com/editapp?act=create - The Callback URL for your app should match the CallbackUrl provided. - - - - - If previous attemts failes, the subsequential calls - build up code value like "code1,code2,code3" - so we need the last one only - - - - - - - The ServiceStack Yammer OAuth provider. - - - - This provider is loosely based on the existing ServiceStack's Facebook OAuth provider. - - - For the full info on Yammer's OAuth2 authentication flow, refer to: - https://developer.yammer.com/authentication/#a-oauth2 - - - Note: Add these to your application / web config settings under appSettings and replace - values as appropriate. - - - - - - - - - ]]> - - - - - - The OAuth provider name / identifier. - - - - - Initializes a new instance of the class. - - - The application settings (in web.config). - - - - - Gets or sets the Yammer OAuth client id. - - - - - Gets or sets the Yammer OAuth client secret. - - - - - Gets or sets the Yammer OAuth pre-auth url. - - - - - Authenticate against Yammer OAuth endpoint. - - - The auth service. - - - The session. - - - The request. - - - The . - - - - - Load the UserAuth info into the session. - - - The User session. - - - The OAuth tokens. - - - The auth info. - - - - - Load the UserOAuth info into the session. - - - The auth session. - - - The OAuth tokens. - - - - - The Yammer User's email addresses. - - - - - Gets or sets the email address type (e.g. primary). - - - - - Gets or sets the email address. - - - - - Create Yandex App at: https://oauth.yandex.ru/client/new - The Callback URL for your app should match the CallbackUrl provided. - - - - - The BaseUrl of the ServiceStack instance (inferred) - - - - - Name of the ServiceStack Instance (inferred) - - - - - Textual description of the AutoQuery Services (shown in Home Services list) - - - - - Icon for this ServiceStack Instance (shown in Home Services list) - - - - - The different Content Type formats to display - - - - - The configured MaxLimit for AutoQuery - - - - - Whether to publish this Service to the public Services registry - - - - - Only show AutoQuery Services attributed with [AutoQueryViewer] - - - - - List of different Search Filters available - - - - - The Column which should be selected by default - - - - - The Query Type filter which should be selected by default - - - - - The search text which should be populated by default - - - - - Link to your website users can click to find out more about you - - - - - A custom logo or image that users can click on to visit your site - - - - - The default color of text - - - - - The default color of links - - - - - The default background color of each screen - - - - - The default background image of each screen anchored to the bottom left - - - - - The default icon for each of your AutoQuery Services - - - - - Returns true if the User Is Authenticated - - - - - How many queries are available to this user - - - - - Removes items from cache that have keys matching the specified wildcard pattern - - Cache client - The wildcard, where "*" means any sequence of characters and "?" means any single character. - - - - Removes items from the cache based on the specified regular expression pattern - - Cache client - Regular expression pattern to search cache keys - - - - The CacheKey to be use store the response against - - - - - The base Cache Key used to cache the Service response - - - - - Additional CacheKey Modifiers used to cache different outputs for a single Service Response - - - - - How long to cache the resource for. Fallsback to HttpCacheFeature.DefaultExpiresIn - - - - - The unique ETag returned for this resource clients can use to determine whether their local version has changed - - - - - The Age for this resource returned to clients - - - - - The MaxAge returned to clients to indicate how long they can use their local cache before re-validating - - - - - The LastModified date to use for the Cache and HTTP Header - - - - - Cache-Control HTTP Headers - - - - - Create unique cache per user - - - - - Use HostContext.LocalCache or HostContext.Cache - - - - - Skip compression for this Cache Result - - - - - Cache the Response of a Service - - - - - Cache expiry in seconds - - - - - MaxAge in seconds - - - - - Cache-Control HTTP Headers - - - - - Vary cache per user - - - - - Vary cache for users in these roles - - - - - Vary cache for different HTTP Headers - - - - - Use HostContext.LocalCache or HostContext.Cache - - - - - Skip compression for this Cache Result - - - - - Decorates the ICacheClient (and its sibblings) prefixing every key with the given prefix - - Usefull for multi-tenant environments - - - - - Decorates the ICacheClient (and its sibblings) prefixing every key with the given prefix - - Usefull for multi-tenant environments - - - - - Create new instance of CacheEntry. - - - - UTC time at which CacheEntry expires. - - - - Stores The value with key only if such key doesn't exist at the server yet. - - - - - Adds or replaces the value with key. - - - - - Adds or replaces the value with key. - - - - - Replace the value with specified key if it exists. - - - - - Add the value with key to the cache, set to never expire. - - - - - Add or replace the value with key to the cache, set to never expire. - - - - - Replace the value with key in the cache, set to never expire. - - - - - Add the value with key to the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Add that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Add or replace the value with key to the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Set that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Replace the value with key in the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Replace that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Add the value with key to the cache, set to expire after specified TimeSpan. - - - - - Add or replace the value with key to the cache, set to expire after specified TimeSpan. - - - - - Replace the value with key in the cache, set to expire after specified TimeSpan. - - - - - More familiar name for the new crowd. - - - - - The tier lets you specify a retrieving a setting with the tier prefix first before falling back to the original key. - E.g a tier of 'Live' looks for 'Live.{Key}' or if not found falls back to '{Key}'. - - - - - Returns string if exists, otherwise null - - - - - - - Would've preferred to use [assembly: ContractNamespace] attribute but it is not supported in Mono - - - - - Gets the nullable app setting. - - - - - Gets the app setting. - - - - - Returns AppSetting[key] if exists otherwise defaultValue - - - - - Returns AppSetting[key] if exists otherwise defaultValue, for non-string values - - - - - Gets the connection string. - - - - - Gets the list from app setting. - - - - - Gets the dictionary from app setting. - - - - - Provides a common interface for Settings providers such as - ConfigurationManager or Azure's RoleEnvironment. The only - requirement is that if the implementation cannot find the - specified key, the return value must be null - - The key for the setting - The string value of the specified key, or null if the key - was invalid - - - - Return all keys in this configuration source. - - - - - - Represents a builder for the class. - - - - - Initializes a new instance of the class with a specified . - - The of the . - - - - Adds an that reads configuration values from the Web.config file. - - The . - - - - Adds an that reads configuration values from the Web.config file. - - The tier of the . - The . - - - - Adds a that reads configuration values from a dictionary. - - The dictionary of settings to add. - The . - - - - Adds an that reads configuration values from environmental variables. - - The . - - - - Adds an that reads configuration values from environmental variables. - - The tier of the . - The . - - - - Adds an that reads configuration values from a text file at . - - The path of the text file. - The . - - - - Adds an that reads configuration values from a text file at with a specified . - - The path of the text file. - The delimeter fo the text file. - The . - - - - Adds an that reads configuration values from a text file at with a specified . - - The path of the text file. - The delimeter fo the text file. - The tier of the . - The . - - - - Builds an . - - An . - - - - Wrappers for improving consistency with .NET Core Conventions - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - - - - - - - Registers a named instance of type in the IoC container and - adds auto-wiring to the specified type. - - - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - The reuse scope is set to none (transient). - - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - The reuse scope is set to none (transient). - - - - - - Registers the types in the IoC container and - adds auto-wiring to the specified types. - The reuse scope is set to none (transient). - - - - - - Register a singleton instance as a runtime type - - - - - Plugin adds support for Cross-origin resource sharing (CORS, see http://www.w3.org/TR/access-control/). - CORS allows to access resources from different domain which usually forbidden by origin policy. - - - - - Represents a default constructor with Allow Origin equals to "*", Allowed GET, POST, PUT, DELETE, OPTIONS request and allowed "Content-Type" header. - - - - - Lets you Register new Services and the optional restPaths will be registered against - this default Request Type - - - - - Change the default HTML view or template used for the HTML response of this service - - - - - Tracks created objects. - Used by to track created IDisposable instances. - These instances are tracked and disposed at the end of a request. - - - - - Adds disposable to the tracker - - - - - Dispose all disposables in the tracker. - If disposable is still alive alose .Release() is called to release the object. - - - - - Naming convention for the ResponseStatus property name on the response DTO - - - - - Create an instance of the service response dto type and inject it with the supplied responseStatus - - - - - - - - - - - - - - - - - Override to provide additional/less context about the Service Exception. - By default the request is serialized and appended to the ResponseStatus StackTrace. - - - - - Attribute marks that specific response class has support for Cross-origin resource sharing (CORS, see http://www.w3.org/TR/access-control/). CORS allows to access resources from different domain which usually forbidden by origin policy. - - - - - Represents a default constructor with Allow Origin equals to "*", Allowed GET, POST, PUT, DELETE, OPTIONS request and allowed "Content-Type" header. - - - - - Redirect to the https:// version of this url if not already. - - - - - Don't redirect when in DebugMode - - - - - Don't redirect if the request was a forwarded request, e.g. from a Load Balancer - - - - - Base class for entity validator classes. - - The type of the object being validated - - Base class for entity validator classes. - - The type of the object being validated - - - - Sets the cascade mode for all rules within this validator. - - - - - Validates the specified instance - - The object to validate - A ValidationResult object containing any validation failures - - - - Validates the specified instance asynchronously - - The object to validate - Cancellation token - A ValidationResult object containing any validation failures - - - - Validates the specified instance. - - Validation Context - A ValidationResult object containing any validation failures. - - - - Validates the specified instance asynchronously. - - Validation Context - Cancellation token - A ValidationResult object containing any validation failures. - - - - Adds a rule to the current validator. - - - - - - Creates a that can be used to obtain metadata about the current validator. - - - - - Defines a validation rule for a specify property. - - - RuleFor(x => x.Surname)... - - The type of property being validated - The expression representing the property to validate - an IRuleBuilder instance on which validators can be defined - - - - Invokes a rule for each item in the collection - - Type of property - Expression representing the collection to validate - An IRuleBuilder instance on which validators can be defined - - - - Defines a custom validation rule using a lambda expression. - If the validation rule fails, it should return a instance of a ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules. - - - - Defines a custom validation rule using a lambda expression. - If the validation rule fails, it should return an instance of ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules - - - - Defines a custom asynchronous validation rule using a lambda expression. - If the validation rule fails, it should asynchronously return a instance of a ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules. - - - - Defines a custom asynchronous validation rule using a lambda expression. - If the validation rule fails, it should asynchronously return an instance of ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules - - - - Defines a RuleSet that can be used to group together several validators. - - The name of the ruleset. - Action that encapsulates the rules in the ruleset. - - - - Defines a condition that applies to several rules - - The condition that should apply to multiple rules - Action that encapsulates the rules. - - - - - Defines an inverse condition that applies to several rules - - The condition that should be applied to multiple rules - Action that encapsulates the rules - - - - Defines an asynchronous condition that applies to several rules - - The asynchronous condition that should apply to multiple rules - Action that encapsulates the rules. - - - - - Defines an inverse asynchronous condition that applies to several rules - - The asynchronous condition that should be applied to multiple rules - Action that encapsulates the rules - - - - Includes the rules from the specified validator - - - - - Returns an enumerator that iterates through the collection of validation rules. - - - A that can be used to iterate through the collection. - - 1 - - - - Throws an exception if the instance being validated is null. - - - - - - Defines a RuleSet that can be used to provide specific validation rules for specific HTTP methods (GET, POST...) - - The HTTP methods where this rule set should be used. - Action that encapuslates the rules in the ruleset. - - - - Container class for dependent rule definitions - - - - - - Class that can be used to find all the validators from a collection of types. - - - - - Creates a scanner that works on a sequence of types. - - - - - Finds all the validators in the specified assembly. - - - - - Finds all the validators in the specified assemblies - - - - - Finds all the validators in the assembly containing the specified type. - - - - - Performs the specified action to all of the assembly scan results. - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Result of performing a scan. - - - - - Creates an instance of an AssemblyScanResult. - - - - - Validator interface type, eg IValidator<Foo> - - - - - Concrete type that implements the InterfaceType, eg FooValidator. - - - - - Implementation of and that looks for - the instance on the specified or - in order to provide the validator instance. - - - - - Creates an instance of . - - - - - Creates an instance of with the supplied instance factory delegate - used for creation of instances. - - The instance factory delegate. - - - - Gets a validator for the appropriate type. - - - - - Gets a validator for the appropriate type. - - Created instance; if a validator cannot be - created. - - - - Gets a validator for . - - The instance to get a validator for. - Created instance; if a validator cannot be - created. - - - - Validator attribute to define the class that will describe the Validation rules. - - - - - The type of the validator used to validate the current type or parameter. - - - - - Creates an instance of allowing a validator type to be specified. - - - - - Overrides the name of the property associated with this rule. - NOTE: This is a considered to be an advanced feature. 99% of the time that you use this, you actually meant to use WithName. - - The current rule - The property name to use - - - - - Specifies a localized name for the error message. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Resource accessor builder to use - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Custom message format args - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - The resource accessor builder to use. - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Custom message format args - - - - - Specifies a localized name for the error message. - - The current rule - The type of the generated resource file - The name of the resource to use - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional arguments to be specified when formatting the custom error message. - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional property values to be included when formatting the custom error message. - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional property values to use when formatting the custom error message. - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Name of resource - Custom message format args - Type of resource representing a resx file - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Resource name - Custom message format args - Resource type representing a resx file - - - - - Replace the first validator of this type and remove all the others. - - - - - Remove all validators of the specified type. - - - - - Remove all validators for the given property. - - - - - Builds a delegate for retrieving a localised resource from a resource type and property name. - - - - - Gets a function that can be used to retrieve a message from a resource type and resource name. - - - - - Allows the default error message translations to be managed. - - - - - Whether localization is enabled. - - - - - Default culture to use for all requests to the LanguageManager. If not specified, uses the current UI culture. - - - - - Gets a translated string based on its key. If the culture is specific and it isn't registered, we try the neutral culture instead. - If no matching culture is found to be registered we use English. - - The key - The culture to translate into - - - - - Provides error message templates - - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Marker interface that indicates a PropertyValidatorContext should be passed to GetString - - - - - Base class for lanaguages - - - - - Name of language (culture code) - - - - - Adds a translation - - - - - - - Adds a translation for a type - - - - - - - Gets the localized version of a string with a specific key. - - - - - - - Allows the default error message translations to be managed. - - - - - Creates a new instance of the LanguageManager class. - - - - - Whether localization is enabled. - - - - - Default culture to use for all requests to the LanguageManager. If not specified, uses the current UI culture. - - - - - Provides a collection of all supported languages. - - - - - - Removes all languages except the default. - - - - - Gets a translated string based on its key. If the culture is specific and it isn't registered, we try the neutral culture instead. - If no matching culture is found to be registered we use English. - - The key - The culture to translate into - - - - - IStringSource implementation that uses the default language manager. - - - - - Lazily loads the string - - - - - Creates a LazyStringSource - - - - - Gets the value - - - - - - Resource type - - - - - Resource name - - - - - Creates a LazyStringSource - - - - - Gets the value - - - - - - Resource type - - - - - Resource name - - - - - Represents a localized string. - - - - - Creates a new instance of the LocalizedErrorMessageSource class using the specified resource name and resource type. - - The resource type - The resource name - - - - Creates a new instance of the LocalizedErrorMessageSource class using the specified resource name and resource type. - - The resource type - The resource name - Strategy used to construct the resource accessor - - - - Creates a LocalizedStringSource from an expression: () => MyResources.SomeResourceName - - The expression - Strategy used to construct the resource accessor - Error message source - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Gets the PropertyInfo for a resource. - ResourceType and ResourceName are ref parameters to allow derived types - to replace the type/name of the resource before the delegate is constructed. - - - - - Version of LocalizedStringSource that allows the specified ResourceType to be replaced by the default resource type specified in ValidatorOptions.ResourceProviderType. - This is typically only used by the default validator types built into FV, or if you're building a library of validators. Don't use it in rule definitions. - - - - - Gets the PropertyInfo for a resource. - ResourceType and ResourceName are ref parameters to allow derived types - to replace the type/name of the resource before the delegate is constructed. - - - - - Creates a OverridableLocalizedStringSource from an expression: () => MyResources.SomeResourceName - - The expression - Error message source - - - - Defines an accessor for localization resources - - - - - Function that can be used to retrieve the resource - - - - - Resource type - - - - - Resource name - - - - - Represents a static string. - - - - - Creates a new StringErrorMessageSource using the specified error message as the error template. - - The error message template. - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Extension methods for collection validation rules - - - - - Associates an instance of IValidator with the current property rule and is used to validate each item within the collection. - - Rule builder - The validator to use - - - - Uses a provider to instantiate a validator instance to be associated with a collection - - - - - - - - - - - Collection rule builder syntax - - - - - - - Defines a condition to be used to determine if validation should run - - - - - - - Extension methods that provide the default set of validators. - - - - - Defines a 'not null' validator on the current rule builder. - Validation will fail if the property is null. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'null' validator on the current rule builder. - Validation will fail if the property is not null. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'not empty' validator on the current rule builder. - Validation will fail if the property is null, an empty or the default value for the type (for example, 0 for integers) - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'empty' validator on the current rule builder. - Validation will fail if the property is not null, an empty or the default value for the type (for example, 0 for integers) - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is outside of the specified range. The range is inclusive. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is outside of the specified range. The range is inclusive. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is not equal to the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is not equal to the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is larger than the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is less than the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to use - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to use - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - Regex options - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - Regex options - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda is not a valid email address. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - Defines a 'not equal' validator on the current rule builder. - Validation will fail if the specified value is equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value to compare - Equality comparer to use - - - - - Defines a 'not equal' validator on the current rule builder using a lambda to specify the value. - Validation will fail if the value returned by the lambda is equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression to provide the comparison value - Equality Comparer to use - - - - - Defines an 'equals' validator on the current rule builder. - Validation will fail if the specified value is not equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value to compare - Equality Comparer to use - - - - - Defines an 'equals' validator on the current rule builder using a lambda to specify the comparison value. - Validation will fail if the value returned by the lambda is not equal to the value of the property. - - The type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression to provide the comparison value - Equality comparer to use - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a 'less than' validator on the current rule builder. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal' validator on the current rule builder. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal' validator on the current rule builder. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal to' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal to' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Validates certain properties of the specified instance. - - The current validator - The object to validate - Expressions to specify the properties to validate - A ValidationResult object containing any validation failures - - - - Validates certain properties of the specified instance. - - - The object to validate - The names of the properties to validate. - A ValidationResult object containing any validation failures. - - - - Validates an object using either a custom validator selector or a ruleset. - - - - - - - - - - - Validates certain properties of the specified instance asynchronously. - - The current validator - The object to validate - Expressions to specify the properties to validate - A ValidationResult object containing any validation failures - - - - Validates certain properties of the specified instance asynchronously. - - - The object to validate - The names of the properties to validate. - A ValidationResult object containing any validation failures. - - - - Validates an object asynchronously using a custom valdiator selector or a ruleset - - - - - - - - - - - Performs validation and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Performs validation asynchronously and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Defines an 'inclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is inclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'inclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is inclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'exclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is exclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'exclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is exclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines a credit card validator for the current rule builder that ensures that the specified string is a valid credit card number. - - - - - Defines a enum value validator on the current rule builder that ensures that the specific value is a valid enum value. - - Type of Enum being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a custom validation rule - - - - - - - - - - Defines a custom validation rule - - - - - - - - - - Performs validation and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - The ruleset to validate against. - - - - Performs validation asynchronously and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Default options that can be used to configure a validator. - - - - - Specifies the cascade mode for failures. - If set to 'Stop' then execution of the rule will stop once the first validator in the chain fails. - If set to 'Continue' then all validators in the chain will execute regardless of failures. - - - - - Specifies a custom action to be invoked when the validator fails. - - - - - - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - - - - - Specifies a custom error message to use when validation fails. - - The current rule - Delegate that will be invoked to retrieve the localized message. - - - - - Specifies a custom error message to use when validation fails. - - The current rule - Delegate that will be invoked to retrieve the localized message. - - - - - Specifies a custom error code to use if validation fails. - - The current rule - The error code to use - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Resource type representing a resx file - Name of resource - - - - - Specifies a condition limiting when the validator should run. - The validator will only be executed if the result of the lambda returns true. - - The current rule - A lambda expression that specifies a condition for when the validator should run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies a condition limiting when the validator should not run. - The validator will only be executed if the result of the lambda returns false. - - The current rule - A lambda expression that specifies a condition for when the validator should not run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies an asynchronous condition limiting when the validator should run. - The validator will only be executed if the result of the lambda returns true. - - The current rule - A lambda expression that specifies a condition for when the validator should run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies an asynchronous condition limiting when the validator should not run. - The validator will only be executed if the result of the lambda returns false. - - The current rule - A lambda expression that specifies a condition for when the validator should not run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Triggers an action when the rule passes. Typically used to configure dependent rules. This applies to all preceding rules in the chain. - - The current rule - An action to be invoked if the rule is valid - - - - - Specifies a custom property name to use within the error message. - - The current rule - The property name to use - - - - - Specifies a custom property name to use within the error message. - - The current rule - Func used to retrieve the property's display name - - - - - Overrides the name of the property associated with this rule. - NOTE: This is a considered to be an advanced feature. 99% of the time that you use this, you actually meant to use WithName. - - The current rule - The property name to use - - - - - Specifies custom state that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Specifies custom state that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Specifies custom severity that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Gets the default message for a property validato - - The validator type - The translated string - - - - Specifies how rules should cascade when one fails. - - - - - When a rule fails, execution continues to the next rule. - - - - - When a rule fails, validation is stopped and all other rules in the chain will not be executed. - - - - - Specifies where a When/Unless condition should be applied - - - - - Applies the condition to all validators declared so far in the chain. - - - - - Applies the condition to the current validator only. - - - - - Specifies the severity of a rule. - - - - - Error - - - - - Warning - - - - - Info - - - - - Validator implementation that allows rules to be defined without inheriting from AbstractValidator. - - - - public class Customer { - public int Id { get; set; } - public string Name { get; set; } - - public static readonly InlineValidator<Customer> Validator = new InlineValidator<Customer> { - v => v.RuleFor(x => x.Name).NotNull(), - v => v.RuleFor(x => x.Id).NotEqual(0), - } - } - - - - - - - Delegate that specifies configuring an InlineValidator. - - - - - Allows configuration of the validator. - - - - - Rule definition for collection properties - - - - - - Initializes new instance of the CollectionPropertyRule class - - - - - - - - - - - Creates a new property rule from a lambda expression. - - - - - Invokes the validator asynchronously - - - - - - - - - - Invokes the validator - - - - - - - - - Custom logic for performing comparisons - - - - - Tries to compare the two objects. - - - The resulting comparison value. - - True if all went well, otherwise False. - - - - Tries to do a proper comparison but may fail. - First it tries the default comparison, if this fails, it will see - if the values are fractions. If they are, then it does a double - comparison, otherwise it does a long comparison. - - - - - Tries to compare the two objects, but will throw an exception if it fails. - - True on success, otherwise False. - - - - Tries to compare the two objects, but will throw an exception if it fails. - - True on success, otherwise False. - - - - Default validator selector that will execute all rules that do not belong to a RuleSet. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Custom IValidationRule for performing custom logic. - - - - - - Rule set to which this rule belongs. - - - - - Creates a new DelegateValidator using the specified function to perform validation. - - - - - Creates a new DelegateValidator using the specified function to perform validation. - - - - - Creates a new DelegateValidator using the specified async function to perform validation. - - - - - Creates a new DelegateValidator using the specified async function to perform validation. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs validation asynchronously using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - The validators that are grouped under this rule. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - When overloaded performs validation asynchronously using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - Applies a condition to the validator. - - - - - - - Applies a condition asynchronously to the validator - - - - - - - Useful extensions - - - - - Checks if the expression is a parameter expression - - - - - - - Gets a MemberInfo from a member expression. - - - - - Gets a MemberInfo from a member expression. - - - - - Splits pascal case, so "FooBar" would become "Foo Bar" - - - - - Helper method to construct a constant expression from a constant. - - Type of object being validated - Type of property being validated - The value being compared - - - - - Represents an object that is configurable. - - Type of object being configured - Return type - - - - Configures the current object. - - Action to configure the object. - - - - - Instancace cache. - - - - - Gets or creates an instance using Activator.CreateInstance - - The type to instantiate - The instantiated object - - - - Gets or creates an instance using a custom factory - - The type to instantiate - The custom factory - The instantiated object - - - - Member accessor cache. - - - - - - Gets an accessor func based on an expression - - - The member represented by the expression - - Accessor func - - - - Determines whether or not a rule should execute. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Selects validators that are associated with a particular property. - - - - - Creates a new instance of MemberNameValidatorSelector. - - - - - Member names that are validated. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Creates a MemberNameValidatorSelector from a collection of expressions. - - - - - Gets member names from expressions - - - - - - - - Assists in the construction of validation messages. - - - - - Default Property Name placeholder. - - - - - Default Property Value placeholder. - - - - - Adds a value for a validation message placeholder. - - - - - - - - Appends a property name to the message. - - The name of the property - - - - - Appends a property value to the message. - - The value of the property - - - - - Adds additional arguments to the message for use with standard string placeholders. - - Additional arguments - - - - - Constructs the final message from the specified template. - - Message template - The message with placeholders replaced with their appropriate values - - - - Additional arguments to use - - - - - Additional placeholder values - - - - - Represents a chain of properties - - - - - Creates a new PropertyChain. - - - - - Creates a new PropertyChain based on another. - - - - - Creates a new PropertyChain - - - - - - Creates a PropertyChain from a lambda expresion - - - - - - - Adds a MemberInfo instance to the chain - - Member to add - - - - Adds a property name to the chain - - Name of the property to add - - - - Adds an indexer to the property chain. For example, if the following chain has been constructed: - Parent.Child - then calling AddIndexer(0) would convert this to: - Parent.Child[0] - - - - - - Creates a string representation of a property chain. - - - - - Checks if the current chain is the child of another chain. - For example, if chain1 were for "Parent.Child" and chain2 were for "Parent.Child.GrandChild" then - chain2.IsChildChainOf(chain1) would be true. - - The parent chain to compare - True if the current chain is the child of the other chain, otherwise false - - - - Builds a property path. - - - - - Number of member names in the chain - - - - - Defines a rule associated with a property. - - - - - Property associated with this rule. - - - - - Function that can be invoked to retrieve the value of the property. - - - - - Expression that was used to create the rule. - - - - - String source that can be used to retrieve the display name (if null, falls back to the property name) - - - - - Rule set that this rule belongs to (if specified) - - - - - Function that will be invoked if any of the validators associated with this rule fail. - - - - - The current validator being configured by this rule. - - - - - Type of the property being validated - - - - - Cascade mode for this rule. - - - - - Validators associated with this rule. - - - - - Creates a new property rule. - - Property - Function to get the property value - Lambda expression used to create the rule - Function to get the cascade mode. - Type to validate - Container type that owns the property - - - - Creates a new property rule from a lambda expression. - - - - - Creates a new property rule from a lambda expression. - - - - - Adds a validator to the rule. - - - - - Replaces a validator in this rule. Used to wrap validators. - - - - - Remove a validator in this rule. - - - - - Clear all validators from this rule. - - - - - Returns the property name for the property being validated. - Returns null if it is not a property being validated (eg a method call) - - - - - Allows custom creation of an error message - - - - - Dependent rules - - - - - Display name for the property. - - - - - Display name for the property. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs asynchronous validation using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - Invokes the validator asynchronously - - - - - - - - - - Invokes a property validator using the specified validation context. - - - - - Applies a condition to the rule - - - - - - - Applies the condition to the rule asynchronously - - - - - - - Include rule - - - - - Creates a new IncludeRule - - - - - - - - - Creates a new include rule from an existing validator - - - - - - - - - Builds a validation rule and constructs a validator. - - Type of object being validated - Type of property being validated - - - - The rule being created by this RuleBuilder. - - - - - Creates a new instance of the RuleBuilder class. - - - - - Sets the validator associated with the rule. - - The validator to set - - - - - Sets the validator associated with the rule. Use with complex properties where an IValidator instance is already declared for the property type. - - The validator to set - - - - Sets the validator associated with the rule. Use with complex properties where an IValidator instance is already declared for the property type. - - The validator provider to set - - - - Selects validators that belong to the specified rulesets. - - - - - Rule sets - - - - - Creates a new instance of the RulesetValidatorSelector. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Checks if the rule is an IncludeRule - - - - - - - Gets validators for method parameters. - - - - - Gets a validator for . - - The instance to get a validator for. - Created instance; if a validator cannot be - created. - - - - Defines a rule associated with a property which can have multiple validators. - - - - - The validators that are grouped under this rule. - - - - - Name of the rule-set to which this rule belongs. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs validation using a validation context and returns a collection of Validation Failures asynchronoulsy. - - Validation Context - Cancellation token - A collection of validation failures - - - - Applies a condition to the rule - - - - - - - Applies a condition to the rule asynchronously - - - - - - - Defines a validator for a particular type. - - - - - - Validates the specified instance. - - The instance to validate - A ValidationResult object containing any validation failures. - - - - Validate the specified instance asynchronously - - The instance to validate - - A ValidationResult object containing any validation failures. - - - - Sets the cascade mode for all rules within this validator. - - - - - Defines a validator for a particular type. - - - - - Validates the specified instance - - - A ValidationResult containing any validation failures - - - - Validates the specified instance asynchronously - - - Cancellation token - A ValidationResult containing any validation failures - - - - Validates the specified instance. - - A ValidationContext - A ValidationResult object containy any validation failures. - - - - Validates the specified instance asynchronously. - - A ValidationContext - Cancellation token - A ValidationResult object containy any validation failures. - - - - Creates a hook to access various meta data properties - - A IValidatorDescriptor object which contains methods to access metadata - - - - Checks to see whether the validator can validate objects of the specified type - - - - - Provides metadata about a validator. - - - - - Gets the name display name for a property. - - - - - Gets a collection of validators grouped by property. - - - - - Gets validators for a particular property. - - - - - Gets rules for a property. - - - - - Gets validators for a particular type. - - - - - Gets the validator for the specified type. - - - - - Gets the validator for the specified type. - - - - - Defines a validation failure - - - - - Creates a new validation failure. - - - - - Creates a new ValidationFailure. - - - - - The name of the property. - - - - - The error message - - - - - The property value that caused the failure. - - - - - Custom state associated with the failure. - - - - - Custom severity level associated with the failure. - - - - - Gets or sets the formatted message arguments. - These are values for custom formatted message in validator resource files - Same formatted message can be reused in UI and with same number of format placeholders - Like "Value {0} that you entered should be {1}" - - - - - Gets or sets the formatted message placeholder values. - - - - - The resource name used for building the message - - - - - Creates a textual representation of the failure. - - - - - Creates a new ValidationFailure with ErrorCode. - - - - - Gets or sets the error code. - - - - - The result of running a validator - - - - - Whether validation succeeded - - - - - A collection of errors - - - - - Creates a new validationResult - - - - - Creates a new ValidationResult from a collection of failures - - List of which is later available through . This list get's copied. - - Every caller is responsible for not adding null to the list. - - - - - Rule builder that starts the chain - - - - - - - Rule builder - - - - - - - Associates a validator with this the property for this rule builder. - - The validator to set - - - - - Associates an instance of IValidator with the current property rule. - - The validator to use - - - - Associates a validator provider with the current property rule. - - The validator provider to use - - - - Rule builder - - - - - - - Validation context - - - - Validation context - - - - - - Creates a new validation context - - - - - - Creates a new validation context with a custom property chain and selector - - - - - - - - The object to validate - - - - - Validation context - - - Validation context - - - - - Creates a new validation context - - - - - - Creates a new validation context with a property chain and validation selector - - - - - - - - Property chain - - - - - Object being validated - - - - - Selector - - - - - Whether this is a child context - - - - - Whether this is a child collection context. - - - - - Creates a new ValidationContext based on this one - - - - - - - - - Creates a new validation context for use with a child validator - - - - - - - Creates a new validation context for use with a child collection validator - - - - - - - An exception that represents failed validation - - - An exception that represents failed validation - - - - - Validation errors - - - - - Creates a new ValidationException - - - - - - Creates a new ValidationException - - - - - - - Creates a new ValidationException - - - - - - Used for providing metadata about a validator. - - - - - Rules associated with the validator - - - - - Creates a ValidatorDescriptor - - - - - - Gets the display name or a property property - - - - - - - Gets all members with their associated validators - - - - - - Gets validators for a specific member - - - - - - - Gets rules for a specific member - - - - - - - Gets the member name from an expression - - - - - - - Gets validators for a member - - - - - - - - Gets rules grouped by ruleset - - - - - - Information about reulesets - - - - - Creates a new RulesetMetadata - - - - - - - Rulset name - - - - - Rules in the ruleset - - - - - Factory for creating validators - - - - - Gets a validator for a type - - - - - - - Gets a validator for a type - - - - - - - Instantiates the validator - - - - - - - Validator runtime options - - - - - Default cascade mode - - - - - Default property chain separator - - - - - Default resource provider - - - - - Default language manager - - - - - Customizations of validator selector - - - - - Specifies a factory for creating MessageFormatter instances. - - - - - Pluggable logic for resolving property names - - - - - Pluggable logic for resolving display names - - - - - Disables the expression accessor cache. Not recommended. - - - - - ValidatorSelector options - - - - - Factory func for creating the default validator selector - - - - - Factory func for creating the member validator selector - - - - - Factory func for creating the ruleset validator selector - - - - - Base class for all comparison validators - - - - - - - - - - - - - - - - - - Performs the comparison - - - - - - - Override to perform the comparison - - - - - - - - Metadata- the comparison type - - - - - Metadata- the member being compared - - - - - Metadata- the value being compared - - - - - Defines a comparison validator - - - - - Metadata- the comparison type - - - - - Metadata- the member being compared - - - - - Metadata- the value being compared - - - - - Asynchronous custom validator - - - - - Creates a new ASyncPredicateValidator - - - - - - Runs the validation check - - - - - - - - Ensures that the property value is a valid credit card number. - - - - - Custom validator that allows for manual/direct creation of ValidationFailure instances. - - - - - - Creates a new instance of the CustomValidator - - - - - - Creates a new isntance of the CutomValidator. - - - - - - Custom validation context - - - - - Creates a new CustomContext - - The parent PropertyValidatorContext that represents this execution - - - - Adds a new validation failure. - - The property name - The error mesage - - - - Adds a new validation failure (the property name is inferred) - - The error message - - - - Adss a new validation failure - - The failure to add - - - - A custom property validator. - This interface should not be implemented directly in your code as it is subject to change. - Please inherit from PropertyValidator instead. - - - - - Prepares the of for an upcoming . - - The validator context - - - - Creates an error validation result for this validator. - - The validator context - Returns an error validation result. - - - - Allows a decimal to be validated for scale and precision. - Scale would be the number of digits to the right of the decimal point. - Precision would be the number of digits. - - It can be configured to use the effective scale and precision - (i.e. ignore trailing zeros) if required. - - 123.4500 has an scale of 4 and a precision of 7, but an effective scale - and precision of 2 and 5 respectively. - - - - - When the AppHost was instantiated. - - - - - When the Init function was done. - Called at begin of - - - - - When all configuration was completed. - Called at the end of - - - - - If app currently runs for unit tests. - Used for overwritting AuthSession. - - - - - The assemblies reflected to find api services. - These can be provided in the constructor call. - - - - - Wether AppHost configuration is done. - Note: It doesn't mean the start function was called. - - - - - Wether AppHost is ready configured and either ready to run or already running. - Equals - - - - - Set the host config of the AppHost. - - - - - Initializes the AppHost. - Calls the method. - Should be called before start. - - - - - Gets Full Directory Path of where the app is running - - - - - Starts the AppHost. - this methods needs to be overwritten in subclass to provider a listener to start handling requests. - - Url to listen to - - - - The AppHost.Container. Note: it is not thread safe to register dependencies after AppStart. - - - - - Collection of PreRequest filters. - They are called before each request is handled by a service, but after an HttpHandler is by the chosen. - called in . - - - - - Collection of RequestConverters. - Can be used to convert/change Input Dto - Called after routing and model binding, but before request filters. - All request converters are called unless - Converter can return null, orginal model will be used. - - Note one converter could influence the input for the next converter! - - - - - Collection of ResponseConverters. - Can be used to convert/change Output Dto - - Called directly after response is handled, even before ! - - - - - Lists of view engines for this app. - If view is needed list is looped until view is found. - - - - - Collection of added plugins. - - - - - Executed immediately before a Service is executed. Use return to change the request DTO used, must be of the same type. - - - - - Executed immediately after a service is executed. Use return to change response used. - - - - - Occurs when the Service throws an Exception. - - - - - Occurs when an exception is thrown whilst processing a request. - - - - - Register singleton in the Ioc Container of the AppHost. - - - - - Registers type to be automatically wired by the Ioc container of the AppHost. - - Concrete type - Abstract type - - - - Tries to resolve type through the ioc container of the AppHost. - Can return null. - - - - - Resolves Type through the Ioc container of the AppHost. - - If type is not registered - - - - Looks for first plugin of this type in Plugins. - Reflection performance penalty. - - - - - Apply PreRequest Filters for participating Custom Handlers, e.g. RazorFormat, MarkdownFormat, etc - - - - - Apply PreAuthenticate Filters from IAuthWithRequest AuthProviders - - - - - Applies the raw request filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Applies the request filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Applies the response filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Inspect or modify ever new UserSession created or resolved from cache. - return null if Session is invalid to create new Session. - - - - - Override built-in Cookies, return false to prevent the Cookie from being set. - - - - - Gets IDbConnection Checks if DbInfo is seat in RequestContext. - See multitenancy: http://docs.servicestack.net/multitenancy - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - Resolves based on .GetClient(); - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - If they don't have an ICacheClient configured use an In Memory one. - - - - - Tries to resolve through Ioc container. - If not registered, it falls back to .GetClient(); - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - Returns . cache is only persisted for this running app instance. - Called by .MemoryCacheClient - - Provided by services and pageView, can be helpfull when overriding this method - Nullable MemoryCacheClient - - - - Returns from the IOC container. - Called by itself, and - - Provided by services and PageViewBase, can be helpfull when overriding this method - - - - - If enabled reverts to persist password hashes using the original SHA256 SaltedHash implementation. - By default ServiceStack uses the more secure ASP.NET Identity v3 PBKDF2 with HMAC-SHA256 implementation. - - New Users will have their passwords persisted with the specified implementation, likewise existing users will have their passwords re-hased - to use the current registered IPasswordHasher. - - - - - Older Password Hashers that were previously used to hash passwords. Failed password matches check to see if the password was hashed with - any of the registered FallbackPasswordHashers, if true the password attempt will succeed and password will get re-hashed with - the current registered IPasswordHasher. - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Cascading collection of virtual file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Call to signal the completion of a ServiceStack-handled Request - - - - - Resolves and auto-wires a ServiceStack Service from a ASP.NET HttpContext. - - - - - Resolves and auto-wires a ServiceStack Service from a HttpListenerContext. - - - - - Resolves and auto-wires a ServiceStack Service. - - - - - Context to capture IService action - - - - - Can ignore as doesn't throw if HTTP Headers already written - - - - - Creates instance using straight Resolve approach. - This will throw an exception if resolution fails - - - - - Creates instance using the TryResolve approach if tryResolve = true. - Otherwise uses Resolve approach, which will throw an exception if resolution fails - - - - - Sets a persistent cookie which never expires - - - - - Sets a session cookie which expires after the browser session closes - - - - - Deletes a specified cookie by setting its value to empty and expiration to -1 days - - - - - Return File at specified virtualPath from AppHost.VirtualFiles ContentRootPath - - - - - Keep default file contents in-memory - - - - - - Wrapper class for the HTTPListener to allow easier access to the - server, for start and stop management and event routing of the actual - inbound requests. - - - - - Starts the Web Service - - - A Uri that acts as the base that the server is listening on. - Format should be: http://127.0.0.1:8080/ or http://127.0.0.1:8080/somevirtual/ - Note: the trailing slash is required! For more info see the - HttpListener.Prefixes property on MSDN. - - - - - Shut down the Web Service - - - - - Overridable method that can be used to implement a custom hnandler - - - - - - Reserves the specified URL for non-administrator users and accounts. - http://msdn.microsoft.com/en-us/library/windows/desktop/cc307223(v=vs.85).aspx - - Reserved Url if the process completes successfully - - - - Can ignore as doesn't throw if HTTP Headers already written - - - - - Used in Unit tests - - - - - - The number of segments separated by '/' determinable by path.Split('/').Length - e.g. /path/to/here.ext == 3 - - - - - The total number of segments after subparts have been exploded ('.') - e.g. /path/to/here.ext == 4 - - - - - Provide for quick lookups based on hashes that can be determined from a request url - - - - - For performance withPathInfoParts should already be a lower case string - to minimize redundant matching operations. - - - - - - For performance withPathInfoParts should already be a lower case string - to minimize redundant matching operations. - - - - - - - - - Get Best Matching Route. - - - - If not null, ensures any Route matches any [Route(Matches)] - - - - - Execute MQ - - - - - Execute MQ with requestContext - - - - - Execute using empty RequestContext - - - - - Execute a Service with a Request DTO. See ExecuteAsync for a non-blocking alternative. - - - - - Execute a Service with a Request DTO. - - - - - Gets the name of the base most type in the heirachy tree with the same. - - We get an exception when trying to create a schema with multiple types of the same name - like when inheriting from a DataContract with the same name. - - The type. - - - - - Back-end Service used by /js/hot-fileloader.js to detect file changes in /wwwroot and auto reload page. - - - - - Predefined pattern that matches <?php ... ?> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined pattern that matches <% ... %> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined pattern that matches <--# ... --> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined list of tags that are very likely to be block-level. - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method. - - - Predefined list of tags that are block-level by default, excluding <div> and <li> tags. - Table tags are also included. - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method. - - - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method - to remove all surrounding spaces (not recommended). - - - If set to false all compression will be bypassed. Might be useful for testing purposes. - Default is true. - - @param enabled set false to bypass all compression - - - Gets or Sets JavaScript compressor implementation that will be used - to compress inline JavaScript in HTML. - - - Returns CSS compressor implementation that will be used - to compress inline CSS in HTML. - - - If set to true all HTML comments will be removed. - Default is true. - - @param removeComments set true to remove all HTML comments - - - If set to true all multiple whitespace characters will be replaced with single spaces. - Default is true. - - @param removeMultiSpaces set true to replace all multiple whitespace characters - will single spaces. - - - - - Enables JavaScript compression within <script> tags - if set to true. Default is false for performance reasons. - -

    Note: Compressing JavaScript is not recommended if pages are - compressed dynamically on-the-fly because of performance impact. - You should consider putting JavaScript into a separate file and - compressing it using standalone YUICompressor for example.

    - - @param compressJavaScript set true to enable JavaScript compression. - Default is false -
    - - Enables CSS compression within <style> tags using - Yahoo YUI ICompressor - if set to true. Default is false for performance reasons. - -

    Note: Compressing CSS is not recommended if pages are - compressed dynamically on-the-fly because of performance impact. - You should consider putting CSS into a separate file and - compressing it using standalone YUICompressor for example.

    - - @param compressCss set true to enable CSS compression. - Default is false -
    - - If set to true, existing DOCTYPE declaration will be replaced with simple <!DOCTYPE html> declaration. - Default is false. - - @param simpleDoctype set true to replace existing DOCTYPE declaration with <!DOCTYPE html> - - - - If set to true, type="text/style" attributes will be removed from <style> tags. Default is false. - - @param removeStyleAttributes set true to remove type="text/style" attributes from <style> tags - - - - If set to true, method="get" attributes will be removed from <form> tags. Default is false. - - @param removeFormAttributes set true to remove method="get" attributes from <form> tags - - - If set to true, type="text" attributes will be removed from <input> tags. Default is false. - - @param removeInputAttributes set true to remove type="text" attributes from <input> tags - - - - - - - - - - Returns {@link HtmlCompressorStatistics} object containing statistics of the last HTML compression, if enabled. - Should be called after {@link #compress(string)} - - @return {@link HtmlCompressorStatistics} object containing last HTML compression statistics - - @see HtmlCompressorStatistics - @see #setGenerateStatistics(bool) - - - The main method that compresses given HTML source and returns compressed - result. - - @param html HTML content to compress - @return compressed content. - - - Returns metrics of an uncompressed document - - @return metrics of an uncompressed document - @see HtmlMetrics - - - Returns metrics of a compressed document - - @return metrics of a compressed document - @see HtmlMetrics - - - - Returns total size of blocks that were skipped by the compressor - (for example content inside <pre> tags or inside - <script> tags with disabled javascript compression) - - @return the total size of blocks that were skipped by the compressor, in bytes - - - Returns total filesize of a document - - @return total filesize of a document, in bytes - - - Returns number of empty characters (spaces, tabs, end of lines) in a document - - @return number of empty characters in a document - - - Returns total size of inline <script> tags - - @return total size of inline <script> tags, in bytes - - - Returns total size of inline <style> tags - - @return total size of inline <style> tags, in bytes - - - Returns total size of inline event handlers (onclick, etc) - - @return total size of inline event handlers, in bytes - - - - End a ServiceStack Request - - - - - End a ServiceStack Request - - - - - End a HttpHandler Request - - - - - End a HttpHandler Request - - - - - End an MQ Request - - - - - End a ServiceStack Request with no content - - - - - Gets string value from Items[name] then Cookies[name] if exists. - Useful when *first* setting the users response cookie in the request filter. - To access the value for this initial request you need to set it in Items[]. - - string value or null if it doesn't exist - - - - Gets request paramater string value by looking in the following order: - - QueryString[name] - - FormData[name] - - Cookies[name] - - Items[name] - - string value or null if it doesn't exist - - - * - Input: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?q=item#fragment - - Some HttpRequest path and URL properties: - Request.ApplicationPath: /Cambia3 - Request.CurrentExecutionFilePath: /Cambia3/Temp/Test.aspx - Request.FilePath: /Cambia3/Temp/Test.aspx - Request.Path: /Cambia3/Temp/Test.aspx/path/info - Request.PathInfo: /path/info - Request.PhysicalApplicationPath: D:\Inetpub\wwwroot\CambiaWeb\Cambia3\ - Request.QueryString: /Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.AbsolutePath: /Cambia3/Temp/Test.aspx/path/info - Request.Url.AbsoluteUri: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.Fragment: - Request.Url.Host: localhost - Request.Url.LocalPath: /Cambia3/Temp/Test.aspx/path/info - Request.Url.PathAndQuery: /Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.Port: 96 - Request.Url.Query: ?query=arg - Request.Url.Scheme: http - Request.Url.Segments: / - Cambia3/ - Temp/ - Test.aspx/ - path/ - info - * - - - - Duplicate Params are given a unique key by appending a #1 suffix - - - - - Duplicate params have their values joined together in a comma-delimited string - - - - - Use this to treat Request.Items[] as a cache by returning pre-computed items to save - calculating them multiple times. - - - - - Sets a persistent cookie which never expires - - - - - Sets a session cookie which expires after the browser session closes - - - - - Sets a persistent cookie which expires after the given time - - - - - Sets a persistent cookie with an expiresAt date - - - - - Deletes a specified cookie by setting its value to empty and expiration to -1 days - - - - - Writes to response. - Response headers are customizable by implementing IHasOptions an returning Dictionary of Http headers. - - The response. - Whether or not it was implicity handled by ServiceStack's built-in handlers. - The default action. - The serialization context. - Add prefix to response body if any - Add suffix to response body if any - - - - - When HTTP Headers have already been written and only the Body can be written - - - - - Respond with a 'Soft redirect' so smart clients (e.g. ajax) have access to the response and - can decide whether or not they should redirect - - - - - Decorate the response with an additional client-side event to instruct participating - smart clients (e.g. ajax) with hints to transparently invoke client-side functionality - - - - - Shortcut to get the ResponseDTO whether it's bare or inside a IHttpResult - - - - - - - Alias of AsDto - - - - - Shortcut to get the ResponseDTO whether it's bare or inside a IHttpResult - - - TResponse if found; otherwise null - - - - Alias of AsDto - - - - - Whether the response is an IHttpError or Exception - - - - - rangeHeader should be of the format "bytes=0-" or "bytes=0-12345" or "bytes=123-456" - - - - - Adds 206 PartialContent Status, Content-Range and Content-Length headers - - - - - Writes partial range as specified by start-end, from fromStream to toStream. - - - - - Writes partial range as specified by start-end, from fromStream to toStream. - - - - - ASP.NET or HttpListener ServiceStack host - - - - - The assemblies reflected to find api services provided in the AppHost constructor - - - - - Register dependency in AppHost IOC on Startup - - - - - AutoWired Registration of an interface with a concrete type in AppHost IOC on Startup. - - - - - Allows the clean up for executed autowired services and filters. - Calls directly after services and filters are executed. - - - - - Called at the end of each request. Enables Request Scope. - - - - - Register callbacks to be called at the end of each request. - - - - - Register user-defined custom routes. - - - - - Inferred Metadata available from existing services - - - - - Register custom ContentType serializers - - - - - Add Request Filters, to be applied before the dto is deserialized - - - - - Add Request Converter to convert Request DTO's - - - - - Add Response Converter to convert Response DTO's - - - - - Add Request Filters for HTTP Requests - - - - - Add Async Request Filters for HTTP Requests - - - - - Add Response Filters for HTTP Responses - - - - - Add Async Response Filters for HTTP Responses - - - - - Add Request Filters for MQ/TCP Requests - - - - - Add Async Request Filters for MQ/TCP Requests - - - - - Add Response Filters for MQ/TCP Responses - - - - - Add Request Filter for a specific Request DTO Type - - - - - Add as a Typed Request Filter for a specific Request DTO Type - - The DTO Type. - The methods to resolve the . - - - - Add Request Filter for a specific Response DTO Type - - - - - Add as a Typed Request Filter for a specific Request DTO Type - - The DTO Type. - The methods to resolve the . - - - - Add Request Filter for a specific MQ Request DTO Type - - - - - Add Request Filter for a specific MQ Response DTO Type - - - - - Add Request Filter for Service Gateway Requests - - - - - Add Response Filter for Service Gateway Responses - - - - - Add alternative HTML View Engines - - - - - Provide an exception handler for unhandled exceptions - - - - - Provide an exception handler for unhandled exceptions (Async) - - - - - Provide an exception handler for un-caught exceptions - - - - - Provide an exception handler for un-caught exceptions (Async) - - - - - Provide callbacks to be fired after the AppHost has finished initializing - - - - - Provide callbacks to be fired when AppHost is being disposed - - - - - Skip the ServiceStack Request Pipeline and process the returned IHttpHandler instead - - - - - Provide a catch-all handler that doesn't match any routes - - - - - Provide a fallback handler for not found requests (last filter in Request Pipeline) - - - - - Use a fall-back Error Handler for handling global errors - - - - - Use a Custom Error Handler for handling specific error HttpStatusCodes - - - - - Provide a custom model minder for a specific Request DTO - - - - - The AppHost config - - - - - The AppHost AppSettings. Defaults to App or Web.config appSettings. - - - - - Allow specific configuration to be overridden at runtime in multi-tenancy Applications - by overriding GetRuntimeConfig in your AppHost - - - - - Register an Adhoc web service on Startup - - - - - Register all Services in Assembly - - - - - List of pre-registered and user-defined plugins to be enabled in this AppHost - - - - - Apply plugins to this AppHost - - - - - Returns the Absolute File Path, relative from your AppHost's Project Path - - - - - Cascading number of file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Register additional Virtual File Sources - - - - - Create a service runner for IService actions - - - - - Resolve the absolute url for this request - - - - - Resolve localized text, returns itself by default. - The Request is provided when exists. - - - - - Execute MQ Message in ServiceStack - - - - - Access Service Controller for ServiceStack - - - - - A convenient repository base class you can inherit from to reduce the boilerplate - with accessing a managed IDbConnection - - - - - A convenient base class for your injected service dependencies that reduces the boilerplate - with managed access to ServiceStack's built-in providers - - - - - Callback for Plugins to register necessary handlers with ServiceStack - - - - - Callback to pre-configure any logic before IPlugin.Register() is fired - - - - - Callback to post-configure any logic after IPlugin.Register() is fired - - - - - Callback for AuthProviders to register callbacks with AuthFeature - - - - - Resolve an alternate Web Service from ServiceStack's IOC container. - - - - - - - Converts markdown contents to HTML using the configured MarkdownConfig.Transformer. - If a variable name is specified the HTML output is captured and saved instead. - - Usages: {{#markdown}} ## The Heading {{/markdown}} - {{#markdown content}} ## The Heading {{/markdown}} HTML: {{content}} - - - - - How many times to retry processing messages before moving them to the DLQ - - - - - Execute global transformation or custom logic before a request is processed. - Must be thread-safe. - - - - - Execute global transformation or custom logic on the response. - Must be thread-safe. - - - - - If you only want to enable priority queue handlers (and threads) for specific msg types - - - - - Create workers for priority queues - - - - - Opt-in to only publish responses on this white list. - Publishes all responses by default. - - - - - Subscribe to messages sent to .outq - - - - - The max size of the Out MQ Collection in each Type (default 100) - - - - - Encapsulates creating a new message handler - - - - - Processes all messages in a Normal and Priority Queue. - Expects to be called in 1 thread. i.e. Non Thread-Safe. - - - - - - Changes the links for the servicestack/metadata page - - - - - Get the static Parse(string) method on the type supplied - - - - - Gets the constructor info for T(string) if exists. - - - - - Returns the value returned by the 'T.Parse(string)' method if exists otherwise 'new T(string)'. - e.g. if T was a TimeSpan it will return TimeSpan.Parse(textValue). - If there is no Parse Method it will attempt to create a new instance of the destined type - - - - - Determines wheter the Config section identified by the sectionName exists. - - - - - Gets the connection string setting. - - - - - Only generate specified Verb entries for "ANY" routes - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Container service is built-in and read-only.. - - - - - Looks up a localized string similar to Service type {0} does not inherit or implement {1}.. - - - - - Looks up a localized string similar to Required dependency of type {0} named '{1}' could not be resolved.. - - - - - Looks up a localized string similar to Required dependency of type {0} could not be resolved.. - - - - - Looks up a localized string similar to Unknown scope.. - - - - - Transparently Proxy requests through to downstream HTTP Servers - - - - - Customize the HTTP Request Headers that are sent to downstream server - - - - - Customize the downstream HTTP Response Headers that are returned to client - - - - - Inspect or Transform the HTTP Request Body that's sent downstream - - - - - Inspect or Transform the downstream HTTP Response Body that's returned - - - - - Required filters to specify which requests to proxy and which url to use. - - Specify which requests should be proxied - Specify which downstream url to use - - - - Service error logs are kept in 'urn:ServiceErrors:{ServiceName}' - - - - - Combined service error logs are maintained in 'urn:ServiceErrors:All' - - - - - Enable the Registration feature and configure the RegistrationService. - - - - - Abstraction to provide a context per request. - in spnet.web its equivalent to .Current.Items falls back to CallContext - - - - - Tell ServiceStack to use ThreadStatic Items Collection for RequestScoped items. - Warning: ThreadStatic Items aren't pinned to the same request in async services which callback on different threads. - - - - - Start a new Request context, everything deeper in Async pipeline will get this new RequestContext dictionary. - - - - - Gets a list of items for this request. - - This list will be cleared on every request and is specific to the original thread that is handling the request. - If a handler uses additional threads, this data will not be available on those threads. - - - - - Track any IDisposable's to dispose of at the end of the request in IAppHost.OnEndRequest() - - - - - - Release currently registered dependencies for this request - - true if any dependencies were released - - - - Returns the optimized result for the IRequestContext. - Does not use or store results in any cache. - - - - - - - - Returns the optimized result for the IRequestContext. - Does not use or store results in any cache. - - - - - Overload for the method returning the most - optimized result based on the MimeType and CompressionType from the IRequestContext. - - - - - Overload for the method returning the most - optimized result based on the MimeType and CompressionType from the IRequestContext. - How long to cache for, null is no expiration - - - - - Clears all the serialized and compressed caches set - by the 'Resolve' method for the cacheKey provided - - - - - - - - Store an entry in the IHttpRequest.Items Dictionary - - - - - Get an entry from the IHttpRequest.Items Dictionary - - - - - Base class to create request filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The request DTO - - - - Create a ShallowCopy of this instance. - - - - - - Base class to create request filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The request DTO - - - - Create a ShallowCopy of this instance. - - - - - - RequestLogs service Route, default is /requestlogs - - - - - Turn On/Off Session Tracking - - - - - Turn On/Off Logging of Raw Request Body, default is Off - - - - - Turn On/Off Tracking of Responses - - - - - Turn On/Off Tracking of Exceptions - - - - - Don't log matching requests - - - - - Size of InMemoryRollingRequestLogger circular buffer - - - - - Limit access to /requestlogs service to these roles - - - - - Change the RequestLogger provider. Default is InMemoryRollingRequestLogger - - - - - Don't log requests of these types. By default RequestLog's are excluded - - - - - Don't log request bodys for services with sensitive information. - By default Auth and Registration requests are hidden. - - - - - Limit logging to only Service Requests - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific permissions. - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific roles. - - - - - Check all session is in all supplied roles otherwise a 401 HttpError is thrown - - - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific permissions. - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has any of the specified roles. - - - - - Check all session is in any supplied roles otherwise a 401 HttpError is thrown - - - - - - - Base class to create response filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The response DTO - - - - Create a ShallowCopy of this instance. - - - - - - Base class to create response filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The response DTO - - - - Create a ShallowCopy of this instance. - - - - - - Call IServerEvents.RemoveExpiredSubscriptions() after every count - - - - - Generic + Useful IService base class - - - - - Returns . cache is only persisted for this running app instance. - - - - - Cascading collection of virtual file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Dynamic Session Bag - - - - - Typed UserSession - - - - - If user found in session for this request is authenticated. - - - - - Publish a MQ message over the implementation. - - - - - Disposes all created disposable properties of this service - and executes disposing of all request s - (warning, manualy triggering this might lead to unwanted disposing of all request related objects and services.) - - - - - Scans the supplied Assemblies to infer REST paths and HTTP verbs. - - The instance. - - The assemblies with REST services. - - The same instance; - never . - - - - Typed UserSession - - - - - Dynamic Session Bag - - - - - Configure ServiceStack to have ISession support - - - - - Create the active Session or Permanent Session Id cookie. - - - - - - Create both Permanent and Session Id cookies and return the active sessionId - - - - - - Optimized code to find if GZIP is supported from: - - http://dotnetperls.com/gzip-request - - Other resources for GZip, deflate resources: - - http://www.west-wind.com/Weblog/posts/10564.aspx - - http://www.west-wind.com/WebLog/posts/102969.aspx - - ICSharpCode - - - - - This class interecepts 401 requests and changes them to 402 errors. When this happens the FormAuthentication module - will no longer hijack it and redirect back to login because it is a 402 error, not a 401. - When the request ends, this class sets the status code back to 401 and everything works as it should. - - PathToSupress is the path inside your website where the above swap should happen. - - If you can build for .net 4.5, you do not have to do this swap. You can take advantage of a new flag (SuppressFormsAuthenticationRedirect) - that tells the FormAuthenticationModule to not redirect, which also means you will not need the EndRequest code. - - - - - Load Embedded Resource Templates in ServiceStack. - To get ServiceStack to use your own instead just add a copy of one or more of the following to your Web Root: - ~/Templates/IndexOperations.html - ~/Templates/OperationControl.html - ~/Templates/HtmlFormat.html - - - - - Converts the validation result to an error result which will be serialized by ServiceStack in a clean and human-readable way. - - The validation result - - - - - Converts the validation result to an error exception which will be serialized by ServiceStack in a clean and human-readable way - if the returned exception is thrown. - - The validation result - - - - - Creates a new instance of the RulesetValidatorSelector. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Activate the validation mechanism, so every request DTO with an existing validator - will be validated. - - The app host - - - - Override to provide additional/less context about the Service Exception. - By default the request is serialized and appended to the ResponseStatus StackTrace. - - - - - Auto-scans the provided assemblies for a - and registers it in the provided IoC container. - - The IoC container - The assemblies to scan for a validator - - - - Main container class for components, supporting container hierarchies and - lifetime management of instances. - - - - - Register an autowired dependency - - - - - - Register an autowired dependency - - Name of dependency - - - - - Register an autowired dependency as a separate type - - - - - - Register an autowired dependency as a separate type - - - - - - Alias for RegisterAutoWiredAs - - - - - - Alias for RegisterAutoWiredAs - - - - - - Auto-wires an existing instance, - ie all public properties are tried to be resolved. - - - - - - Generates a function which creates and auto-wires . - - - - - - - - Auto-wires an existing instance of a specific type. - The auto-wiring progress is also cached to be faster - when calling next time with the same type. - - - - - - Initializes a new empty container. - - - - - Default owner for new registrations. by default. - - - - - Default reuse scope for new registrations. by default. - - - - - Creates a child container of the current one, which exposes its - current service registration to the new child container. - - - - - Disposes the container and all instances owned by it (see - ), as well as all child containers - created through . - - - - - Registers a service instance with the container. This instance - will have and - behavior. - Service instance to use. - - - - Registers a named service instance with the container. This instance - will have and - behavior. - Name of the service to register.Service instance to use. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service when needed. - Type of the service to retrieve.The function that can resolve to the service instance when invoked.The requested service has not been registered previously. - - - - - - - - - - - - - - - - - - - - - - Retrieves a function that can be used to lazily resolve an instance - of the service with the given name when needed. - Type of the service to retrieve.Name of the service to retrieve.The function that can resolve to the service instance with the given name when invoked.The requested service with the given name has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Registers the given service by providing a factory delegate to - instantiate it. - The service type to register.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.Sixth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate to - instantiate it. - The service type to register.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.Sixth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Resolves the given service by type, without passing any arguments for - its construction. - Type of the service to retrieve.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, without passing arguments for its initialization. - Type of the service to retrieve.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Attempts to resolve the given service by type, without passing arguments for its initialization. - Type of the service to retrieve. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, without passing - arguments arguments for its initialization. - Type of the service to retrieve. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Encapsulates a method that has five parameters and returns a value of the - type specified by the parameter. - - - - - Encapsulates a method that has six parameters and returns a value of the - type specified by the parameter. - - - - - Encapsulates a method that has seven parameters and returns a value of the - type specified by the parameter. - - - - - Helper interface used to hide the base - members from the fluent API to make for much cleaner - Visual Studio intellisense experience. - - - - - - - - - - - - - - - - - Funqlets are a set of components provided as a package - to an existing container (like a module). - - - - - Configure the given container with the - registrations provided by the funqlet. - - Container to register. - - - - Interface used by plugins to contribute registrations - to an existing container. - - - - - Determines who is responsible for disposing instances - registered with a container. - - - - - Container should dispose provided instances when it is disposed. This is the - default. - - - - - Container does not dispose provided instances. - - - - - Default owner, which equals . - - - - - Exception thrown by the container when a service cannot be resolved. - - - - - Initializes the exception with the service that could not be resolved. - - - - - Initializes the exception with the service (and its name) that could not be resolved. - - - - - Initializes the exception with an arbitrary message. - - - - - Determines visibility and reuse of instances provided by the container. - - - - - Instances are reused within a container hierarchy. Instances - are created (if necessary) in the container where the registration - was performed, and are reused by all descendent containers. - - - - - Instances are reused only at the given container. Descendent - containers do not reuse parent container instances and get - a new instance at their level. - - - - - Each request to resolve the dependency will result in a new - instance being returned. - - - - - Instaces are reused within the given request - - - - - Default scope, which equals . - - - - - Ownership setting for the service. - - - - - Reuse scope setting for the service. - - - - - The container where the entry was registered. - - - - - Specifies the owner for instances, which determines how - they will be disposed. - - - - - Specifies the scope for instances, which determines - visibility of instances across containers and hierarchies. - - - - - The Func delegate that creates instances of the service. - - - - - The cached service instance if the scope is or - . - - - - - The Func delegate that initializes the object after creation. - - - - - Clones the service entry assigning the to the - . Does not copy the . - - - - - Fluent API for customizing the registration of a service. - - - - - Fluent API for customizing the registration of a service. - - - - - Fluent API that allows registering an initializer for the - service. - - - - - Specifies an initializer that should be invoked after - the service instance has been created by the factory. - - - - - Fluent API that exposes both - and owner (). - - - - - Fluent API that allows specifying the reuse instances. - - - - - Specifies how instances are reused within a container or hierarchy. Default - scope is . - - - - - Fluent API that allows specifying the owner of instances - created from a registration. - - - - - Specifies the owner of instances created from this registration. Default - owner is . - - - - diff --git a/lib/netstandard2.0/ServiceStack.Client.deps.json b/lib/netstandard2.0/ServiceStack.Client.deps.json deleted file mode 100644 index 400c89da..00000000 --- a/lib/netstandard2.0/ServiceStack.Client.deps.json +++ /dev/null @@ -1,1324 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "288501feda23728e017ff04011f3e87396f1675e" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Client/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.3", - "ServiceStack.Interfaces": "1.0.0", - "System.Collections.Specialized": "4.3.0", - "System.Memory": "4.5.1", - "System.Net.Requests": "4.3.0", - "System.ServiceModel.Primitives": "4.5.3", - "System.Xml.XmlSerializer": "4.3.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.Client.dll": {} - } - }, - "Microsoft.NETCore.Platforms/2.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Buffers/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Buffers.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": { - "assemblyVersion": "4.0.13.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Collections.NonGeneric/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.NonGeneric.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Collections.Specialized/4.3.0": { - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Specialized.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Memory/4.5.1": { - "dependencies": { - "System.Buffers": "4.4.0", - "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/System.Memory.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.26606.5" - } - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Numerics.Vectors/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Numerics.Vectors.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.Private.ServiceModel/4.5.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Reflection.DispatchProxy": "4.5.0", - "System.Security.Principal.Windows": "4.5.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.DispatchProxy/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Reflection.DispatchProxy.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.5/System.Reflection.TypeExtensions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "0.0.0.0" - } - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": { - "assemblyVersion": "4.1.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll": { - "assemblyVersion": "4.0.0.0", - "fileVersion": "1.0.24212.1" - } - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Security.Principal.Windows.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.ServiceModel.Primitives/4.5.3": { - "dependencies": { - "System.Private.ServiceModel": "4.5.3" - }, - "runtime": { - "lib/netstandard2.0/System.ServiceModel.Primitives.dll": { - "assemblyVersion": "4.5.0.3", - "fileVersion": "4.6.26720.1" - }, - "lib/netstandard2.0/System.ServiceModel.dll": { - "assemblyVersion": "4.0.0.0", - "fileVersion": "4.6.26720.1" - } - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": { - "assemblyVersion": "4.1.0.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.ReaderWriter.dll": { - "assemblyVersion": "4.1.0.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlDocument.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Xml.XmlSerializer/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlSerializer.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": { - "assemblyVersion": "5.0.0.0", - "fileVersion": "5.0.0.0" - } - } - } - } - }, - "libraries": { - "ServiceStack.Client/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.NETCore.Platforms/2.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TT+QCi9LcxGTjBssH7S7n5+8DVcwfG4DYgXX7Dk7+BfZ4oVHj8Q0CbYk9glzAlHLsSt3bYzol+fOdra2iu6GOw==", - "path": "microsoft.netcore.platforms/2.1.0", - "hashPath": "microsoft.netcore.platforms.2.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Buffers/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==", - "path": "system.buffers/4.4.0", - "hashPath": "system.buffers.4.4.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.Collections.NonGeneric/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "path": "system.collections.nongeneric/4.3.0", - "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" - }, - "System.Collections.Specialized/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "path": "system.collections.specialized/4.3.0", - "hashPath": "system.collections.specialized.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Memory/4.5.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vcG3/MbfpxznMkkkaAblJi7RHOmuP7kawQMhDgLSuA1tRpRQYsFSCTxRSINDUgn2QNn2jWeLxv8er5BXbyACkw==", - "path": "system.memory/4.5.1", - "hashPath": "system.memory.4.5.1.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.Numerics.Vectors/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==", - "path": "system.numerics.vectors/4.4.0", - "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512" - }, - "System.Private.ServiceModel/4.5.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TRkNLXhUasgpBESQnxbZozE2PxTmvKbkuHqjhqaPa+N0gsvfbWTRqHWg4KvcTFb9Xy1k3n8Y6VX3BRbYhU5Wsg==", - "path": "system.private.servicemodel/4.5.3", - "hashPath": "system.private.servicemodel.4.5.3.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.DispatchProxy/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-2iafuenmPvt9Y+j8nscAVQyj7g4EU55ICNdMIrbF8WN+MbNehiJaunucpFjE1SAdLRclDbm0kqmYatWsiqeCvQ==", - "path": "system.reflection.dispatchproxy/4.5.0", - "hashPath": "system.reflection.dispatchproxy.4.5.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==", - "path": "system.reflection.typeextensions/4.3.0", - "hashPath": "system.reflection.typeextensions.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6zBxkHYemB0kQiHP3vGXGRXejZVoNVuMn2paUuqKKi5Wyjkxfkp+D0rd0c3VrGwotidRINt6KpOi2smL4VkJKw==", - "path": "system.runtime.compilerservices.unsafe/4.5.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.5.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-WA9ETb/pY3BjnxKjBUHEgO59B7d/nnmjHFsqjJ2eDT780nD769CT1/bw2ia0Z6W7NqlcqokE6sKGKa6uw88XGA==", - "path": "system.security.principal.windows/4.5.0", - "hashPath": "system.security.principal.windows.4.5.0.nupkg.sha512" - }, - "System.ServiceModel.Primitives/4.5.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-cZp5aMKHubNtuGAQviYXFqskewYBdyWpqT3g8rPAE5awve/kRK6g2uTZnFr4BhrjkT2bu9sR4N6Mx+vsQUtjpg==", - "path": "system.servicemodel.primitives/4.5.3", - "hashPath": "system.servicemodel.primitives.4.5.3.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlSerializer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "path": "system.xml.xmlserializer/4.3.0", - "hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Client.dll b/lib/netstandard2.0/ServiceStack.Client.dll deleted file mode 100644 index 15231130..00000000 Binary files a/lib/netstandard2.0/ServiceStack.Client.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Client.xml b/lib/netstandard2.0/ServiceStack.Client.xml deleted file mode 100644 index ec5c5b0a..00000000 --- a/lib/netstandard2.0/ServiceStack.Client.xml +++ /dev/null @@ -1,430 +0,0 @@ - - - - ServiceStack.Client - - - - Need to provide async request options - http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx - - - - The request filter is called before any request. - This request filter is executed globally. - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - This response action is executed globally. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Called before request resend, when the initial request required authentication - - - - - The request filter is called before any request. - This request filter only works with the instance where it was set (not global). - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - The ResultsFilter is called before the Request is sent allowing you to return a cached response. - - - - - The ResultsFilterResponse is called before returning the response allowing responses to be cached. - - - - - Called with requestUri, ResponseType when server returns 304 NotModified - - - - - Useful .NET Encryption Utils from: - https://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider(v=vs.110).aspx - - - - - Returns the next message from queueName or null if no message - - - - - - - Creates the error response from the values provided. - - If the errorCode is empty it will use the first validation error code, - if there is none it will throw an error. - - The error code. - The error message. - The validation errors. - - - - - Default MaxStringContentLength is 8k, and throws an exception when reached - - - - - Serializer cache of delegates required to create a type from a string map (e.g. for REST urls) - - - - - Gets the namespace from an attribute marked on the type's definition - - - Namespace of type - - - - Removes all registered Handlers, Named Receivers and Listeners - - - - Need to provide async request options - http://msdn.microsoft.com/en-us/library/86wf6409(VS.71).aspx - - - - The request filter is called before any request. - This request filter is executed globally. - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - This response action is executed globally. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Gets the collection of headers to be added to outgoing requests. - - - - - Sets all baseUri properties, using the Format property for the SyncReplyBaseUri and AsyncOneWayBaseUri - - Base URI of the service - - - - Whether to Accept Gzip,Deflate Content-Encoding and to auto decompress responses - - - - - The user name for basic authentication - - - - - The password for basic authentication - - - - - Sets the username and the password for basic authentication. - - - - - The Authorization Bearer Token to send with this request - - - - - Gets or sets authentication information for the request. - Warning: It's recommened to use and for basic auth. - This property is only used for IIS level authentication. - - - - - Determines if the basic auth header should be sent with every request. - By default, the basic auth header is only sent when "401 Unauthorized" is returned. - - - - - Specifies if cookies should be stored - - - - - Called before request resend, when the initial request required authentication - - - - - If a request fails with a 401 Unauthorized and a BearerToken is present the client - will automatically fetch a new AccessToken using this RefreshToken and retry the request - - - - - Send the Request to get the AccessToken with the RefreshToken at a non-default location - - - - - The request filter is called before any request. - This request filter only works with the instance where it was set (not global). - - - - - The ResultsFilter is called before the Request is sent allowing you to return a cached response. - - - - - The ResultsFilterResponse is called before returning the response allowing responses to be cached. - - - - - Called with requestUri, ResponseType when server returns 304 NotModified - - - - - The response action is called once the server response is available. - It will allow you to access raw response information. - Note that you should NOT consume the response stream as this is handled by ServiceStack - - - - - Called by Send method if an exception occurs, for instance a System.Net.WebException because the server - returned an HTTP error code. Override if you want to handle specific exceptions or always want to parse the - response to a custom ErrorResponse DTO type instead of ServiceStack's ErrorResponse class. In case ex is a - System.Net.WebException, do not use - createWebRequest/getResponse/HandleResponse<TResponse> to parse the response - because that will result in the same exception again. Use - ThrowWebServiceException<YourErrorResponseType> to parse the response and to throw a - WebServiceException containing the parsed DTO. Then override Send to handle that exception. - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Get(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Delete(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Delete(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Post(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Put(url)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.CustomMethod(method,dto)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.CustomMethod(method,dto)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - APIs returning HttpWebResponse must be explicitly Disposed, e.g using (var res = client.Head(request)) { ... } - - - - - Compresses the specified text using the default compression method: Deflate - - The text. - Type of the compression. - - - - - Compresses the specified text using the default compression method: Deflate - - - - - Decompresses the specified gz buffer using the default compression method: Inflate - - The gz buffer. - Type of the compression. - - - - - Decompresses the specified gz buffer using inflate or gzip method - - Compressed stream - Type of the compression. Can be "gzip" or "deflate" - Decompressed stream - - - - Decompresses the specified gz buffer using the default compression method: Inflate - - - - - Donated by Ivan Korneliuk from his post: - http://korneliuk.blogspot.com/2012/08/servicestack-reusing-dtos.html - - Modified to only allow using routes matching the supplied HTTP Verb - - - - - Generate a url from a Request DTO. Pretty URL generation require Routes to be defined using `[Route]` on the Request DTO - - - - - The exception which is thrown when a validation error occurred. - This validation is serialized in a extra clean and human-readable way by ServiceStack. - - - - - Returns the first error code - - The error code. - - - - Used if we need to serialize this exception to XML - - - - - - Encapsulates a validation result. - - - - - Gets or sets the success code. - - The success code. - - - - Gets or sets the error code. - - The error code. - - - - Gets or sets the success message. - - The success message. - - - - Gets or sets the error message. - - The error message. - - - - The errors generated by the validation. - - - - - Returns True if the validation was successful (errors list is empty). - - - - - Constructs a new ValidationResult - - - - - Constructs a new ValidationResult - - A list of validation results - - - - Initializes a new instance of the class. - - The errors. - The success code. - The error code. - - - - Merge errors from another - - - - - - Naming convention for the request's Response DTO - - - - - Shortcut to get the ResponseStatus whether it's bare or inside a IHttpResult - - - - - - diff --git a/lib/netstandard2.0/ServiceStack.Common.deps.json b/lib/netstandard2.0/ServiceStack.Common.deps.json deleted file mode 100644 index 75495468..00000000 --- a/lib/netstandard2.0/ServiceStack.Common.deps.json +++ /dev/null @@ -1,1446 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "6219967d8a6fa03213901d1909e55d499d2a54cf" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Common/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.3", - "ServiceStack.Interfaces": "1.0.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Data.Common": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Memory": "4.5.1", - "System.Net.NetworkInformation": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.ValueTuple": "4.5.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.Common.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Microsoft.Win32.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Buffers/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Buffers.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": { - "assemblyVersion": "4.0.13.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ComponentModel/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ComponentModel.Primitives/4.3.0": { - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.ComponentModel.Primitives.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Data.Common/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.2/System.Data.Common.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Dynamic.Runtime.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.Expressions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Memory/4.5.1": { - "dependencies": { - "System.Buffers": "4.4.0", - "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/System.Memory.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.26606.5" - } - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.NetworkInformation/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Overlapped": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.Sockets/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Numerics.Vectors/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Numerics.Vectors.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ObjectModel.dll": { - "assemblyVersion": "4.0.13.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Reflection.TypeExtensions.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "0.0.0.0" - } - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": { - "assemblyVersion": "4.1.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Claims/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Security.Principal": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Claims.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll": { - "assemblyVersion": "4.0.0.0", - "fileVersion": "1.0.24212.1" - } - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Security.Principal.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Principal.Windows/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Claims": "4.3.0", - "System.Security.Principal": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.Overlapped/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Thread/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.Thread.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.ThreadPool/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.ThreadPool.dll": { - "assemblyVersion": "4.0.11.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ValueTuple/4.5.0": {}, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": { - "assemblyVersion": "5.0.0.0", - "fileVersion": "5.0.0.0" - } - } - } - } - }, - "libraries": { - "ServiceStack.Common/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Microsoft.Win32.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "path": "microsoft.win32.primitives/4.3.0", - "hashPath": "microsoft.win32.primitives.4.3.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Buffers/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==", - "path": "system.buffers/4.4.0", - "hashPath": "system.buffers.4.4.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.ComponentModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "path": "system.componentmodel/4.3.0", - "hashPath": "system.componentmodel.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "path": "system.componentmodel.primitives/4.3.0", - "hashPath": "system.componentmodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Data.Common/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lm6E3T5u7BOuEH0u18JpbJHxBfOJPuCyl4Kg1RH10ktYLp5uEEE1xKrHW56/We4SnZpGAuCc9N0MJpSDhTHZGQ==", - "path": "system.data.common/4.3.0", - "hashPath": "system.data.common.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Dynamic.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", - "path": "system.dynamic.runtime/4.3.0", - "hashPath": "system.dynamic.runtime.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Memory/4.5.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vcG3/MbfpxznMkkkaAblJi7RHOmuP7kawQMhDgLSuA1tRpRQYsFSCTxRSINDUgn2QNn2jWeLxv8er5BXbyACkw==", - "path": "system.memory/4.5.1", - "hashPath": "system.memory.4.5.1.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.NetworkInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-zNVmWVry0pAu7lcrRBhwwU96WUdbsrGL3azyzsbXmVNptae1+Za+UgOe9Z6s8iaWhPn7/l4wQqhC56HZWq7tkg==", - "path": "system.net.networkinformation/4.3.0", - "hashPath": "system.net.networkinformation.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.Sockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "path": "system.net.sockets/4.3.0", - "hashPath": "system.net.sockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.Numerics.Vectors/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==", - "path": "system.numerics.vectors/4.4.0", - "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-jdK7She37kK5gtXbznOVwcfbtCb2vIs8UfMsYd2OFiGLmj+3oXrA3bgUQPzbe3boNJApkA7oLb42nCHQ0g813A==", - "path": "system.reflection.typeextensions/4.5.0", - "hashPath": "system.reflection.typeextensions.4.5.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6zBxkHYemB0kQiHP3vGXGRXejZVoNVuMn2paUuqKKi5Wyjkxfkp+D0rd0c3VrGwotidRINt6KpOi2smL4VkJKw==", - "path": "system.runtime.compilerservices.unsafe/4.5.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.5.0.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Claims/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-P/+BR/2lnc4PNDHt/TPBAWHVMLMRHsyYZbU1NphW4HIWzCggz8mJbTQQ3MKljFE7LS3WagmVFuBgoLcFzYXlkA==", - "path": "system.security.claims/4.3.0", - "hashPath": "system.security.claims.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I1tkfQlAoMM2URscUtpcRo/hX0jinXx6a/KUtEQoz3owaYwl3qwsO8cbzYVVnjxrzxjHo3nJC+62uolgeGIS9A==", - "path": "system.security.principal/4.3.0", - "hashPath": "system.security.principal.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HVL1rvqYtnRCxFsYag/2le/ZfKLK4yMw79+s6FmKXbSCNN0JeAhrYxnRAHFoWRa0dEojsDcbBSpH3l22QxAVyw==", - "path": "system.security.principal.windows/4.3.0", - "hashPath": "system.security.principal.windows.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Overlapped/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LRcp7Ir4HQ3I7uIhUb8ypaMDw23W1t3GMCA2+yUxTL3HG9ZrVJtLufoK/mF9zfCG2bxuRBf1Bj8FC9mOPddhIg==", - "path": "system.threading.overlapped/4.3.0", - "hashPath": "system.threading.overlapped.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Thread/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "path": "system.threading.thread/4.3.0", - "hashPath": "system.threading.thread.4.3.0.nupkg.sha512" - }, - "System.Threading.ThreadPool/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "path": "system.threading.threadpool/4.3.0", - "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" - }, - "System.ValueTuple/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xZtSZNEHGa+tGsKuP4sh257vxJ/yemShz4EusmomkynMzuEDDjVaErBNewpzEF6swUgbcrSQAX3ELsEp1zCOwA==", - "path": "system.valuetuple/4.5.0", - "hashPath": "system.valuetuple.4.5.0.nupkg.sha512" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Common.dll b/lib/netstandard2.0/ServiceStack.Common.dll deleted file mode 100644 index b5b0f7ab..00000000 Binary files a/lib/netstandard2.0/ServiceStack.Common.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Common.xml b/lib/netstandard2.0/ServiceStack.Common.xml deleted file mode 100644 index b3a5e3e6..00000000 --- a/lib/netstandard2.0/ServiceStack.Common.xml +++ /dev/null @@ -1,668 +0,0 @@ - - - - ServiceStack.Common - - - - - - Provide the an option for the callee to block until all commands are executed - - - - - - - Return T[0] when enumerable is null, safe to use in enumerations like foreach - - - - - Gets the textual description of the enum if it has one. e.g. - - - enum UserColors - { - [Description("Bright Red")] - BrightRed - } - UserColors.BrightRed.ToDescription(); - - - - - - - - Default base sleep time (milliseconds). - - - - - Default maximum back-off time before retrying a request - - - - - Maximum retry limit. Avoids integer overflow issues. - - - - - How long to sleep before next retry using Exponential BackOff delay with Full Jitter. - - - - - - Exponential BackOff Delay with Full Jitter - - - - - - - Exponential BackOff Delay with Full Jitter from: - https://github.com/aws/aws-sdk-java/blob/master/aws-java-sdk-core/src/main/java/com/amazonaws/retry/PredefinedBackoffStrategies.java - - - - - - - - - Calculate exponential retry back-off. - - - - - - - Calculate exponential retry back-off. - - - - - - - - - Invokes the action provided and returns true if no excpetion was thrown. - Otherwise logs the exception and returns false if an exception was thrown. - - The action. - - - - - Useful IPAddressExtensions from: - http://blogs.msdn.com/knom/archive/2008/12/31/ip-address-calculations-with-c-subnetmasks-networks.aspx - - - - - - Gets the ipv4 addresses from all Network Interfaces that have Subnet masks. - - - - - - Gets the ipv6 addresses from all Network Interfaces. - - - - - - Configure ServiceStack.Text JSON Serializer to use Templates JS parsing - - - - - Creates a Console Logger, that logs all messages to: System.Console - - Made public so its testable - - - - - Default logger is to Console.WriteLine - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Creates a Debug Logger, that logs all messages to: System.Diagnostics.Debug - - Made public so its testable - - - - - Default logger is to System.Diagnostics.Debug.WriteLine - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Categories of sql statements. - - - - - Unknown - - - - - DML statements that alter database state, e.g. INSERT, UPDATE - - - - - Statements that return a single record - - - - - Statements that iterate over a result set - - - - - A callback for ProfiledDbConnection and family - - - - - Called when a command starts executing - - - - - - - Called when a reader finishes executing - - - - - - - - Called when a reader is done iterating through the data - - - - - - Called when an error happens during execution of a command - - - - - - - - True if the profiler instance is active - - - - - Wraps a database connection, allowing sql execution timings to be collected when a session is started. - - - - - Returns a new that wraps , - providing query execution profiling. If profiler is null, no profiling will occur. - - Your provider-specific flavor of connection, e.g. SqlConnection, OracleConnection - The currently started or null. - Determines whether the ProfiledDbConnection will dispose the underlying connection. - - - - The underlying, real database connection to your db provider. - - - - - The current profiler instance; could be null. - - - - - The raw connection this is wrapping - - - - - Wrapper for a db provider factory to enable profiling - - - - - Every provider factory must have an Instance public field - - - - - Allow to re-init the provider factory. - - - - - - - proxy - - - - - - - proxy - - - - - proxy - - - - - proxy - - - - - proxy - - - - - Runs an action for a minimum of runForMs - - What to run - Minimum ms to run for - time elapsed in micro seconds - - - - Returns average microseconds an action takes when run for the specified runForMs - - What to run - How many times to run for each iteration - Minimum ms to run for - - - - - - - - Multiple string replacements - - Even number of old and new value pairs - - - - Replace string contents outside of string quotes - - - - - Protect against XSS by cleaning non-standared User Input - - - - - Common functionality when creating adapters - - - - - Executes the specified expression. - - - The action. - - - - - Executes the specified action (for void methods). - - The action. - - - - Note: InMemoryLog keeps all logs in memory, so don't use it long running exceptions - - Returns a thread-safe InMemoryLog which you can use while *TESTING* - to provide a detailed analysis of your logs. - - - - - Captures the output and assigns it to the specified variable. - Accepts an optional Object Dictionary as scope arguments when evaluating body. - - Usages: {{#capture output}} {{#each args}} - [{{it}}](/path?arg={{it}}) {{/each}} {{/capture}} - {{#capture output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}} - {{#capture appendTo output {nums:[1,2,3]} }} {{#each nums}} {{it}} {{/each}} {{/capture}} - - - - - Handlebars.js like each block - Usages: {{#each customers}} {{Name}} {{/each}} - {{#each customers}} {{it.Name}} {{/each}} - {{#each num in numbers}} {{num}} {{/each}} - {{#each num in [1,2,3]}} {{num}} {{/each}} - {{#each numbers}} {{it}} {{else}} no numbers {{/each}} - {{#each numbers}} {{it}} {{else if letters != null}} has letters {{else}} no numbers {{/each}} - {{#each n in numbers where n > 5}} {{it}} {{else}} no numbers > 5 {{/each}} - {{#each n in numbers where n > 5 orderby n skip 1 take 2}} {{it}} {{else}} no numbers > 5 {{/each}} - - - - - Special block which evaluates the rendered body as a ServiceStack Template - - Usages: {{#eval}}emit {{evaluateBodyOfArg}} at {{now}} {{/eval}} - {{#eval {scopeArg:1} }}emit {{evaluateBodyOfArg}} at {{now}} with {{scopeArg}} {{/eval}} - {{#eval {htmlDecode:true} }}emit htmldecoded {{evaluateBodyOfArg}} at {{now}} {{/eval}} - {{#eval {use:{filters:'TemplateServiceStackFilters',plugins:['MarkdownTemplatePlugin'],context:true} }} - emit {{evaluateBodyOfArg}} at {{now}} in new context - {{/eval}} - - - - - Usages: {{#ul {each:items, class:'nav'} }}
  • {{it}}
  • {{/ul}} -
    -
    - - - Handlebars.js like if block - Usages: {{#if a > b}} max {{a}} {{/if}} - {{#if a > b}} max {{a}} {{else}} max {{b}} {{/if}} - {{#if a > b}} max {{a}} {{else if b > c}} max {{b}} {{else}} max {{c}} {{/if}} - - - - - Handlebars.js like noop block - Usage: Remove {{#noop}} contents in here {{/noop}} - - - - - Partial Block doesn't emit anything it only creates and saves a partial in the PageResult - - Usages: {{#partial mypartial}} contents {{/partial}} - {{#partial mypartial {format:'html'} }} contents {{/partial}} - {{#partial mypartial {format:'html', pageArg:1} }} contents {{/partial}} - - - - - Special block which captures the raw body as a string fragment - - Usages: {{#raw}}emit {{ verbatim }} body{{/raw}} - {{#raw varname}}assigned to varname{{/raw}} - {{#raw appendTo varname}}appended to varname{{/raw}} - - - - - Handlebars.js like with block - Usages: {{#with person}} Hi {{name}}, I'm {{age}} years old{{/with}} - {{#with person}} Hi {{name}}, I'm {{age}} years old {{else}} no person {{/with}} - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaulate if result can be async, if so converts async result to Task<object> otherwise wraps result in a Task - - - - - Evaluate then set asyncResult if Result was async, otherwise set result. - - - - - true if result was synchronous otherwise false - - - - The Template Page to Render - - - - - The Code Page to Render - - - - - Use specified Layout - - - - - Use Layout with specified name - - - - - Render without any Layout - - - - - Extract Model Properties into Scope Args - - - - - Add additional Args available to all pages - - - - - Add additional template filters available to all pages - - - - - Add additional template blocks available to all pages - - - - - Add additional partials available to all pages - - - - - Return additional HTTP Headers in HTTP Requests - - - - - Specify the Content-Type of the Response - - - - - Transform the Page output using a chain of stream transformers - - - - - Transform the entire output using a chain of stream transformers - - - - - Available transformers that can transform context filter stream outputs - - - - - Don't allow access to specified filters - - - - - The last error thrown by a filter - - - - - The StackTrace where the Last Error Occured - - - - - What argument errors should be binded to - - - - - Whether to skip execution of all page filters and just write template string fragments - - - - - Overrides Context to specify whether to Ignore or Continue executing filters on error - - - - - Whether to always rethrow Exceptions - - - - - Immediately halt execution of the page - - - - - Rethrow fatal exceptions thrown on incorrect API usage - - - - - Available transformers that can transform context filter stream outputs - - - - - Whether to check for modified pages by default when not in DebugMode - - - - - How long in between checking for modified pages - - - - - Existing caches and pages created prior to specified date should be invalidated - - - - - Render render filter exceptions in-line where filter is located - - - - - What argument to assign Filter Exceptions to - - - - - Whether to skip executing Filters if an Exception was thrown - - - - - Creates a Unified Resource Name (URN) with the following formats: - - - urn:{TypeName}:{IdFieldValue} e.g. urn:UserSession:1 - - urn:{TypeName}:{IdFieldName}:{IdFieldValue} e.g. urn:UserSession:UserId:1 - - - -
    -
    diff --git a/lib/netstandard2.0/ServiceStack.Interfaces.deps.json b/lib/netstandard2.0/ServiceStack.Interfaces.deps.json deleted file mode 100644 index dda11624..00000000 --- a/lib/netstandard2.0/ServiceStack.Interfaces.deps.json +++ /dev/null @@ -1,192 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "efe40b86ebf920f606e93002ddc9b43b053dc3f3" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.3", - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": { - "assemblyVersion": "4.1.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - } - } - }, - "libraries": { - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Interfaces.dll b/lib/netstandard2.0/ServiceStack.Interfaces.dll deleted file mode 100644 index f0fc906c..00000000 Binary files a/lib/netstandard2.0/ServiceStack.Interfaces.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Interfaces.xml b/lib/netstandard2.0/ServiceStack.Interfaces.xml deleted file mode 100644 index a5e2aae1..00000000 --- a/lib/netstandard2.0/ServiceStack.Interfaces.xml +++ /dev/null @@ -1,2419 +0,0 @@ - - - - ServiceStack.Interfaces - - - - - Gets or sets parameter name with which allowable values will be associated. - - - - - Generates body DTO parameter only if `DisableAutoDtoInBodyParam = false` - - - - - Always generate body DTO for request - - - - - Never generate body DTO for request - - - - - The overall description of an API. Used by Swagger. - - - - - Create or not body param for request type when verb is POST or PUT. - Value can be one of the constants of `GenerateBodyParam` class: - `GenerateBodyParam.IfNotDisabled` (default value), `GenerateBodyParam.Always`, `GenerateBodyParam.Never` - - - - - Tells if body param is required - - - - - Gets or sets verb to which applies attribute. By default applies to all verbs. - - - - - Gets or sets parameter type: It can be only one of the following: path, query, body, form, or header. - - - - - Gets or sets unique name for the parameter. Each name must be unique, even if they are associated with different paramType values. - - - - Other notes on the name field: - If paramType is body, the name is used only for UI and codegeneration. - If paramType is path, the name field must correspond to the associated path segment from the path field in the api object. - If paramType is query, the name field corresponds to the query param name. - - - - - - Gets or sets the human-readable description for the parameter. - - - - - For path, query, and header paramTypes, this field must be a primitive. For body, this can be a complex or container datatype. - - - - - Fine-tuned primitive type definition. - - - - - For path, this is always true. Otherwise, this field tells the client whether or not the field must be supplied. - - - - - For query params, this specifies that a comma-separated list of values can be passed to the API. For path and body types, this field cannot be true. - - - - - Gets or sets route to which applies attribute, matches using StartsWith. By default applies to all routes. - - - - - Whether to exclude this property from being included in the ModelSchema - - - - - The status code of a response - - - - - The description of a response status code - - - - - HTTP status code of response - - - - - End-user description of the data which is returned by response - - - - - If set to true, the response is default for all non-explicity defined status codes - - - - - Open API schema definition type for response - - - - - The Password Hasher provider used to hash users passwords, by default uses the same algorithm used by ASP.NET Identity v3: - PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - - - - - The first byte marker used to specify the format used. The default implementation uses the following format: - { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey } - - - - - Returns a boolean indicating whether the matches the . - The out parameter indicates whether the password should be re-hashed. - - The hash value for a user's stored password. - The password supplied for comparison. - Implementations of this method should be time consistent. - - - - Returns a hashed representation of the supplied . - - The password to hash. - A hashed representation of the supplied . - - - - A common interface implementation that is implemented by most cache providers - - - - - Removes the specified item from the cache. - - The identifier for the item to delete. - - true if the item was successfully removed from the cache; false otherwise. - - - - - Removes the cache for all the keys provided. - - The keys. - - - - Retrieves the specified item from the cache. - - - The identifier for the item to retrieve. - - The retrieved item, or null if the key was not found. - - - - - Increments the value of the specified key by the given amount. - The operation is atomic and happens on the server. - A non existent value at key starts at 0 - - The identifier for the item to increment. - The amount by which the client wants to increase the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Increments the value of the specified key by the given amount. - The operation is atomic and happens on the server. - A non existent value at key starts at 0 - - The identifier for the item to increment. - The amount by which the client wants to decrease the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Adds a new item into the cache at the specified cache key only if the cache is empty. - - The key used to reference the item. - The object to be inserted into the cache. - - true if the item was successfully stored in the cache; false otherwise. - - The item does not expire unless it is removed due memory pressure. - - - - Sets an item into the cache at the cache key specified regardless if it already exists or not. - - - - - Replaces the item at the cachekey specified only if an items exists at the location already. - - - - - Invalidates all data on the cache. - - - - - Retrieves multiple items from the cache. - The default value of T is set for all keys that do not exist. - - The list of identifiers for the items to retrieve. - - a Dictionary holding all items indexed by their key. - - - - - Sets multiple items to the cache. - - - The values. - - - - Extend ICacheClient API with shared, non-core features - - - - - A light interface over a cache client. - This interface was inspired by Enyim.Caching.MemcachedClient - - Only the methods that are intended to be used are required, if you require - extra functionality you can uncomment the unused methods below as they have been - implemented in DdnMemcachedClient - - - - - Removes the specified item from the cache. - - The identifier for the item to delete. - - true if the item was successfully removed from the cache; false otherwise. - - - - - Removes the cache for all the keys provided. - - The keys. - - - - Retrieves the specified item from the cache. - - The identifier for the item to retrieve. - - The retrieved item, or null if the key was not found. - - - - - Increments the value of the specified key by the given amount. The operation is atomic and happens on the server. - - The identifier for the item to increment. - The amount by which the client wants to increase the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Increments the value of the specified key by the given amount. The operation is atomic and happens on the server. - - The identifier for the item to increment. - The amount by which the client wants to decrease the item. - - The new value of the item or -1 if not found. - - The item must be inserted into the cache before it can be changed. The item must be inserted as a . The operation only works with values, so -1 always indicates that the item was not found. - - - - Inserts an item into the cache with a cache key to reference its location. - - The key used to reference the item. - The object to be inserted into the cache. - - true if the item was successfully stored in the cache; false otherwise. - - The item does not expire unless it is removed due memory pressure. - - - - Inserts an item into the cache with a cache key to reference its location. - - The key used to reference the item. - The object to be inserted into the cache. - The time when the item is invalidated in the cache. - true if the item was successfully stored in the cache; false otherwise. - - - - Removes all data from the cache. - - - - - Retrieves multiple items from the cache. - - The list of identifiers for the items to retrieve. - - a Dictionary holding all items indexed by their key. - - - - - Removes items from cache that have keys matching the specified wildcard pattern - - The wildcard, where "*" means any sequence of characters and "?" means any single character. - - - - Removes items from the cache based on the specified regular expression pattern - - Regular expression pattern to search cache keys - - - - A Users Session - - - - - Store any object at key - - - - - - - Set a typed value at key - - - - - - - - Get a typed value at key - - - - - - - - Remove the value at key - - - - - - - Delete all Cache Entries (requires ICacheClient that implements IRemoveByPattern) - - - - - Retrieves a User Session - - - - - Gets the Session Bag for this request, creates one if it doesn't exist. - - - - - - - - Gets the Session Bag for this request, creates one if it doesn't exist. - Only for ASP.NET apps. Uses the HttpContext.Current singleton. - - - - - Create a Session Bag using a custom sessionId - - - - - - - Allow delegation of dependencies to other IOC's - - - - - Resolve Constructor Dependency - - - - - - - Resolve a dependency from the AppHost's IOC - - - - - - - BelongToAttribute - Use to indicate that a join column belongs to another table. - - - - - Compute attribute. - Use to indicate that a property is a Calculated Field - - - - - Decimal length attribute. - - - - - Mark types that are to be excluded from specified features - - - - - Explicit foreign key name. If it's null, or empty, the FK name will be autogenerated as before. - - - - - Hash Key Attribute used to specify which property is the HashKey, e.g. in DynamoDb. - - - - - IgnoreAttribute - Use to indicate that a property is not a field in the table - properties with this attribute are ignored when building sql sentences - - - - - Ignore this property in SELECT statements - - - - - Ignore this property in UPDATE statements - - - - - Ignore this property in INSERT statements - - - - - Decorate any type or property with adhoc info - - - - - Primary key attribute. - use to indicate that property is part of the pk - - - - - Range Key Attribute used to specify which property is the RangeKey, e.g. in DynamoDb. - - - - - ReturnAttribute - Use to indicate that a property should be included in the - returning/output clause of INSERT sql sentences - - - - - Used to indicate that property is a row version incremented automatically by the database - - - - - Used to annotate an Entity with its DB schema - - - - - - For providers that want a cleaner API with a little more perf - - - - - - Generic ResponseStatus for when Response Type can't be inferred. - In schemaless formats like JSON, JSV it has the same shape as a typed Response DTO - - - - - Contract indication that the Response DTO has a ResponseStatus - - - - - Refresh file stats for this node if supported - - - - - How many results to skip - - - - - How many results to return - - - - - List of fields to sort by, can order by multiple fields and inverse order, e.g: Id,-Amount - - - - - List of fields to sort by descending, can order by multiple fields and inverse order, e.g: -Id,Amount - - - - - Include aggregate data like Total, COUNT(*), COUNT(DISTINCT Field), Sum(Amount), etc - - - - - The fields to return - - - - - Populate with Include=Total or if registered with: AutoQueryFeature { IncludeTotal = true } - - - - - Unifed API to create any missing Tables, Data Structure Schema - or perform any other tasks dependencies require to run at Startup. - - - - - Provide unique, incrementing sequences. Used in PocoDynamo. - - - - - Marker interfaces - - - - - The minimal API Surface to capture the most common SYNC requests. - Convenience extensions over these core API's available in ServiceGatewayExtensions - - - - - Normal Request/Reply Services - - - - - Auto Batched Request/Reply Requests - - - - - OneWay Service - - - - - Auto Batched OneWay Requests - - - - - The minimal API Surface to capture the most common ASYNC requests. - Convenience extensions over these core API's available in ServiceGatewayExtensions - - - - - Normal Request/Reply Services - - - - - Auto Batched Request/Reply Requests - - - - - OneWay Service - - - - - Auto Batched OneWay Requests - - - - - Helper ILog implementation that reduces effort to extend or use without needing to impl each API - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Logs a message in a running application - - - - - Gets or sets a value indicating whether this instance is debug enabled. - - - true if this instance is debug enabled; otherwise, false. - - - - - Logs a Debug message. - - The message. - - - - Logs a Debug message and exception. - - The message. - The exception. - - - - Logs a Debug format message. - - The format. - The args. - - - - Logs a Error message. - - The message. - - - - Logs a Error message and exception. - - The message. - The exception. - - - - Logs a Error format message. - - The format. - The args. - - - - Logs a Fatal message. - - The message. - - - - Logs a Fatal message and exception. - - The message. - The exception. - - - - Logs a Error format message. - - The format. - The args. - - - - Logs an Info message and exception. - - The message. - - - - Logs an Info message and exception. - - The message. - The exception. - - - - Logs an Info format message. - - The format. - The args. - - - - Logs a Warning message. - - The message. - - - - Logs a Warning message and exception. - - The message. - The exception. - - - - Logs a Warning format message. - - The format. - The args. - - - - Factory to create ILog instances - - - - - Gets the logger. - - - - - Gets the logger. - - - - - Pushes a property on the current log context, returning an - to remove the property again from the async context. - - Property Name - Property Value - Interface for popping the property off the stack - - - - Pushes a property on the current log context, returning an - to remove the property again from the async context. - - The logger - Property Name - Property Value - Interface for popping the property off the stack - - - - Logs a Debug format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs an Info format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Warn format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs an Error format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Fatal format message and exception. - - Exception related to the event. - The format. - The args. - - - - Logs a Debug format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs an Info format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs a Warn format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs an Error format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logs a Fatal format message and exception. - - The logger - Exception related to the event. - The format. - The args. - - - - Logging API for this library. You can inject your own implementation otherwise - will use the DebugLogFactory to write to System.Diagnostics.Debug - - - - - Gets or sets the log factory. - Use this to override the factory that is used to create loggers - - - - - Gets the logger. - - - - - Gets the logger. - - - - - Default logger is to System.Diagnostics.Debug.Print - - Made public so its testable - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - - - - Creates a Debug Logger, that logs all messages to: System.Diagnostics.Debug - - Made public so its testable - - - - - StringBuilderLog writes to shared StringBuffer. - Made public so its testable - - - - - Logs the specified message. - - - - - Logs the format. - - - - - Logs the specified message. - - The message. - - - - Creates a test Logger, that stores all log messages in a member list - - - - - Tests logger which stores all log messages in a member list which can be examined later - - Made public so its testable - - - - - Initializes a new instance of the class. - - The type. - - - - Initializes a new instance of the class. - - The type. - - - - Logs the specified message. - - The message. - The exception. - - - - Logs the format. - - The message. - The args. - - - - Logs the specified message. - - The message. - - - - Single threaded message handler that can process all messages - of a particular message type. - - - - - The type of the message this handler processes - - - - - The MqClient processing the message - - - - - Process all messages pending - - - - - - Process messages from a single queue. - - - The queue to process - A predicate on whether to continue processing the next message if any - - - - - Process a single message - - - - - Get Current Stats for this Message Handler - - - - - - Publish the specified message into the durable queue @queueName - - - - - Publish the specified message into the transient queue @queueName - - - - - Synchronous blocking get. - - - - - Non blocking get message - - - - - Acknowledge the message has been successfully received or processed - - - - - Negative acknowledgement the message was not processed correctly - - - - - Create a typed message from a raw MQ Response artefact - - - - - Create a temporary Queue for Request / Reply - - - - - - Simple definition of an MQ Host - - - - - Factory to create consumers and producers that work with this service - - - - - Register DTOs and hanlders the MQ Server will process - - - - - - - Register DTOs and hanlders the MQ Server will process using specified number of threads - - - - - - - - Register DTOs and hanlders the MQ Server will process - - - - - - - - Register DTOs and hanlders the MQ Server will process using specified number of threads - - - - - - - - - Get Total Current Stats for all Message Handlers - - - - - - Get a list of all message types registered on this MQ Host - - - - - Get the status of the service. Potential Statuses: Disposed, Stopped, Stopping, Starting, Started - - - - - - Get a Stats dump - - - - - - Start the MQ Host if not already started. - - - - - Stop the MQ Host if not already stopped. - - - - - Basic implementation of IMessage[T] - - - - - - Util static generic class to create unique queue names for types - - - - - - Util class to create unique queue names for runtime types - - - - - For messaging exceptions that should by-pass the messaging service's configured - retry attempts and store the message straight into the DLQ - - - - - Specify a VirtualPath or Layout for a Code Page - - - - - Specify static page arguments - - - - - Wrap the common redis list operations under a IList[string] interface. - - - - - Redis transaction for typed client - - - - - - Interface to redis typed pipeline - - - - - interface to queueable operation using typed redis client - - - - - - Returns a high-level typed client API - - - - - - Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write ICacheClient (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly ICacheClient using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. - - - - - - Returns a Read/Write ICacheClient (The default) using the hosts defined in ReadWriteHosts - - - - - - Returns a ReadOnly ICacheClient using the hosts defined in ReadOnlyHosts. - - - - - - The number of active subscriptions this client has - - - - - Registered handler called after client *Subscribes* to each new channel - - - - - Registered handler called when each message is received - - - - - Registered handler called when each channel is unsubscribed - - - - - Subscribe to channels by name - - - - - - Subscribe to channels matching the supplied patterns - - - - - - Interface to redis transaction - - - - - Base transaction interface, shared by typed and non-typed transactions - - - - - Interface to redis pipeline - - - - - Pipeline interface shared by typed and non-typed pipelines - - - - - interface to operation that can queue commands - - - - - Interface to operations that allow queued commands to be completed - - - - - A log entry added by the IRequestLogger - - - - - Error information pertaining to a particular named field. - Used for returning multiple field validation errors.s - - - - - Common ResponseStatus class that should be present on all response DTO's - - - - - Initializes a new instance of the class. - - A response status without an errorcode == success - - - - - Initializes a new instance of the class. - A response status with an errorcode == failure - - - - - Initializes a new instance of the class. - A response status with an errorcode == failure - - - - - Holds the custom ErrorCode enum if provided in ValidationException - otherwise will hold the name of the Exception type, e.g. typeof(Exception).Name - - A value of non-null means the service encountered an error while processing the request. - - - - - A human friendly error message - - - - - The Server StackTrace when DebugMode is enabled - - - - - For multiple detailed validation errors. - Can hold a specific error message for each named field. - - - - - For additional custom metadata about the error - - - - - Decorate on Request DTO's to alter the accessibility of a service and its visibility on /metadata pages - - - - - Allow access but hide from metadata to requests from Localhost only - - - - - Allow access but hide from metadata to requests from Localhost and Local Intranet only - - - - - Restrict access and hide from metadata to requests from Localhost only - - - - - Restrict access and hide from metadata to requests from Localhost and Local Intranet only - - - - - Restrict access and hide from metadata to requests from External only - - - - - Sets a single access restriction - - Restrict Access to. - - - - Restrict access to any of the specified access scenarios - - Access restrictions - - - - Sets a single metadata Visibility restriction - - Restrict metadata Visibility to. - - - - Restrict metadata visibility to any of the specified access scenarios - - Visibility restrictions - - - - Restrict access and metadata visibility to any of the specified access scenarios - - The restrict access to scenarios. - - - - Restrict access and metadata visibility to any of the specified access scenarios - - The restrict access to scenarios. - - - - Returns the allowed set of scenarios based on the user-specified restrictions - - - - - - - Converts from a User intended restriction to a flag with all the allowed attribute flags set, e.g: - - If No Network restrictions were specified all Network access types are allowed, e.g: - restrict EndpointAttributes.None => ... 111 - - If a Network restriction was specified, only it will be allowed, e.g: - restrict EndpointAttributes.LocalSubnet => ... 010 - - The returned Enum will have a flag with all the allowed attributes set - - - - - - - Used to decorate Request DTO's to associate a RESTful request - path mapping with a service. Multiple attributes can be applied to - each request DTO, to map multiple paths to the service. - - - - - Initializes an instance of the class. - - - The path template to map to the request. See - RouteAttribute.Path - for details on the correct format. - - - - - Initializes an instance of the class. - - - The path template to map to the request. See - RouteAttribute.Path - for details on the correct format. - - A comma-delimited list of HTTP verbs supported by the - service. If unspecified, all verbs are assumed to be supported. - - - - Gets or sets the path template to be mapped to the request. - - - A value providing the path mapped to - the request. Never . - - - Some examples of valid paths are: - - - "/Inventory" - "/Inventory/{Category}/{ItemId}" - "/Inventory/{ItemPath*}" - - - Variables are specified within "{}" - brackets. Each variable in the path is mapped to the same-named property - on the request DTO. At runtime, ServiceStack will parse the - request URL, extract the variable values, instantiate the request DTO, - and assign the variable values into the corresponding request properties, - prior to passing the request DTO to the service object for processing. - - It is not necessary to specify all request properties as - variables in the path. For unspecified properties, callers may provide - values in the query string. For example: the URL - "http://services/Inventory?Category=Books&ItemId=12345" causes the same - request DTO to be processed as "http://services/Inventory/Books/12345", - provided that the paths "/Inventory" (which supports the first URL) and - "/Inventory/{Category}/{ItemId}" (which supports the second URL) - are both mapped to the request DTO. - - Please note that while it is possible to specify property values - in the query string, it is generally considered to be less RESTful and - less desirable than to specify them as variables in the path. Using the - query string to specify property values may also interfere with HTTP - caching. - - The final variable in the path may contain a "*" suffix - to grab all remaining segments in the path portion of the request URL and assign - them to a single property on the request DTO. - For example, if the path "/Inventory/{ItemPath*}" is mapped to the request DTO, - then the request URL "http://services/Inventory/Books/12345" will result - in a request DTO whose ItemPath property contains "Books/12345". - You may only specify one such variable in the path, and it must be positioned at - the end of the path. - - - - - Gets or sets short summary of what the route does. - - - - - Gets or sets longer text to explain the behaviour of the route. - - - - - Gets or sets a comma-delimited list of HTTP verbs supported by the service, such as - "GET,PUT,POST,DELETE". - - - A providing a comma-delimited list of HTTP verbs supported - by the service, or empty if all verbs are supported. - - - - - Used to rank the precedences of route definitions in reverse routing. - i.e. Priorities below 0 are auto-generated have less precedence. - - - - - Must match rule defined in Config.RequestRules or Regex expression with format: - "{IHttpRequest.Field} =~ {pattern}", e.g "PathInfo =~ \/[0-9]+$" - - - - - Fallback routes have the lowest precedence, i.e. after normal Routes, static files or any matching Catch All Handlers. - - - - - Additional checks to notify of invalid state, configuration or use of ServiceStack libraries. - Can disable StrictMode checks with Config.StrictMode = false; - - - - - Get or sets tag name - - - - - Get or sets operation verbs for which the attribute be applied - - - - - Adds an expired Set-Cookie instruction for clients to delete this Cookie - - - - - Adds a new Set-Cookie instruction for ss-pid - - - - - Adds a new Set-Cookie instruction for ss-id - - - - - Order in which Request Filters are executed. - <0 Executed before global request filters - >0 Executed after global request filters - - - - - A new shallow copy of this filter is used on every request. - - - - - - This interface can be implemented by an attribute - which adds an request filter for the specific request DTO the attribute marked. - - - - - The request filter is executed before the service. - - The http request wrapper - The http response wrapper - The request DTO - - - - This interface can be implemented by an attribute - which adds an request filter for the specific request DTO the attribute marked. - - - - - The request filter is executed before the service. - - The http request wrapper - The http response wrapper - The request DTO - - - - Order in which Response Filters are executed. - <0 Executed before global response filters - >0 Executed after global response filters - - - - - A new shallow copy of this filter is used on every request. - - - - - - This interface can be implemented by an attribute - which adds an response filter for the specific response DTO the attribute marked. - - - - - The response filter is executed after the service - - The http request wrapper - The http response wrapper - - - - This interface can be implemented by an attribute - which adds an response filter for the specific response DTO the attribute marked. - - - - - The response filter is executed after the service - - The http request wrapper - The http response wrapper - - - - A thin wrapper around ASP.NET or HttpListener's HttpRequest - - - - - The HttpResponse - - - - - The HTTP Verb - - - - - The IP Address of the X-Forwarded-For header, null if null or empty - - - - - The Port number of the X-Forwarded-Port header, null if null or empty - - - - - The http or https scheme of the X-Forwarded-Proto header, null if null or empty - - - - - The value of the X-Real-IP header, null if null or empty - - - - - The value of the Accept HTTP Request Header - - - - - A thin wrapper around ASP.NET or HttpListener's HttpResponse - - - - - Adds a new Set-Cookie instruction to Response - - - - - - Removes all pending Set-Cookie instructions - - - - - The HTTP Response Status - - - - - The HTTP Response Status Code - - - - - The HTTP Status Description - - - - - The HTTP Response ContentType - - - - - Additional HTTP Headers - - - - - Additional HTTP Cookies - - - - - Response DTO - - - - - if not provided, get's injected by ServiceStack - - - - - Holds the request call context - - - - - The padding length written with the body, to be added to ContentLength of body - - - - - Serialize the Response within the specified scope - - - - - Whether this HttpResult allows Partial Response - - - - - Write a partial content result - - - - - Whether this HttpResult allows Partial Response - - - - - Write a partial content result - - - - - A thin wrapper around each host's Request e.g: ASP.NET, HttpListener, MQ, etc - - - - - The underlying ASP.NET or HttpListener HttpRequest - - - - - The Response API for this Request - - - - - The name of the service being called (e.g. Request DTO Name) - - - - - The Verb / HttpMethod or Action for this request - - - - - Different Attribute Enum flags classifying this Request - - - - - Optional preferences for the processing of this Request - - - - - The Request DTO, after it has been deserialized. - - - - - The request ContentType - - - - - Whether this was an Internal Request - - - - - The UserAgent for the request - - - - - A Dictionary of HTTP Cookies sent with this Request - - - - - The expected Response ContentType for this request - - - - - Whether the ResponseContentType has been explicitly overrided or whether it was just the default - - - - - Attach any data to this request that all filters and services can access. - - - - - The HTTP Headers in a NameValueCollection - - - - - The ?query=string in a NameValueCollection - - - - - The HTTP POST'ed Form Data in a NameValueCollection - - - - - Buffer the Request InputStream so it can be re-read - - - - - The entire string contents of Request.InputStream - - - - - - Relative URL containing /path/info?query=string - - - - - The Absolute URL for the request - - - - - The Remote IP as reported by Request.UserHostAddress - - - - - The Remote Ip as reported by X-Forwarded-For, X-Real-IP or Request.UserHostAddress - - - - - The value of the Authorization Header used to send the Api Key, null if not available - - - - - e.g. is https or not - - - - - Array of different Content-Types accepted by the client - - - - - The normalized /path/info for the request - - - - - The original /path/info as sent - - - - - The Request Body Input Stream - - - - - The size of the Request Body if provided - - - - - Access to the multi-part/formdata files posted on this request - - - - - The value of the Referrer, null if not available - - - - - Log every service request - - - - - Turn On/Off Session Tracking - - - - - Turn On/Off Raw Request Body Tracking - - - - - Turn On/Off Tracking of Responses - - - - - Turn On/Off Tracking of Exceptions - - - - - Limit logging to only Service Requests - - - - - Limit access to /requestlogs service to role - - - - - Don't log matching requests - - - - - Don't log requests of these types. - - - - - Don't log request bodys for services with sensitive information. - By default Auth and Registration requests are hidden. - - - - - Log a request - - The RequestContext - Request DTO - Response DTO or Exception - How long did the Request take - - - - View the most recent logs - - - - - - - Implement on services that need access to the RequestContext - - - - - Implement on Request DTOs that need access to the raw Request Stream - - - - - The raw Http Request Input Stream - - - - - A thin wrapper around each host's Response e.g: ASP.NET, HttpListener, MQ, etc - - - - - The underlying ASP.NET, .NET Core or HttpListener HttpResponse - - - - - The corresponding IRequest API for this Response - - - - - The Response Status Code - - - - - The Response Status Description - - - - - The Content-Type for this Response - - - - - Add a Header to this Response - - - - - - - Remove an existing Header added on this Response - - - - - - Get an existing Header added to this Response - - - - - - - Return a Redirect Response to the URL specified - - - - - - The Response Body Output Stream - - - - - The Response DTO - - - - - Buffer the Response OutputStream so it can be written in 1 batch - - - - - Signal that this response has been handled and no more processing should be done. - When used in a request or response filter, no more filters or processing is done on this request. - - - - - Close this Response Output Stream Async - - - - - Calls Response.End() on ASP.NET HttpResponse otherwise is an alias for Close(). - Useful when you want to prevent ASP.NET to provide it's own custom error page. - - - - - Response.Flush() and OutputStream.Flush() seem to have different behaviour in ASP.NET - - - - - Flush this Response Output Stream Async - - - - - Gets a value indicating whether this instance is closed. - - - - - Set the Content Length in Bytes for this Response - - - - - - Whether the underlying TCP Connection for this Response should remain open - - - - - Whether the HTTP Response Headers have already been written. - - - - - Responsible for executing the operation within the specified context. - - The operation types. - - - - Returns the first matching RestPath - - - - - Executes the MQ DTO request. - - - - - Executes the MQ DTO request with the supplied requestContext - - - - - Executes the DTO request under the supplied requestContext. - - - - - Executes the DTO request under supplied context and option to Execute Request/Response Filters. - - - - - Executes the DTO request with an empty RequestContext. - - - - - Executes the DTO request with the current HttpRequest and option to Execute Request/Response Filters. - - - - - Allow the registration of user-defined routes for services - - - - - Maps the specified REST path to the specified request DTO. - - The type of request DTO to map - the path to. - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, and - specifies the HTTP verbs supported by the path. - - The type of request DTO to map - the path to. - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". Specify empty or - to indicate that all verbs are supported. - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - Used to rank the precedences of route definitions in reverse routing. - i.e. Priorities below 0 are auto-generated have less precedence. - - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - - The short summary of what the REST does. - - - The longer text to explain the behaviour of the REST. - - The same instance; - never . - - - - Maps the specified REST path to the specified request DTO, - specifies the HTTP verbs supported by the path, and indicates - the default MIME type of the returned response. - - - The type of request DTO to map the path to. - - The path to map the request DTO to. - See RouteAttribute.Path - for details on the correct format. - - The comma-delimited list of HTTP verbs supported by the path, - such as "GET,PUT,DELETE". - - - The short summary of what the REST does. - - - The longer text to explain the behaviour of the REST. - - - Must match rule defined in Config.RequestRules or Regex expression with format: - "{IHttpRequest.Field} =~ {pattern}", e.g "PathInfo =~ \/[0-9]+$" - - The same instance; - never . - - - diff --git a/lib/netstandard2.0/ServiceStack.Server.deps.json b/lib/netstandard2.0/ServiceStack.Server.deps.json deleted file mode 100644 index 5905d325..00000000 --- a/lib/netstandard2.0/ServiceStack.Server.deps.json +++ /dev/null @@ -1,2116 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "5e4a4cbfb7547cd14093f73ad945f78bd4596b78" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Server/1.0.0": { - "dependencies": { - "NETStandard.Library": "2.0.3", - "ServiceStack": "1.0.0", - "ServiceStack.Client": "1.0.0", - "ServiceStack.Common": "1.0.0", - "ServiceStack.Interfaces": "1.0.0", - "ServiceStack.OrmLite": "5.0.0.0", - "ServiceStack.Redis": "5.0.0.0", - "ServiceStack.Text": "5.0.0.0" - }, - "runtime": { - "ServiceStack.Server.dll": {} - } - }, - "Microsoft.AspNetCore.Cryptography.Internal/2.1.1": { - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Cryptography.Internal.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Cryptography.KeyDerivation/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Cryptography.Internal": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Cryptography.KeyDerivation.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Hosting.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.1.1", - "Microsoft.AspNetCore.Http.Abstractions": "2.1.1", - "Microsoft.Extensions.Hosting.Abstractions": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "2.1.1", - "Microsoft.Extensions.Configuration.Abstractions": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Server.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Http/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "2.1.1", - "Microsoft.AspNetCore.WebUtilities": "2.1.1", - "Microsoft.Extensions.ObjectPool": "2.1.1", - "Microsoft.Extensions.Options": "2.1.1", - "Microsoft.Net.Http.Headers": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Http.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Http.Features": "2.1.1", - "System.Text.Encodings.Web": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Http.Extensions/2.1.1": { - "dependencies": { - "Microsoft.AspNetCore.Http.Abstractions": "2.1.1", - "Microsoft.Extensions.FileProviders.Abstractions": "2.1.1", - "Microsoft.Net.Http.Headers": "2.1.1", - "System.Buffers": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Extensions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.Http.Features/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.AspNetCore.WebUtilities/2.1.1": { - "dependencies": { - "Microsoft.Net.Http.Headers": "2.1.1", - "System.Text.Encodings.Web": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Configuration/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Configuration.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Configuration.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Configuration.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Configuration.Binder/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Configuration": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Configuration.Binder.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions/2.1.1": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.FileProviders.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.FileProviders.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Hosting.Abstractions/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "2.1.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1", - "Microsoft.Extensions.FileProviders.Abstractions": "2.1.1", - "Microsoft.Extensions.Logging.Abstractions": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Hosting.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Logging.Abstractions/2.1.1": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Logging.Abstractions.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.ObjectPool/2.1.1": { - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Options/2.1.1": { - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1", - "Microsoft.Extensions.Primitives": "2.1.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Options.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Extensions.Primitives/2.1.1": { - "dependencies": { - "System.Memory": "4.5.1", - "System.Runtime.CompilerServices.Unsafe": "4.5.1" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.Net.Http.Headers/2.1.1": { - "dependencies": { - "Microsoft.Extensions.Primitives": "2.1.1", - "System.Buffers": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": { - "assemblyVersion": "2.1.1.0", - "fileVersion": "2.1.1.18157" - } - } - }, - "Microsoft.NETCore.Platforms/2.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "Microsoft.Win32.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0" - } - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.native.System/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "dependencies": { - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0" - } - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0", - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": {}, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": {}, - "System.Buffers/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Buffers.dll": { - "assemblyVersion": "4.0.3.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Collections/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Collections.Concurrent/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Concurrent.dll": { - "assemblyVersion": "4.0.13.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Collections.NonGeneric/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.NonGeneric.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Collections.Specialized/4.3.0": { - "dependencies": { - "System.Collections.NonGeneric": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Collections.Specialized.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ComponentModel/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ComponentModel.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ComponentModel.Primitives/4.3.0": { - "dependencies": { - "System.ComponentModel": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.ComponentModel.Primitives.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Data.Common/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.2/System.Data.Common.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Debug/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Diagnostics.DiagnosticSource.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Diagnostics.Tracing/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Dynamic.Runtime/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Dynamic.Runtime.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Globalization/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Calendars/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Globalization.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0" - } - }, - "System.IO/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.IO.FileSystem.Primitives.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq.Expressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.ObjectModel": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Emit.Lightweight": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Linq.Expressions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Linq.Queryable/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Linq": "4.3.0", - "System.Linq.Expressions": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Linq.Queryable.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Memory/4.5.1": { - "dependencies": { - "System.Buffers": "4.5.0", - "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.1" - }, - "runtime": { - "lib/netstandard2.0/System.Memory.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.26606.5" - } - } - }, - "System.Net.Http/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.DiagnosticSource": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Extensions": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Security.Cryptography.X509Certificates": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Net.NetworkInformation/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.Win32.Primitives": "4.3.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Linq": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.Sockets": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Principal.Windows": "4.5.0", - "System.Threading": "4.3.0", - "System.Threading.Overlapped": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Thread": "4.3.0", - "System.Threading.ThreadPool": "4.3.0", - "runtime.native.System": "4.3.0" - } - }, - "System.Net.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Net.Requests/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Diagnostics.Tracing": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Net.Http": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Net.WebHeaderCollection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.Sockets/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Net.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Net.WebHeaderCollection/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Net.WebHeaderCollection.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Numerics.Vectors/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Numerics.Vectors.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.ObjectModel/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.ObjectModel.dll": { - "assemblyVersion": "4.0.13.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Private.ServiceModel/4.5.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Reflection.DispatchProxy": "4.5.0", - "System.Security.Principal.Windows": "4.5.0" - } - }, - "System.Reflection/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.DispatchProxy/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Reflection.DispatchProxy.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Reflection.Emit/4.3.0": { - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.ILGeneration.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "dependencies": { - "System.Reflection": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Reflection.Emit.Lightweight.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Reflection.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.Primitives/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Reflection.TypeExtensions/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Reflection.TypeExtensions.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Resources.ResourceManager/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Globalization": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.5.1": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "0.0.0.0" - } - } - }, - "System.Runtime.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.Handles/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime.InteropServices/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Reflection": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Runtime.Numerics/4.3.0": { - "dependencies": { - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Numerics.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "dependencies": { - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Runtime.Serialization.Primitives.dll": { - "assemblyVersion": "4.1.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.Apple": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.Cng/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Security.Cryptography.Csp/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0" - } - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Collections.Concurrent": "4.3.0", - "System.Linq": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Security.Cryptography.OpenSsl.dll": { - "assemblyVersion": "4.0.0.0", - "fileVersion": "1.0.24212.1" - } - } - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "dependencies": { - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Security.Cryptography.Primitives.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.Globalization.Calendars": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.Handles": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Runtime.Numerics": "4.3.0", - "System.Security.Cryptography.Algorithms": "4.3.0", - "System.Security.Cryptography.Cng": "4.3.0", - "System.Security.Cryptography.Csp": "4.3.0", - "System.Security.Cryptography.Encoding": "4.3.0", - "System.Security.Cryptography.OpenSsl": "4.3.0", - "System.Security.Cryptography.Primitives": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "runtime.native.System": "4.3.0", - "runtime.native.System.Net.Http": "4.3.0", - "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0" - } - }, - "System.Security.Principal.Windows/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Security.Principal.Windows.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.ServiceModel.Primitives/4.5.3": { - "dependencies": { - "System.Private.ServiceModel": "4.5.3" - }, - "runtime": { - "lib/netstandard2.0/System.ServiceModel.Primitives.dll": { - "assemblyVersion": "4.5.0.3", - "fileVersion": "4.6.26720.1" - }, - "lib/netstandard2.0/System.ServiceModel.dll": { - "assemblyVersion": "4.0.0.0", - "fileVersion": "4.6.26720.1" - } - } - }, - "System.Text.Encoding/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.Extensions/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encodings.Web/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Text.Encodings.Web.dll": { - "assemblyVersion": "4.0.3.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "System.Text.RegularExpressions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Threading": "4.3.0" - }, - "runtime": { - "lib/netstandard1.6/System.Text.RegularExpressions.dll": { - "assemblyVersion": "4.1.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.Overlapped/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - } - }, - "System.Threading.Tasks/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Runtime": "4.3.0", - "System.Threading.Tasks": "4.3.0" - }, - "runtime": { - "lib/netstandard1.0/System.Threading.Tasks.Extensions.dll": { - "assemblyVersion": "4.1.0.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.Thread/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.Thread.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Threading.ThreadPool/4.3.0": { - "dependencies": { - "System.Runtime": "4.3.0", - "System.Runtime.Handles": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Threading.ThreadPool.dll": { - "assemblyVersion": "4.0.11.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.ValueTuple/4.5.0": {}, - "System.Xml.ReaderWriter/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.IO.FileSystem": "4.3.0", - "System.IO.FileSystem.Primitives": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Runtime.InteropServices": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encoding.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "System.Threading.Tasks.Extensions": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.ReaderWriter.dll": { - "assemblyVersion": "4.1.0.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Xml.XmlDocument/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Diagnostics.Debug": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlDocument.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "System.Xml.XmlSerializer/4.3.0": { - "dependencies": { - "System.Collections": "4.3.0", - "System.Globalization": "4.3.0", - "System.IO": "4.3.0", - "System.Linq": "4.3.0", - "System.Reflection": "4.3.0", - "System.Reflection.Emit": "4.3.0", - "System.Reflection.Emit.ILGeneration": "4.3.0", - "System.Reflection.Extensions": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Resources.ResourceManager": "4.3.0", - "System.Runtime": "4.3.0", - "System.Runtime.Extensions": "4.3.0", - "System.Text.RegularExpressions": "4.3.0", - "System.Threading": "4.3.0", - "System.Xml.ReaderWriter": "4.3.0", - "System.Xml.XmlDocument": "4.3.0" - }, - "runtime": { - "lib/netstandard1.3/System.Xml.XmlSerializer.dll": { - "assemblyVersion": "4.0.12.0", - "fileVersion": "4.6.24705.1" - } - } - }, - "ServiceStack/1.0.0": { - "dependencies": { - "Microsoft.AspNetCore.Cryptography.KeyDerivation": "2.1.1", - "Microsoft.AspNetCore.Hosting.Abstractions": "2.1.1", - "Microsoft.AspNetCore.Http": "2.1.1", - "Microsoft.AspNetCore.Http.Abstractions": "2.1.1", - "Microsoft.AspNetCore.Http.Extensions": "2.1.1", - "Microsoft.Extensions.Configuration.Binder": "2.1.1", - "Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1", - "Microsoft.Extensions.Logging.Abstractions": "2.1.1", - "Microsoft.Extensions.Primitives": "2.1.1", - "ServiceStack.Client": "1.0.0", - "ServiceStack.Common": "1.0.0", - "ServiceStack.Interfaces": "1.0.0", - "System.Linq.Queryable": "4.3.0", - "System.Memory": "4.5.1", - "System.Reflection.Emit": "4.3.0", - "System.Threading.Thread": "4.3.0" - }, - "runtime": { - "ServiceStack.dll": {} - } - }, - "ServiceStack.Client/1.0.0": { - "dependencies": { - "ServiceStack.Interfaces": "1.0.0", - "System.Collections.Specialized": "4.3.0", - "System.Memory": "4.5.1", - "System.Net.Requests": "4.3.0", - "System.ServiceModel.Primitives": "4.5.3", - "System.Xml.XmlSerializer": "4.3.0" - }, - "runtime": { - "ServiceStack.Client.dll": {} - } - }, - "ServiceStack.Common/1.0.0": { - "dependencies": { - "ServiceStack.Interfaces": "1.0.0", - "System.ComponentModel.Primitives": "4.3.0", - "System.Data.Common": "4.3.0", - "System.Dynamic.Runtime": "4.3.0", - "System.Memory": "4.5.1", - "System.Net.NetworkInformation": "4.3.0", - "System.Net.Requests": "4.3.0", - "System.Reflection.TypeExtensions": "4.5.0", - "System.Runtime.Serialization.Primitives": "4.3.0", - "System.ValueTuple": "4.5.0" - }, - "runtime": { - "ServiceStack.Common.dll": {} - } - }, - "ServiceStack.Interfaces/1.0.0": { - "dependencies": { - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "runtime": { - "ServiceStack.Interfaces.dll": {} - } - }, - "ServiceStack.OrmLite/5.0.0.0": { - "runtime": { - "ServiceStack.OrmLite.dll": { - "assemblyVersion": "5.0.0.0", - "fileVersion": "5.0.0.0" - } - } - }, - "ServiceStack.Redis/5.0.0.0": { - "runtime": { - "ServiceStack.Redis.dll": { - "assemblyVersion": "5.0.0.0", - "fileVersion": "5.0.0.0" - } - } - }, - "ServiceStack.Text/5.0.0.0": { - "runtime": { - "ServiceStack.Text.dll": { - "assemblyVersion": "5.0.0.0", - "fileVersion": "5.0.0.0" - } - } - } - } - }, - "libraries": { - "ServiceStack.Server/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.AspNetCore.Cryptography.Internal/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9X49e4ZTv6ipL/Yh1GvVxpgh+ghWMHi+PPE3tQI2HRgG6Jixvmt8LgT/KvAvfgYEDnjsSTRyt/arrHsekHwfMA==", - "path": "microsoft.aspnetcore.cryptography.internal/2.1.1", - "hashPath": "microsoft.aspnetcore.cryptography.internal.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Cryptography.KeyDerivation/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HqgqUlIbOUgCWcW51NyJuws2FcY4mCfhIFcw1+NnK+p3XwdYmJ/XI+Q/WM4GTshTh9Dgn1h7i/WsrzgQr/292g==", - "path": "microsoft.aspnetcore.cryptography.keyderivation/2.1.1", - "hashPath": "microsoft.aspnetcore.cryptography.keyderivation.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Hosting.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FFZxJAK3sV9JxZ7YP47upycv6VZOcNvJLiLM0FXfvlrb67RC9y4AjCUX1RvI0W1n1v6GMZhWSNb3KYs+O6s26g==", - "path": "microsoft.aspnetcore.hosting.abstractions/2.1.1", - "hashPath": "microsoft.aspnetcore.hosting.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Hosting.Server.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xqfxC5t1Jk4ZOQN5xfR2Q0nqTOTN5R6FORk4LqjEzmfX8NDdEsds+Fj6d9bMYqhPWZ4ATRAi8RmaUKYPQuAWbQ==", - "path": "microsoft.aspnetcore.hosting.server.abstractions/2.1.1", - "hashPath": "microsoft.aspnetcore.hosting.server.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-u8Fmky/nirrxOU1gBGh97J5gPoniWDc1QiT+J0EFuXJWcFo3BgPGiv7RLvYCi89QpLgIt5CkkPqTkPnWz0eaSA==", - "path": "microsoft.aspnetcore.http/2.1.1", - "hashPath": "microsoft.aspnetcore.http.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-0TPQgjRy2xJ75GcK18vvrT6/zCtSAWUEBSskSJN/lY0zuvQx2or8lzwr0TdKyMNK8A8MLP4QMLPqL9NOAxe0yg==", - "path": "microsoft.aspnetcore.http.abstractions/2.1.1", - "hashPath": "microsoft.aspnetcore.http.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Extensions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-0dgKLajNfwElW6fLElwjo+fEyfhXdSN74QeXhOUgPam5UIbU3EBQU/+xD83MnfprAiUPDWHqueTKuB8oa/cjNQ==", - "path": "microsoft.aspnetcore.http.extensions/2.1.1", - "hashPath": "microsoft.aspnetcore.http.extensions.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.Http.Features/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-cMnTXRH+8T7GLht6cXRCMmN1HaYfXti2WEUdXqMUuyJgi4oH9cmzW4nECSBkQjsCs5O06BphyDDDAsTW/zQmpg==", - "path": "microsoft.aspnetcore.http.features/2.1.1", - "hashPath": "microsoft.aspnetcore.http.features.2.1.1.nupkg.sha512" - }, - "Microsoft.AspNetCore.WebUtilities/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-gvCdObgQDLdZ9enyFQuPb3Rae6QyzZAPgHiv5JhYjORLMW1UNgWXvdqLov6iGtnyG+BBCavPooW9ScWGQCJHLg==", - "path": "microsoft.aspnetcore.webutilities/2.1.1", - "hashPath": "microsoft.aspnetcore.webutilities.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Configuration/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1JaydycXzbfAExlsD7XIWykzVnU/wZM86KzrHyGlXuxqnqzcWSXLJn4Ejn8bDnq07CEJNZ+GjsxWKlJ8kFfnvQ==", - "path": "microsoft.extensions.configuration/2.1.1", - "hashPath": "microsoft.extensions.configuration.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Configuration.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9EMhOWU2eOQOtMIJ+vfwKJpnLRc1Wl3vXu8qXeevA91cSY4j3WvArmF7ApGtJwa7yKewJTvlQlBSn9OSnLFg6Q==", - "path": "microsoft.extensions.configuration.abstractions/2.1.1", - "hashPath": "microsoft.extensions.configuration.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Configuration.Binder/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t7KFAv6AxyUsZj9QN8FAbusg+X5baCELl+XtscyuP1IGUv5UctyY7/rNZLyiKaV7HhAcDQ1zC5ZQNQQFn6JpAA==", - "path": "microsoft.extensions.configuration.binder/2.1.1", - "hashPath": "microsoft.extensions.configuration.binder.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.DependencyInjection.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PW1596sF97gpIc1JuUuYvTmeLfeqC5whbWPsWgJhN0fdwz683him3b/HB0dqhFesVssOjnnA0fEz4+S0gUeBqA==", - "path": "microsoft.extensions.dependencyinjection.abstractions/2.1.1", - "hashPath": "microsoft.extensions.dependencyinjection.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.FileProviders.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOJP+VAlXDeMQSJ6iflW62bEsN3S1NJIPHmhKFA9L37yU+jce2wbwesA7sDe9WdJ8+SoKtLnHPUxvOyQrAcRCA==", - "path": "microsoft.extensions.fileproviders.abstractions/2.1.1", - "hashPath": "microsoft.extensions.fileproviders.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Hosting.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-v7mPlJ68Dsev9gn6w5tJJZI798r6gCmwKBv0pwJ5PunLEITYjrv1+QJ/wYkp7KuRcr8VRUML8mJg/mgUjgHggA==", - "path": "microsoft.extensions.hosting.abstractions/2.1.1", - "hashPath": "microsoft.extensions.hosting.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Logging.Abstractions/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-QWFWKrdeoDSEr8nVJaBAVDMj24wnh9clGzDNmMdgHHRsOIwTUMeh4XljeZXJhIKPT00jWuzwEzn3uNxOtO4cYg==", - "path": "microsoft.extensions.logging.abstractions/2.1.1", - "hashPath": "microsoft.extensions.logging.abstractions.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.ObjectPool/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FE4JmV6FEZdmqSKqvld5TRnvHfJfrw9QzvvZlAiTn+FCiq/1ZaQDpcYBRH7dMHFWIsYD6Z2UTsufdbCGznox8g==", - "path": "microsoft.extensions.objectpool/2.1.1", - "hashPath": "microsoft.extensions.objectpool.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Options/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-j0zOfTt1Qm+JDW2m+6Q/aj1m4C8+onudUu4ls/fN69VxruZkMWmX1bPKkbkYIPNNxJsf4k7FOkVq5o1vEFq9pQ==", - "path": "microsoft.extensions.options/2.1.1", - "hashPath": "microsoft.extensions.options.2.1.1.nupkg.sha512" - }, - "Microsoft.Extensions.Primitives/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Svz25/egj1TsNL4118jyMqkhDiu0l8QYWq2p52P4BBN0GbqwR18ZRIctSP5TTDJy0m0EFC8aB2FOVjGtvEGWSA==", - "path": "microsoft.extensions.primitives/2.1.1", - "hashPath": "microsoft.extensions.primitives.2.1.1.nupkg.sha512" - }, - "Microsoft.Net.Http.Headers/2.1.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tNh1YCfZ943/d3WSE6cD57O05rhvi3lmKgwoi3zFg4wc/O/oec5FNHZmBCRau4GfzRC5zS/CBdOAkRwbvtZSaQ==", - "path": "microsoft.net.http.headers/2.1.1", - "hashPath": "microsoft.net.http.headers.2.1.1.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/2.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TT+QCi9LcxGTjBssH7S7n5+8DVcwfG4DYgXX7Dk7+BfZ4oVHj8Q0CbYk9glzAlHLsSt3bYzol+fOdra2iu6GOw==", - "path": "microsoft.netcore.platforms/2.1.0", - "hashPath": "microsoft.netcore.platforms.2.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "Microsoft.Win32.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==", - "path": "microsoft.win32.primitives/4.3.0", - "hashPath": "microsoft.win32.primitives.4.3.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" - }, - "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q==", - "path": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.debian.8-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA==", - "path": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.23-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw==", - "path": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.fedora.24-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.native.System/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==", - "path": "runtime.native.system/4.3.0", - "hashPath": "runtime.native.system.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==", - "path": "runtime.native.system.net.http/4.3.0", - "hashPath": "runtime.native.system.net.http.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==", - "path": "runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==", - "path": "runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A==", - "path": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.13.2-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ==", - "path": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.opensuse.42.1-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.apple.4.3.0.nupkg.sha512" - }, - "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g==", - "path": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.osx.10.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg==", - "path": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.rhel.7-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ==", - "path": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.14.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A==", - "path": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.04-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg==", - "path": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl/4.3.0", - "hashPath": "runtime.ubuntu.16.10-x64.runtime.native.system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Buffers/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xpHYjjtyTEpzMwtSQBWdVc3dPjLdQtvyUg6fBlBqcLl1r2Y7gDG/W/enAYOB98nG3oD3Q153Y2FBO8JDWd+0Xw==", - "path": "system.buffers/4.5.0", - "hashPath": "system.buffers.4.5.0.nupkg.sha512" - }, - "System.Collections/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==", - "path": "system.collections/4.3.0", - "hashPath": "system.collections.4.3.0.nupkg.sha512" - }, - "System.Collections.Concurrent/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==", - "path": "system.collections.concurrent/4.3.0", - "hashPath": "system.collections.concurrent.4.3.0.nupkg.sha512" - }, - "System.Collections.NonGeneric/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-prtjIEMhGUnQq6RnPEYLpFt8AtLbp9yq2zxOSrY7KJJZrw25Fi97IzBqY7iqssbM61Ek5b8f3MG/sG1N2sN5KA==", - "path": "system.collections.nongeneric/4.3.0", - "hashPath": "system.collections.nongeneric.4.3.0.nupkg.sha512" - }, - "System.Collections.Specialized/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Epx8PoVZR0iuOnJJDzp7pWvdfMMOAvpUo95pC4ScH2mJuXkKA2Y4aR3cG9qt2klHgSons1WFh4kcGW7cSXvrxg==", - "path": "system.collections.specialized/4.3.0", - "hashPath": "system.collections.specialized.4.3.0.nupkg.sha512" - }, - "System.ComponentModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VyGn1jGRZVfxnh8EdvDCi71v3bMXrsu8aYJOwoV7SNDLVhiEqwP86pPMyRGsDsxhXAm2b3o9OIqeETfN5qfezw==", - "path": "system.componentmodel/4.3.0", - "hashPath": "system.componentmodel.4.3.0.nupkg.sha512" - }, - "System.ComponentModel.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-j8GUkCpM8V4d4vhLIIoBLGey2Z5bCkMVNjEZseyAlm4n5arcsJOeI3zkUP+zvZgzsbLTYh4lYeP/ZD/gdIAPrw==", - "path": "system.componentmodel.primitives/4.3.0", - "hashPath": "system.componentmodel.primitives.4.3.0.nupkg.sha512" - }, - "System.Data.Common/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lm6E3T5u7BOuEH0u18JpbJHxBfOJPuCyl4Kg1RH10ktYLp5uEEE1xKrHW56/We4SnZpGAuCc9N0MJpSDhTHZGQ==", - "path": "system.data.common/4.3.0", - "hashPath": "system.data.common.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Debug/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==", - "path": "system.diagnostics.debug/4.3.0", - "hashPath": "system.diagnostics.debug.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.DiagnosticSource/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==", - "path": "system.diagnostics.diagnosticsource/4.3.0", - "hashPath": "system.diagnostics.diagnosticsource.4.3.0.nupkg.sha512" - }, - "System.Diagnostics.Tracing/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==", - "path": "system.diagnostics.tracing/4.3.0", - "hashPath": "system.diagnostics.tracing.4.3.0.nupkg.sha512" - }, - "System.Dynamic.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-SNVi1E/vfWUAs/WYKhE9+qlS6KqK0YVhnlT0HQtr8pMIA8YX3lwy3uPMownDwdYISBdmAF/2holEIldVp85Wag==", - "path": "system.dynamic.runtime/4.3.0", - "hashPath": "system.dynamic.runtime.4.3.0.nupkg.sha512" - }, - "System.Globalization/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==", - "path": "system.globalization/4.3.0", - "hashPath": "system.globalization.4.3.0.nupkg.sha512" - }, - "System.Globalization.Calendars/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==", - "path": "system.globalization.calendars/4.3.0", - "hashPath": "system.globalization.calendars.4.3.0.nupkg.sha512" - }, - "System.Globalization.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==", - "path": "system.globalization.extensions/4.3.0", - "hashPath": "system.globalization.extensions.4.3.0.nupkg.sha512" - }, - "System.IO/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "path": "system.io/4.3.0", - "hashPath": "system.io.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==", - "path": "system.io.filesystem/4.3.0", - "hashPath": "system.io.filesystem.4.3.0.nupkg.sha512" - }, - "System.IO.FileSystem.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==", - "path": "system.io.filesystem.primitives/4.3.0", - "hashPath": "system.io.filesystem.primitives.4.3.0.nupkg.sha512" - }, - "System.Linq/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==", - "path": "system.linq/4.3.0", - "hashPath": "system.linq.4.3.0.nupkg.sha512" - }, - "System.Linq.Expressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==", - "path": "system.linq.expressions/4.3.0", - "hashPath": "system.linq.expressions.4.3.0.nupkg.sha512" - }, - "System.Linq.Queryable/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-In1Bmmvl/j52yPu3xgakQSI0YIckPUr870w4K5+Lak3JCCa8hl+my65lABOuKfYs4ugmZy25ScFerC4nz8+b6g==", - "path": "system.linq.queryable/4.3.0", - "hashPath": "system.linq.queryable.4.3.0.nupkg.sha512" - }, - "System.Memory/4.5.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vcG3/MbfpxznMkkkaAblJi7RHOmuP7kawQMhDgLSuA1tRpRQYsFSCTxRSINDUgn2QNn2jWeLxv8er5BXbyACkw==", - "path": "system.memory/4.5.1", - "hashPath": "system.memory.4.5.1.nupkg.sha512" - }, - "System.Net.Http/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==", - "path": "system.net.http/4.3.0", - "hashPath": "system.net.http.4.3.0.nupkg.sha512" - }, - "System.Net.NetworkInformation/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-zNVmWVry0pAu7lcrRBhwwU96WUdbsrGL3azyzsbXmVNptae1+Za+UgOe9Z6s8iaWhPn7/l4wQqhC56HZWq7tkg==", - "path": "system.net.networkinformation/4.3.0", - "hashPath": "system.net.networkinformation.4.3.0.nupkg.sha512" - }, - "System.Net.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==", - "path": "system.net.primitives/4.3.0", - "hashPath": "system.net.primitives.4.3.0.nupkg.sha512" - }, - "System.Net.Requests/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OZNUuAs0kDXUzm7U5NZ1ojVta5YFZmgT2yxBqsQ7Eseq5Ahz88LInGRuNLJ/NP2F8W1q7tse1pKDthj3reF5QA==", - "path": "system.net.requests/4.3.0", - "hashPath": "system.net.requests.4.3.0.nupkg.sha512" - }, - "System.Net.Sockets/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==", - "path": "system.net.sockets/4.3.0", - "hashPath": "system.net.sockets.4.3.0.nupkg.sha512" - }, - "System.Net.WebHeaderCollection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-XZrXYG3c7QV/GpWeoaRC02rM6LH2JJetfVYskf35wdC/w2fFDFMphec4gmVH2dkll6abtW14u9Rt96pxd9YH2A==", - "path": "system.net.webheadercollection/4.3.0", - "hashPath": "system.net.webheadercollection.4.3.0.nupkg.sha512" - }, - "System.Numerics.Vectors/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==", - "path": "system.numerics.vectors/4.4.0", - "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512" - }, - "System.ObjectModel/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==", - "path": "system.objectmodel/4.3.0", - "hashPath": "system.objectmodel.4.3.0.nupkg.sha512" - }, - "System.Private.ServiceModel/4.5.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-TRkNLXhUasgpBESQnxbZozE2PxTmvKbkuHqjhqaPa+N0gsvfbWTRqHWg4KvcTFb9Xy1k3n8Y6VX3BRbYhU5Wsg==", - "path": "system.private.servicemodel/4.5.3", - "hashPath": "system.private.servicemodel.4.5.3.nupkg.sha512" - }, - "System.Reflection/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "path": "system.reflection/4.3.0", - "hashPath": "system.reflection.4.3.0.nupkg.sha512" - }, - "System.Reflection.DispatchProxy/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-2iafuenmPvt9Y+j8nscAVQyj7g4EU55ICNdMIrbF8WN+MbNehiJaunucpFjE1SAdLRclDbm0kqmYatWsiqeCvQ==", - "path": "system.reflection.dispatchproxy/4.5.0", - "hashPath": "system.reflection.dispatchproxy.4.5.0.nupkg.sha512" - }, - "System.Reflection.Emit/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==", - "path": "system.reflection.emit/4.3.0", - "hashPath": "system.reflection.emit.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.ILGeneration/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==", - "path": "system.reflection.emit.ilgeneration/4.3.0", - "hashPath": "system.reflection.emit.ilgeneration.4.3.0.nupkg.sha512" - }, - "System.Reflection.Emit.Lightweight/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==", - "path": "system.reflection.emit.lightweight/4.3.0", - "hashPath": "system.reflection.emit.lightweight.4.3.0.nupkg.sha512" - }, - "System.Reflection.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==", - "path": "system.reflection.extensions/4.3.0", - "hashPath": "system.reflection.extensions.4.3.0.nupkg.sha512" - }, - "System.Reflection.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "path": "system.reflection.primitives/4.3.0", - "hashPath": "system.reflection.primitives.4.3.0.nupkg.sha512" - }, - "System.Reflection.TypeExtensions/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-jdK7She37kK5gtXbznOVwcfbtCb2vIs8UfMsYd2OFiGLmj+3oXrA3bgUQPzbe3boNJApkA7oLb42nCHQ0g813A==", - "path": "system.reflection.typeextensions/4.5.0", - "hashPath": "system.reflection.typeextensions.4.5.0.nupkg.sha512" - }, - "System.Resources.ResourceManager/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==", - "path": "system.resources.resourcemanager/4.3.0", - "hashPath": "system.resources.resourcemanager.4.3.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.5.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-qUJMNWhbm9oZ3XaMFiEMiYmRPszbnXIkRIi7+4b2Md2xZ6JUOepf0/kY3S85qistRohl9OdMe4PsO+RdG2kTIQ==", - "path": "system.runtime.compilerservices.unsafe/4.5.1", - "hashPath": "system.runtime.compilerservices.unsafe.4.5.1.nupkg.sha512" - }, - "System.Runtime.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==", - "path": "system.runtime.extensions/4.3.0", - "hashPath": "system.runtime.extensions.4.3.0.nupkg.sha512" - }, - "System.Runtime.Handles/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==", - "path": "system.runtime.handles/4.3.0", - "hashPath": "system.runtime.handles.4.3.0.nupkg.sha512" - }, - "System.Runtime.InteropServices/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==", - "path": "system.runtime.interopservices/4.3.0", - "hashPath": "system.runtime.interopservices.4.3.0.nupkg.sha512" - }, - "System.Runtime.Numerics/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==", - "path": "system.runtime.numerics/4.3.0", - "hashPath": "system.runtime.numerics.4.3.0.nupkg.sha512" - }, - "System.Runtime.Serialization.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-Wz+0KOukJGAlXjtKr+5Xpuxf8+c8739RI1C+A2BoQZT+wMCCoMDDdO8/4IRHfaVINqL78GO8dW8G2lW/e45Mcw==", - "path": "system.runtime.serialization.primitives/4.3.0", - "hashPath": "system.runtime.serialization.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Algorithms/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", - "path": "system.security.cryptography.algorithms/4.3.0", - "hashPath": "system.security.cryptography.algorithms.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Cng/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==", - "path": "system.security.cryptography.cng/4.3.0", - "hashPath": "system.security.cryptography.cng.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Csp/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==", - "path": "system.security.cryptography.csp/4.3.0", - "hashPath": "system.security.cryptography.csp.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==", - "path": "system.security.cryptography.encoding/4.3.0", - "hashPath": "system.security.cryptography.encoding.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.OpenSsl/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==", - "path": "system.security.cryptography.openssl/4.3.0", - "hashPath": "system.security.cryptography.openssl.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.Primitives/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==", - "path": "system.security.cryptography.primitives/4.3.0", - "hashPath": "system.security.cryptography.primitives.4.3.0.nupkg.sha512" - }, - "System.Security.Cryptography.X509Certificates/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", - "path": "system.security.cryptography.x509certificates/4.3.0", - "hashPath": "system.security.cryptography.x509certificates.4.3.0.nupkg.sha512" - }, - "System.Security.Principal.Windows/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-WA9ETb/pY3BjnxKjBUHEgO59B7d/nnmjHFsqjJ2eDT780nD769CT1/bw2ia0Z6W7NqlcqokE6sKGKa6uw88XGA==", - "path": "system.security.principal.windows/4.5.0", - "hashPath": "system.security.principal.windows.4.5.0.nupkg.sha512" - }, - "System.ServiceModel.Primitives/4.5.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-cZp5aMKHubNtuGAQviYXFqskewYBdyWpqT3g8rPAE5awve/kRK6g2uTZnFr4BhrjkT2bu9sR4N6Mx+vsQUtjpg==", - "path": "system.servicemodel.primitives/4.5.3", - "hashPath": "system.servicemodel.primitives.4.5.3.nupkg.sha512" - }, - "System.Text.Encoding/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "path": "system.text.encoding/4.3.0", - "hashPath": "system.text.encoding.4.3.0.nupkg.sha512" - }, - "System.Text.Encoding.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==", - "path": "system.text.encoding.extensions/4.3.0", - "hashPath": "system.text.encoding.extensions.4.3.0.nupkg.sha512" - }, - "System.Text.Encodings.Web/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JF+wDdfFiRl3rz3dPMfR6aR568AW2J5CUMmhSflgHDz4zbVK4/00ax8UHnHyEMvblPewgNugjuA4oyoL8Pex2g==", - "path": "system.text.encodings.web/4.5.0", - "hashPath": "system.text.encodings.web.4.5.0.nupkg.sha512" - }, - "System.Text.RegularExpressions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==", - "path": "system.text.regularexpressions/4.3.0", - "hashPath": "system.text.regularexpressions.4.3.0.nupkg.sha512" - }, - "System.Threading/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==", - "path": "system.threading/4.3.0", - "hashPath": "system.threading.4.3.0.nupkg.sha512" - }, - "System.Threading.Overlapped/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LRcp7Ir4HQ3I7uIhUb8ypaMDw23W1t3GMCA2+yUxTL3HG9ZrVJtLufoK/mF9zfCG2bxuRBf1Bj8FC9mOPddhIg==", - "path": "system.threading.overlapped/4.3.0", - "hashPath": "system.threading.overlapped.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "path": "system.threading.tasks/4.3.0", - "hashPath": "system.threading.tasks.4.3.0.nupkg.sha512" - }, - "System.Threading.Tasks.Extensions/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==", - "path": "system.threading.tasks.extensions/4.3.0", - "hashPath": "system.threading.tasks.extensions.4.3.0.nupkg.sha512" - }, - "System.Threading.Thread/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-OHmbT+Zz065NKII/ZHcH9XO1dEuLGI1L2k7uYss+9C1jLxTC9kTZZuzUOyXHayRk+dft9CiDf3I/QZ0t8JKyBQ==", - "path": "system.threading.thread/4.3.0", - "hashPath": "system.threading.thread.4.3.0.nupkg.sha512" - }, - "System.Threading.ThreadPool/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-k/+g4b7vjdd4aix83sTgC9VG6oXYKAktSfNIJUNGxPEj7ryEOfzHHhfnmsZvjxawwcD9HyWXKCXmPjX8U4zeSw==", - "path": "system.threading.threadpool/4.3.0", - "hashPath": "system.threading.threadpool.4.3.0.nupkg.sha512" - }, - "System.ValueTuple/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-xZtSZNEHGa+tGsKuP4sh257vxJ/yemShz4EusmomkynMzuEDDjVaErBNewpzEF6swUgbcrSQAX3ELsEp1zCOwA==", - "path": "system.valuetuple/4.5.0", - "hashPath": "system.valuetuple.4.5.0.nupkg.sha512" - }, - "System.Xml.ReaderWriter/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==", - "path": "system.xml.readerwriter/4.3.0", - "hashPath": "system.xml.readerwriter.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlDocument/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lJ8AxvkX7GQxpC6GFCeBj8ThYVyQczx2+f/cWHJU8tjS7YfI6Cv6bon70jVEgs2CiFbmmM8b9j1oZVx0dSI2Ww==", - "path": "system.xml.xmldocument/4.3.0", - "hashPath": "system.xml.xmldocument.4.3.0.nupkg.sha512" - }, - "System.Xml.XmlSerializer/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-MYoTCP7EZ98RrANESW05J5ZwskKDoN0AuZ06ZflnowE50LTpbR5yRg3tHckTVm5j/m47stuGgCrCHWePyHS70Q==", - "path": "system.xml.xmlserializer/4.3.0", - "hashPath": "system.xml.xmlserializer.4.3.0.nupkg.sha512" - }, - "ServiceStack/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Client/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Common/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Interfaces/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.OrmLite/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Redis/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - }, - "ServiceStack.Text/5.0.0.0": { - "type": "reference", - "serviceable": false, - "sha512": "" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Server.dll b/lib/netstandard2.0/ServiceStack.Server.dll deleted file mode 100644 index cbda88c5..00000000 Binary files a/lib/netstandard2.0/ServiceStack.Server.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Server.xml b/lib/netstandard2.0/ServiceStack.Server.xml deleted file mode 100644 index 14ed780c..00000000 --- a/lib/netstandard2.0/ServiceStack.Server.xml +++ /dev/null @@ -1,234 +0,0 @@ - - - - ServiceStack.Server - - - - - Base rcon class. - - - - - Event delegate when disconnected from the server. - - - - - - Delegate for async callbacks. - - - - - - - Disconnected event. - - - - - Rcon connection socket. Always set to null when not connected. - - - - - Unique ID for each message. - - - - - Registered callbacks. - - - - - Create a new instance of rcon. - - Endpoint to connect to, usually the game server with query port. - - - - Attempts to connect to the game server for rcon operations. - - True if connection established, false otherwise. - - - - Processes a received packet. - - The packet. - - - - Disconnects from rcon. - - - - - Sends message to the server. - - Words to send. - - - - Game server endpoint. - - - - - Last exception that occured during operation. - - - - - Connected? - - - - - Gets the next unique ID to be used for transmisson. Read this before sending to pair responses to sent messages. - - - - - Exception thrown when attempting to send on a non-connected service client. - - - - - True if the packet originated on the server. - - - - - True if the packet is a response from a sent packet. - - - - - Sequence identifier. Unique to the connection. - - - - - Words. - - - - - Contains methods required for encoding and decoding rcon packets. - - - - - Decodes a packet. - - The packet. - A packet object. - - - - Decodes the packet header. - - - - - - - Decodes words in a packet. - - - - - - - Encodes a packet for transmission to the server. - - - - - - - - - - Encodes a packet header. - - - - - - - - - Encodes words. - - - - - - - Creates a Redis MQ Server that processes each message on its own background thread. - i.e. if you register 3 handlers it will create 7 background threads: - - 1 listening to the Redis MQ Subscription, getting notified of each new message - - 3x1 Normal InQ for each message handler - - 3x1 PriorityQ for each message handler (Turn off with DisablePriorityQueues) - - When RedisMqServer Starts it creates a background thread subscribed to the Redis MQ Topic that - listens for new incoming messages. It also starts 2 background threads for each message type: - - 1 for processing the services Priority Queue and 1 processing the services normal Inbox Queue. - - Priority Queue's can be enabled on a message-per-message basis by specifying types in the - OnlyEnablePriortyQueuesForTypes property. The DisableAllPriorityQueues property disables all Queues. - - The Start/Stop methods are idempotent i.e. It's safe to call them repeatedly on multiple threads - and the Redis MQ Server will only have Started or Stopped once. - - - - - Execute global transformation or custom logic before a request is processed. - Must be thread-safe. - - - - - Execute global transformation or custom logic on the response. - Must be thread-safe. - - - - - Execute global error handler logic. Must be thread-safe. - - - - - If you only want to enable priority queue handlers (and threads) for specific msg types - - - - - Don't listen on any Priority Queues - - - - - Opt-in to only publish responses on this white list. - Publishes all responses by default. - - - - - Transient message queues are a one-pass message queue service that starts - processing messages when Start() is called. Any subsequent Start() calls - while the service is running is ignored. - - The transient service will continue to run until all messages have been - processed after which time it will shutdown all processing until Start() is called again. - - - - diff --git a/lib/netstandard2.0/ServiceStack.Text.deps.json b/lib/netstandard2.0/ServiceStack.Text.deps.json deleted file mode 100644 index 5829a455..00000000 --- a/lib/netstandard2.0/ServiceStack.Text.deps.json +++ /dev/null @@ -1,151 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETStandard,Version=v2.0/", - "signature": "ba7b3a58fb24836fc26aec528cb95b976a733156" - }, - "compilationOptions": {}, - "targets": { - ".NETStandard,Version=v2.0": {}, - ".NETStandard,Version=v2.0/": { - "ServiceStack.Text/1.0.0": { - "dependencies": { - "Microsoft.CSharp": "4.5.0", - "NETStandard.Library": "2.0.3", - "System.Memory": "4.5.1", - "System.Runtime": "4.3.0" - }, - "runtime": { - "ServiceStack.Text.dll": {} - } - }, - "Microsoft.CSharp/4.5.0": { - "runtime": { - "lib/netstandard2.0/Microsoft.CSharp.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "4.6.26515.6" - } - } - }, - "Microsoft.NETCore.Platforms/1.1.0": {}, - "Microsoft.NETCore.Targets/1.1.0": {}, - "NETStandard.Library/2.0.3": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0" - } - }, - "System.Buffers/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Buffers.dll": { - "assemblyVersion": "4.0.2.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.Memory/4.5.1": { - "dependencies": { - "System.Buffers": "4.4.0", - "System.Numerics.Vectors": "4.4.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.0" - }, - "runtime": { - "lib/netstandard2.0/System.Memory.dll": { - "assemblyVersion": "4.0.1.0", - "fileVersion": "4.6.26606.5" - } - } - }, - "System.Numerics.Vectors/4.4.0": { - "runtime": { - "lib/netstandard2.0/System.Numerics.Vectors.dll": { - "assemblyVersion": "4.1.3.0", - "fileVersion": "4.6.25519.3" - } - } - }, - "System.Runtime/4.3.0": { - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "runtime": { - "lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": { - "assemblyVersion": "4.0.4.0", - "fileVersion": "0.0.0.0" - } - } - } - } - }, - "libraries": { - "ServiceStack.Text/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "Microsoft.CSharp/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-EGoBmf3Na2ppbhPePDE9PlX81r1HuOZH5twBrq7couJZiPTjUnD3648balerQJO6EJ8Sj+43+XuRwQ7r+3tE3w==", - "path": "microsoft.csharp/4.5.0", - "hashPath": "microsoft.csharp.4.5.0.nupkg.sha512" - }, - "Microsoft.NETCore.Platforms/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==", - "path": "microsoft.netcore.platforms/1.1.0", - "hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512" - }, - "Microsoft.NETCore.Targets/1.1.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==", - "path": "microsoft.netcore.targets/1.1.0", - "hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512" - }, - "NETStandard.Library/2.0.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==", - "path": "netstandard.library/2.0.3", - "hashPath": "netstandard.library.2.0.3.nupkg.sha512" - }, - "System.Buffers/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-AwarXzzoDwX6BgrhjoJsk6tUezZEozOT5Y9QKF94Gl4JK91I4PIIBkBco9068Y9/Dra8Dkbie99kXB8+1BaYKw==", - "path": "system.buffers/4.4.0", - "hashPath": "system.buffers.4.4.0.nupkg.sha512" - }, - "System.Memory/4.5.1": { - "type": "package", - "serviceable": true, - "sha512": "sha512-vcG3/MbfpxznMkkkaAblJi7RHOmuP7kawQMhDgLSuA1tRpRQYsFSCTxRSINDUgn2QNn2jWeLxv8er5BXbyACkw==", - "path": "system.memory/4.5.1", - "hashPath": "system.memory.4.5.1.nupkg.sha512" - }, - "System.Numerics.Vectors/4.4.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==", - "path": "system.numerics.vectors/4.4.0", - "hashPath": "system.numerics.vectors.4.4.0.nupkg.sha512" - }, - "System.Runtime/4.3.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "path": "system.runtime/4.3.0", - "hashPath": "system.runtime.4.3.0.nupkg.sha512" - }, - "System.Runtime.CompilerServices.Unsafe/4.5.0": { - "type": "package", - "serviceable": true, - "sha512": "sha512-6zBxkHYemB0kQiHP3vGXGRXejZVoNVuMn2paUuqKKi5Wyjkxfkp+D0rd0c3VrGwotidRINt6KpOi2smL4VkJKw==", - "path": "system.runtime.compilerservices.unsafe/4.5.0", - "hashPath": "system.runtime.compilerservices.unsafe.4.5.0.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/lib/netstandard2.0/ServiceStack.Text.dll b/lib/netstandard2.0/ServiceStack.Text.dll deleted file mode 100644 index efb5e5b8..00000000 Binary files a/lib/netstandard2.0/ServiceStack.Text.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.Text.xml b/lib/netstandard2.0/ServiceStack.Text.xml deleted file mode 100644 index 63ec7a8d..00000000 --- a/lib/netstandard2.0/ServiceStack.Text.xml +++ /dev/null @@ -1,1396 +0,0 @@ - - - - ServiceStack.Text - - - - - Utils to load types - - - - - Find the type from the name supplied - - [typeName] or [typeName, assemblyName] - - - - - The top-most interface of the given type, if any. - - - - - Find type if it exists - - - - The type if it exists - - - - If AlwaysUseUtc is set to true then convert all DateTime to UTC. If PreserveUtc is set to true then UTC dates will not convert to local - - - - - - - Repairs an out-of-spec XML date/time string which incorrectly uses a space instead of a 'T' to separate the date from the time. - These string are occasionally generated by SQLite and can cause errors in OrmLite when reading these columns from the DB. - - The XML date/time string to repair - The repaired string. If no repairs were made, the original string is returned. - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - WCF Json format: /Date(unixts+0000)/ - - - - - - - Get the type(string) constructor if exists - - The type. - - - - - micro optimizations: using flags instead of value.IndexOfAny(EscapeChars) - - - - - - - Class to hold - - - - - - A fast, standards-based, serialization-issue free DateTime serailizer. - - - - - Determines whether this serializer can create the specified type from a string. - - The type. - - true if this instance [can create from string] the specified type; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Deserializes from reader. - - The reader. - - - - - Serializes to string. - - The value. - - - - - Serializes to writer. - - The value. - The writer. - - - - Sets which format to use when serializing TimeSpans - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - if the is configured - to support web-friendly serialized formats, ie emitting lowercase_underscore_casing for JSON - - - - - Define how property names are mapped during deserialization - - - - - Gets or sets a value indicating if the framework should throw serialization exceptions - or continue regardless of serialization errors. If the framework - will throw; otherwise, it will parse as many fields as possible. The default is . - - - - - Gets or sets a value indicating if the framework should always convert to UTC format instead of local time. - - - - - Gets or sets a value indicating if the framework should skip automatic conversions. - Dates will be handled literally, any included timezone encoding will be lost and the date will be treaded as DateTimeKind.Local - Utc formatted input will result in DateTimeKind.Utc output. Any input without TZ data will be set DateTimeKind.Unspecified - This will take precedence over other flags like AlwaysUseUtc - JsConfig.DateHandler = DateHandler.ISO8601 should be used when set true for consistent de/serialization. - - - - - Gets or sets a value indicating if the framework should always assume is in UTC format if Kind is Unspecified. - - - - - Gets or sets whether we should append the Utc offset when we serialize Utc dates. Defaults to no. - Only supported for when the JsConfig.DateHandler == JsonDateHandler.TimestampOffset - - - - - Gets or sets a value indicating if unicode symbols should be serialized as "\uXXXX". - - - - - Gets or sets a value indicating if HTML entity chars [> < & = '] should be escaped as "\uXXXX". - - - - - Gets or sets a value indicating if the framework should call an error handler when - an exception happens during the deserialization. - - Parameters have following meaning in order: deserialized entity, property name, parsed value, property type, caught exception. - - - - If set to true, Interface types will be prefered over concrete types when serializing. - - - - - If set to true, Interface types will be prefered over concrete types when serializing. - - - - - Sets the maximum depth to avoid circular dependencies - - - - - Set this to enable your own type construction provider. - This is helpful for integration with IoC containers where you need to call the container constructor. - Return null if you don't know how to construct the type and the parameterless constructor will be used. - - - - - Always emit type info for this type. Takes precedence over ExcludeTypeInfo - - - - - Never emit type info for this type - - - - - if the is configured - to take advantage of specification, - to support user-friendly serialized formats, ie emitting camelCasing for JSON - and parsing member names and enum values in a case-insensitive manner. - - - - - Define custom serialization fn for BCL Structs - - - - - Opt-in flag to set some Value Types to be treated as a Ref Type - - - - - Whether there is a fn (raw or otherwise) - - - - - Define custom raw serialization fn - - - - - Define custom serialization hook - - - - - Define custom after serialization hook - - - - - Define custom deserialization fn for BCL Structs - - - - - Define custom raw deserialization fn for objects - - - - - Exclude specific properties of this type from being serialized - - - - - The property names on target types must match property names in the JSON source - - - - - The property names on target types may not match the property names in the JSON source - - - - - Uses the xsd format like PT15H10M20S - - - - - Uses the standard .net ToString method of the TimeSpan class - - - - - Get JSON string value converted to T - - - - - Get JSON string value - - - - - Get JSON string value - - - - - Get unescaped string value - - - - - Get unescaped string value - - - - - Write JSON Array, Object, bool or number values as raw string - - - - - Creates an instance of a Type from a string value - - - - - Parses the specified value. - - The value. - - - - - Shortcut escape when we're sure value doesn't contain any escaped chars - - - - - - - Given a character as utf32, returns the equivalent string provided that the character - is legal json. - - - - - - - Micro-optimization keep pre-built char arrays saving a .ToCharArray() + function call (see .net implementation of .Write(string)) - - - - - Searches the string for one or more non-printable characters. - - The string to search. - - True if there are any characters that require escaping. False if the value can be written verbatim. - - Micro optimizations: since quote and backslash are the only printable characters requiring escaping, removed previous optimization - (using flags instead of value.IndexOfAny(EscapeChars)) in favor of two equality operations saving both memory and CPU time. - Also slightly reduced code size by re-arranging conditions. - TODO: Possible Linq-only solution requires profiling: return value.Any(c => !c.IsPrintable() || c == QuoteChar || c == EscapeChar); - - - - - Implement the serializer using a more static approach - - - - - - Implement the serializer using a more static approach - - - - - - Pretty Thread-Safe cache class from: - http://code.google.com/p/dapper-dot-net/source/browse/Dapper/SqlMapper.cs - - This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), - and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** - equality. The type is fully thread-safe. - - - - - Courtesy of @marcgravell - https://github.com/mgravell/protobuf-net/blob/master/src/protobuf-net/BufferPool.cs - - - - - https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element - - - - - https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element - - - - - Generic implementation of object pooling pattern with predefined pool size limit. The main - purpose is that limited number of frequently used objects can be kept in the pool for - further recycling. - - Notes: - 1) it is not the goal to keep all returned objects. Pool is not meant for storage. If there - is no space in the pool, extra returned objects will be dropped. - - 2) it is implied that if object was obtained from a pool, the caller will return it back in - a relatively short time. Keeping checked out objects for long durations is ok, but - reduces usefulness of pooling. Just new up your own. - - Not returning objects to the pool in not detrimental to the pool's work, but is a bad practice. - Rationale: - If there is no intent for reusing the object, do not use pool - just use "new". - - - - - Not using System.Func{T} because this file is linked into the (debugger) Formatter, - which does not have that type (since it compiles against .NET 2.0). - - - - - Produces an instance. - - - Search strategy is a simple linear probing which is chosen for it cache-friendliness. - Note that Free will try to store recycled objects close to the start thus statistically - reducing how far we will typically search. - - - - - Returns objects to the pool. - - - Search strategy is a simple linear probing which is chosen for it cache-friendliness. - Note that Free will try to store recycled objects close to the start thus statistically - reducing how far we will typically search in Allocate. - - - - - Removes an object from leak tracking. - - This is called when an object is returned to the pool. It may also be explicitly - called if an object allocated from the pool is intentionally not being returned - to the pool. This can be of use with pooled arrays if the consumer wants to - return a larger array to the pool than was originally allocated. - - - - - this is RAII object to automatically release pooled object when its owning pool - - - - - Shared object pool for roslyn - - Use this shared pool if only concern is reducing object allocations. - if perf of an object pool itself is also a concern, use ObjectPool directly. - - For example, if you want to create a million of small objects within a second, - use the ObjectPool directly. it should have much less overhead than using this. - - - - - pool that uses default constructor with 100 elements pooled - - - - - pool that uses default constructor with 20 elements pooled - - - - - pool that uses string as key with StringComparer.OrdinalIgnoreCase as key comparer - - - - - pool that uses string as element with StringComparer.OrdinalIgnoreCase as element comparer - - - - - pool that uses string as element with StringComparer.Ordinal as element comparer - - - - - Used to reduce the # of temporary byte[]s created to satisfy serialization and - other I/O requests - - - - pooled memory : 4K * 512 = 4MB - - - - Manages pools of RecyclableMemoryStream objects. - - - There are two pools managed in here. The small pool contains same-sized buffers that are handed to streams - as they write more data. - - For scenarios that need to call GetBuffer(), the large pool contains buffers of various sizes, all - multiples of LargeBufferMultiple (1 MB by default). They are split by size to avoid overly-wasteful buffer - usage. There should be far fewer 8 MB buffers than 1 MB buffers, for example. - - - - - Generic delegate for handling events without any arguments. - - - - - Delegate for handling large buffer discard reports. - - Reason the buffer was discarded. - - - - Delegate for handling reports of stream size when streams are allocated - - Bytes allocated. - - - - Delegate for handling periodic reporting of memory use statistics. - - Bytes currently in use in the small pool. - Bytes currently free in the small pool. - Bytes currently in use in the large pool. - Bytes currently free in the large pool. - - - - pools[0] = 1x largeBufferMultiple buffers - pools[1] = 2x largeBufferMultiple buffers - etc., up to maximumBufferSize - - - - - Initializes the memory manager with the default block/buffer specifications. - - - - - Initializes the memory manager with the given block requiredSize. - - Size of each block that is pooled. Must be > 0. - Each large buffer will be a multiple of this value. - Buffers larger than this are not pooled - blockSize is not a positive number, or largeBufferMultiple is not a positive number, or maximumBufferSize is less than blockSize. - maximumBufferSize is not a multiple of largeBufferMultiple - - - - The size of each block. It must be set at creation and cannot be changed. - - - - - All buffers are multiples of this number. It must be set at creation and cannot be changed. - - - - - Gets or sets the maximum buffer size. - - Any buffer that is returned to the pool that is larger than this will be - discarded and garbage collected. - - - - Number of bytes in small pool not currently in use - - - - - Number of bytes currently in use by stream from the small pool - - - - - Number of bytes in large pool not currently in use - - - - - Number of bytes currently in use by streams from the large pool - - - - - How many blocks are in the small pool - - - - - How many buffers are in the large pool - - - - - How many bytes of small free blocks to allow before we start dropping - those returned to us. - - - - - How many bytes of large free buffers to allow before we start dropping - those returned to us. - - - - - Maximum stream capacity in bytes. Attempts to set a larger capacity will - result in an exception. - - A value of 0 indicates no limit. - - - - Whether to save callstacks for stream allocations. This can help in debugging. - It should NEVER be turned on generally in production. - - - - - Whether dirty buffers can be immediately returned to the buffer pool. E.g. when GetBuffer() is called on - a stream and creates a single large buffer, if this setting is enabled, the other blocks will be returned - to the buffer pool immediately. - Note when enabling this setting that the user is responsible for ensuring that any buffer previously - retrieved from a stream which is subsequently modified is not used after modification (as it may no longer - be valid). - - - - - Removes and returns a single block from the pool. - - A byte[] array - - - - Returns a buffer of arbitrary size from the large buffer pool. This buffer - will be at least the requiredSize and always be a multiple of largeBufferMultiple. - - The minimum length of the buffer - The tag of the stream returning this buffer, for logging if necessary. - A buffer of at least the required size. - - - - Returns the buffer to the large pool - - The buffer to return. - The tag of the stream returning this buffer, for logging if necessary. - buffer is null - buffer.Length is not a multiple of LargeBufferMultiple (it did not originate from this pool) - - - - Returns the blocks to the pool - - Collection of blocks to return to the pool - The tag of the stream returning these blocks, for logging if necessary. - blocks is null - blocks contains buffers that are the wrong size (or null) for this memory manager - - - - Retrieve a new MemoryStream object with no tag and a default initial capacity. - - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and a default initial capacity. - - A tag which can be used to track the source of the stream. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and at least the given capacity. - - A tag which can be used to track the source of the stream. - The minimum desired capacity for the stream. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and at least the given capacity, possibly using - a single continugous underlying buffer. - - Retrieving a MemoryStream which provides a single contiguous buffer can be useful in situations - where the initial size is known and it is desirable to avoid copying data between the smaller underlying - buffers to a single large one. This is most helpful when you know that you will always call GetBuffer - on the underlying stream. - A tag which can be used to track the source of the stream. - The minimum desired capacity for the stream. - Whether to attempt to use a single contiguous buffer. - A MemoryStream. - - - - Retrieve a new MemoryStream object with the given tag and with contents copied from the provided - buffer. The provided buffer is not wrapped or used after construction. - - The new stream's position is set to the beginning of the stream when returned. - A tag which can be used to track the source of the stream. - The byte buffer to copy data from. - The offset from the start of the buffer to copy from. - The number of bytes to copy from the buffer. - A MemoryStream. - - - - Triggered when a new block is created. - - - - - Triggered when a new block is created. - - - - - Triggered when a new large buffer is created. - - - - - Triggered when a new stream is created. - - - - - Triggered when a stream is disposed. - - - - - Triggered when a stream is finalized. - - - - - Triggered when a stream is finalized. - - - - - Triggered when a user converts a stream to array. - - - - - Triggered when a large buffer is discarded, along with the reason for the discard. - - - - - Periodically triggered to report usage statistics. - - - - - MemoryStream implementation that deals with pooling and managing memory streams which use potentially large - buffers. - - - This class works in tandem with the RecylableMemoryStreamManager to supply MemoryStream - objects to callers, while avoiding these specific problems: - 1. LOH allocations - since all large buffers are pooled, they will never incur a Gen2 GC - 2. Memory waste - A standard memory stream doubles its size when it runs out of room. This - leads to continual memory growth as each stream approaches the maximum allowed size. - 3. Memory copying - Each time a MemoryStream grows, all the bytes are copied into new buffers. - This implementation only copies the bytes when GetBuffer is called. - 4. Memory fragmentation - By using homogeneous buffer sizes, it ensures that blocks of memory - can be easily reused. - - The stream is implemented on top of a series of uniformly-sized blocks. As the stream's length grows, - additional blocks are retrieved from the memory manager. It is these blocks that are pooled, not the stream - object itself. - - The biggest wrinkle in this implementation is when GetBuffer() is called. This requires a single - contiguous buffer. If only a single block is in use, then that block is returned. If multiple blocks - are in use, we retrieve a larger buffer from the memory manager. These large buffers are also pooled, - split by size--they are multiples of a chunk size (1 MB by default). - - Once a large buffer is assigned to the stream the blocks are NEVER again used for this stream. All operations take place on the - large buffer. The large buffer can be replaced by a larger buffer from the pool as needed. All blocks and large buffers - are maintained in the stream until the stream is disposed (unless AggressiveBufferReturn is enabled in the stream manager). - - - - - - All of these blocks must be the same size - - - - - This is only set by GetBuffer() if the necessary buffer is larger than a single block size, or on - construction if the caller immediately requests a single large buffer. - - If this field is non-null, it contains the concatenation of the bytes found in the individual - blocks. Once it is created, this (or a larger) largeBuffer will be used for the life of the stream. - - - - - This list is used to store buffers once they're replaced by something larger. - This is for the cases where you have users of this class that may hold onto the buffers longer - than they should and you want to prevent race conditions which could corrupt the data. - - - - - Unique identifier for this stream across it's entire lifetime - - Object has been disposed - - - - A temporary identifier for the current usage of this stream. - - Object has been disposed - - - - Gets the memory manager being used by this stream. - - Object has been disposed - - - - Callstack of the constructor. It is only set if MemoryManager.GenerateCallStacks is true, - which should only be in debugging situations. - - - - - Callstack of the Dispose call. It is only set if MemoryManager.GenerateCallStacks is true, - which should only be in debugging situations. - - - - - This buffer exists so that WriteByte can forward all of its calls to Write - without creating a new byte[] buffer on every call. - - - - - Allocate a new RecyclableMemoryStream object. - - The memory manager - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - The initial requested size to prevent future allocations - - - - Allocate a new RecyclableMemoryStream object - - The memory manager - A string identifying this stream for logging and debugging purposes - The initial requested size to prevent future allocations - An initial buffer to use. This buffer will be owned by the stream and returned to the memory manager upon Dispose. - - - - Returns the memory used by this stream back to the pool. - - Whether we're disposing (true), or being called by the finalizer (false) - This method is not thread safe and it may not be called more than once. - - - - Equivalent to Dispose - - - - - Gets or sets the capacity - - Capacity is always in multiples of the memory manager's block size, unless - the large buffer is in use. Capacity never decreases during a stream's lifetime. - Explicitly setting the capacity to a lower value than the current value will have no effect. - This is because the buffers are all pooled by chunks and there's little reason to - allow stream truncation. - - Object has been disposed - - - - Gets the number of bytes written to this stream. - - Object has been disposed - - - - Gets the current position in the stream - - Object has been disposed - - - - Whether the stream can currently read - - - - - Whether the stream can currently seek - - - - - Always false - - - - - Whether the stream can currently write - - - - - Returns a single buffer containing the contents of the stream. - The buffer may be longer than the stream length. - - A byte[] buffer - IMPORTANT: Doing a Write() after calling GetBuffer() invalidates the buffer. The old buffer is held onto - until Dispose is called, but the next time GetBuffer() is called, a new buffer from the pool will be required. - Object has been disposed - - - - Returns a new array with a copy of the buffer's contents. You should almost certainly be using GetBuffer combined with the Length to - access the bytes in this stream. Calling ToArray will destroy the benefits of pooled buffers, but it is included - for the sake of completeness. - - Object has been disposed - - - - Reads from the current position into the provided buffer - - Destination buffer - Offset into buffer at which to start placing the read bytes. - Number of bytes to read. - The number of bytes read - buffer is null - offset or count is less than 0 - offset subtracted from the buffer length is less than count - Object has been disposed - - - - Writes the buffer to the stream - - Source buffer - Start position - Number of bytes to write - buffer is null - offset or count is negative - buffer.Length - offset is not less than count - Object has been disposed - - - - Returns a useful string for debugging. This should not normally be called in actual production code. - - - - - Writes a single byte to the current position in the stream. - - byte value to write - Object has been disposed - - - - Reads a single byte from the current position in the stream. - - The byte at the current position, or -1 if the position is at the end of the stream. - Object has been disposed - - - - Sets the length of the stream - - value is negative or larger than MaxStreamLength - Object has been disposed - - - - Sets the position to the offset from the seek location - - How many bytes to move - From where - The new position - Object has been disposed - offset is larger than MaxStreamLength - Invalid seek origin - Attempt to set negative position - - - - Synchronously writes this stream's bytes to the parameter stream. - - Destination stream - Important: This does a synchronous write, which may not be desired in some situations - - - - Release the large buffer (either stores it for eventual release or returns it immediately). - - - - - Allow Type to be deserialized into late-bould object Types using __type info - - - - - Allow Type to be deserialized into late-bould object Types using __type info - - - - - Reusable StringBuilder ThreadStatic Cache - - - - - Alternative Reusable StringBuilder ThreadStatic Cache - - - - - Helpful extensions on ReadOnlySpan<char> - Previous extensions on StringSegment available from: https://gist.github.com/mythz/9825689f0db7464d1d541cb62954614c - - - - - Returns null if Length == 0, string.Empty if value[0] == NonWidthWhitespace, otherise returns value.ToString() - - - - - Reusable StringWriter ThreadStatic Cache - - - - - Alternative Reusable StringWriter ThreadStatic Cache - - - - - A class to allow the conversion of doubles to string representations of - their exact decimal values. The implementation aims for readability over - efficiency. - - Courtesy of @JonSkeet - http://www.yoda.arachsys.com/csharp/DoubleConverter.cs - - - - - - - - How many digits are *after* the decimal point - - - - - Constructs an arbitrary decimal expansion from the given long. - The long must not be negative. - - - - - Multiplies the current expansion by the given amount, which should - only be 2 or 5. - - - - - Shifts the decimal point; a negative value makes - the decimal expansion bigger (as fewer digits come after the - decimal place) and a positive value makes the decimal - expansion smaller. - - - - - Removes leading/trailing zeroes from the expansion. - - - - - Converts the value to a proper decimal string representation. - - - - - Creates an instance of a Type from a string value - - - - - Determines whether the specified type is convertible from string. - - The type. - - true if the specified type is convertible from string; otherwise, false. - - - - - Parses the specified value. - - The value. - - - - - Parses the specified type. - - The type. - The value. - - - - - Useful extension method to get the Dictionary[string,string] representation of any POCO type. - - - - - - Recursively prints the contents of any POCO object in a human-friendly, readable format - - - - - - Print Dump to Console.WriteLine - - - - - Print string.Format to Console.WriteLine - - - - - Parses the specified value. - - The value. - - - - - Populate an object with Example data. - - - - - - - Populates the object with example data. - - - Tracks how deeply nested we are - - - - - Public Code API to register commercial license for ServiceStack. - - - - - Internal Utilities to verify licensing - - - - - Maps the path of a file in the context of a VS project in a Console App - - the relative path - the absolute path - Assumes static content is two directories above the /bin/ directory, - eg. in a unit test scenario the assembly would be in /bin/Debug/. - - - - Maps the path of a file in the context of a VS 2017+ multi-platform project in a Console App - - the relative path - the absolute path - Assumes static content is two directories above the /bin/ directory, - eg. in a unit test scenario the assembly would be in /bin/Debug/net45 - - - - Maps the path of a file in the bin\ folder of a self-hosted scenario - - the relative path - the absolute path - Assumes static content is copied to /bin/ folder with the assemblies - - - - Maps the path of a file in an ASP.NET hosted scenario - - the relative path - the absolute path - Assumes static content is in the parent folder of the /bin/ directory - - - - Add a Property attribute at runtime. - Not threadsafe, should only add attributes on Startup. - - - - - Add a Property attribute at runtime. - Not threadsafe, should only add attributes on Startup. - - - - - Implement the serializer using a more static approach - - - - - - Creates a new instance of type. - First looks at JsConfig.ModelFactory before falling back to CreateInstance - - - - - Creates a new instance of type. - First looks at JsConfig.ModelFactory before falling back to CreateInstance - - - - - Creates a new instance from the default constructor of type - - - - - @jonskeet: Collection of utility methods which operate on streams. - r285, February 26th 2009: http://www.yoda.arachsys.com/csharp/miscutil/ - - - - - Reads the given stream up to the end, returning the data as a byte - array. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer size. - - - - - Reads the given stream up to the end, returning the data as a byte - array, using the given buffer for transferring data. Note that the - current contents of the buffer is ignored, so the buffer needn't - be cleared beforehand. - - - - - Copies all the data from one stream into another. - - - - - Copies all the data from one stream into another, using a buffer - of the given size. - - - - - Copies all the data from one stream into another, using the given - buffer for transferring data. Note that the current contents of - the buffer is ignored, so the buffer needn't be cleared beforehand. - - - - - Reads exactly the given number of bytes from the specified stream. - If the end of the stream is reached before the specified amount - of data is read, an exception is thrown. - - - - - Reads into a buffer, filling it completely. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Reads exactly the given number of bytes from the specified stream, - into the given buffer, starting at position 0 of the array. - - - - - Same as ReadExactly, but without the argument checks. - - - - - Returns bytes in publiclyVisible MemoryStream - - - - - Converts from base: 0 - 62 - - The source. - From. - To. - - - - - Skip the encoding process for 'safe strings' - - - - - - diff --git a/lib/netstandard2.0/ServiceStack.dll b/lib/netstandard2.0/ServiceStack.dll deleted file mode 100644 index 6873d3d5..00000000 Binary files a/lib/netstandard2.0/ServiceStack.dll and /dev/null differ diff --git a/lib/netstandard2.0/ServiceStack.xml b/lib/netstandard2.0/ServiceStack.xml deleted file mode 100644 index b0aaeffe..00000000 --- a/lib/netstandard2.0/ServiceStack.xml +++ /dev/null @@ -1,6984 +0,0 @@ - - - - ServiceStack - - - - - The FilePath used in Virtual File Sources - - - - - Creates an IRequest from IHttpContextAccessor if it's been registered as a singleton - - - - - Get an IAppHost container. - Note: Registering dependencies should only be done during setup/configuration - stage and remain immutable there after for thread-safety. - - - - - - - Indicates that the request dto, which is associated with this attribute, - requires authentication. - - - - - Restrict authentication to a specific . - For example, if this attribute should only permit access - if the user is authenticated with , - you should set this property to . - - - - - Redirect the client to a specific URL if authentication failed. - If this property is null, simply `401 Unauthorized` is returned. - - - - - Enable the authentication feature and configure the AuthService. - - - - - Whether to Create Digest Auth MD5 Hash when Creating/Updating Users. - Defaults to only creating Digest Auth when DigestAuthProvider is registered. - - - - - Should UserName or Emails be saved in AuthRepository in LowerCase - - - - - Use a plugin to register authProvider dynamically. Your plugin can implement `IPreInitPlugin` interface - to call `appHost.GetPlugin<AuthFeature>().RegisterAuthProvider()` before the AuthFeature is registered. - - - - - The Interface Auth Repositories need to implement to support API Keys - - - - - The POCO Table used to persist API Keys - - - - - Enable access to protected Services using API Keys - - - - - Modify the registration of GetApiKeys and RegenerateApiKeys Services - - - - - How much entropy should the generated keys have. (default 24) - - - - - Generate different keys for different environments. (default live,test) - - - - - Different types of Keys each user can have. (default secret) - - - - - Whether to automatically expire keys. (default no expiry) - - - - - Automatically create the ApiKey Table for AuthRepositories which need it. (default true) - - - - - Whether to only allow access via API Key from a secure connection. (default true) - - - - - Change how API Key is generated - - - - - Run custom filter after API Key is created - - - - - Cache the User Session so it can be reused between subsequent API Key Requests - - - - - Whether to allow API Keys in 'apikey' QueryString or FormData - - - - - Inject logic into existing services by introspecting the request and injecting your own - validation logic. Exceptions thrown will have the same behaviour as if the service threw it. - - If a non-null object is returned the request will short-circuit and return that response. - - The instance of the service - GET,POST,PUT,DELETE - - Response DTO; non-null will short-circuit execution and return that response - - - - Get AuthProviders Registered in AuthFeature Plugin. - - specific provider, or null for all providers - - - - - - Get specific AuthProvider - - - - - - - Public API entry point to authenticate via code - - - null; if already autenticated otherwise a populated instance of AuthResponse - - - - The specified may change as a side-effect of this method. If - subsequent code relies on current data be sure to reload - the session istance via . - - - - - Conveneint base class with empty virtual methods so subclasses only need to override the hooks they need. - - - - - Allows specifying a global fallback config that if exists is formatted with the Provider as the first arg. - E.g. this appSetting with the TwitterAuthProvider: - oauth.CallbackUrl="http://localhost:11001/auth/{0}" - Would result in: - oauth.CallbackUrl="http://localhost:11001/auth/twitter" - - - - - - Remove the Users Session - - - - - - - - Create a Facebook App at: https://developers.facebook.com/apps - The Callback URL for your app should match the CallbackUrl provided. - - - - - Create an App at: https://github.com/settings/applications/new - The Callback URL for your app should match the CallbackUrl provided. - - - - - Download Yammer User Info given its ID. - - - The Yammer User ID. - - - The User info in JSON format. - - - - Yammer provides a method to retrieve current user information via - "https://www.yammer.com/api/v1/users/current.json". - - - However, to ensure consistency with the rest of the Auth codebase, - the explicit URL will be used, where [:id] denotes the User ID: - "https://www.yammer.com/api/v1/users/[:id].json" - - - Refer to: https://developer.yammer.com/restapi/ for full documentation. - - - - - - Remove the Users Session - - - - - - - - The entry point for all AuthProvider providers. Runs inside the AuthService so exceptions are treated normally. - Overridable so you can provide your own Auth implementation. - - - - - Determine if the current session is already authenticated with this AuthProvider - - - - - Called when the user is registered or on the first OAuth login - - - - - Called after the user has successfully authenticated - - - - - Fired before the session is removed after the /auth/logout Service is called - - - - - Fired when a new Session is created - - - - - - Fired before Session is resolved - - - - - Override with Custom Validation logic to Assert if User is allowed to Authenticate. - Returning a non-null response invalidates Authentication with IHttpResult response returned to client. - - - - - Thread-safe In memory UserAuth data store so it can be used without a dependency on Redis. - - - - - Used to Issue and process JWT Tokens and registers ConvertSessionToToken Service to convert Sessions to JWT Tokens - - - - - Whether to populate the Bearer Token in the AuthenticateResponse - - - - - Enable access to protected Services using JWT Tokens - - - - - Different HMAC Algorithms supported - - - - - Different RSA Signing Algorithms supported - - - - - Whether to only allow access via API Key from a secure connection. (default true) - - - - - Run custom filter after JWT Header is created - - - - - Run custom filter after JWT Payload is created - - - - - Run custom filter after session is restored from a JWT Token - - - - - Whether to encrypt JWE Payload (default false). - Uses RSA-OAEP for Key Encryption and AES/128/CBC HMAC SHA256 for Conent Encryption - - - - - Which Hash Algorithm should be used to sign the JWT Token. (default HS256) - - - - - Whether to only allow processing of JWT Tokens using the configured HashAlgorithm. (default true) - - - - - The Issuer to embed in the token. (default ssjwt) - - - - - The Audience to embed in the token. (default null) - - - - - Embed Multiple Audiences in the token. (default none) - A JWT is valid if it contains ANY audience in this List - - - - - What Id to use to identify the Key used to sign the token. (default First 3 chars of Base64 Key) - - - - - The AuthKey used to sign the JWT Token - - - - - Allow verification using multiple Auth keys - - - - - The RSA Private Key used to Sign the JWT Token when RSA is used - - - - - Convenient overload to intialize the Private Key via exported XML - - - - - The RSA Public Key used to Verify the JWT Token when RSA is used - - - - - Convenient overload to intialize the Public Key via exported XML - - - - - Allow verification using multiple public keys - - - - - How long should JWT Tokens be valid for. (default 14 days) - - - - - How long should JWT Refresh Tokens be valid for. (default 365 days) - - - - - Convenient overload to initialize ExpireTokensIn with an Integer - - - - - Allow custom logic to invalidate JWT Tokens - - - - - Allow custom logic to invalidate Refresh Tokens - - - - - Whether to invalidate all JWT Tokens issued before a specified date. - - - - - Modify the registration of ConvertSessionToToken Service - - - - - Allow JWT in ?ss-tok=jwt QueryString. (default false) - - - - - Allow JWT in ss-tok=jwt HTML POST FormData. (default false) - - - - - Whether to automatically remove expired or invalid cookies - - - - - Whether to also Include Token in ConvertSessionToTokenResponse - - - - - Return token payload which is both verified and still valid - - - - - Return token payload which has been verified to be created using the configured encryption key. - - - - - The entry point for all AuthProvider providers. Runs inside the AuthService so exceptions are treated normally. - Overridable so you can provide your own Auth implementation. - - - - - - - - - Sets the CallbackUrl and session.ReferrerUrl if not set and initializes the session tokens for this AuthProvider - - - - - - - - - Create Odnoklassniki App at: http://www.odnoklassniki.ru/devaccess - The Callback URL for your app should match the CallbackUrl provided. - - NB: They claim they use OAuth 2.0, but they in fact don't. - http://apiok.ru/wiki/display/api/Authorization+OAuth+2.0 - - - - - Allow utilizing an alternative PBKDF2 implementation. - - - - - The PBKDF2 strategy PasswordHasher implementation that's used for hashing PBKDF2 passwords. - - - - - The Password Hasher provider used to hash users passwords which uses the same algorithm used by ASP.NET Identity v3: - PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations. - - - - - Gets the number of iterations used when hashing passwords using PBKDF2. Default is 10,000. - - - - - The number of iterations used when hashing passwords using PBKDF2. Default is 10,000. - - - - - The Format Version specifier for this PasswordHasher embedded as the first byte in password hashes. - - - - - Returns a hashed representation of the supplied for the specified . - - The user whose password is to be hashed. - The password to hash. - A hashed representation of the supplied for the specified . - - - - Update an existing registraiton - - - - - Create new Registration - - - - - Logic to update UserAuth from Registration info, not enabled on PUT because of security. - - - - - Thank you Martijn - http://www.dijksterhuis.org/creating-salted-hash-values-in-c/ - - Stronger/Slower Alternative: - https://github.com/defuse/password-hashing/blob/master/PasswordStorage.cs - - - - - Create an app at https://dev.twitter.com/apps to get your ConsumerKey and ConsumerSecret for your app. - The Callback URL for your app should match the CallbackUrl provided. - - - - - Creates the required missing tables or DB schema - - - - - Create VK App at: http://vk.com/editapp?act=create - The Callback URL for your app should match the CallbackUrl provided. - - - - - If previous attemts failes, the subsequential calls - build up code value like "code1,code2,code3" - so we need the last one only - - - - - - - The ServiceStack Yammer OAuth provider. - - - - This provider is loosely based on the existing ServiceStack's Facebook OAuth provider. - - - For the full info on Yammer's OAuth2 authentication flow, refer to: - https://developer.yammer.com/authentication/#a-oauth2 - - - Note: Add these to your application / web config settings under appSettings and replace - values as appropriate. - - - - - - - - - ]]> - - - - - - The OAuth provider name / identifier. - - - - - Initializes a new instance of the class. - - - The application settings (in web.config). - - - - - Gets or sets the Yammer OAuth client id. - - - - - Gets or sets the Yammer OAuth client secret. - - - - - Gets or sets the Yammer OAuth pre-auth url. - - - - - Authenticate against Yammer OAuth endpoint. - - - The auth service. - - - The session. - - - The request. - - - The . - - - - - Load the UserAuth info into the session. - - - The User session. - - - The OAuth tokens. - - - The auth info. - - - - - Load the UserOAuth info into the session. - - - The auth session. - - - The OAuth tokens. - - - - - The Yammer User's email addresses. - - - - - Gets or sets the email address type (e.g. primary). - - - - - Gets or sets the email address. - - - - - Create Yandex App at: https://oauth.yandex.ru/client/new - The Callback URL for your app should match the CallbackUrl provided. - - - - - The BaseUrl of the ServiceStack instance (inferred) - - - - - Name of the ServiceStack Instance (inferred) - - - - - Textual description of the AutoQuery Services (shown in Home Services list) - - - - - Icon for this ServiceStack Instance (shown in Home Services list) - - - - - The different Content Type formats to display - - - - - The configured MaxLimit for AutoQuery - - - - - Whether to publish this Service to the public Services registry - - - - - Only show AutoQuery Services attributed with [AutoQueryViewer] - - - - - List of different Search Filters available - - - - - The Column which should be selected by default - - - - - The Query Type filter which should be selected by default - - - - - The search text which should be populated by default - - - - - Link to your website users can click to find out more about you - - - - - A custom logo or image that users can click on to visit your site - - - - - The default color of text - - - - - The default color of links - - - - - The default background color of each screen - - - - - The default background image of each screen anchored to the bottom left - - - - - The default icon for each of your AutoQuery Services - - - - - Returns true if the User Is Authenticated - - - - - How many queries are available to this user - - - - - Removes items from cache that have keys matching the specified wildcard pattern - - Cache client - The wildcard, where "*" means any sequence of characters and "?" means any single character. - - - - Removes items from the cache based on the specified regular expression pattern - - Cache client - Regular expression pattern to search cache keys - - - - The CacheKey to be use store the response against - - - - - The base Cache Key used to cache the Service response - - - - - Additional CacheKey Modifiers used to cache different outputs for a single Service Response - - - - - How long to cache the resource for. Fallsback to HttpCacheFeature.DefaultExpiresIn - - - - - The unique ETag returned for this resource clients can use to determine whether their local version has changed - - - - - The Age for this resource returned to clients - - - - - The MaxAge returned to clients to indicate how long they can use their local cache before re-validating - - - - - The LastModified date to use for the Cache and HTTP Header - - - - - Cache-Control HTTP Headers - - - - - Create unique cache per user - - - - - Use HostContext.LocalCache or HostContext.Cache - - - - - Skip compression for this Cache Result - - - - - Cache the Response of a Service - - - - - Cache expiry in seconds - - - - - MaxAge in seconds - - - - - Cache-Control HTTP Headers - - - - - Vary cache per user - - - - - Vary cache for users in these roles - - - - - Vary cache for different HTTP Headers - - - - - Use HostContext.LocalCache or HostContext.Cache - - - - - Skip compression for this Cache Result - - - - - Decorates the ICacheClient (and its sibblings) prefixing every key with the given prefix - - Usefull for multi-tenant environments - - - - - Decorates the ICacheClient (and its sibblings) prefixing every key with the given prefix - - Usefull for multi-tenant environments - - - - - Create new instance of CacheEntry. - - - - UTC time at which CacheEntry expires. - - - - Stores The value with key only if such key doesn't exist at the server yet. - - - - - Adds or replaces the value with key. - - - - - Adds or replaces the value with key. - - - - - Replace the value with specified key if it exists. - - - - - Add the value with key to the cache, set to never expire. - - - - - Add or replace the value with key to the cache, set to never expire. - - - - - Replace the value with key in the cache, set to never expire. - - - - - Add the value with key to the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Add that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Add or replace the value with key to the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Set that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Replace the value with key in the cache, set to expire at specified DateTime. - - This method examines the DateTimeKind of expiresAt to determine if conversion to - universal time is needed. The version of Replace that takes a TimeSpan expiration is faster - than using this method with a DateTime of Kind other than Utc, and is not affected by - ambiguous local time during daylight savings/standard time transition. - - - - Add the value with key to the cache, set to expire after specified TimeSpan. - - - - - Add or replace the value with key to the cache, set to expire after specified TimeSpan. - - - - - Replace the value with key in the cache, set to expire after specified TimeSpan. - - - - - More familiar name for the new crowd. - - - - - The tier lets you specify a retrieving a setting with the tier prefix first before falling back to the original key. - E.g a tier of 'Live' looks for 'Live.{Key}' or if not found falls back to '{Key}'. - - - - - Returns string if exists, otherwise null - - - - - - - Would've preferred to use [assembly: ContractNamespace] attribute but it is not supported in Mono - - - - - Gets the nullable app setting. - - - - - Gets the app setting. - - - - - Returns AppSetting[key] if exists otherwise defaultValue - - - - - Returns AppSetting[key] if exists otherwise defaultValue, for non-string values - - - - - Gets the connection string. - - - - - Gets the list from app setting. - - - - - Gets the dictionary from app setting. - - - - - Provides a common interface for Settings providers such as - ConfigurationManager or Azure's RoleEnvironment. The only - requirement is that if the implementation cannot find the - specified key, the return value must be null - - The key for the setting - The string value of the specified key, or null if the key - was invalid - - - - Return all keys in this configuration source. - - - - - - Represents a builder for the class. - - - - - Initializes a new instance of the class with a specified . - - The of the . - - - - Adds an that reads configuration values from the Web.config file. - - The . - - - - Adds an that reads configuration values from the Web.config file. - - The tier of the . - The . - - - - Adds a that reads configuration values from a dictionary. - - The dictionary of settings to add. - The . - - - - Adds an that reads configuration values from environmental variables. - - The . - - - - Adds an that reads configuration values from environmental variables. - - The tier of the . - The . - - - - Adds an that reads configuration values from a text file at . - - The path of the text file. - The . - - - - Adds an that reads configuration values from a text file at with a specified . - - The path of the text file. - The delimeter fo the text file. - The . - - - - Adds an that reads configuration values from a text file at with a specified . - - The path of the text file. - The delimeter fo the text file. - The tier of the . - The . - - - - Builds an . - - An . - - - - Wrappers for improving consistency with .NET Core Conventions - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - - - - - - - Registers a named instance of type in the IoC container and - adds auto-wiring to the specified type. - - - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - The reuse scope is set to none (transient). - - - - - - Registers the type in the IoC container and - adds auto-wiring to the specified type. - The reuse scope is set to none (transient). - - - - - - Registers the types in the IoC container and - adds auto-wiring to the specified types. - The reuse scope is set to none (transient). - - - - - - Register a singleton instance as a runtime type - - - - - Plugin adds support for Cross-origin resource sharing (CORS, see http://www.w3.org/TR/access-control/). - CORS allows to access resources from different domain which usually forbidden by origin policy. - - - - - Represents a default constructor with Allow Origin equals to "*", Allowed GET, POST, PUT, DELETE, OPTIONS request and allowed "Content-Type" header. - - - - - Lets you Register new Services and the optional restPaths will be registered against - this default Request Type - - - - - Change the default HTML view or template used for the HTML response of this service - - - - - Tracks created objects. - Used by to track created IDisposable instances. - These instances are tracked and disposed at the end of a request. - - - - - Adds disposable to the tracker - - - - - Dispose all disposables in the tracker. - If disposable is still alive alose .Release() is called to release the object. - - - - - Naming convention for the ResponseStatus property name on the response DTO - - - - - Create an instance of the service response dto type and inject it with the supplied responseStatus - - - - - - - - - - - - - - - - - Override to provide additional/less context about the Service Exception. - By default the request is serialized and appended to the ResponseStatus StackTrace. - - - - - Attribute marks that specific response class has support for Cross-origin resource sharing (CORS, see http://www.w3.org/TR/access-control/). CORS allows to access resources from different domain which usually forbidden by origin policy. - - - - - Represents a default constructor with Allow Origin equals to "*", Allowed GET, POST, PUT, DELETE, OPTIONS request and allowed "Content-Type" header. - - - - - Redirect to the https:// version of this url if not already. - - - - - Don't redirect when in DebugMode - - - - - Don't redirect if the request was a forwarded request, e.g. from a Load Balancer - - - - - Base class for entity validator classes. - - The type of the object being validated - - Base class for entity validator classes. - - The type of the object being validated - - - - Sets the cascade mode for all rules within this validator. - - - - - Validates the specified instance - - The object to validate - A ValidationResult object containing any validation failures - - - - Validates the specified instance asynchronously - - The object to validate - Cancellation token - A ValidationResult object containing any validation failures - - - - Validates the specified instance. - - Validation Context - A ValidationResult object containing any validation failures. - - - - Validates the specified instance asynchronously. - - Validation Context - Cancellation token - A ValidationResult object containing any validation failures. - - - - Adds a rule to the current validator. - - - - - - Creates a that can be used to obtain metadata about the current validator. - - - - - Defines a validation rule for a specify property. - - - RuleFor(x => x.Surname)... - - The type of property being validated - The expression representing the property to validate - an IRuleBuilder instance on which validators can be defined - - - - Invokes a rule for each item in the collection - - Type of property - Expression representing the collection to validate - An IRuleBuilder instance on which validators can be defined - - - - Defines a custom validation rule using a lambda expression. - If the validation rule fails, it should return a instance of a ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules. - - - - Defines a custom validation rule using a lambda expression. - If the validation rule fails, it should return an instance of ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules - - - - Defines a custom asynchronous validation rule using a lambda expression. - If the validation rule fails, it should asynchronously return a instance of a ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules. - - - - Defines a custom asynchronous validation rule using a lambda expression. - If the validation rule fails, it should asynchronously return an instance of ValidationFailure - If the validation rule succeeds, it should return null. - - A lambda that executes custom validation rules - - - - Defines a RuleSet that can be used to group together several validators. - - The name of the ruleset. - Action that encapsulates the rules in the ruleset. - - - - Defines a condition that applies to several rules - - The condition that should apply to multiple rules - Action that encapsulates the rules. - - - - - Defines an inverse condition that applies to several rules - - The condition that should be applied to multiple rules - Action that encapsulates the rules - - - - Defines an asynchronous condition that applies to several rules - - The asynchronous condition that should apply to multiple rules - Action that encapsulates the rules. - - - - - Defines an inverse asynchronous condition that applies to several rules - - The asynchronous condition that should be applied to multiple rules - Action that encapsulates the rules - - - - Includes the rules from the specified validator - - - - - Returns an enumerator that iterates through the collection of validation rules. - - - A that can be used to iterate through the collection. - - 1 - - - - Throws an exception if the instance being validated is null. - - - - - - Defines a RuleSet that can be used to provide specific validation rules for specific HTTP methods (GET, POST...) - - The HTTP methods where this rule set should be used. - Action that encapuslates the rules in the ruleset. - - - - Container class for dependent rule definitions - - - - - - Class that can be used to find all the validators from a collection of types. - - - - - Creates a scanner that works on a sequence of types. - - - - - Finds all the validators in the specified assembly. - - - - - Finds all the validators in the specified assemblies - - - - - Finds all the validators in the assembly containing the specified type. - - - - - Performs the specified action to all of the assembly scan results. - - - - - Returns an enumerator that iterates through the collection. - - - A that can be used to iterate through the collection. - - 1 - - - - Result of performing a scan. - - - - - Creates an instance of an AssemblyScanResult. - - - - - Validator interface type, eg IValidator<Foo> - - - - - Concrete type that implements the InterfaceType, eg FooValidator. - - - - - Implementation of and that looks for - the instance on the specified or - in order to provide the validator instance. - - - - - Creates an instance of . - - - - - Creates an instance of with the supplied instance factory delegate - used for creation of instances. - - The instance factory delegate. - - - - Gets a validator for the appropriate type. - - - - - Gets a validator for the appropriate type. - - Created instance; if a validator cannot be - created. - - - - Gets a validator for . - - The instance to get a validator for. - Created instance; if a validator cannot be - created. - - - - Validator attribute to define the class that will describe the Validation rules. - - - - - The type of the validator used to validate the current type or parameter. - - - - - Creates an instance of allowing a validator type to be specified. - - - - - Overrides the name of the property associated with this rule. - NOTE: This is a considered to be an advanced feature. 99% of the time that you use this, you actually meant to use WithName. - - The current rule - The property name to use - - - - - Specifies a localized name for the error message. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Resource accessor builder to use - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Custom message format args - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - The resource accessor builder to use. - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - The resource to use as an expression, eg () => Messages.MyResource - Custom message format args - - - - - Specifies a localized name for the error message. - - The current rule - The type of the generated resource file - The name of the resource to use - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional arguments to be specified when formatting the custom error message. - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional property values to be included when formatting the custom error message. - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - Additional property values to use when formatting the custom error message. - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Name of resource - Custom message format args - Type of resource representing a resx file - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Resource name - Custom message format args - Resource type representing a resx file - - - - - Replace the first validator of this type and remove all the others. - - - - - Remove all validators of the specified type. - - - - - Remove all validators for the given property. - - - - - Builds a delegate for retrieving a localised resource from a resource type and property name. - - - - - Gets a function that can be used to retrieve a message from a resource type and resource name. - - - - - Allows the default error message translations to be managed. - - - - - Whether localization is enabled. - - - - - Default culture to use for all requests to the LanguageManager. If not specified, uses the current UI culture. - - - - - Gets a translated string based on its key. If the culture is specific and it isn't registered, we try the neutral culture instead. - If no matching culture is found to be registered we use English. - - The key - The culture to translate into - - - - - Provides error message templates - - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Marker interface that indicates a PropertyValidatorContext should be passed to GetString - - - - - Base class for lanaguages - - - - - Name of language (culture code) - - - - - Adds a translation - - - - - - - Adds a translation for a type - - - - - - - Gets the localized version of a string with a specific key. - - - - - - - Allows the default error message translations to be managed. - - - - - Creates a new instance of the LanguageManager class. - - - - - Whether localization is enabled. - - - - - Default culture to use for all requests to the LanguageManager. If not specified, uses the current UI culture. - - - - - Provides a collection of all supported languages. - - - - - - Removes all languages except the default. - - - - - Gets a translated string based on its key. If the culture is specific and it isn't registered, we try the neutral culture instead. - If no matching culture is found to be registered we use English. - - The key - The culture to translate into - - - - - IStringSource implementation that uses the default language manager. - - - - - Lazily loads the string - - - - - Creates a LazyStringSource - - - - - Gets the value - - - - - - Resource type - - - - - Resource name - - - - - Creates a LazyStringSource - - - - - Gets the value - - - - - - Resource type - - - - - Resource name - - - - - Represents a localized string. - - - - - Creates a new instance of the LocalizedErrorMessageSource class using the specified resource name and resource type. - - The resource type - The resource name - - - - Creates a new instance of the LocalizedErrorMessageSource class using the specified resource name and resource type. - - The resource type - The resource name - Strategy used to construct the resource accessor - - - - Creates a LocalizedStringSource from an expression: () => MyResources.SomeResourceName - - The expression - Strategy used to construct the resource accessor - Error message source - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Gets the PropertyInfo for a resource. - ResourceType and ResourceName are ref parameters to allow derived types - to replace the type/name of the resource before the delegate is constructed. - - - - - Version of LocalizedStringSource that allows the specified ResourceType to be replaced by the default resource type specified in ValidatorOptions.ResourceProviderType. - This is typically only used by the default validator types built into FV, or if you're building a library of validators. Don't use it in rule definitions. - - - - - Gets the PropertyInfo for a resource. - ResourceType and ResourceName are ref parameters to allow derived types - to replace the type/name of the resource before the delegate is constructed. - - - - - Creates a OverridableLocalizedStringSource from an expression: () => MyResources.SomeResourceName - - The expression - Error message source - - - - Defines an accessor for localization resources - - - - - Function that can be used to retrieve the resource - - - - - Resource type - - - - - Resource name - - - - - Represents a static string. - - - - - Creates a new StringErrorMessageSource using the specified error message as the error template. - - The error message template. - - - - Construct the error message template - - Error message template - - - - The name of the resource if localized. - - - - - The type of the resource provider if localized. - - - - - Extension methods for collection validation rules - - - - - Associates an instance of IValidator with the current property rule and is used to validate each item within the collection. - - Rule builder - The validator to use - - - - Uses a provider to instantiate a validator instance to be associated with a collection - - - - - - - - - - - Collection rule builder syntax - - - - - - - Defines a condition to be used to determine if validation should run - - - - - - - Extension methods that provide the default set of validators. - - - - - Defines a 'not null' validator on the current rule builder. - Validation will fail if the property is null. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'null' validator on the current rule builder. - Validation will fail if the property is not null. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'not empty' validator on the current rule builder. - Validation will fail if the property is null, an empty or the default value for the type (for example, 0 for integers) - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a 'empty' validator on the current rule builder. - Validation will fail if the property is not null, an empty or the default value for the type (for example, 0 for integers) - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is outside of the specified range. The range is inclusive. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is outside of the specified range. The range is inclusive. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is not equal to the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is not equal to the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is larger than the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a length validator on the current rule builder, but only for string properties. - Validation will fail if the length of the string is less than the length specified. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to use - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to use - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - Regex options - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda does not match the regular expression. - - Type of object being validated - The rule builder on which the validator should be defined - The regular expression to check the value against. - Regex options - - - - - Defines a regular expression validator on the current rule builder, but only for string properties. - Validation will fail if the value returned by the lambda is not a valid email address. - - Type of object being validated - The rule builder on which the validator should be defined - - - - - Defines a 'not equal' validator on the current rule builder. - Validation will fail if the specified value is equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value to compare - Equality comparer to use - - - - - Defines a 'not equal' validator on the current rule builder using a lambda to specify the value. - Validation will fail if the value returned by the lambda is equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression to provide the comparison value - Equality Comparer to use - - - - - Defines an 'equals' validator on the current rule builder. - Validation will fail if the specified value is not equal to the value of the property. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value to compare - Equality Comparer to use - - - - - Defines an 'equals' validator on the current rule builder using a lambda to specify the comparison value. - Validation will fail if the value returned by the lambda is not equal to the value of the property. - - The type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression to provide the comparison value - Equality comparer to use - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines an asynchronous predicate validator on the current rule builder using a lambda expression to specify the predicate. - Validation will fail if the specified lambda returns false. - Validation will succeed if the specified lambda returns true. - This overload accepts the object being validated in addition to the property being validated. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda expression specifying the predicate - - - - - Defines a 'less than' validator on the current rule builder. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal' validator on the current rule builder. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal' validator on the current rule builder. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than the specified value. - The validation will fail if the property value is greater than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - A lambda that should return the value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than or equal' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is less than or equal to the specified value. - The validation will fail if the property value is greater than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'less than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than the specified value. - The validation will fail if the property value is less than or equal to the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal to' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Defines a 'greater than or equal to' validator on the current rule builder using a lambda expression. - The validation will succeed if the property value is greater than or equal the specified value. - The validation will fail if the property value is less than the specified value. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The value being compared - - - - - Validates certain properties of the specified instance. - - The current validator - The object to validate - Expressions to specify the properties to validate - A ValidationResult object containing any validation failures - - - - Validates certain properties of the specified instance. - - - The object to validate - The names of the properties to validate. - A ValidationResult object containing any validation failures. - - - - Validates an object using either a custom validator selector or a ruleset. - - - - - - - - - - - Validates certain properties of the specified instance asynchronously. - - The current validator - The object to validate - Expressions to specify the properties to validate - A ValidationResult object containing any validation failures - - - - Validates certain properties of the specified instance asynchronously. - - - The object to validate - The names of the properties to validate. - A ValidationResult object containing any validation failures. - - - - Validates an object asynchronously using a custom valdiator selector or a ruleset - - - - - - - - - - - Performs validation and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Performs validation asynchronously and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Defines an 'inclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is inclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'inclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is inclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'exclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is exclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines an 'exclusive between' validator on the current rule builder, but only for properties of types that implement IComparable. - Validation will fail if the value of the property is outside of the specifed range. The range is exclusive. - - Type of object being validated - Type of property being validated - The rule builder on which the validator should be defined - The lowest allowed value - The highest allowed value - - - - - Defines a credit card validator for the current rule builder that ensures that the specified string is a valid credit card number. - - - - - Defines a enum value validator on the current rule builder that ensures that the specific value is a valid enum value. - - Type of Enum being validated - Type of property being validated - The rule builder on which the validator should be defined - - - - - Defines a custom validation rule - - - - - - - - - - Defines a custom validation rule - - - - - - - - - - Performs validation and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - The ruleset to validate against. - - - - Performs validation asynchronously and then throws an exception if validation fails. - - The validator this method is extending. - The instance of the type we are validating. - Optional: a ruleset when need to validate against. - - - - Default options that can be used to configure a validator. - - - - - Specifies the cascade mode for failures. - If set to 'Stop' then execution of the rule will stop once the first validator in the chain fails. - If set to 'Continue' then all validators in the chain will execute regardless of failures. - - - - - Specifies a custom action to be invoked when the validator fails. - - - - - - - - - - Specifies a custom error message to use if validation fails. - - The current rule - The error message to use - - - - - Specifies a custom error message to use when validation fails. - - The current rule - Delegate that will be invoked to retrieve the localized message. - - - - - Specifies a custom error message to use when validation fails. - - The current rule - Delegate that will be invoked to retrieve the localized message. - - - - - Specifies a custom error code to use if validation fails. - - The current rule - The error code to use - - - - - Specifies a custom error message resource to use when validation fails. - - The current rule - Resource type representing a resx file - Name of resource - - - - - Specifies a condition limiting when the validator should run. - The validator will only be executed if the result of the lambda returns true. - - The current rule - A lambda expression that specifies a condition for when the validator should run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies a condition limiting when the validator should not run. - The validator will only be executed if the result of the lambda returns false. - - The current rule - A lambda expression that specifies a condition for when the validator should not run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies an asynchronous condition limiting when the validator should run. - The validator will only be executed if the result of the lambda returns true. - - The current rule - A lambda expression that specifies a condition for when the validator should run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Specifies an asynchronous condition limiting when the validator should not run. - The validator will only be executed if the result of the lambda returns false. - - The current rule - A lambda expression that specifies a condition for when the validator should not run - Whether the condition should be applied to the current rule or all rules in the chain - - - - - Triggers an action when the rule passes. Typically used to configure dependent rules. This applies to all preceding rules in the chain. - - The current rule - An action to be invoked if the rule is valid - - - - - Specifies a custom property name to use within the error message. - - The current rule - The property name to use - - - - - Specifies a custom property name to use within the error message. - - The current rule - Func used to retrieve the property's display name - - - - - Overrides the name of the property associated with this rule. - NOTE: This is a considered to be an advanced feature. 99% of the time that you use this, you actually meant to use WithName. - - The current rule - The property name to use - - - - - Specifies custom state that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Specifies custom state that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Specifies custom severity that should be stored alongside the validation message when validation fails for this rule. - - - - - - - - - - Gets the default message for a property validato - - The validator type - The translated string - - - - Specifies how rules should cascade when one fails. - - - - - When a rule fails, execution continues to the next rule. - - - - - When a rule fails, validation is stopped and all other rules in the chain will not be executed. - - - - - Specifies where a When/Unless condition should be applied - - - - - Applies the condition to all validators declared so far in the chain. - - - - - Applies the condition to the current validator only. - - - - - Specifies the severity of a rule. - - - - - Error - - - - - Warning - - - - - Info - - - - - Validator implementation that allows rules to be defined without inheriting from AbstractValidator. - - - - public class Customer { - public int Id { get; set; } - public string Name { get; set; } - - public static readonly InlineValidator<Customer> Validator = new InlineValidator<Customer> { - v => v.RuleFor(x => x.Name).NotNull(), - v => v.RuleFor(x => x.Id).NotEqual(0), - } - } - - - - - - - Delegate that specifies configuring an InlineValidator. - - - - - Allows configuration of the validator. - - - - - Rule definition for collection properties - - - - - - Initializes new instance of the CollectionPropertyRule class - - - - - - - - - - - Creates a new property rule from a lambda expression. - - - - - Invokes the validator asynchronously - - - - - - - - - - Invokes the validator - - - - - - - - - Custom logic for performing comparisons - - - - - Tries to compare the two objects. - - - The resulting comparison value. - - True if all went well, otherwise False. - - - - Tries to do a proper comparison but may fail. - First it tries the default comparison, if this fails, it will see - if the values are fractions. If they are, then it does a double - comparison, otherwise it does a long comparison. - - - - - Tries to compare the two objects, but will throw an exception if it fails. - - True on success, otherwise False. - - - - Tries to compare the two objects, but will throw an exception if it fails. - - True on success, otherwise False. - - - - Default validator selector that will execute all rules that do not belong to a RuleSet. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Custom IValidationRule for performing custom logic. - - - - - - Rule set to which this rule belongs. - - - - - Creates a new DelegateValidator using the specified function to perform validation. - - - - - Creates a new DelegateValidator using the specified function to perform validation. - - - - - Creates a new DelegateValidator using the specified async function to perform validation. - - - - - Creates a new DelegateValidator using the specified async function to perform validation. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs validation asynchronously using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - The validators that are grouped under this rule. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - When overloaded performs validation asynchronously using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - Applies a condition to the validator. - - - - - - - Applies a condition asynchronously to the validator - - - - - - - Useful extensions - - - - - Checks if the expression is a parameter expression - - - - - - - Gets a MemberInfo from a member expression. - - - - - Gets a MemberInfo from a member expression. - - - - - Splits pascal case, so "FooBar" would become "Foo Bar" - - - - - Helper method to construct a constant expression from a constant. - - Type of object being validated - Type of property being validated - The value being compared - - - - - Represents an object that is configurable. - - Type of object being configured - Return type - - - - Configures the current object. - - Action to configure the object. - - - - - Instancace cache. - - - - - Gets or creates an instance using Activator.CreateInstance - - The type to instantiate - The instantiated object - - - - Gets or creates an instance using a custom factory - - The type to instantiate - The custom factory - The instantiated object - - - - Member accessor cache. - - - - - - Gets an accessor func based on an expression - - - The member represented by the expression - - Accessor func - - - - Determines whether or not a rule should execute. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Selects validators that are associated with a particular property. - - - - - Creates a new instance of MemberNameValidatorSelector. - - - - - Member names that are validated. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Creates a MemberNameValidatorSelector from a collection of expressions. - - - - - Gets member names from expressions - - - - - - - - Assists in the construction of validation messages. - - - - - Default Property Name placeholder. - - - - - Default Property Value placeholder. - - - - - Adds a value for a validation message placeholder. - - - - - - - - Appends a property name to the message. - - The name of the property - - - - - Appends a property value to the message. - - The value of the property - - - - - Adds additional arguments to the message for use with standard string placeholders. - - Additional arguments - - - - - Constructs the final message from the specified template. - - Message template - The message with placeholders replaced with their appropriate values - - - - Additional arguments to use - - - - - Additional placeholder values - - - - - Represents a chain of properties - - - - - Creates a new PropertyChain. - - - - - Creates a new PropertyChain based on another. - - - - - Creates a new PropertyChain - - - - - - Creates a PropertyChain from a lambda expresion - - - - - - - Adds a MemberInfo instance to the chain - - Member to add - - - - Adds a property name to the chain - - Name of the property to add - - - - Adds an indexer to the property chain. For example, if the following chain has been constructed: - Parent.Child - then calling AddIndexer(0) would convert this to: - Parent.Child[0] - - - - - - Creates a string representation of a property chain. - - - - - Checks if the current chain is the child of another chain. - For example, if chain1 were for "Parent.Child" and chain2 were for "Parent.Child.GrandChild" then - chain2.IsChildChainOf(chain1) would be true. - - The parent chain to compare - True if the current chain is the child of the other chain, otherwise false - - - - Builds a property path. - - - - - Number of member names in the chain - - - - - Defines a rule associated with a property. - - - - - Property associated with this rule. - - - - - Function that can be invoked to retrieve the value of the property. - - - - - Expression that was used to create the rule. - - - - - String source that can be used to retrieve the display name (if null, falls back to the property name) - - - - - Rule set that this rule belongs to (if specified) - - - - - Function that will be invoked if any of the validators associated with this rule fail. - - - - - The current validator being configured by this rule. - - - - - Type of the property being validated - - - - - Cascade mode for this rule. - - - - - Validators associated with this rule. - - - - - Creates a new property rule. - - Property - Function to get the property value - Lambda expression used to create the rule - Function to get the cascade mode. - Type to validate - Container type that owns the property - - - - Creates a new property rule from a lambda expression. - - - - - Creates a new property rule from a lambda expression. - - - - - Adds a validator to the rule. - - - - - Replaces a validator in this rule. Used to wrap validators. - - - - - Remove a validator in this rule. - - - - - Clear all validators from this rule. - - - - - Returns the property name for the property being validated. - Returns null if it is not a property being validated (eg a method call) - - - - - Allows custom creation of an error message - - - - - Dependent rules - - - - - Display name for the property. - - - - - Display name for the property. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs asynchronous validation using a validation context and returns a collection of Validation Failures. - - Validation Context - - A collection of validation failures - - - - Invokes the validator asynchronously - - - - - - - - - - Invokes a property validator using the specified validation context. - - - - - Applies a condition to the rule - - - - - - - Applies the condition to the rule asynchronously - - - - - - - Include rule - - - - - Creates a new IncludeRule - - - - - - - - - Creates a new include rule from an existing validator - - - - - - - - - Builds a validation rule and constructs a validator. - - Type of object being validated - Type of property being validated - - - - The rule being created by this RuleBuilder. - - - - - Creates a new instance of the RuleBuilder class. - - - - - Sets the validator associated with the rule. - - The validator to set - - - - - Sets the validator associated with the rule. Use with complex properties where an IValidator instance is already declared for the property type. - - The validator to set - - - - Sets the validator associated with the rule. Use with complex properties where an IValidator instance is already declared for the property type. - - The validator provider to set - - - - Selects validators that belong to the specified rulesets. - - - - - Rule sets - - - - - Creates a new instance of the RulesetValidatorSelector. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Checks if the rule is an IncludeRule - - - - - - - Gets validators for method parameters. - - - - - Gets a validator for . - - The instance to get a validator for. - Created instance; if a validator cannot be - created. - - - - Defines a rule associated with a property which can have multiple validators. - - - - - The validators that are grouped under this rule. - - - - - Name of the rule-set to which this rule belongs. - - - - - Performs validation using a validation context and returns a collection of Validation Failures. - - Validation Context - A collection of validation failures - - - - Performs validation using a validation context and returns a collection of Validation Failures asynchronoulsy. - - Validation Context - Cancellation token - A collection of validation failures - - - - Applies a condition to the rule - - - - - - - Applies a condition to the rule asynchronously - - - - - - - Defines a validator for a particular type. - - - - - - Validates the specified instance. - - The instance to validate - A ValidationResult object containing any validation failures. - - - - Validate the specified instance asynchronously - - The instance to validate - - A ValidationResult object containing any validation failures. - - - - Sets the cascade mode for all rules within this validator. - - - - - Defines a validator for a particular type. - - - - - Validates the specified instance - - - A ValidationResult containing any validation failures - - - - Validates the specified instance asynchronously - - - Cancellation token - A ValidationResult containing any validation failures - - - - Validates the specified instance. - - A ValidationContext - A ValidationResult object containy any validation failures. - - - - Validates the specified instance asynchronously. - - A ValidationContext - Cancellation token - A ValidationResult object containy any validation failures. - - - - Creates a hook to access various meta data properties - - A IValidatorDescriptor object which contains methods to access metadata - - - - Checks to see whether the validator can validate objects of the specified type - - - - - Provides metadata about a validator. - - - - - Gets the name display name for a property. - - - - - Gets a collection of validators grouped by property. - - - - - Gets validators for a particular property. - - - - - Gets rules for a property. - - - - - Gets validators for a particular type. - - - - - Gets the validator for the specified type. - - - - - Gets the validator for the specified type. - - - - - Defines a validation failure - - - - - Creates a new validation failure. - - - - - Creates a new ValidationFailure. - - - - - The name of the property. - - - - - The error message - - - - - The property value that caused the failure. - - - - - Custom state associated with the failure. - - - - - Custom severity level associated with the failure. - - - - - Gets or sets the formatted message arguments. - These are values for custom formatted message in validator resource files - Same formatted message can be reused in UI and with same number of format placeholders - Like "Value {0} that you entered should be {1}" - - - - - Gets or sets the formatted message placeholder values. - - - - - The resource name used for building the message - - - - - Creates a textual representation of the failure. - - - - - Creates a new ValidationFailure with ErrorCode. - - - - - Gets or sets the error code. - - - - - The result of running a validator - - - - - Whether validation succeeded - - - - - A collection of errors - - - - - Creates a new validationResult - - - - - Creates a new ValidationResult from a collection of failures - - List of which is later available through . This list get's copied. - - Every caller is responsible for not adding null to the list. - - - - - Rule builder that starts the chain - - - - - - - Rule builder - - - - - - - Associates a validator with this the property for this rule builder. - - The validator to set - - - - - Associates an instance of IValidator with the current property rule. - - The validator to use - - - - Associates a validator provider with the current property rule. - - The validator provider to use - - - - Rule builder - - - - - - - Validation context - - - - Validation context - - - - - - Creates a new validation context - - - - - - Creates a new validation context with a custom property chain and selector - - - - - - - - The object to validate - - - - - Validation context - - - Validation context - - - - - Creates a new validation context - - - - - - Creates a new validation context with a property chain and validation selector - - - - - - - - Property chain - - - - - Object being validated - - - - - Selector - - - - - Whether this is a child context - - - - - Whether this is a child collection context. - - - - - Creates a new ValidationContext based on this one - - - - - - - - - Creates a new validation context for use with a child validator - - - - - - - Creates a new validation context for use with a child collection validator - - - - - - - An exception that represents failed validation - - - An exception that represents failed validation - - - - - Validation errors - - - - - Creates a new ValidationException - - - - - - Creates a new ValidationException - - - - - - - Creates a new ValidationException - - - - - - Used for providing metadata about a validator. - - - - - Rules associated with the validator - - - - - Creates a ValidatorDescriptor - - - - - - Gets the display name or a property property - - - - - - - Gets all members with their associated validators - - - - - - Gets validators for a specific member - - - - - - - Gets rules for a specific member - - - - - - - Gets the member name from an expression - - - - - - - Gets validators for a member - - - - - - - - Gets rules grouped by ruleset - - - - - - Information about reulesets - - - - - Creates a new RulesetMetadata - - - - - - - Rulset name - - - - - Rules in the ruleset - - - - - Factory for creating validators - - - - - Gets a validator for a type - - - - - - - Gets a validator for a type - - - - - - - Instantiates the validator - - - - - - - Validator runtime options - - - - - Default cascade mode - - - - - Default property chain separator - - - - - Default resource provider - - - - - Default language manager - - - - - Customizations of validator selector - - - - - Specifies a factory for creating MessageFormatter instances. - - - - - Pluggable logic for resolving property names - - - - - Pluggable logic for resolving display names - - - - - Disables the expression accessor cache. Not recommended. - - - - - ValidatorSelector options - - - - - Factory func for creating the default validator selector - - - - - Factory func for creating the member validator selector - - - - - Factory func for creating the ruleset validator selector - - - - - Base class for all comparison validators - - - - - - - - - - - - - - - - - - Performs the comparison - - - - - - - Override to perform the comparison - - - - - - - - Metadata- the comparison type - - - - - Metadata- the member being compared - - - - - Metadata- the value being compared - - - - - Defines a comparison validator - - - - - Metadata- the comparison type - - - - - Metadata- the member being compared - - - - - Metadata- the value being compared - - - - - Asynchronous custom validator - - - - - Creates a new ASyncPredicateValidator - - - - - - Runs the validation check - - - - - - - - Ensures that the property value is a valid credit card number. - - - - - Custom validator that allows for manual/direct creation of ValidationFailure instances. - - - - - - Creates a new instance of the CustomValidator - - - - - - Creates a new isntance of the CutomValidator. - - - - - - Custom validation context - - - - - Creates a new CustomContext - - The parent PropertyValidatorContext that represents this execution - - - - Adds a new validation failure. - - The property name - The error mesage - - - - Adds a new validation failure (the property name is inferred) - - The error message - - - - Adss a new validation failure - - The failure to add - - - - A custom property validator. - This interface should not be implemented directly in your code as it is subject to change. - Please inherit from PropertyValidator instead. - - - - - Prepares the of for an upcoming . - - The validator context - - - - Creates an error validation result for this validator. - - The validator context - Returns an error validation result. - - - - Allows a decimal to be validated for scale and precision. - Scale would be the number of digits to the right of the decimal point. - Precision would be the number of digits. - - It can be configured to use the effective scale and precision - (i.e. ignore trailing zeros) if required. - - 123.4500 has an scale of 4 and a precision of 7, but an effective scale - and precision of 2 and 5 respectively. - - - - - If enabled reverts to persist password hashes using the original SHA256 SaltedHash implementation. - By default ServiceStack uses the more secure ASP.NET Identity v3 PBKDF2 with HMAC-SHA256 implementation. - - New Users will have their passwords persisted with the specified implementation, likewise existing users will have their passwords re-hased - to use the current registered IPasswordHasher. - - - - - Older Password Hashers that were previously used to hash passwords. Failed password matches check to see if the password was hashed with - any of the registered FallbackPasswordHashers, if true the password attempt will succeed and password will get re-hashed with - the current registered IPasswordHasher. - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Cascading collection of virtual file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Call to signal the completion of a ServiceStack-handled Request - - - - - Resolves and auto-wires a ServiceStack Service. - - - - - Context to capture IService action - - - - - Creates instance using straight Resolve approach. - This will throw an exception if resolution fails - - - - - Creates instance using the TryResolve approach if tryResolve = true. - Otherwise uses Resolve approach, which will throw an exception if resolution fails - - - - - Sets a persistent cookie which never expires - - - - - Sets a session cookie which expires after the browser session closes - - - - - Deletes a specified cookie by setting its value to empty and expiration to -1 days - - - - - Return File at specified virtualPath from AppHost.VirtualFiles ContentRootPath - - - - - Keep default file contents in-memory - - - - - - Used in Unit tests - - - - - - The number of segments separated by '/' determinable by path.Split('/').Length - e.g. /path/to/here.ext == 3 - - - - - The total number of segments after subparts have been exploded ('.') - e.g. /path/to/here.ext == 4 - - - - - Provide for quick lookups based on hashes that can be determined from a request url - - - - - For performance withPathInfoParts should already be a lower case string - to minimize redundant matching operations. - - - - - - For performance withPathInfoParts should already be a lower case string - to minimize redundant matching operations. - - - - - - - - - Get Best Matching Route. - - - - If not null, ensures any Route matches any [Route(Matches)] - - - - - Execute MQ - - - - - Execute MQ with requestContext - - - - - Execute using empty RequestContext - - - - - Execute a Service with a Request DTO. See ExecuteAsync for a non-blocking alternative. - - - - - Execute a Service with a Request DTO. - - - - - Gets the name of the base most type in the heirachy tree with the same. - - We get an exception when trying to create a schema with multiple types of the same name - like when inheriting from a DataContract with the same name. - - The type. - - - - - Back-end Service used by /js/hot-fileloader.js to detect file changes in /wwwroot and auto reload page. - - - - - Predefined pattern that matches <?php ... ?> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined pattern that matches <% ... %> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined pattern that matches <--# ... --> tags. - Could be passed inside a list to {@link #setPreservePatterns(List) setPreservePatterns} method. - - - Predefined list of tags that are very likely to be block-level. - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method. - - - Predefined list of tags that are block-level by default, excluding <div> and <li> tags. - Table tags are also included. - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method. - - - Could be passed to {@link #setRemoveSurroundingSpaces(string) setRemoveSurroundingSpaces} method - to remove all surrounding spaces (not recommended). - - - If set to false all compression will be bypassed. Might be useful for testing purposes. - Default is true. - - @param enabled set false to bypass all compression - - - Gets or Sets JavaScript compressor implementation that will be used - to compress inline JavaScript in HTML. - - - Returns CSS compressor implementation that will be used - to compress inline CSS in HTML. - - - If set to true all HTML comments will be removed. - Default is true. - - @param removeComments set true to remove all HTML comments - - - If set to true all multiple whitespace characters will be replaced with single spaces. - Default is true. - - @param removeMultiSpaces set true to replace all multiple whitespace characters - will single spaces. - - - - - Enables JavaScript compression within <script> tags - if set to true. Default is false for performance reasons. - -

    Note: Compressing JavaScript is not recommended if pages are - compressed dynamically on-the-fly because of performance impact. - You should consider putting JavaScript into a separate file and - compressing it using standalone YUICompressor for example.

    - - @param compressJavaScript set true to enable JavaScript compression. - Default is false -
    - - Enables CSS compression within <style> tags using - Yahoo YUI ICompressor - if set to true. Default is false for performance reasons. - -

    Note: Compressing CSS is not recommended if pages are - compressed dynamically on-the-fly because of performance impact. - You should consider putting CSS into a separate file and - compressing it using standalone YUICompressor for example.

    - - @param compressCss set true to enable CSS compression. - Default is false -
    - - If set to true, existing DOCTYPE declaration will be replaced with simple <!DOCTYPE html> declaration. - Default is false. - - @param simpleDoctype set true to replace existing DOCTYPE declaration with <!DOCTYPE html> - - - - If set to true, type="text/style" attributes will be removed from <style> tags. Default is false. - - @param removeStyleAttributes set true to remove type="text/style" attributes from <style> tags - - - - If set to true, method="get" attributes will be removed from <form> tags. Default is false. - - @param removeFormAttributes set true to remove method="get" attributes from <form> tags - - - If set to true, type="text" attributes will be removed from <input> tags. Default is false. - - @param removeInputAttributes set true to remove type="text" attributes from <input> tags - - - - - - - - - - Returns {@link HtmlCompressorStatistics} object containing statistics of the last HTML compression, if enabled. - Should be called after {@link #compress(string)} - - @return {@link HtmlCompressorStatistics} object containing last HTML compression statistics - - @see HtmlCompressorStatistics - @see #setGenerateStatistics(bool) - - - The main method that compresses given HTML source and returns compressed - result. - - @param html HTML content to compress - @return compressed content. - - - Returns metrics of an uncompressed document - - @return metrics of an uncompressed document - @see HtmlMetrics - - - Returns metrics of a compressed document - - @return metrics of a compressed document - @see HtmlMetrics - - - - Returns total size of blocks that were skipped by the compressor - (for example content inside <pre> tags or inside - <script> tags with disabled javascript compression) - - @return the total size of blocks that were skipped by the compressor, in bytes - - - Returns total filesize of a document - - @return total filesize of a document, in bytes - - - Returns number of empty characters (spaces, tabs, end of lines) in a document - - @return number of empty characters in a document - - - Returns total size of inline <script> tags - - @return total size of inline <script> tags, in bytes - - - Returns total size of inline <style> tags - - @return total size of inline <style> tags, in bytes - - - Returns total size of inline event handlers (onclick, etc) - - @return total size of inline event handlers, in bytes - - - - End a ServiceStack Request - - - - - End a HttpHandler Request - - - - - End an MQ Request - - - - - End a ServiceStack Request with no content - - - - - Gets string value from Items[name] then Cookies[name] if exists. - Useful when *first* setting the users response cookie in the request filter. - To access the value for this initial request you need to set it in Items[]. - - string value or null if it doesn't exist - - - - Gets request paramater string value by looking in the following order: - - QueryString[name] - - FormData[name] - - Cookies[name] - - Items[name] - - string value or null if it doesn't exist - - - * - Input: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?q=item#fragment - - Some HttpRequest path and URL properties: - Request.ApplicationPath: /Cambia3 - Request.CurrentExecutionFilePath: /Cambia3/Temp/Test.aspx - Request.FilePath: /Cambia3/Temp/Test.aspx - Request.Path: /Cambia3/Temp/Test.aspx/path/info - Request.PathInfo: /path/info - Request.PhysicalApplicationPath: D:\Inetpub\wwwroot\CambiaWeb\Cambia3\ - Request.QueryString: /Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.AbsolutePath: /Cambia3/Temp/Test.aspx/path/info - Request.Url.AbsoluteUri: http://localhost:96/Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.Fragment: - Request.Url.Host: localhost - Request.Url.LocalPath: /Cambia3/Temp/Test.aspx/path/info - Request.Url.PathAndQuery: /Cambia3/Temp/Test.aspx/path/info?query=arg - Request.Url.Port: 96 - Request.Url.Query: ?query=arg - Request.Url.Scheme: http - Request.Url.Segments: / - Cambia3/ - Temp/ - Test.aspx/ - path/ - info - * - - - - Duplicate Params are given a unique key by appending a #1 suffix - - - - - Duplicate params have their values joined together in a comma-delimited string - - - - - Use this to treat Request.Items[] as a cache by returning pre-computed items to save - calculating them multiple times. - - - - - Sets a persistent cookie which never expires - - - - - Sets a session cookie which expires after the browser session closes - - - - - Sets a persistent cookie which expires after the given time - - - - - Sets a persistent cookie with an expiresAt date - - - - - Deletes a specified cookie by setting its value to empty and expiration to -1 days - - - - - Writes to response. - Response headers are customizable by implementing IHasOptions an returning Dictionary of Http headers. - - The response. - Whether or not it was implicity handled by ServiceStack's built-in handlers. - The default action. - The serialization context. - Add prefix to response body if any - Add suffix to response body if any - - - - - When HTTP Headers have already been written and only the Body can be written - - - - - Respond with a 'Soft redirect' so smart clients (e.g. ajax) have access to the response and - can decide whether or not they should redirect - - - - - Decorate the response with an additional client-side event to instruct participating - smart clients (e.g. ajax) with hints to transparently invoke client-side functionality - - - - - Shortcut to get the ResponseDTO whether it's bare or inside a IHttpResult - - - - - - - Alias of AsDto - - - - - Shortcut to get the ResponseDTO whether it's bare or inside a IHttpResult - - - TResponse if found; otherwise null - - - - Alias of AsDto - - - - - Whether the response is an IHttpError or Exception - - - - - rangeHeader should be of the format "bytes=0-" or "bytes=0-12345" or "bytes=123-456" - - - - - Adds 206 PartialContent Status, Content-Range and Content-Length headers - - - - - Writes partial range as specified by start-end, from fromStream to toStream. - - - - - Writes partial range as specified by start-end, from fromStream to toStream. - - - - - ASP.NET or HttpListener ServiceStack host - - - - - The assemblies reflected to find api services provided in the AppHost constructor - - - - - Register dependency in AppHost IOC on Startup - - - - - AutoWired Registration of an interface with a concrete type in AppHost IOC on Startup. - - - - - Allows the clean up for executed autowired services and filters. - Calls directly after services and filters are executed. - - - - - Called at the end of each request. Enables Request Scope. - - - - - Register callbacks to be called at the end of each request. - - - - - Register user-defined custom routes. - - - - - Inferred Metadata available from existing services - - - - - Register custom ContentType serializers - - - - - Add Request Filters, to be applied before the dto is deserialized - - - - - Add Request Converter to convert Request DTO's - - - - - Add Response Converter to convert Response DTO's - - - - - Add Request Filters for HTTP Requests - - - - - Add Async Request Filters for HTTP Requests - - - - - Add Response Filters for HTTP Responses - - - - - Add Async Response Filters for HTTP Responses - - - - - Add Request Filters for MQ/TCP Requests - - - - - Add Async Request Filters for MQ/TCP Requests - - - - - Add Response Filters for MQ/TCP Responses - - - - - Add Request Filter for a specific Request DTO Type - - - - - Add as a Typed Request Filter for a specific Request DTO Type - - The DTO Type. - The methods to resolve the . - - - - Add Request Filter for a specific Response DTO Type - - - - - Add as a Typed Request Filter for a specific Request DTO Type - - The DTO Type. - The methods to resolve the . - - - - Add Request Filter for a specific MQ Request DTO Type - - - - - Add Request Filter for a specific MQ Response DTO Type - - - - - Add Request Filter for Service Gateway Requests - - - - - Add Response Filter for Service Gateway Responses - - - - - Add alternative HTML View Engines - - - - - Provide an exception handler for unhandled exceptions - - - - - Provide an exception handler for unhandled exceptions (Async) - - - - - Provide an exception handler for un-caught exceptions - - - - - Provide an exception handler for un-caught exceptions (Async) - - - - - Provide callbacks to be fired after the AppHost has finished initializing - - - - - Provide callbacks to be fired when AppHost is being disposed - - - - - Skip the ServiceStack Request Pipeline and process the returned IHttpHandler instead - - - - - Provide a catch-all handler that doesn't match any routes - - - - - Provide a fallback handler for not found requests (last filter in Request Pipeline) - - - - - Use a fall-back Error Handler for handling global errors - - - - - Use a Custom Error Handler for handling specific error HttpStatusCodes - - - - - Provide a custom model minder for a specific Request DTO - - - - - The AppHost config - - - - - The AppHost AppSettings. Defaults to App or Web.config appSettings. - - - - - Allow specific configuration to be overridden at runtime in multi-tenancy Applications - by overriding GetRuntimeConfig in your AppHost - - - - - Register an Adhoc web service on Startup - - - - - Register all Services in Assembly - - - - - List of pre-registered and user-defined plugins to be enabled in this AppHost - - - - - Apply plugins to this AppHost - - - - - Returns the Absolute File Path, relative from your AppHost's Project Path - - - - - Cascading number of file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Register additional Virtual File Sources - - - - - Create a service runner for IService actions - - - - - Resolve the absolute url for this request - - - - - Resolve localized text, returns itself by default. - The Request is provided when exists. - - - - - Execute MQ Message in ServiceStack - - - - - Access Service Controller for ServiceStack - - - - - A convenient repository base class you can inherit from to reduce the boilerplate - with accessing a managed IDbConnection - - - - - A convenient base class for your injected service dependencies that reduces the boilerplate - with managed access to ServiceStack's built-in providers - - - - - Callback for Plugins to register necessary handlers with ServiceStack - - - - - Callback to pre-configure any logic before IPlugin.Register() is fired - - - - - Callback to post-configure any logic after IPlugin.Register() is fired - - - - - Callback for AuthProviders to register callbacks with AuthFeature - - - - - Resolve an alternate Web Service from ServiceStack's IOC container. - - - - - - - Converts markdown contents to HTML using the configured MarkdownConfig.Transformer. - If a variable name is specified the HTML output is captured and saved instead. - - Usages: {{#markdown}} ## The Heading {{/markdown}} - {{#markdown content}} ## The Heading {{/markdown}} HTML: {{content}} - - - - - How many times to retry processing messages before moving them to the DLQ - - - - - Execute global transformation or custom logic before a request is processed. - Must be thread-safe. - - - - - Execute global transformation or custom logic on the response. - Must be thread-safe. - - - - - If you only want to enable priority queue handlers (and threads) for specific msg types - - - - - Create workers for priority queues - - - - - Opt-in to only publish responses on this white list. - Publishes all responses by default. - - - - - Subscribe to messages sent to .outq - - - - - The max size of the Out MQ Collection in each Type (default 100) - - - - - Encapsulates creating a new message handler - - - - - Processes all messages in a Normal and Priority Queue. - Expects to be called in 1 thread. i.e. Non Thread-Safe. - - - - - - Changes the links for the servicestack/metadata page - - - - - Get the static Parse(string) method on the type supplied - - - - - Gets the constructor info for T(string) if exists. - - - - - Returns the value returned by the 'T.Parse(string)' method if exists otherwise 'new T(string)'. - e.g. if T was a TimeSpan it will return TimeSpan.Parse(textValue). - If there is no Parse Method it will attempt to create a new instance of the destined type - - - - - Only generate specified Verb entries for "ANY" routes - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Container service is built-in and read-only.. - - - - - Looks up a localized string similar to Service type {0} does not inherit or implement {1}.. - - - - - Looks up a localized string similar to Required dependency of type {0} named '{1}' could not be resolved.. - - - - - Looks up a localized string similar to Required dependency of type {0} could not be resolved.. - - - - - Looks up a localized string similar to Unknown scope.. - - - - - Transparently Proxy requests through to downstream HTTP Servers - - - - - Customize the HTTP Request Headers that are sent to downstream server - - - - - Customize the downstream HTTP Response Headers that are returned to client - - - - - Inspect or Transform the HTTP Request Body that's sent downstream - - - - - Inspect or Transform the downstream HTTP Response Body that's returned - - - - - Required filters to specify which requests to proxy and which url to use. - - Specify which requests should be proxied - Specify which downstream url to use - - - - Service error logs are kept in 'urn:ServiceErrors:{ServiceName}' - - - - - Combined service error logs are maintained in 'urn:ServiceErrors:All' - - - - - Enable the Registration feature and configure the RegistrationService. - - - - - Abstraction to provide a context per request. - in spnet.web its equivalent to .Current.Items falls back to CallContext - - - - - Start a new Request context, everything deeper in Async pipeline will get this new RequestContext dictionary. - - - - - Gets a list of items for this request. - - This list will be cleared on every request and is specific to the original thread that is handling the request. - If a handler uses additional threads, this data will not be available on those threads. - - - - - Track any IDisposable's to dispose of at the end of the request in IAppHost.OnEndRequest() - - - - - - Release currently registered dependencies for this request - - true if any dependencies were released - - - - Returns the optimized result for the IRequestContext. - Does not use or store results in any cache. - - - - - - - - Returns the optimized result for the IRequestContext. - Does not use or store results in any cache. - - - - - Overload for the method returning the most - optimized result based on the MimeType and CompressionType from the IRequestContext. - - - - - Overload for the method returning the most - optimized result based on the MimeType and CompressionType from the IRequestContext. - How long to cache for, null is no expiration - - - - - Clears all the serialized and compressed caches set - by the 'Resolve' method for the cacheKey provided - - - - - - - - Store an entry in the IHttpRequest.Items Dictionary - - - - - Get an entry from the IHttpRequest.Items Dictionary - - - - - Base class to create request filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The request DTO - - - - Create a ShallowCopy of this instance. - - - - - - Base class to create request filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The request DTO - - - - Create a ShallowCopy of this instance. - - - - - - RequestLogs service Route, default is /requestlogs - - - - - Turn On/Off Session Tracking - - - - - Turn On/Off Logging of Raw Request Body, default is Off - - - - - Turn On/Off Tracking of Responses - - - - - Turn On/Off Tracking of Exceptions - - - - - Don't log matching requests - - - - - Size of InMemoryRollingRequestLogger circular buffer - - - - - Limit access to /requestlogs service to these roles - - - - - Change the RequestLogger provider. Default is InMemoryRollingRequestLogger - - - - - Don't log requests of these types. By default RequestLog's are excluded - - - - - Don't log request bodys for services with sensitive information. - By default Auth and Registration requests are hidden. - - - - - Limit logging to only Service Requests - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific permissions. - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific roles. - - - - - Check all session is in all supplied roles otherwise a 401 HttpError is thrown - - - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has specific permissions. - - - - - Indicates that the request dto, which is associated with this attribute, - can only execute, if the user has any of the specified roles. - - - - - Check all session is in any supplied roles otherwise a 401 HttpError is thrown - - - - - - - Base class to create response filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The response DTO - - - - Create a ShallowCopy of this instance. - - - - - - Base class to create response filter attributes only for specific HTTP methods (GET, POST...) - - - - - Creates a new - - Defines when the filter should be executed - - - - This method is only executed if the HTTP method matches the property. - - The http request wrapper - The http response wrapper - The response DTO - - - - Create a ShallowCopy of this instance. - - - - - - Call IServerEvents.RemoveExpiredSubscriptions() after every count - - - - - Generic + Useful IService base class - - - - - Returns . cache is only persisted for this running app instance. - - - - - Cascading collection of virtual file sources, inc. Embedded Resources, File System, In Memory, S3 - - - - - Read/Write Virtual FileSystem. Defaults to FileSystemVirtualPathProvider - - - - - Dynamic Session Bag - - - - - Typed UserSession - - - - - If user found in session for this request is authenticated. - - - - - Publish a MQ message over the implementation. - - - - - Disposes all created disposable properties of this service - and executes disposing of all request s - (warning, manualy triggering this might lead to unwanted disposing of all request related objects and services.) - - - - - Scans the supplied Assemblies to infer REST paths and HTTP verbs. - - The instance. - - The assemblies with REST services. - - The same instance; - never . - - - - When the AppHost was instantiated. - - - - - When the Init function was done. - Called at begin of - - - - - When all configuration was completed. - Called at the end of - - - - - If app currently runs for unit tests. - Used for overwritting AuthSession. - - - - - The assemblies reflected to find api services. - These can be provided in the constructor call. - - - - - Wether AppHost configuration is done. - Note: It doesn't mean the start function was called. - - - - - Wether AppHost is ready configured and either ready to run or already running. - Equals - - - - - Set the host config of the AppHost. - - - - - Initializes the AppHost. - Calls the method. - Should be called before start. - - - - - Gets Full Directory Path of where the app is running - - - - - Starts the AppHost. - this methods needs to be overwritten in subclass to provider a listener to start handling requests. - - Url to listen to - - - - The AppHost.Container. Note: it is not thread safe to register dependencies after AppStart. - - - - - Collection of PreRequest filters. - They are called before each request is handled by a service, but after an HttpHandler is by the chosen. - called in . - - - - - Collection of RequestConverters. - Can be used to convert/change Input Dto - Called after routing and model binding, but before request filters. - All request converters are called unless - Converter can return null, orginal model will be used. - - Note one converter could influence the input for the next converter! - - - - - Collection of ResponseConverters. - Can be used to convert/change Output Dto - - Called directly after response is handled, even before ! - - - - - Lists of view engines for this app. - If view is needed list is looped until view is found. - - - - - Collection of added plugins. - - - - - Executed immediately before a Service is executed. Use return to change the request DTO used, must be of the same type. - - - - - Executed immediately after a service is executed. Use return to change response used. - - - - - Occurs when the Service throws an Exception. - - - - - Occurs when an exception is thrown whilst processing a request. - - - - - Register singleton in the Ioc Container of the AppHost. - - - - - Registers type to be automatically wired by the Ioc container of the AppHost. - - Concrete type - Abstract type - - - - Tries to resolve type through the ioc container of the AppHost. - Can return null. - - - - - Resolves Type through the Ioc container of the AppHost. - - If type is not registered - - - - Looks for first plugin of this type in Plugins. - Reflection performance penalty. - - - - - Apply PreRequest Filters for participating Custom Handlers, e.g. RazorFormat, MarkdownFormat, etc - - - - - Apply PreAuthenticate Filters from IAuthWithRequest AuthProviders - - - - - Applies the raw request filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Applies the request filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Applies the response filters. Returns whether or not the request has been handled - and no more processing should be done. - - - - - - Inspect or modify ever new UserSession created or resolved from cache. - return null if Session is invalid to create new Session. - - - - - Override built-in Cookies, return false to prevent the Cookie from being set. - - - - - Gets IDbConnection Checks if DbInfo is seat in RequestContext. - See multitenancy: http://docs.servicestack.net/multitenancy - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - Resolves based on .GetClient(); - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - If they don't have an ICacheClient configured use an In Memory one. - - - - - Tries to resolve through Ioc container. - If not registered, it falls back to .GetClient(); - Called by itself, and - - Provided by services and pageView, can be helpfull when overriding this method - - - - - Returns . cache is only persisted for this running app instance. - Called by .MemoryCacheClient - - Provided by services and pageView, can be helpfull when overriding this method - Nullable MemoryCacheClient - - - - Returns from the IOC container. - Called by itself, and - - Provided by services and PageViewBase, can be helpfull when overriding this method - - - - - Typed UserSession - - - - - Dynamic Session Bag - - - - - Configure ServiceStack to have ISession support - - - - - Create the active Session or Permanent Session Id cookie. - - - - - - Create both Permanent and Session Id cookies and return the active sessionId - - - - - - Role Required to call Templates Admin Service (/templates/admin), Default is Admin. - If null Templates Admin Service will not be registered. - - - - - Load Embedded Resource Templates in ServiceStack. - To get ServiceStack to use your own instead just add a copy of one or more of the following to your Web Root: - ~/Templates/IndexOperations.html - ~/Templates/OperationControl.html - ~/Templates/HtmlFormat.html - - - - - Converts the validation result to an error result which will be serialized by ServiceStack in a clean and human-readable way. - - The validation result - - - - - Converts the validation result to an error exception which will be serialized by ServiceStack in a clean and human-readable way - if the returned exception is thrown. - - The validation result - - - - - Creates a new instance of the RulesetValidatorSelector. - - - - - Determines whether or not a rule should execute. - - The rule - Property path (eg Customer.Address.Line1) - Contextual information - Whether or not the validator can execute. - - - - Activate the validation mechanism, so every request DTO with an existing validator - will be validated. - - The app host - - - - Override to provide additional/less context about the Service Exception. - By default the request is serialized and appended to the ResponseStatus StackTrace. - - - - - Auto-scans the provided assemblies for a - and registers it in the provided IoC container. - - The IoC container - The assemblies to scan for a validator - - - - Main container class for components, supporting container hierarchies and - lifetime management of instances. - - - - - Register an autowired dependency - - - - - - Register an autowired dependency - - Name of dependency - - - - - Register an autowired dependency as a separate type - - - - - - Register an autowired dependency as a separate type - - - - - - Alias for RegisterAutoWiredAs - - - - - - Alias for RegisterAutoWiredAs - - - - - - Auto-wires an existing instance, - ie all public properties are tried to be resolved. - - - - - - Generates a function which creates and auto-wires . - - - - - - - - Auto-wires an existing instance of a specific type. - The auto-wiring progress is also cached to be faster - when calling next time with the same type. - - - - - - Initializes a new empty container. - - - - - Default owner for new registrations. by default. - - - - - Default reuse scope for new registrations. by default. - - - - - Creates a child container of the current one, which exposes its - current service registration to the new child container. - - - - - Disposes the container and all instances owned by it (see - ), as well as all child containers - created through . - - - - - Registers a service instance with the container. This instance - will have and - behavior. - Service instance to use. - - - - Registers a named service instance with the container. This instance - will have and - behavior. - Name of the service to register.Service instance to use. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service when needed. - Type of the service to retrieve.The function that can resolve to the service instance when invoked.The requested service has not been registered previously. - - - - - - - - - - - - - - - - - - - - - - Retrieves a function that can be used to lazily resolve an instance - of the service with the given name when needed. - Type of the service to retrieve.Name of the service to retrieve.The function that can resolve to the service instance with the given name when invoked.The requested service with the given name has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Retrieves a function that can be used to lazily resolve an instance - of the service of the given type, name and service constructor arguments when needed. - Name of the service to retrieve.The function that can resolve to the service instance with the given and service constructor arguments name when invoked.The requested service with the given name and constructor arguments has not been registered previously. - - - - Registers the given service by providing a factory delegate to - instantiate it. - The service type to register.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.Sixth argument that should be passed to the factory delegate to create the instace.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate to - instantiate it. - The service type to register.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Registers the given named service by providing a factory delegate that receives arguments to - instantiate it. - The service type to register.First argument that should be passed to the factory delegate to create the instace.Second argument that should be passed to the factory delegate to create the instace.Third argument that should be passed to the factory delegate to create the instace.Fourth argument that should be passed to the factory delegate to create the instace.Fifth argument that should be passed to the factory delegate to create the instace.Sixth argument that should be passed to the factory delegate to create the instace.A name used to differenciate this service registration.The factory delegate to initialize new instances of the service when needed.The registration object to perform further configuration via its fluent interface. - - - - Resolves the given service by type, without passing any arguments for - its construction. - Type of the service to retrieve.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, without passing arguments for its initialization. - Type of the service to retrieve.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Resolves the given service by type and name, passing the given arguments - for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace.The resolved service instance.The given service could not be resolved. - - - - Attempts to resolve the given service by type, without passing arguments for its initialization. - Type of the service to retrieve. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, without passing - arguments arguments for its initialization. - Type of the service to retrieve. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Attempts to resolve the given service by type and name, passing the - given arguments arguments for its initialization. - Type of the service to retrieve.First argument to pass to the factory delegate that may create the instace.Second argument to pass to the factory delegate that may create the instace.Third argument to pass to the factory delegate that may create the instace.Fourth argument to pass to the factory delegate that may create the instace.Fifth argument to pass to the factory delegate that may create the instace.Sixth argument to pass to the factory delegate that may create the instace. - The resolved service instance or if it cannot be resolved. - - - - - Encapsulates a method that has five parameters and returns a value of the - type specified by the parameter. - - - - - Encapsulates a method that has six parameters and returns a value of the - type specified by the parameter. - - - - - Encapsulates a method that has seven parameters and returns a value of the - type specified by the parameter. - - - - - Helper interface used to hide the base - members from the fluent API to make for much cleaner - Visual Studio intellisense experience. - - - - - - - - - - - - - - - - - Funqlets are a set of components provided as a package - to an existing container (like a module). - - - - - Configure the given container with the - registrations provided by the funqlet. - - Container to register. - - - - Interface used by plugins to contribute registrations - to an existing container. - - - - - Determines who is responsible for disposing instances - registered with a container. - - - - - Container should dispose provided instances when it is disposed. This is the - default. - - - - - Container does not dispose provided instances. - - - - - Default owner, which equals . - - - - - Exception thrown by the container when a service cannot be resolved. - - - - - Initializes the exception with the service that could not be resolved. - - - - - Initializes the exception with the service (and its name) that could not be resolved. - - - - - Initializes the exception with an arbitrary message. - - - - - Determines visibility and reuse of instances provided by the container. - - - - - Instances are reused within a container hierarchy. Instances - are created (if necessary) in the container where the registration - was performed, and are reused by all descendent containers. - - - - - Instances are reused only at the given container. Descendent - containers do not reuse parent container instances and get - a new instance at their level. - - - - - Each request to resolve the dependency will result in a new - instance being returned. - - - - - Instaces are reused within the given request - - - - - Default scope, which equals . - - - - - Ownership setting for the service. - - - - - Reuse scope setting for the service. - - - - - The container where the entry was registered. - - - - - Specifies the owner for instances, which determines how - they will be disposed. - - - - - Specifies the scope for instances, which determines - visibility of instances across containers and hierarchies. - - - - - The Func delegate that creates instances of the service. - - - - - The cached service instance if the scope is or - . - - - - - The Func delegate that initializes the object after creation. - - - - - Clones the service entry assigning the to the - . Does not copy the . - - - - - Fluent API for customizing the registration of a service. - - - - - Fluent API for customizing the registration of a service. - - - - - Fluent API that allows registering an initializer for the - service. - - - - - Specifies an initializer that should be invoked after - the service instance has been created by the factory. - - - - - Fluent API that exposes both - and owner (). - - - - - Fluent API that allows specifying the reuse instances. - - - - - Specifies how instances are reused within a container or hierarchy. Default - scope is . - - - - - Fluent API that allows specifying the owner of instances - created from a registration. - - - - - Specifies the owner of instances created from this registration. Default - owner is . - - - - diff --git a/src/.nuget/NuGet.config b/src/.nuget/NuGet.config deleted file mode 100644 index fe6a62c9..00000000 --- a/src/.nuget/NuGet.config +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/.nuget/NuGet.exe b/src/.nuget/NuGet.exe deleted file mode 100755 index 6bb79fe5..00000000 Binary files a/src/.nuget/NuGet.exe and /dev/null differ diff --git a/src/.nuget/NuGet.targets b/src/.nuget/NuGet.targets deleted file mode 100644 index f2d0eb36..00000000 --- a/src/.nuget/NuGet.targets +++ /dev/null @@ -1,77 +0,0 @@ - - - - $(MSBuildProjectDirectory)\..\ - - - - - $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) - $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) - $([System.IO.Path]::Combine($(SolutionDir), "packages")) - - - - - $(SolutionDir).nuget - packages.config - $(SolutionDir)packages - - - - - $(NuGetToolsPath)\NuGet.exe - "$(NuGetExePath)" - mono --runtime=v4.0.30319 $(NuGetExePath) - - $(TargetDir.Trim('\\')) - - - "" - - - false - - - false - - - $(NuGetCommand) install "$(PackagesConfig)" -source $(PackageSources) -o "$(PackagesDir)" - $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols - - - - RestorePackages; - $(BuildDependsOn); - - - - - $(BuildDependsOn); - BuildPackage; - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 00000000..2a6ba4e0 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,54 @@ + + + + 6.0.3 + ServiceStack + ServiceStack, Inc. + © 2008-2018 ServiceStack, Inc + true + https://github.com/ServiceStack/ServiceStack.Redis + https://servicestack.net/terms + https://servicestack.net/img/logo-64.png + https://docs.servicestack.net/release-notes-history + git + https://github.com/ServiceStack/ServiceStack.Redis.git + embedded + latest + true + true + false + + + + true + true + + + + $(DefineConstants);NETFX;NET45;NET472 + True + False + ../servicestack.snk + + + + $(DefineConstants);NETSTANDARD;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + + + + + + DEBUG + + + diff --git a/src/ServiceStack.Redis.sln b/src/ServiceStack.Redis.sln index a2ab9b29..c2ea43b7 100644 --- a/src/ServiceStack.Redis.sln +++ b/src/ServiceStack.Redis.sln @@ -1,27 +1,30 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2010 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29721.120 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{38F69F8F-9303-4BAF-B081-D28339163E07}" ProjectSection(SolutionItems) = preProject + ..\build\build-core.proj = ..\build\build-core.proj ..\build\build.bat = ..\build\build.bat ..\build\build.proj = ..\build\build.proj ..\build\build.tasks = ..\build\build.tasks - ..\build\copy.bat = ..\build\copy.bat + Directory.Build.props = Directory.Build.props + ..\tests\Directory.Build.props = ..\tests\Directory.Build.props ..\README.md = ..\README.md - ..\NuGet\ServiceStack.Redis.Core\servicestack.redis.core.nuspec = ..\NuGet\ServiceStack.Redis.Core\servicestack.redis.core.nuspec - ..\NuGet\ServiceStack.Redis\servicestack.redis.nuspec = ..\NuGet\ServiceStack.Redis\servicestack.redis.nuspec + ServiceStack.Redis\ServiceStack.Redis.Core.csproj = ServiceStack.Redis\ServiceStack.Redis.Core.csproj EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Redis", "ServiceStack.Redis\ServiceStack.Redis.csproj", "{AF99F19B-4C04-4F58-81EF-B092F1FCC540}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Redis.Tests", "..\tests\ServiceStack.Redis.Tests\ServiceStack.Redis.Tests.csproj", "{951D28EE-5D22-4C62-AC0F-1661A8CEEC5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console.Tests", "..\tests\Console.Tests\Console.Tests.csproj", "{8368C965-B4F6-4263-9ABB-731A175B2E77}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceStack.Redis.Tests.Sentinel", "..\tests\ServiceStack.Redis.Tests.Sentinel\ServiceStack.Redis.Tests.Sentinel.csproj", "{91C55091-A946-49B5-9517-8794EBCC5784}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceStack.Redis.Benchmark", "..\tests\ServiceStack.Redis.Benchmark\ServiceStack.Redis.Benchmark.csproj", "{959CA5FE-6525-4EEF-86CA-F4978BEFF14F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console.Tests", "..\tests\Console.Tests\Console.Tests.csproj", "{56DEDC64-B349-4150-BE9C-5805D831678D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -56,16 +59,6 @@ Global {951D28EE-5D22-4C62-AC0F-1661A8CEEC5A}.Release|x64.Build.0 = Release|Any CPU {951D28EE-5D22-4C62-AC0F-1661A8CEEC5A}.Release|x86.ActiveCfg = Release|Any CPU {951D28EE-5D22-4C62-AC0F-1661A8CEEC5A}.Release|x86.Build.0 = Release|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Debug|x64.ActiveCfg = Debug|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Debug|x64.Build.0 = Debug|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Debug|x86.ActiveCfg = Debug|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Release|Any CPU.Build.0 = Release|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Release|x64.ActiveCfg = Release|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Release|x64.Build.0 = Release|Any CPU - {8368C965-B4F6-4263-9ABB-731A175B2E77}.Release|x86.ActiveCfg = Release|Any CPU {91C55091-A946-49B5-9517-8794EBCC5784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {91C55091-A946-49B5-9517-8794EBCC5784}.Debug|Any CPU.Build.0 = Debug|Any CPU {91C55091-A946-49B5-9517-8794EBCC5784}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -76,6 +69,30 @@ Global {91C55091-A946-49B5-9517-8794EBCC5784}.Release|x64.ActiveCfg = Release|Any CPU {91C55091-A946-49B5-9517-8794EBCC5784}.Release|x64.Build.0 = Release|Any CPU {91C55091-A946-49B5-9517-8794EBCC5784}.Release|x86.ActiveCfg = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|x64.ActiveCfg = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|x64.Build.0 = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|x86.ActiveCfg = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Debug|x86.Build.0 = Debug|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|Any CPU.Build.0 = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|x64.ActiveCfg = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|x64.Build.0 = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|x86.ActiveCfg = Release|Any CPU + {959CA5FE-6525-4EEF-86CA-F4978BEFF14F}.Release|x86.Build.0 = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|x64.ActiveCfg = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|x64.Build.0 = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|x86.ActiveCfg = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Debug|x86.Build.0 = Debug|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|Any CPU.Build.0 = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|x64.ActiveCfg = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|x64.Build.0 = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|x86.ActiveCfg = Release|Any CPU + {56DEDC64-B349-4150-BE9C-5805D831678D}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/ServiceStack.Redis.sln.DotSettings b/src/ServiceStack.Redis.sln.DotSettings index 11f2c267..28747c5c 100644 --- a/src/ServiceStack.Redis.sln.DotSettings +++ b/src/ServiceStack.Redis.sln.DotSettings @@ -1,3 +1,4 @@  + True <data><IncludeFilters /><ExcludeFilters /></data> <data /> \ No newline at end of file diff --git a/src/ServiceStack.Redis/BasicRedisClientManager.Async.cs b/src/ServiceStack.Redis/BasicRedisClientManager.Async.cs new file mode 100644 index 00000000..1b2bd382 --- /dev/null +++ b/src/ServiceStack.Redis/BasicRedisClientManager.Async.cs @@ -0,0 +1,180 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Caching; +using ServiceStack.Redis.Internal; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + /// + /// Provides thread-safe retrieval of redis clients since each client is a new one. + /// Allows the configuration of different ReadWrite and ReadOnly hosts + /// + public partial class BasicRedisClientManager + : IRedisClientsManagerAsync, ICacheClientAsync + { + private ValueTask GetCacheClientAsync(in CancellationToken _) + => new RedisClientManagerCacheClient(this).AsValueTaskResult(); + + private ValueTask GetReadOnlyCacheClientAsync(in CancellationToken _) + => ConfigureRedisClientAsync(this.GetReadOnlyClientImpl()).AsValueTaskResult(); + + private IRedisClientAsync ConfigureRedisClientAsync(IRedisClientAsync client) + => client; + + ValueTask IRedisClientsManagerAsync.GetCacheClientAsync(CancellationToken token) + => GetCacheClientAsync(token); + + ValueTask IRedisClientsManagerAsync.GetClientAsync(CancellationToken token) + => GetClientImpl().AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyCacheClientAsync(CancellationToken token) + => GetReadOnlyCacheClientAsync(token); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyClientAsync(CancellationToken token) + => GetReadOnlyClientImpl().AsValueTaskResult(); + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + async Task ICacheClientAsync.GetAsync(string key, CancellationToken token) + { + await using var client = await GetReadOnlyCacheClientAsync(token).ConfigureAwait(false); + return await client.GetAsync(key, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.FlushAllAsync(CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + await client.FlushAllAsync(token).ConfigureAwait(false); + } + + async Task> ICacheClientAsync.GetAllAsync(IEnumerable keys, CancellationToken token) + { + await using var client = await GetReadOnlyCacheClientAsync(token).ConfigureAwait(false); + return await client.GetAllAsync(keys, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAllAsync(IDictionary values, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + await client.SetAllAsync(values, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.RemoveAsync(string key, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.RemoveAsync(key, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.RemoveAllAsync(IEnumerable keys, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + await client.RemoveAllAsync(keys, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.IncrementAsync(string key, uint amount, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.IncrementAsync(key, amount, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.DecrementAsync(string key, uint amount, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.DecrementAsync(key, amount, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.GetTimeToLiveAsync(string key, CancellationToken token) + { + await using var client = await GetReadOnlyCacheClientAsync(token).ConfigureAwait(false); + return await client.GetTimeToLiveAsync(key, token).ConfigureAwait(false); + } + + async IAsyncEnumerable ICacheClientAsync.GetKeysByPatternAsync(string pattern, [EnumeratorCancellation] CancellationToken token) + { + await using var client = await GetReadOnlyCacheClientAsync(token).ConfigureAwait(false); + await foreach (var key in client.GetKeysByPatternAsync(pattern, token).ConfigureAwait(false).WithCancellation(token)) + { + yield return key; + } + } + + async Task ICacheClientAsync.RemoveExpiredEntriesAsync(CancellationToken token) + { + await using var client = await GetCacheClientAsync(token).ConfigureAwait(false); + await client.RemoveExpiredEntriesAsync(token).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/BasicRedisClientManager.ICacheClient.cs b/src/ServiceStack.Redis/BasicRedisClientManager.ICacheClient.cs index 19a81a0a..aedf60eb 100644 --- a/src/ServiceStack.Redis/BasicRedisClientManager.ICacheClient.cs +++ b/src/ServiceStack.Redis/BasicRedisClientManager.ICacheClient.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,163 +19,120 @@ namespace ServiceStack.Redis /// /// BasicRedisClientManager for ICacheClient /// - /// For more interoperabilty I'm also implementing the ICacheClient on + /// For more interoperability I'm also implementing the ICacheClient on /// this cache client manager which has the affect of calling /// GetCacheClient() for all write operations and GetReadOnlyCacheClient() /// for the read ones. /// - /// This works well for master-slave replication scenarios where you have - /// 1 master that replicates to multiple read slaves. + /// This works well for master-replica replication scenarios where you have + /// 1 master that replicates to multiple read replicas. /// public partial class BasicRedisClientManager : ICacheClient { - public ICacheClient GetCacheClient() - { - return new RedisClientManagerCacheClient(this); - } + public ICacheClient GetCacheClient() => + new RedisClientManagerCacheClient(this); - public ICacheClient GetReadOnlyCacheClient() - { - return ConfigureRedisClient(this.GetReadOnlyClient()); - } + public ICacheClient GetReadOnlyCacheClient() => + ConfigureRedisClient(this.GetReadOnlyClientImpl()); - private ICacheClient ConfigureRedisClient(IRedisClient client) - { - return client; - } - - #region Implementation of ICacheClient + private ICacheClient ConfigureRedisClient(IRedisClient client) => client; public bool Remove(string key) { - using (var client = GetReadOnlyCacheClient()) - { - return client.Remove(key); - } + using var client = GetReadOnlyCacheClient(); + return client.Remove(key); } public void RemoveAll(IEnumerable keys) { - using (var client = GetCacheClient()) - { - client.RemoveAll(keys); - } + using var client = GetCacheClient(); + client.RemoveAll(keys); } public T Get(string key) { - using (var client = GetReadOnlyCacheClient()) - { - return client.Get(key); - } + using var client = GetReadOnlyCacheClient(); + return client.Get(key); } public long Increment(string key, uint amount) { - using (var client = GetCacheClient()) - { - return client.Increment(key, amount); - } + using var client = GetCacheClient(); + return client.Increment(key, amount); } public long Decrement(string key, uint amount) { - using (var client = GetCacheClient()) - { - return client.Decrement(key, amount); - } + using var client = GetCacheClient(); + return client.Decrement(key, amount); } public bool Add(string key, T value) { - using (var client = GetCacheClient()) - { - return client.Add(key, value); - } + using var client = GetCacheClient(); + return client.Add(key, value); } public bool Set(string key, T value) { - using (var client = GetCacheClient()) - { - return client.Set(key, value); - } + using var client = GetCacheClient(); + return client.Set(key, value); } public bool Replace(string key, T value) { - using (var client = GetCacheClient()) - { - return client.Replace(key, value); - } + using var client = GetCacheClient(); + return client.Replace(key, value); } public bool Add(string key, T value, DateTime expiresAt) { - using (var client = GetCacheClient()) - { - return client.Add(key, value, expiresAt); - } + using var client = GetCacheClient(); + return client.Add(key, value, expiresAt); } public bool Set(string key, T value, DateTime expiresAt) { - using (var client = GetCacheClient()) - { - return client.Set(key, value, expiresAt); - } + using var client = GetCacheClient(); + return client.Set(key, value, expiresAt); } public bool Replace(string key, T value, DateTime expiresAt) { - using (var client = GetCacheClient()) - { - return client.Replace(key, value, expiresAt); - } + using var client = GetCacheClient(); + return client.Replace(key, value, expiresAt); } public bool Add(string key, T value, TimeSpan expiresIn) { - using (var client = GetCacheClient()) - { - return client.Add(key, value, expiresIn); - } + using var client = GetCacheClient(); + return client.Add(key, value, expiresIn); } public bool Set(string key, T value, TimeSpan expiresIn) { - using (var client = GetCacheClient()) - { - return client.Set(key, value, expiresIn); - } + using var client = GetCacheClient(); + return client.Set(key, value, expiresIn); } public bool Replace(string key, T value, TimeSpan expiresIn) { - using (var client = GetCacheClient()) - { - return client.Replace(key, value, expiresIn); - } + using var client = GetCacheClient(); + return client.Replace(key, value, expiresIn); } public void FlushAll() { - using (var client = GetCacheClient()) - { - client.FlushAll(); - } + using var client = GetCacheClient(); + client.FlushAll(); } public IDictionary GetAll(IEnumerable keys) { - using (var client = GetReadOnlyCacheClient()) - { - return client.GetAll(keys); - } + using var client = GetReadOnlyCacheClient(); + return client.GetAll(keys); } - - #endregion } diff --git a/src/ServiceStack.Redis/BasicRedisClientManager.cs b/src/ServiceStack.Redis/BasicRedisClientManager.cs index 7938ff23..d42c4f51 100644 --- a/src/ServiceStack.Redis/BasicRedisClientManager.cs +++ b/src/ServiceStack.Redis/BasicRedisClientManager.cs @@ -5,25 +5,29 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; +using System.Linq; using System.Threading; +using ServiceStack.Logging; using ServiceStack.Text; namespace ServiceStack.Redis { /// - /// Provides thread-safe retrievel of redis clients since each client is a new one. + /// Provides thread-safe retrieval of redis clients since each client is a new one. /// Allows the configuration of different ReadWrite and ReadOnly hosts /// public partial class BasicRedisClientManager : IRedisClientsManager, IRedisFailover, IHasRedisResolver { + public static ILog Log = LogManager.GetLogger(typeof(BasicRedisClientManager)); + public int? ConnectTimeout { get; set; } public int? SocketSendTimeout { get; set; } public int? SocketReceiveTimeout { get; set; } @@ -95,7 +99,8 @@ protected virtual void OnStart() /// Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts /// /// - public IRedisClient GetClient() + public IRedisClient GetClient() => GetClientImpl(); + private RedisClient GetClientImpl() { var client = InitNewClient(RedisResolver.CreateMasterClient(readWriteHostsIndex++)); return client; @@ -105,7 +110,8 @@ public IRedisClient GetClient() /// Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. /// /// - public virtual IRedisClient GetReadOnlyClient() + public virtual IRedisClient GetReadOnlyClient() => GetReadOnlyClientImpl(); + private RedisClient GetReadOnlyClientImpl() { var client = InitNewClient(RedisResolver.CreateSlaveClient(readOnlyHostsIndex++)); return client; @@ -154,10 +160,15 @@ public void FailoverTo(IEnumerable readWriteHosts, IEnumerable r { Interlocked.Increment(ref RedisState.TotalFailovers); + var masters = readWriteHosts.ToList(); + var replicas = readOnlyHosts.ToList(); + + Log.Info($"FailoverTo: {string.Join(",", masters)} : {string.Join(",", replicas)} Total: {RedisState.TotalFailovers}"); + lock (this) { - RedisResolver.ResetMasters(readWriteHosts); - RedisResolver.ResetSlaves(readOnlyHosts); + RedisResolver.ResetMasters(masters); + RedisResolver.ResetSlaves(replicas); } Start(); diff --git a/src/ServiceStack.Redis/BasicRedisResolver.cs b/src/ServiceStack.Redis/BasicRedisResolver.cs index f757820f..31986996 100644 --- a/src/ServiceStack.Redis/BasicRedisResolver.cs +++ b/src/ServiceStack.Redis/BasicRedisResolver.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Linq; using ServiceStack.Logging; -using ServiceStack.Text; namespace ServiceStack.Redis { @@ -16,21 +15,15 @@ public class BasicRedisResolver : IRedisResolver, IRedisResolverExtended public int ReadOnlyHostsCount { get; private set; } private RedisEndpoint[] masters; - private RedisEndpoint[] slaves; + private RedisEndpoint[] replicas; - public RedisEndpoint[] Masters - { - get { return masters; } - } - public RedisEndpoint[] Slaves - { - get { return slaves; } - } + public RedisEndpoint[] Masters => masters; + public RedisEndpoint[] Replicas => replicas; - public BasicRedisResolver(IEnumerable masters, IEnumerable slaves) + public BasicRedisResolver(IEnumerable masters, IEnumerable replicas) { ResetMasters(masters.ToList()); - ResetSlaves(slaves.ToList()); + ResetSlaves(replicas.ToList()); ClientFactory = RedisConfig.ClientFactory; } @@ -56,13 +49,13 @@ public virtual void ResetSlaves(IEnumerable hosts) ResetSlaves(hosts.ToRedisEndPoints()); } - public virtual void ResetSlaves(List newSlaves) + public virtual void ResetSlaves(List newReplicas) { - slaves = (newSlaves ?? TypeConstants.EmptyList).ToArray(); - ReadOnlyHostsCount = slaves.Length; + replicas = (newReplicas ?? TypeConstants.EmptyList).ToArray(); + ReadOnlyHostsCount = replicas.Length; if (log.IsDebugEnabled) - log.Debug("New Redis Slaves: " + string.Join(", ", slaves.Map(x => x.GetHostString()))); + log.Debug("New Redis Replicas: " + string.Join(", ", replicas.Map(x => x.GetHostString()))); } public RedisClient CreateRedisClient(RedisEndpoint config, bool master) @@ -78,7 +71,7 @@ public RedisEndpoint GetReadWriteHost(int desiredIndex) public RedisEndpoint GetReadOnlyHost(int desiredIndex) { return ReadOnlyHostsCount > 0 - ? slaves[desiredIndex % slaves.Length] + ? replicas[desiredIndex % replicas.Length] : GetReadWriteHost(desiredIndex); } diff --git a/src/ServiceStack.Redis/BufferedReader.Async.cs b/src/ServiceStack.Redis/BufferedReader.Async.cs new file mode 100644 index 00000000..39ad9d7e --- /dev/null +++ b/src/ServiceStack.Redis/BufferedReader.Async.cs @@ -0,0 +1,93 @@ +using ServiceStack.Redis.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + internal sealed partial class BufferedReader + { + internal ValueTask ReadByteAsync(in CancellationToken token = default) + => _available > 0 ? ReadByteFromBuffer().AsValueTaskResult() : ReadByteSlowAsync(token); + + private ValueTask ReadByteSlowAsync(in CancellationToken token) + { + token.ThrowIfCancellationRequested(); + _offset = 0; +#if ASYNC_MEMORY + var pending = _source.ReadAsync(new Memory(_buffer), token); + if (!pending.IsCompletedSuccessfully) + return Awaited(this, pending); +#else + var pending = _source.ReadAsync(_buffer, 0, _buffer.Length, token); + if (pending.Status != TaskStatus.RanToCompletion) + return Awaited(this, pending); +#endif + + _available = pending.Result; + return (_available > 0 ? ReadByteFromBuffer() : -1).AsValueTaskResult(); + +#if ASYNC_MEMORY + static async ValueTask Awaited(BufferedReader @this, ValueTask pending) + { + @this._available = await pending.ConfigureAwait(false); + return @this._available > 0 ? @this.ReadByteFromBuffer() : -1; + } +#else + static async ValueTask Awaited(BufferedReader @this, Task pending) + { + @this._available = await pending.ConfigureAwait(false); + return @this._available > 0 ? @this.ReadByteFromBuffer() : -1; + } +#endif + } + + internal ValueTask ReadAsync(byte[] buffer, int offset, int count, in CancellationToken token = default) + => _available > 0 + ? ReadFromBuffer(buffer, offset, count).AsValueTaskResult() + : ReadSlowAsync(buffer, offset, count, token); + + private ValueTask ReadSlowAsync(byte[] buffer, int offset, int count, in CancellationToken token) + { + // if they're asking for more than we deal in, just step out of the way + if (count >= buffer.Length) + { +#if ASYNC_MEMORY + return _source.ReadAsync(new Memory(buffer, offset, count), token); +#else + return new ValueTask(_source.ReadAsync(buffer, offset, count, token)); +#endif + } + + // they're asking for less, so we could still have some left + _offset = 0; +#if ASYNC_MEMORY + var pending = _source.ReadAsync(new Memory(_buffer), token); + if (!pending.IsCompletedSuccessfully) + return Awaited(this, pending, buffer, offset, count); + + _available = pending.Result; // already checked status, this is fine + return (_available > 0 ? ReadFromBuffer(buffer, offset, count) : 0).AsValueTaskResult(); + + static async ValueTask Awaited(BufferedReader @this, ValueTask pending, byte[] buffer, int offset, int count) + { + @this._available = await pending.ConfigureAwait(false); + return @this._available > 0 ? @this.ReadFromBuffer(buffer, offset, count) : 0; + } +#else + var pending = _source.ReadAsync(_buffer, 0, _buffer.Length, token); + if (pending.Status != TaskStatus.RanToCompletion) + return Awaited(this, pending, buffer, offset, count); + + _available = pending.Result; // already checked status, this is fine + return (_available > 0 ? ReadFromBuffer(buffer, offset, count) : 0).AsValueTaskResult(); + + static async ValueTask Awaited(BufferedReader @this, Task pending, byte[] buffer, int offset, int count) + { + @this._available = await pending.ConfigureAwait(false); + return @this._available > 0 ? @this.ReadFromBuffer(buffer, offset, count) : 0; + } +#endif + } + } +} diff --git a/src/ServiceStack.Redis/BufferedReader.cs b/src/ServiceStack.Redis/BufferedReader.cs new file mode 100644 index 00000000..b5aa67df --- /dev/null +++ b/src/ServiceStack.Redis/BufferedReader.cs @@ -0,0 +1,81 @@ +using System; +using System.IO; + +namespace ServiceStack.Redis +{ + /// + /// BufferedReader is a minimal buffer implementation that provides + /// efficient sync and async access for byte-by-byte consumption; + /// like BufferedStream, but with the async part + /// + internal sealed partial class BufferedReader : IDisposable + { + private readonly Stream _source; + readonly byte[] _buffer; + private int _offset, _available; + public void Dispose() + { + _available = 0; + _source.Dispose(); + } + internal void Close() + { + _available = 0; + _source.Close(); + } + + internal BufferedReader(Stream source, int bufferSize) + { + _source = source; + _buffer = new byte[bufferSize]; + Reset(); + } + + internal void Reset() + { + _offset = _available = 0; + } + + internal int ReadByte() + => _available > 0 ? ReadByteFromBuffer() : ReadByteSlow(); + + private int ReadByteFromBuffer() + { + --_available; + return _buffer[_offset++]; + } + + private int ReadByteSlow() + { + _available = _source.Read(_buffer, _offset = 0, _buffer.Length); + return _available > 0 ? ReadByteFromBuffer() : -1; + } + + + private int ReadFromBuffer(byte[] buffer, int offset, int count) + { + // we have data in the buffer; hand it back + if (_available < count) count = _available; + Buffer.BlockCopy(_buffer, _offset, buffer, offset, count); + _available -= count; + _offset += count; + return count; + } + + internal int Read(byte[] buffer, int offset, int count) + => _available > 0 + ? ReadFromBuffer(buffer, offset, count) + : ReadSlow(buffer, offset, count); + + private int ReadSlow(byte[] buffer, int offset, int count) + { + // if they're asking for more than we deal in, just step out of the way + if (count >= buffer.Length) + return _source.Read(buffer, offset, count); + + // they're asking for less, so we could still have some left + _available = _source.Read(_buffer, _offset = 0, _buffer.Length); + return _available > 0 ? ReadFromBuffer(buffer, offset, count) : 0; + } + } +} diff --git a/src/ServiceStack.Redis/BufferedStream.cs b/src/ServiceStack.Redis/BufferedStream.cs index 78b8c870..2d745c5f 100644 --- a/src/ServiceStack.Redis/BufferedStream.cs +++ b/src/ServiceStack.Redis/BufferedStream.cs @@ -1,10 +1,15 @@ -#if NETSTANDARD2_0 +#if NETCORE using System; using System.IO; using System.Net.Sockets; namespace ServiceStack.Redis { + // recommendation: mark this obsolete as it is incomplete, and no longer used; + // I've marked it obsolete in DEBUG to be sure +#if DEBUG + [Obsolete("Prefer System.IO.BufferedStream")] +#endif public sealed class BufferedStream : Stream { Stream networkStream; diff --git a/src/ServiceStack.Redis/ConnectionUtils.cs b/src/ServiceStack.Redis/ConnectionUtils.cs deleted file mode 100644 index f1731b3e..00000000 --- a/src/ServiceStack.Redis/ConnectionUtils.cs +++ /dev/null @@ -1,449 +0,0 @@ -//using System; -//using System.Collections.Generic; -//using System.Collections.Specialized; -//using System.IO; -//using System.Linq; -//using System.Text; - -//namespace ServiceStack.Redis -//{ -// /// -// /// Provides utility methods for managing connections to multiple (master/slave) redis servers (with the same -// /// information - not sharding). -// /// -// public static class ConnectionUtils -// { -// /// -// /// Inspect the provided configration, and connect to the available servers to report which server is the preferred/active node. -// /// -// public static string SelectConfiguration(string configuration, out string[] availableEndpoints, TextWriter log = null) -// { -// string selected; -// using (SelectAndCreateConnection(configuration, log, out selected, out availableEndpoints, false)) { } -// return selected; -// } -// /// -// /// Inspect the provided configration, and connect to the preferred/active node after checking what nodes are available. -// /// -// public static RedisConnection Connect(string configuration, TextWriter log = null) -// { -// string selectedConfiguration; -// string[] availableEndpoints; -// return SelectAndCreateConnection(configuration, log, out selectedConfiguration, out availableEndpoints, true); -// } - -// /// -// /// Subscribe to perform some operation when a change to the preferred/active node is broadcast. -// /// -// public static void SubscribeToMasterSwitch(RedisSubscriberConnection connection, Action handler) -// { -// if (connection == null) throw new ArgumentNullException("connection"); -// if (handler == null) throw new ArgumentNullException("handler"); - -// connection.Subscribe(RedisMasterChangedChannel, (channel, message) => handler(Encoding.UTF8.GetString(message))); -// } -// /// -// /// Using the configuration available, and after checking which nodes are available, switch the master node and broadcast this change. -// /// -// public static void SwitchMaster(string configuration, string newMaster, TextWriter log = null) -// { -// string newConfig; -// string[] availableEndpoints; - -// SelectAndCreateConnection(configuration, log, out newConfig, out availableEndpoints, false, newMaster); -// } - -// const string RedisMasterChangedChannel = "__Booksleeve_MasterChanged", TieBreakerKey = "__Booksleeve_TieBreak"; - -// /// -// /// Prompt all clients to reconnect. -// /// -// public static void BroadcastReconnectMessage(RedisConnection connection) -// { -// if (connection == null) throw new ArgumentNullException("connection"); - -// connection.Wait(connection.Publish(RedisMasterChangedChannel, "*")); -// } -// private static RedisConnection SelectWithTieBreak(TextWriter log, List nodes, Dictionary tiebreakers) -// { -// if (nodes.Count == 0) return null; -// if (nodes.Count == 1) return nodes[0]; -// Func valueOrDefault = key => -// { -// int tmp; -// if (!tiebreakers.TryGetValue(key, out tmp)) tmp = 0; -// return tmp; -// }; -// var tuples = (from node in nodes -// let key = node.Host + ":" + node.Port -// let count = valueOrDefault(key) -// select new { Node = node, Key = key, Count = count }).ToList(); - -// // check for uncontested scenario -// int contenderCount = tuples.Count(x => x.Count > 0); -// switch (contenderCount) -// { -// case 0: -// log.WriteLine("No tie-break contenders; selecting arbitrary node"); -// return tuples[0].Node; -// case 1: -// log.WriteLine("Unaminous tie-break winner"); -// return tuples.Single(x => x.Count > 0).Node; -// } - -// // contested -// int maxCount = tuples.Max(x => x.Count); -// var competing = tuples.Where(x => x.Count == maxCount).ToList(); - -// switch (competing.Count) -// { -// case 0: -// return null; // impossible, but never rely on the impossible not happening ;p -// case 1: -// log.WriteLine("Contested, but clear, tie-break winner"); -// break; -// default: -// log.WriteLine("Contested and ambiguous tie-break; selecting arbitrary node"); -// break; -// } -// return competing[0].Node; -// } - -// private static string[] GetConfigurationOptions(string configuration, out int syncTimeout, out bool allowAdmin) -// { -// syncTimeout = 1000; -// allowAdmin = false; - -// // break it down by commas -// var arr = configuration.Split(','); -// var options = new List(); -// foreach (var option in arr) -// { -// var trimmed = option.Trim(); - -// if (trimmed.IsNullOrWhiteSpace() || options.Contains(trimmed)) continue; - -// // check for special tokens -// int idx = trimmed.IndexOf('='); -// if (idx > 0) -// { -// if (option.StartsWith(SyncTimeoutPrefix)) -// { -// int tmp; -// if (int.TryParse(option.Substring(idx + 1), out tmp)) syncTimeout = tmp; -// continue; -// } -// if (option.StartsWith(AllowAdminPrefix)) -// { -// bool tmp; -// if (bool.TryParse(option.Substring(idx + 1), out tmp)) allowAdmin = tmp; -// continue; -// } -// } - -// options.Add(trimmed); -// } -// return options.ToArray(); -// } - -// internal const string AllowAdminPrefix = "allowAdmin=", SyncTimeoutPrefix = "syncTimeout="; -// private static RedisConnection SelectAndCreateConnection(string configuration, TextWriter log, out string selectedConfiguration, out string[] availableEndpoints, bool autoMaster, string newMaster = null) -// { -// int syncTimeout; -// bool allowAdmin; -// if (log == null) log = new StringWriter(); -// var arr = GetConfigurationOptions(configuration, out syncTimeout, out allowAdmin); -// if (!newMaster.IsNullOrWhiteSpace()) allowAdmin = true; // need this to diddle the slave/master config - -// log.WriteLine("{0} unique nodes specified", arr.Length); -// log.WriteLine("sync timeout: {0}ms, admin commands: {1}", syncTimeout, -// allowAdmin ? "enabled" : "disabled"); -// if (arr.Length == 0) -// { -// log.WriteLine("No nodes to consider"); -// selectedConfiguration = null; -// availableEndpoints = new string[0]; -// return null; -// } -// var connections = new List(arr.Length); -// RedisConnection preferred = null; - -// try -// { -// var infos = new List>(arr.Length); -// var tiebreakers = new List>(arr.Length); -// foreach (var option in arr) -// { -// if (option.IsNullOrWhiteSpace()) continue; - -// RedisConnection conn = null; -// try -// { - -// var parts = option.Split(':'); -// if (parts.Length == 0) continue; - -// string host = parts[0].Trim(); -// int port = 6379, tmp; -// if (parts.Length > 1 && int.TryParse(parts[1].Trim(), out tmp)) port = tmp; -// conn = new RedisConnection(host, port, syncTimeout: syncTimeout, allowAdmin: allowAdmin); - -// log.WriteLine("Opening connection to {0}:{1}...", host, port); -// conn.Open(); -// var info = conn.GetInfo(); -// var tiebreak = conn.Strings.GetString(0, TieBreakerKey); -// connections.Add(conn); -// infos.Add(info); -// tiebreakers.Add(tiebreak); -// } -// catch (Exception ex) -// { -// if (conn == null) -// { -// log.WriteLine("Error parsing option \"{0}\": {1}", option, ex.Message); -// } -// else -// { -// log.WriteLine("Error connecting: {0}", ex.Message); -// } -// } -// } -// List masters = new List(), slaves = new List(); -// var breakerScores = new Dictionary(); -// foreach (var tiebreak in tiebreakers) -// { -// try -// { -// if (tiebreak.Wait(syncTimeout)) -// { -// string key = tiebreak.Result; -// if (key.IsNullOrWhiteSpace()) continue; -// int score; -// if (breakerScores.TryGetValue(key, out score)) breakerScores[key] = score + 1; -// else breakerScores.Add(key, 1); -// } -// } -// catch { /* if a node is down, that's fine too */ } -// } -// // check for tie-breakers (i.e. when we store which is the master) -// switch (breakerScores.Count) -// { -// case 0: -// log.WriteLine("No tie-breakers found"); -// break; -// case 1: -// log.WriteLine("Tie-breaker is unanimous: {0}", breakerScores.Keys.Single()); -// break; -// default: -// log.WriteLine("Ambiguous tie-breakers:"); -// foreach (var kvp in breakerScores.OrderByDescending(x => x.Value)) -// { -// log.WriteLine("\t{0}: {1}", kvp.Key, kvp.Value); -// } -// break; -// } - -// for (int i = 0; i < connections.Count; i++) -// { -// log.WriteLine("Reading configuration from {0}:{1}...", connections[i].Host, connections[i].Port); -// try -// { -// if (!infos[i].Wait(syncTimeout)) -// { -// log.WriteLine("\tTimeout fetching INFO"); -// continue; -// } -// var infoPairs = new StringDictionary(); -// using (var sr = new StringReader(infos[i].Result)) -// { -// string line; -// while ((line = sr.ReadLine()) != null) -// { -// int idx = line.IndexOf(':'); -// if (idx < 0) continue; -// string key = line.Substring(0, idx).Trim(), -// value = line.Substring(idx + 1, line.Length - (idx + 1)).Trim(); -// infoPairs[key] = value; -// } -// } -// string role = infoPairs["role"]; -// switch (role) -// { -// case "slave": -// log.WriteLine("\tServer is SLAVE of {0}:{1}", -// infoPairs["master_host"], infoPairs["master_port"]); -// log.Write("\tLink is {0}, seen {1} seconds ago", -// infoPairs["master_link_status"], infoPairs["master_last_io_seconds_ago"]); -// if (infoPairs["master_sync_in_progress"] == "1") log.Write(" (sync is in progress)"); -// log.WriteLine(); -// slaves.Add(connections[i]); -// break; -// case "master": -// log.WriteLine("\tServer is MASTER, with {0} slaves", infoPairs["connected_slaves"]); -// masters.Add(connections[i]); -// break; -// default: -// log.WriteLine("\tUnknown role: {0}", role); -// break; -// } -// string tmp = infoPairs["connected_clients"]; -// int clientCount, channelCount, patternCount; -// if (tmp.IsNullOrWhiteSpace() || !int.TryParse(tmp, out clientCount)) clientCount = -1; -// tmp = infoPairs["pubsub_channels"]; -// if (tmp.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out channelCount)) channelCount = -1; -// tmp = infoPairs["pubsub_patterns"]; -// if (tmp.IsNullOrWhiteSpace(tmp) || !int.TryParse(tmp, out patternCount)) patternCount = -1; -// log.WriteLine("\tClients: {0}; channels: {1}; patterns: {2}", clientCount, channelCount, patternCount); -// } -// catch (Exception ex) -// { -// log.WriteLine("\tError reading INFO results: {0}", ex.Message); -// } -// } - -// if (newMaster == null) -// { -// switch (masters.Count) -// { -// case 0: -// switch (slaves.Count) -// { -// case 0: -// log.WriteLine("No masters or slaves found"); -// break; -// case 1: -// log.WriteLine("No masters found; selecting single slave"); -// preferred = slaves[0]; -// break; -// default: -// log.WriteLine("No masters found; considering {0} slaves...", slaves.Count); -// preferred = SelectWithTieBreak(log, slaves, breakerScores); -// break; -// } -// if (preferred != null) -// { -// if (autoMaster) -// { -// //LogException("Promoting redis SLAVE to MASTER"); -// log.WriteLine("Promoting slave to master..."); -// if (allowAdmin) -// { // can do on this connection -// preferred.Wait(preferred.Server.MakeMaster()); -// } -// else -// { // need an admin connection for this -// using (var adminPreferred = new RedisConnection(preferred.Host, preferred.Port, allowAdmin: true, syncTimeout: syncTimeout)) -// { -// adminPreferred.Open(); -// adminPreferred.Wait(adminPreferred.Server.MakeMaster()); -// } -// } -// } -// else -// { -// log.WriteLine("Slave should be promoted to master (but not done yet)..."); -// } -// } -// break; -// case 1: -// log.WriteLine("One master found; selecting"); -// preferred = masters[0]; -// break; -// default: -// log.WriteLine("Considering {0} masters...", masters.Count); -// preferred = SelectWithTieBreak(log, masters, breakerScores); -// break; -// } - - -// } -// else -// { // we have been instructed to change master server -// preferred = masters.Concat(slaves).FirstOrDefault(conn => (conn.Host + ":" + conn.Port) == newMaster); -// if (preferred == null) -// { -// log.WriteLine("Selected new master not available: {0}", newMaster); -// } -// else -// { -// int errorCount = 0; -// try -// { -// log.WriteLine("Promoting to master: {0}:{1}...", preferred.Host, preferred.Port); -// preferred.Wait(preferred.Server.MakeMaster()); -// preferred.Strings.Set(0, TieBreakerKey, newMaster); -// preferred.Wait(preferred.Publish(RedisMasterChangedChannel, newMaster)); -// } -// catch (Exception ex) -// { -// log.WriteLine("\t{0}", ex.Message); -// errorCount++; -// } - -// if (errorCount == 0) // only make slaves if the master was happy -// { -// foreach (var conn in masters.Concat(slaves)) -// { -// if (conn == preferred) continue; // can't make self a slave! - -// try -// { -// log.WriteLine("Enslaving: {0}:{1}...", conn.Host, conn.Port); -// // set the tie-breaker **first** in case of problems -// conn.Strings.Set(0, TieBreakerKey, newMaster); -// // and broadcast to anyone who thinks this is the master -// conn.Publish(RedisMasterChangedChannel, newMaster); -// // now make it a slave -// conn.Wait(conn.Server.MakeSlave(preferred.Host, preferred.Port)); -// } -// catch (Exception ex) -// { -// log.WriteLine("\t{0}", ex.Message); -// errorCount++; -// } -// } -// } -// if (errorCount != 0) -// { -// log.WriteLine("Things didn't go smoothly; CHECK WHAT HAPPENED!"); -// } - -// // want the connection disposed etc -// preferred = null; -// } -// } - -// if (preferred == null) -// { -// selectedConfiguration = null; -// } -// else -// { -// selectedConfiguration = preferred.Host + ":" + preferred.Port; -// log.WriteLine("Selected server {0}", selectedConfiguration); -// } - -// availableEndpoints = (from conn in masters.Concat(slaves) -// select conn.Host + ":" + conn.Port).ToArray(); -// return preferred; -// } -// finally -// { -// foreach (var conn in connections) -// { -// if (conn != null && conn != preferred) try { conn.Dispose(); } -// catch { } -// } -// } -// } - -// } - -// public static class ConnectionUtilsExtensions -// { -// public static bool IsNullOrWhiteSpace(this string str) -// { -// return str == null || str.Trim().Length == 0; -// } -// } -//} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.Async.cs b/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.Async.cs new file mode 100644 index 00000000..0c75d760 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.Async.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Internal; +using ServiceStack.Redis.Pipeline; + +namespace ServiceStack.Redis.Generic +{ + /// + /// A complete redis command, with method to send command, receive response, and run callback on success or failure + /// + internal partial class QueuedRedisTypedCommand : QueuedRedisOperation + { + private Delegate _asyncReturnCommand; + partial void OnExecuteThrowIfAsync() + { + if (_asyncReturnCommand is object) + { + throw new InvalidOperationException("An async return command was present, but the queued operation is being processed synchronously"); + } + } + private QueuedRedisTypedCommand SetAsyncReturnCommand(Delegate value) + { + if (_asyncReturnCommand is object && _asyncReturnCommand != value) + throw new InvalidOperationException("Only a single async return command can be assigned"); + _asyncReturnCommand = value; + return this; + } + + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> VoidReturnCommandAsync) + => SetAsyncReturnCommand(VoidReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> IntReturnCommandAsync) + => SetAsyncReturnCommand(IntReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> LongReturnCommandAsync) + => SetAsyncReturnCommand(LongReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> BoolReturnCommandAsync) + => SetAsyncReturnCommand(BoolReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> BytesReturnCommandAsync) + => SetAsyncReturnCommand(BytesReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> MultiBytesReturnCommandAsync) + => SetAsyncReturnCommand(MultiBytesReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> StringReturnCommandAsync) + => SetAsyncReturnCommand(StringReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask>> MultiStringReturnCommandAsync) + => SetAsyncReturnCommand(MultiStringReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> DoubleReturnCommandAsync) + => SetAsyncReturnCommand(DoubleReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask>> MultiObjectReturnCommandAsync) + => SetAsyncReturnCommand(MultiObjectReturnCommandAsync); + internal QueuedRedisTypedCommand WithAsyncReturnCommand(Func, ValueTask> ObjectReturnCommandAsync) + => SetAsyncReturnCommand(ObjectReturnCommandAsync); + + public ValueTask ExecuteAsync(IRedisTypedClientAsync client) + { + try + { + switch (_asyncReturnCommand) + { + case null: + ExecuteThrowIfSync(); + return default; + case Func, ValueTask> VoidReturnCommandAsync: + return VoidReturnCommandAsync(client); + case Func, ValueTask> IntReturnCommandAsync: + return IntReturnCommandAsync(client).Await(); + case Func, ValueTask> LongReturnCommandAsync: + return LongReturnCommandAsync(client).Await(); + case Func, ValueTask> DoubleReturnCommandAsync: + return DoubleReturnCommandAsync(client).Await(); + case Func, ValueTask> BytesReturnCommandAsync: + return BytesReturnCommandAsync(client).Await(); + case Func, ValueTask> StringReturnCommandAsync: + return StringReturnCommandAsync(client).Await(); + case Func, ValueTask> MultiBytesReturnCommandAsync: + return MultiBytesReturnCommandAsync(client).Await(); + case Func, ValueTask>> MultiStringReturnCommandAsync: + return MultiStringReturnCommandAsync(client).Await(); + case object obj: + ExecuteThrowIfSync(); + return default; + } + } + catch (Exception ex) + { + Log.Error(ex); + return default; // non-async version swallows + } + } + + protected void ExecuteThrowIfSync() + { + if (VoidReturnCommand is object + || IntReturnCommand is object + || LongReturnCommand is object + || BoolReturnCommand is object + || BytesReturnCommand is object + || MultiBytesReturnCommand is object + || StringReturnCommand is object + || MultiStringReturnCommand is object + || DoubleReturnCommand is object + || MultiObjectReturnCommand is object + || ObjectReturnCommand is object) + { + throw new InvalidOperationException("A sync return command was present, but the queued operation is being processed asynchronously"); + } + } + + } +} diff --git a/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.cs b/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.cs index ca8b2870..fe9c8b0b 100644 --- a/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.cs +++ b/src/ServiceStack.Redis/Generic/QueuedRedisTypedCommand.cs @@ -1,14 +1,14 @@ -using System; +using ServiceStack.Redis.Pipeline; +using System; using System.Collections.Generic; -using ServiceStack.Redis.Generic; -using ServiceStack.Redis.Pipeline; +using System.Threading.Tasks; namespace ServiceStack.Redis.Generic { /// /// A complete redis command, with method to send command, receive response, and run callback on success or failure /// - internal class QueuedRedisTypedCommand : QueuedRedisOperation + internal partial class QueuedRedisTypedCommand : QueuedRedisOperation { public Action> VoidReturnCommand { get; set; } @@ -74,5 +74,7 @@ public void Execute(IRedisTypedClient client) } } + private void ExecuteThrowIfAsync() => OnExecuteThrowIfAsync(); + partial void OnExecuteThrowIfAsync(); } } diff --git a/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.Async.cs b/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.Async.cs new file mode 100644 index 00000000..86de9a27 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.Async.cs @@ -0,0 +1,55 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + internal partial class RedisClientHash + : IRedisHashAsync + { + IRedisTypedClientAsync AsyncClient => client; + + ValueTask IRedisHashAsync.AddAsync(KeyValuePair item, CancellationToken token) + => AsyncClient.SetEntryInHashAsync(this, item.Key, item.Value, token).Await(); + + ValueTask IRedisHashAsync.AddAsync(TKey key, T value, CancellationToken token) + => AsyncClient.SetEntryInHashAsync(this, key, value, token).Await(); + + ValueTask IRedisHashAsync.ClearAsync(CancellationToken token) + => AsyncClient.RemoveEntryAsync(new[] { this }, token).Await(); + + ValueTask IRedisHashAsync.ContainsKeyAsync(TKey key, CancellationToken token) + => AsyncClient.HashContainsEntryAsync(this, key, token); + + ValueTask IRedisHashAsync.CountAsync(CancellationToken token) + => AsyncClient.GetHashCountAsync(this, token).AsInt32(); + + ValueTask> IRedisHashAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllEntriesFromHashAsync(this, token); + + async IAsyncEnumerator> IAsyncEnumerable>.GetAsyncEnumerator(CancellationToken token) + { + var all = await AsyncClient.GetAllEntriesFromHashAsync(this, token).ConfigureAwait(false); + foreach (var pair in all) + { + yield return pair; + } + } + + ValueTask IRedisHashAsync.RemoveAsync(TKey key, CancellationToken token) + => AsyncClient.RemoveEntryFromHashAsync(this, key, token); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.cs b/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.cs index f128c895..20fd91bc 100644 --- a/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.cs +++ b/src/ServiceStack.Redis/Generic/RedisClientHash.Generic.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,7 +19,7 @@ namespace ServiceStack.Redis.Generic /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientHash + internal partial class RedisClientHash : IRedisHash { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisClientList.Generic.Async.cs b/src/ServiceStack.Redis/Generic/RedisClientList.Generic.Async.cs new file mode 100644 index 00000000..66b3e596 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisClientList.Generic.Async.cs @@ -0,0 +1,184 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + internal partial class RedisClientList + : IRedisListAsync + { + IRedisTypedClientAsync AsyncClient => client; + IRedisListAsync AsAsync() => this; + + async ValueTask IRedisListAsync.AddRangeAsync(IEnumerable values, CancellationToken token) + { + //TODO: replace it with a pipeline implementation ala AddRangeToSet + foreach (var value in values) + { + await AsyncClient.AddItemToListAsync(this, value, token).ConfigureAwait(false); + } + } + + ValueTask IRedisListAsync.AppendAsync(T value, CancellationToken token) + => AsyncClient.AddItemToListAsync(this, value, token); + + ValueTask IRedisListAsync.BlockingDequeueAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingDequeueItemFromListAsync(this, timeOut, token); + + ValueTask IRedisListAsync.BlockingPopAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingPopItemFromListAsync(this, timeOut, token); + + ValueTask IRedisListAsync.BlockingRemoveStartAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingRemoveStartFromListAsync(this, timeOut, token); + + ValueTask IRedisListAsync.CountAsync(CancellationToken token) + => AsyncClient.GetListCountAsync(this, token).AsInt32(); + + ValueTask IRedisListAsync.DequeueAsync(CancellationToken token) + => AsyncClient.DequeueItemFromListAsync(this, token); + + ValueTask IRedisListAsync.EnqueueAsync(T value, CancellationToken token) + => AsyncClient.EnqueueItemOnListAsync(this, value, token); + + ValueTask> IRedisListAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromListAsync(this, token); + + async IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + { + var count = await AsAsync().CountAsync(token).ConfigureAwait(false); + if (count <= PageLimit) + { + var all = await AsyncClient.GetAllItemsFromListAsync(this, token).ConfigureAwait(false); + foreach (var item in all) + { + yield return item; + } + } + else + { + // from GetPagingEnumerator() + var skip = 0; + List pageResults; + do + { + pageResults = await AsyncClient.GetRangeFromListAsync(this, skip, PageLimit, token).ConfigureAwait(false); + foreach (var result in pageResults) + { + yield return result; + } + skip += PageLimit; + } while (pageResults.Count == PageLimit); + } + } + + ValueTask> IRedisListAsync.GetRangeAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.GetRangeFromListAsync(this, startingFrom, endingAt, token); + + ValueTask> IRedisListAsync.GetRangeFromSortedListAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.SortListAsync(this, startingFrom, endingAt, token); + + ValueTask IRedisListAsync.PopAndPushAsync(IRedisListAsync toList, CancellationToken token) + => AsyncClient.PopAndPushItemBetweenListsAsync(this, toList, token); + + ValueTask IRedisListAsync.PopAsync(CancellationToken token) + => AsyncClient.PopItemFromListAsync(this, token); + + ValueTask IRedisListAsync.PrependAsync(T value, CancellationToken token) + => AsyncClient.PrependItemToListAsync(this, value, token); + + ValueTask IRedisListAsync.PushAsync(T value, CancellationToken token) + => AsyncClient.PushItemToListAsync(this, value, token); + + ValueTask IRedisListAsync.RemoveAllAsync(CancellationToken token) + => AsyncClient.RemoveAllFromListAsync(this, token); + + ValueTask IRedisListAsync.RemoveEndAsync(CancellationToken token) + => AsyncClient.RemoveEndFromListAsync(this, token); + + ValueTask IRedisListAsync.RemoveStartAsync(CancellationToken token) + => AsyncClient.RemoveStartFromListAsync(this, token); + + ValueTask IRedisListAsync.RemoveValueAsync(T value, CancellationToken token) + => AsyncClient.RemoveItemFromListAsync(this, value, token); + + ValueTask IRedisListAsync.RemoveValueAsync(T value, int noOfMatches, CancellationToken token) + => AsyncClient.RemoveItemFromListAsync(this, value, noOfMatches, token); + + ValueTask IRedisListAsync.TrimAsync(int keepStartingFrom, int keepEndingAt, CancellationToken token) + => AsyncClient.TrimListAsync(this, keepStartingFrom, keepEndingAt, token); + + async ValueTask IRedisListAsync.RemoveAsync(T value, CancellationToken token) + { + var index = await AsAsync().IndexOfAsync(value, token).ConfigureAwait(false); + if (index != -1) + { + await AsAsync().RemoveAtAsync(index, token).ConfigureAwait(false); + return true; + } + return false; + } + + ValueTask IRedisListAsync.AddAsync(T value, CancellationToken token) + => AsyncClient.AddItemToListAsync(this, value, token); + + async ValueTask IRedisListAsync.RemoveAtAsync(int index, CancellationToken token) + { + //TODO: replace with native implementation when one exists + + var nativeClient = client.NativeClient as IRedisNativeClientAsync ?? throw new NotSupportedException( + $"The native client ('{client.NativeClient.GetType().Name}') does not implement {nameof(IRedisNativeClientAsync)}"); + + var markForDelete = Guid.NewGuid().ToString(); + await nativeClient.LSetAsync(listId, index, Encoding.UTF8.GetBytes(markForDelete), token).ConfigureAwait(false); + + const int removeAll = 0; + await nativeClient.LRemAsync(listId, removeAll, Encoding.UTF8.GetBytes(markForDelete), token).ConfigureAwait(false); + } + + async ValueTask IRedisListAsync.ContainsAsync(T value, CancellationToken token) + { + //TODO: replace with native implementation when exists + await foreach (var existingItem in this.ConfigureAwait(false).WithCancellation(token)) + { + if (Equals(existingItem, value)) return true; + } + return false; + } + + ValueTask IRedisListAsync.ClearAsync(CancellationToken token) + => AsyncClient.RemoveAllFromListAsync(this, token); + + async ValueTask IRedisListAsync.IndexOfAsync(T value, CancellationToken token) + { + //TODO: replace with native implementation when exists + var i = 0; + await foreach (var existingItem in this.ConfigureAwait(false).WithCancellation(token)) + { + if (Equals(existingItem, value)) return i; + i++; + } + return -1; + } + + ValueTask IRedisListAsync.ElementAtAsync(int index, CancellationToken token) + => AsyncClient.GetItemFromListAsync(this, index, token); + + ValueTask IRedisListAsync.SetValueAsync(int index, T value, CancellationToken token) + => AsyncClient.SetItemInListAsync(this, index, value, token); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisClientList.Generic.cs b/src/ServiceStack.Redis/Generic/RedisClientList.Generic.cs index 41eb1ae9..61cd0049 100644 --- a/src/ServiceStack.Redis/Generic/RedisClientList.Generic.cs +++ b/src/ServiceStack.Redis/Generic/RedisClientList.Generic.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -17,7 +17,7 @@ namespace ServiceStack.Redis.Generic { - internal class RedisClientList + internal partial class RedisClientList : IRedisList { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.Async.cs b/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.Async.cs new file mode 100644 index 00000000..0d0ba19b --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.Async.cs @@ -0,0 +1,109 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + internal partial class RedisClientSet + : IRedisSetAsync + { + IRedisTypedClientAsync AsyncClient => client; + + ValueTask IRedisSetAsync.AddAsync(T value, CancellationToken token) + => AsyncClient.AddItemToSetAsync(this, value, token); + + IRedisSetAsync AsAsync() => this; + + ValueTask IRedisSetAsync.ClearAsync(CancellationToken token) + => AsyncClient.RemoveEntryAsync(setId, token).Await(); + + ValueTask IRedisSetAsync.ContainsAsync(T item, CancellationToken token) + => AsyncClient.SetContainsItemAsync(this, item, token); + + ValueTask IRedisSetAsync.CountAsync(CancellationToken token) + => AsyncClient.GetSetCountAsync(this, token).AsInt32(); + + ValueTask> IRedisSetAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromSetAsync(this, token); + + async IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + { + var count = await AsAsync().CountAsync(token).ConfigureAwait(false); + if (count <= PageLimit) + { + var all = await AsyncClient.GetAllItemsFromSetAsync(this, token).ConfigureAwait(false); + foreach (var item in all) + { + yield return item; + } + } + else + { + // from GetPagingEnumerator + var skip = 0; + List pageResults; + do + { + pageResults = await AsyncClient.GetSortedEntryValuesAsync(this, skip, skip + PageLimit - 1, token).ConfigureAwait(false); + foreach (var result in pageResults) + { + yield return result; + } + skip += PageLimit; + } while (pageResults.Count == PageLimit); + } + } + + ValueTask IRedisSetAsync.GetDifferencesAsync(IRedisSetAsync[] withSets, CancellationToken token) + => AsyncClient.StoreUnionFromSetsAsync(this, withSets, token); + + ValueTask IRedisSetAsync.GetDifferencesAsync(params IRedisSetAsync[] withSets) + => AsAsync().GetDifferencesAsync(withSets, token: default); + + ValueTask IRedisSetAsync.GetRandomItemAsync(CancellationToken token) + => AsyncClient.GetRandomItemFromSetAsync(this, token); + + ValueTask IRedisSetAsync.MoveToAsync(T item, IRedisSetAsync toSet, CancellationToken token) + => AsyncClient.MoveBetweenSetsAsync(this, toSet, item, token); + + ValueTask IRedisSetAsync.PopRandomItemAsync(CancellationToken token) + => AsyncClient.PopItemFromSetAsync(this, token); + + ValueTask IRedisSetAsync.PopulateWithDifferencesOfAsync(IRedisSetAsync fromSet, IRedisSetAsync[] withSets, CancellationToken token) + => AsyncClient.StoreDifferencesFromSetAsync(this, fromSet, withSets, token); + + ValueTask IRedisSetAsync.PopulateWithDifferencesOfAsync(IRedisSetAsync fromSet, params IRedisSetAsync[] withSets) + => AsAsync().PopulateWithDifferencesOfAsync(fromSet, withSets, token: default); + + ValueTask IRedisSetAsync.PopulateWithIntersectOfAsync(IRedisSetAsync[] sets, CancellationToken token) + => AsyncClient.StoreIntersectFromSetsAsync(this, sets, token); + + ValueTask IRedisSetAsync.PopulateWithIntersectOfAsync(params IRedisSetAsync[] sets) + => AsAsync().PopulateWithIntersectOfAsync(sets, token: default); + + ValueTask IRedisSetAsync.PopulateWithUnionOfAsync(IRedisSetAsync[] sets, CancellationToken token) + => AsyncClient.StoreUnionFromSetsAsync(this, sets, token); + + ValueTask IRedisSetAsync.PopulateWithUnionOfAsync(params IRedisSetAsync[] sets) + => AsAsync().PopulateWithUnionOfAsync(sets, token: default); + + ValueTask IRedisSetAsync.RemoveAsync(T value, CancellationToken token) + => AsyncClient.RemoveItemFromSetAsync(this, value, token).AwaitAsTrue(); // see Remove for why "true" + + ValueTask> IRedisSetAsync.SortAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.GetSortedEntryValuesAsync(this, startingFrom, endingAt, token); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.cs b/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.cs index c6a2514a..5c48dd6a 100644 --- a/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.cs +++ b/src/ServiceStack.Redis/Generic/RedisClientSet.Generic.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,7 +19,7 @@ namespace ServiceStack.Redis.Generic /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientSet + internal partial class RedisClientSet : IRedisSet { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.Async.cs b/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.Async.cs new file mode 100644 index 00000000..fae91472 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.Async.cs @@ -0,0 +1,136 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + internal partial class RedisClientSortedSet + : IRedisSortedSetAsync + { + IRedisTypedClientAsync AsyncClient => client; + + IRedisSortedSetAsync AsAsync() => this; + + ValueTask IRedisSortedSetAsync.AddAsync(T item, double score, CancellationToken token) + => AsyncClient.AddItemToSortedSetAsync(this, item, score, token); + + ValueTask IRedisSortedSetAsync.CountAsync(CancellationToken token) + => AsyncClient.GetSortedSetCountAsync(this, token).AsInt32(); + + ValueTask> IRedisSortedSetAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromSortedSetAsync(this, token); + + ValueTask> IRedisSortedSetAsync.GetAllDescendingAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromSortedSetDescAsync(this, token); + + async IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + { + var count = await AsAsync().CountAsync(token).ConfigureAwait(false); + if (count <= PageLimit) + { + var all = await AsyncClient.GetAllItemsFromSortedSetAsync(this, token).ConfigureAwait(false); + foreach (var item in all) + { + yield return item; + } + } + else + { + // from GetPagingEnumerator(); + var skip = 0; + List pageResults; + do + { + pageResults = await AsyncClient.GetRangeFromSortedSetAsync(this, skip, skip + PageLimit - 1, token).ConfigureAwait(false); + foreach (var result in pageResults) + { + yield return result; + } + skip += PageLimit; + } while (pageResults.Count == PageLimit); + } + } + + ValueTask IRedisSortedSetAsync.GetItemScoreAsync(T item, CancellationToken token) + => AsyncClient.GetItemScoreInSortedSetAsync(this, item, token); + + ValueTask> IRedisSortedSetAsync.GetRangeAsync(int fromRank, int toRank, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetAsync(this, fromRank, toRank, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByHighestScoreAsync(double fromScore, double toScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(this, fromScore, toScore, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByHighestScoreAsync(double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(this, fromScore, toScore, skip, take, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByLowestScoreAsync(double fromScore, double toScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(this, fromScore, toScore, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByLowestScoreAsync(double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(this, fromScore, toScore, skip, take, token); + + ValueTask IRedisSortedSetAsync.IncrementItemAsync(T item, double incrementBy, CancellationToken token) + => AsyncClient.IncrementItemInSortedSetAsync(this, item, incrementBy, token); + + ValueTask IRedisSortedSetAsync.IndexOfAsync(T item, CancellationToken token) + => AsyncClient.GetItemIndexInSortedSetAsync(this, item, token).AsInt32(); + + ValueTask IRedisSortedSetAsync.IndexOfDescendingAsync(T item, CancellationToken token) + => AsyncClient.GetItemIndexInSortedSetDescAsync(this, item, token); + + ValueTask IRedisSortedSetAsync.PopItemWithHighestScoreAsync(CancellationToken token) + => AsyncClient.PopItemWithHighestScoreFromSortedSetAsync(this, token); + + ValueTask IRedisSortedSetAsync.PopItemWithLowestScoreAsync(CancellationToken token) + => AsyncClient.PopItemWithLowestScoreFromSortedSetAsync(this, token); + + ValueTask IRedisSortedSetAsync.PopulateWithIntersectOfAsync(IRedisSortedSetAsync[] setIds, CancellationToken token) + => AsyncClient.StoreIntersectFromSortedSetsAsync(this, setIds, token); + + ValueTask IRedisSortedSetAsync.PopulateWithIntersectOfAsync(IRedisSortedSetAsync[] setIds, string[] args, CancellationToken token) + => AsyncClient.StoreIntersectFromSortedSetsAsync(this, setIds, args, token); + + ValueTask IRedisSortedSetAsync.PopulateWithUnionOfAsync(IRedisSortedSetAsync[] setIds, CancellationToken token) + => AsyncClient.StoreUnionFromSortedSetsAsync(this, setIds, token); + + ValueTask IRedisSortedSetAsync.PopulateWithUnionOfAsync(IRedisSortedSetAsync[] setIds, string[] args, CancellationToken token) + => AsyncClient.StoreUnionFromSortedSetsAsync(this, setIds, args, token); + + ValueTask IRedisSortedSetAsync.RemoveRangeAsync(int minRank, int maxRank, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetAsync(this, minRank, maxRank, token); + + ValueTask IRedisSortedSetAsync.RemoveRangeByScoreAsync(double fromScore, double toScore, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetByScoreAsync(this, fromScore, toScore, token); + + ValueTask IRedisSortedSetAsync.ClearAsync(CancellationToken token) + => AsyncClient.RemoveEntryAsync(setId, token).Await(); + + ValueTask IRedisSortedSetAsync.ContainsAsync(T value, CancellationToken token) + => AsyncClient.SortedSetContainsItemAsync(this, value, token); + + ValueTask IRedisSortedSetAsync.AddAsync(T value, CancellationToken token) + => AsyncClient.AddItemToSortedSetAsync(this, value, token); + + ValueTask IRedisSortedSetAsync.RemoveAsync(T value, CancellationToken token) + => AsyncClient.RemoveItemFromSortedSetAsync(this, value, token).AwaitAsTrue(); // see Remove for why "true" + + ValueTask IRedisSortedSetAsync.PopulateWithIntersectOfAsync(params IRedisSortedSetAsync[] setIds) + => AsAsync().PopulateWithIntersectOfAsync(setIds, token: default); + + ValueTask IRedisSortedSetAsync.PopulateWithUnionOfAsync(params IRedisSortedSetAsync[] setIds) + => AsAsync().PopulateWithUnionOfAsync(setIds, token: default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.cs b/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.cs index bda2f1eb..5e7fdf13 100644 --- a/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.cs +++ b/src/ServiceStack.Redis/Generic/RedisClientSortedSet.Generic.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,7 +19,7 @@ namespace ServiceStack.Redis.Generic /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientSortedSet + internal partial class RedisClientSortedSet : IRedisSortedSet { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient.Async.cs new file mode 100644 index 00000000..01018721 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient.Async.cs @@ -0,0 +1,765 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Data; +using ServiceStack.Model; +using ServiceStack.Redis.Internal; +using ServiceStack.Text; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + partial class RedisTypedClient + : IRedisTypedClientAsync + { + public IRedisTypedClientAsync AsAsync() => this; + + private IRedisClientAsync AsyncClient => client; + private IRedisNativeClientAsync AsyncNative => client; + + IRedisSetAsync IRedisTypedClientAsync.TypeIdsSet => TypeIdsSetRaw; + + IRedisClientAsync IRedisTypedClientAsync.RedisClient => client; + + internal ValueTask ExpectQueuedAsync(CancellationToken token) + => client.ExpectQueuedAsync(token); + + internal ValueTask ExpectOkAsync(CancellationToken token) + => client.ExpectOkAsync(token); + + internal ValueTask ReadMultiDataResultCountAsync(CancellationToken token) + => client.ReadMultiDataResultCountAsync(token); + + ValueTask IRedisTypedClientAsync.GetValueAsync(string key, CancellationToken token) + => DeserializeValueAsync(AsyncNative.GetAsync(key, token)); + + async ValueTask IRedisTypedClientAsync.SetValueAsync(string key, T entity, CancellationToken token) + { + AssertNotNull(key); + await AsyncClient.SetAsync(key, SerializeValue(entity), token).ConfigureAwait(false); + await client.RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + } + + Task IEntityStoreAsync.GetByIdAsync(object id, CancellationToken token) + { + var key = client.UrnKey(id); + return AsAsync().GetValueAsync(key, token).AsTask(); + } + + internal ValueTask FlushSendBufferAsync(CancellationToken token) + => client.FlushSendBufferAsync(token); + + internal ValueTask AddTypeIdsRegisteredDuringPipelineAsync(CancellationToken token) + => client.AddTypeIdsRegisteredDuringPipelineAsync(token); + + async Task> IEntityStoreAsync.GetByIdsAsync(IEnumerable ids, CancellationToken token) + { + if (ids != null) + { + var urnKeys = ids.Map(x => client.UrnKey(x)); + if (urnKeys.Count != 0) + return await AsAsync().GetValuesAsync(urnKeys, token).ConfigureAwait(false); + } + + return new List(); + } + + async Task> IEntityStoreAsync.GetAllAsync(CancellationToken token) + { + var allKeys = await AsyncClient.GetAllItemsFromSetAsync(this.TypeIdsSetKey, token).ConfigureAwait(false); + return await AsAsync().GetByIdsAsync(allKeys.ToArray(), token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.StoreAsync(T entity, CancellationToken token) + { + var urnKey = client.UrnKey(entity); + await AsAsync().SetValueAsync(urnKey, entity, token).ConfigureAwait(false); + return entity; + } + + async Task IEntityStoreAsync.StoreAllAsync(IEnumerable entities, CancellationToken token) + { + if (PrepareStoreAll(entities, out var keys, out var values, out var entitiesList)) + { + await AsyncNative.MSetAsync(keys, values, token).ConfigureAwait(false); + await client.RegisterTypeIdsAsync(entitiesList, token).ConfigureAwait(false); + } + } + + async Task IEntityStoreAsync.DeleteAsync(T entity, CancellationToken token) + { + var urnKey = client.UrnKey(entity); + await AsyncClient.RemoveEntryAsync(new[] { urnKey }, token).ConfigureAwait(false); + await client.RemoveTypeIdsByValueAsync(entity, token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.DeleteByIdAsync(object id, CancellationToken token) + { + var urnKey = client.UrnKey(id); + + await AsyncClient.RemoveEntryAsync(new[] { urnKey }, token).ConfigureAwait(false); + await client.RemoveTypeIdsByIdAsync(id.ToString(), token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.DeleteByIdsAsync(IEnumerable ids, CancellationToken token) + { + if (ids == null) return; + + var idStrings = ids.Cast().Select(x => x.ToString()).ToArray(); + var urnKeys = idStrings.Select(t => client.UrnKey(t)).ToArray(); + if (urnKeys.Length > 0) + { + await AsyncClient.RemoveEntryAsync(urnKeys, token).ConfigureAwait(false); + await client.RemoveTypeIdsByIdsAsync(idStrings, token).ConfigureAwait(false); + } + } + + async Task IEntityStoreAsync.DeleteAllAsync(CancellationToken token) + { + await DeleteAllAsync(0,RedisConfig.CommandKeysBatchSize, token).ConfigureAwait(false); + } + + private async Task DeleteAllAsync(ulong cursor, int batchSize, CancellationToken token) + { + do + { + var scanResult = await AsyncNative.SScanAsync(this.TypeIdsSetKey, cursor, batchSize, token: token).ConfigureAwait(false); + cursor = scanResult.Cursor; + var urnKeys = scanResult.Results.Select(x => client.UrnKey(Encoding.UTF8.GetString(x))).ToArray(); + if (urnKeys.Length > 0) + { + await AsyncClient.RemoveEntryAsync(urnKeys, token).ConfigureAwait(false); + } + } while (cursor != 0); + await AsyncClient.RemoveEntryAsync(new[] { this.TypeIdsSetKey }, token).ConfigureAwait(false); + } + + async ValueTask> IRedisTypedClientAsync.GetValuesAsync(List keys, CancellationToken token) + { + if (keys.IsNullOrEmpty()) return new List(); + + var resultBytesArray = await AsyncNative.MGetAsync(keys.ToArray(), token).ConfigureAwait(false); + return ProcessGetValues(resultBytesArray); + } + + ValueTask> IRedisTypedClientAsync.CreateTransactionAsync(CancellationToken token) + { + IRedisTypedTransactionAsync obj = new RedisTypedTransaction(this, true); + return obj.AsValueTaskResult(); + } + + IRedisTypedPipelineAsync IRedisTypedClientAsync.CreatePipeline() + => new RedisTypedPipeline(this); + + + ValueTask IRedisTypedClientAsync.AcquireLockAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.AcquireLockAsync(this.TypeLockKey, timeOut, token); + + long IRedisTypedClientAsync.Db => AsyncClient.Db; + + IHasNamed> IRedisTypedClientAsync.Lists => Lists as IHasNamed> ?? throw new NotSupportedException("The provided Lists does not support IRedisListAsync"); + IHasNamed> IRedisTypedClientAsync.Sets => Sets as IHasNamed> ?? throw new NotSupportedException("The provided Sets does not support IRedisSetAsync"); + IHasNamed> IRedisTypedClientAsync.SortedSets => SortedSets as IHasNamed> ?? throw new NotSupportedException("The provided SortedSets does not support IRedisSortedSetAsync"); + + IRedisHashAsync IRedisTypedClientAsync.GetHash(string hashId) => GetHash(hashId) as IRedisHashAsync ?? throw new NotSupportedException("The provided Hash does not support IRedisHashAsync"); + + ValueTask IRedisTypedClientAsync.SelectAsync(long db, CancellationToken token) + => AsyncClient.SelectAsync(db, token); + + ValueTask> IRedisTypedClientAsync.GetAllKeysAsync(CancellationToken token) + => AsyncClient.GetAllKeysAsync(token); + + ValueTask IRedisTypedClientAsync.SetSequenceAsync(int value, CancellationToken token) + => AsyncNative.GetSetAsync(SequenceKey, Encoding.UTF8.GetBytes(value.ToString()), token).Await(); + + ValueTask IRedisTypedClientAsync.GetNextSequenceAsync(CancellationToken token) + => AsAsync().IncrementValueAsync(SequenceKey, token); + + ValueTask IRedisTypedClientAsync.GetNextSequenceAsync(int incrBy, CancellationToken token) + => AsAsync().IncrementValueByAsync(SequenceKey, incrBy, token); + + ValueTask IRedisTypedClientAsync.GetEntryTypeAsync(string key, CancellationToken token) + => AsyncClient.GetEntryTypeAsync(key, token); + + ValueTask IRedisTypedClientAsync.GetRandomKeyAsync(CancellationToken token) + => AsyncClient.GetRandomKeyAsync(token); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void AssertNotNull(object obj, string name = "key") + { + if (obj is null) Throw(name); + static void Throw(string name) => throw new ArgumentNullException(name); + } + + async ValueTask IRedisTypedClientAsync.SetValueAsync(string key, T entity, TimeSpan expireIn, CancellationToken token) + { + AssertNotNull(key); + await AsyncClient.SetAsync(key, SerializeValue(entity), expireIn, token).ConfigureAwait(false); + await client.RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + } + + async ValueTask IRedisTypedClientAsync.SetValueIfNotExistsAsync(string key, T entity, CancellationToken token) + { + var success = await AsyncNative.SetNXAsync(key, SerializeValue(entity), token).IsSuccessAsync().ConfigureAwait(false); + if (success) await client.RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + return success; + } + + async ValueTask IRedisTypedClientAsync.SetValueIfExistsAsync(string key, T entity, CancellationToken token) + { + var success = await AsyncNative.SetAsync(key, SerializeValue(entity), exists: true, token: token).ConfigureAwait(false); + if (success) await client.RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + return success; + } + + async ValueTask IRedisTypedClientAsync.StoreAsync(T entity, TimeSpan expireIn, CancellationToken token) + { + var urnKey = client.UrnKey(entity); + await AsAsync().SetValueAsync(urnKey, entity, expireIn, token).ConfigureAwait(false); + return entity; + } + + ValueTask IRedisTypedClientAsync.GetAndSetValueAsync(string key, T value, CancellationToken token) + => DeserializeValueAsync(AsyncNative.GetSetAsync(key, SerializeValue(value), token)); + + ValueTask IRedisTypedClientAsync.ContainsKeyAsync(string key, CancellationToken token) + => AsyncNative.ExistsAsync(key, token).IsSuccessAsync(); + + ValueTask IRedisTypedClientAsync.RemoveEntryAsync(string key, CancellationToken token) + => AsyncNative.DelAsync(key, token).IsSuccessAsync(); + + ValueTask IRedisTypedClientAsync.RemoveEntryAsync(string[] keys, CancellationToken token) + => AsyncNative.DelAsync(keys, token).IsSuccessAsync(); + + async ValueTask IRedisTypedClientAsync.RemoveEntryAsync(IHasStringId[] entities, CancellationToken token) + { + var ids = entities.Select(x => x.Id).ToArray(); + var success = await AsyncNative.DelAsync(ids, token).IsSuccessAsync().ConfigureAwait(false); + if (success) await client.RemoveTypeIdsByValuesAsync(ids, token).ConfigureAwait(false); + return success; + } + + ValueTask IRedisTypedClientAsync.IncrementValueAsync(string key, CancellationToken token) + => AsyncNative.IncrAsync(key, token); + + ValueTask IRedisTypedClientAsync.IncrementValueByAsync(string key, int count, CancellationToken token) + => AsyncNative.IncrByAsync(key, count, token); + + ValueTask IRedisTypedClientAsync.DecrementValueAsync(string key, CancellationToken token) + => AsyncNative.DecrAsync(key, token); + + ValueTask IRedisTypedClientAsync.DecrementValueByAsync(string key, int count, CancellationToken token) + => AsyncNative.DecrByAsync(key, count, token); + + ValueTask IRedisTypedClientAsync.ExpireInAsync(object id, TimeSpan expiresIn, CancellationToken token) + { + var key = client.UrnKey(id); + return AsyncClient.ExpireEntryInAsync(key, expiresIn, token); + } + + ValueTask IRedisTypedClientAsync.ExpireAtAsync(object id, DateTime expireAt, CancellationToken token) + { + var key = client.UrnKey(id); + return AsyncClient.ExpireEntryAtAsync(key, expireAt, token); + } + + ValueTask IRedisTypedClientAsync.ExpireEntryInAsync(string key, TimeSpan expireIn, CancellationToken token) + => AsyncClient.ExpireEntryInAsync(key, expireIn, token); + + ValueTask IRedisTypedClientAsync.ExpireEntryAtAsync(string key, DateTime expireAt, CancellationToken token) + => AsyncClient.ExpireEntryAtAsync(key, expireAt, token); + + async ValueTask IRedisTypedClientAsync.GetTimeToLiveAsync(string key, CancellationToken token) + => TimeSpan.FromSeconds(await AsyncNative.TtlAsync(key, token).ConfigureAwait(false)); + + ValueTask IRedisTypedClientAsync.ForegroundSaveAsync(CancellationToken token) + => AsyncClient.ForegroundSaveAsync(token); + + ValueTask IRedisTypedClientAsync.BackgroundSaveAsync(CancellationToken token) + => AsyncClient.BackgroundSaveAsync(token); + + ValueTask IRedisTypedClientAsync.FlushDbAsync(CancellationToken token) + => AsyncClient.FlushDbAsync(token); + + ValueTask IRedisTypedClientAsync.FlushAllAsync(CancellationToken token) + => new ValueTask(AsyncClient.FlushAllAsync(token)); + + async ValueTask IRedisTypedClientAsync.SearchKeysAsync(string pattern, CancellationToken token) + { + var strKeys = await AsyncClient.SearchKeysAsync(pattern, token).ConfigureAwait(false); + return SearchKeysParse(strKeys); + } + + private ValueTask> CreateList(ValueTask pending) + { + return pending.IsCompletedSuccessfully ? CreateList(pending.Result).AsValueTaskResult() : Awaited(this, pending); + static async ValueTask> Awaited(RedisTypedClient obj, ValueTask pending) + => obj.CreateList(await pending.ConfigureAwait(false)); + } + private ValueTask DeserializeValueAsync(ValueTask pending) + { + return pending.IsCompletedSuccessfully ? DeserializeValue(pending.Result).AsValueTaskResult() : Awaited(this, pending); + static async ValueTask Awaited(RedisTypedClient obj, ValueTask pending) + => obj.DeserializeValue(await pending.ConfigureAwait(false)); + } + + private static ValueTask DeserializeFromStringAsync(ValueTask pending) + { + return pending.IsCompletedSuccessfully ? DeserializeFromString(pending.Result).AsValueTaskResult() : Awaited(pending); + static async ValueTask Awaited(ValueTask pending) + => DeserializeFromString(await pending.ConfigureAwait(false)); + } + + private static ValueTask> CreateGenericMapAsync(ValueTask> pending) + { + return pending.IsCompletedSuccessfully ? CreateGenericMap(pending.Result).AsValueTaskResult() : Awaited(pending); + static async ValueTask> Awaited(ValueTask> pending) + => CreateGenericMap(await pending.ConfigureAwait(false)); + } + + private static ValueTask> ConvertEachToAsync(ValueTask> pending) + { + return pending.IsCompletedSuccessfully ? ConvertEachTo(pending.Result).AsValueTaskResult() : Awaited(pending); + static async ValueTask> Awaited(ValueTask> pending) + => ConvertEachTo(await pending.ConfigureAwait(false)); + } + + ValueTask> IRedisTypedClientAsync.GetSortedEntryValuesAsync(IRedisSetAsync fromSet, int startingFrom, int endingAt, CancellationToken token) + { + var sortOptions = new SortOptions { Skip = startingFrom, Take = endingAt, }; + var multiDataList = AsyncNative.SortAsync(fromSet.Id, sortOptions, token); + return CreateList(multiDataList); + } + + ValueTask IRedisTypedClientAsync.StoreAsHashAsync(T entity, CancellationToken token) + => AsyncClient.StoreAsHashAsync(entity, token); + + ValueTask IRedisTypedClientAsync.GetFromHashAsync(object id, CancellationToken token) + => AsyncClient.GetFromHashAsync(id, token); + + async ValueTask> IRedisTypedClientAsync.GetAllItemsFromSetAsync(IRedisSetAsync fromSet, CancellationToken token) + { + var multiDataList = await AsyncNative.SMembersAsync(fromSet.Id, token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisTypedClientAsync.AddItemToSetAsync(IRedisSetAsync toSet, T item, CancellationToken token) + => AsyncNative.SAddAsync(toSet.Id, SerializeValue(item), token).Await(); + + ValueTask IRedisTypedClientAsync.RemoveItemFromSetAsync(IRedisSetAsync fromSet, T item, CancellationToken token) + => AsyncNative.SRemAsync(fromSet.Id, SerializeValue(item), token).Await(); + + ValueTask IRedisTypedClientAsync.PopItemFromSetAsync(IRedisSetAsync fromSet, CancellationToken token) + => DeserializeValueAsync(AsyncNative.SPopAsync(fromSet.Id, token)); + + ValueTask IRedisTypedClientAsync.MoveBetweenSetsAsync(IRedisSetAsync fromSet, IRedisSetAsync toSet, T item, CancellationToken token) + => AsyncNative.SMoveAsync(fromSet.Id, toSet.Id, SerializeValue(item), token); + + ValueTask IRedisTypedClientAsync.GetSetCountAsync(IRedisSetAsync set, CancellationToken token) + => AsyncNative.SCardAsync(set.Id, token); + + ValueTask IRedisTypedClientAsync.SetContainsItemAsync(IRedisSetAsync set, T item, CancellationToken token) + => AsyncNative.SIsMemberAsync(set.Id, SerializeValue(item), token).IsSuccessAsync(); + + async ValueTask> IRedisTypedClientAsync.GetIntersectFromSetsAsync(IRedisSetAsync[] sets, CancellationToken token) + { + var multiDataList = await AsyncNative.SInterAsync(sets.Map(x => x.Id).ToArray(), token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisTypedClientAsync.StoreIntersectFromSetsAsync(IRedisSetAsync intoSet, IRedisSetAsync[] sets, CancellationToken token) + => AsyncNative.SInterStoreAsync(intoSet.Id, sets.Map(x => x.Id).ToArray(), token); + + async ValueTask> IRedisTypedClientAsync.GetUnionFromSetsAsync(IRedisSetAsync[] sets, CancellationToken token) + { + var multiDataList = await AsyncNative.SUnionAsync(sets.Map(x => x.Id).ToArray(), token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisTypedClientAsync.StoreUnionFromSetsAsync(IRedisSetAsync intoSet, IRedisSetAsync[] sets, CancellationToken token) + => AsyncNative.SUnionStoreAsync(intoSet.Id, sets.Map(x => x.Id).ToArray(), token); + + async ValueTask> IRedisTypedClientAsync.GetDifferencesFromSetAsync(IRedisSetAsync fromSet, IRedisSetAsync[] withSets, CancellationToken token) + { + var multiDataList = await AsyncNative.SDiffAsync(fromSet.Id, withSets.Map(x => x.Id).ToArray(), token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisTypedClientAsync.StoreDifferencesFromSetAsync(IRedisSetAsync intoSet, IRedisSetAsync fromSet, IRedisSetAsync[] withSets, CancellationToken token) + => AsyncNative.SDiffStoreAsync(intoSet.Id, fromSet.Id, withSets.Map(x => x.Id).ToArray(), token); + + ValueTask IRedisTypedClientAsync.GetRandomItemFromSetAsync(IRedisSetAsync fromSet, CancellationToken token) + => DeserializeValueAsync(AsyncNative.SRandMemberAsync(fromSet.Id, token)); + + ValueTask> IRedisTypedClientAsync.GetAllItemsFromListAsync(IRedisListAsync fromList, CancellationToken token) + { + var multiDataList = AsyncNative.LRangeAsync(fromList.Id, FirstElement, LastElement, token); + return CreateList(multiDataList); + } + + ValueTask> IRedisTypedClientAsync.GetRangeFromListAsync(IRedisListAsync fromList, int startingFrom, int endingAt, CancellationToken token) + { + var multiDataList = AsyncNative.LRangeAsync(fromList.Id, startingFrom, endingAt, token); + return CreateList(multiDataList); + } + + ValueTask> IRedisTypedClientAsync.SortListAsync(IRedisListAsync fromList, int startingFrom, int endingAt, CancellationToken token) + { + var sortOptions = new SortOptions { Skip = startingFrom, Take = endingAt, }; + var multiDataList = AsyncNative.SortAsync(fromList.Id, sortOptions, token); + return CreateList(multiDataList); + } + + ValueTask IRedisTypedClientAsync.AddItemToListAsync(IRedisListAsync fromList, T value, CancellationToken token) + => AsyncNative.RPushAsync(fromList.Id, SerializeValue(value), token).Await(); + + ValueTask IRedisTypedClientAsync.PrependItemToListAsync(IRedisListAsync fromList, T value, CancellationToken token) + => AsyncNative.LPushAsync(fromList.Id, SerializeValue(value), token).Await(); + + ValueTask IRedisTypedClientAsync.RemoveStartFromListAsync(IRedisListAsync fromList, CancellationToken token) + => DeserializeValueAsync(AsyncNative.LPopAsync(fromList.Id, token)); + + async ValueTask IRedisTypedClientAsync.BlockingRemoveStartFromListAsync(IRedisListAsync fromList, TimeSpan? timeOut, CancellationToken token) + { + var unblockingKeyAndValue = await AsyncNative.BLPopAsync(fromList.Id, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + return unblockingKeyAndValue.Length == 0 + ? default + : DeserializeValue(unblockingKeyAndValue[1]); + } + + ValueTask IRedisTypedClientAsync.RemoveEndFromListAsync(IRedisListAsync fromList, CancellationToken token) + => DeserializeValueAsync(AsyncNative.RPopAsync(fromList.Id, token)); + + ValueTask IRedisTypedClientAsync.RemoveAllFromListAsync(IRedisListAsync fromList, CancellationToken token) + => AsyncNative.LTrimAsync(fromList.Id, int.MaxValue, FirstElement, token); + + ValueTask IRedisTypedClientAsync.TrimListAsync(IRedisListAsync fromList, int keepStartingFrom, int keepEndingAt, CancellationToken token) + => AsyncNative.LTrimAsync(fromList.Id, keepStartingFrom, keepEndingAt, token); + + ValueTask IRedisTypedClientAsync.RemoveItemFromListAsync(IRedisListAsync fromList, T value, CancellationToken token) + { + const int removeAll = 0; + return AsyncNative.LRemAsync(fromList.Id, removeAll, SerializeValue(value), token); + } + + ValueTask IRedisTypedClientAsync.RemoveItemFromListAsync(IRedisListAsync fromList, T value, int noOfMatches, CancellationToken token) + => AsyncNative.LRemAsync(fromList.Id, noOfMatches, SerializeValue(value), token); + + ValueTask IRedisTypedClientAsync.GetListCountAsync(IRedisListAsync fromList, CancellationToken token) + => AsyncNative.LLenAsync(fromList.Id, token); + + ValueTask IRedisTypedClientAsync.GetItemFromListAsync(IRedisListAsync fromList, int listIndex, CancellationToken token) + => DeserializeValueAsync(AsyncNative.LIndexAsync(fromList.Id, listIndex, token)); + + ValueTask IRedisTypedClientAsync.SetItemInListAsync(IRedisListAsync toList, int listIndex, T value, CancellationToken token) + => AsyncNative.LSetAsync(toList.Id, listIndex, SerializeValue(value), token); + + ValueTask IRedisTypedClientAsync.InsertBeforeItemInListAsync(IRedisListAsync toList, T pivot, T value, CancellationToken token) + => AsyncNative.LInsertAsync(toList.Id, insertBefore: true, pivot: SerializeValue(pivot), value: SerializeValue(value), token: token); + + ValueTask IRedisTypedClientAsync.InsertAfterItemInListAsync(IRedisListAsync toList, T pivot, T value, CancellationToken token) + => AsyncNative.LInsertAsync(toList.Id, insertBefore: false, pivot: SerializeValue(pivot), value: SerializeValue(value), token: token); + + ValueTask IRedisTypedClientAsync.EnqueueItemOnListAsync(IRedisListAsync fromList, T item, CancellationToken token) + => AsyncNative.LPushAsync(fromList.Id, SerializeValue(item), token).Await(); + + ValueTask IRedisTypedClientAsync.DequeueItemFromListAsync(IRedisListAsync fromList, CancellationToken token) + => DeserializeValueAsync(AsyncNative.RPopAsync(fromList.Id, token)); + + async ValueTask IRedisTypedClientAsync.BlockingDequeueItemFromListAsync(IRedisListAsync fromList, TimeSpan? timeOut, CancellationToken token) + { + var unblockingKeyAndValue = await AsyncNative.BRPopAsync(fromList.Id, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + return unblockingKeyAndValue.Length == 0 + ? default + : DeserializeValue(unblockingKeyAndValue[1]); + } + + ValueTask IRedisTypedClientAsync.PushItemToListAsync(IRedisListAsync fromList, T item, CancellationToken token) + => AsyncNative.RPushAsync(fromList.Id, SerializeValue(item), token).Await(); + + ValueTask IRedisTypedClientAsync.PopItemFromListAsync(IRedisListAsync fromList, CancellationToken token) + => DeserializeValueAsync(AsyncNative.RPopAsync(fromList.Id, token)); + + async ValueTask IRedisTypedClientAsync.BlockingPopItemFromListAsync(IRedisListAsync fromList, TimeSpan? timeOut, CancellationToken token) + { + var unblockingKeyAndValue = await AsyncNative.BRPopAsync(fromList.Id, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + return unblockingKeyAndValue.Length == 0 + ? default + : DeserializeValue(unblockingKeyAndValue[1]); + } + + ValueTask IRedisTypedClientAsync.PopAndPushItemBetweenListsAsync(IRedisListAsync fromList, IRedisListAsync toList, CancellationToken token) + => DeserializeValueAsync(AsyncNative.RPopLPushAsync(fromList.Id, toList.Id, token)); + + ValueTask IRedisTypedClientAsync.BlockingPopAndPushItemBetweenListsAsync(IRedisListAsync fromList, IRedisListAsync toList, TimeSpan? timeOut, CancellationToken token) + => DeserializeValueAsync(AsyncNative.BRPopLPushAsync(fromList.Id, toList.Id, (int)timeOut.GetValueOrDefault().TotalSeconds, token)); + + ValueTask IRedisTypedClientAsync.AddItemToSortedSetAsync(IRedisSortedSetAsync toSet, T value, CancellationToken token) + => AsyncClient.AddItemToSortedSetAsync(toSet.Id, value.SerializeToString(), token).Await(); + + ValueTask IRedisTypedClientAsync.AddItemToSortedSetAsync(IRedisSortedSetAsync toSet, T value, double score, CancellationToken token) + => AsyncClient.AddItemToSortedSetAsync(toSet.Id, value.SerializeToString(), score, token).Await(); + + ValueTask IRedisTypedClientAsync.RemoveItemFromSortedSetAsync(IRedisSortedSetAsync fromSet, T value, CancellationToken token) + => AsyncClient.RemoveItemFromSortedSetAsync(fromSet.Id, value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.PopItemWithLowestScoreFromSortedSetAsync(IRedisSortedSetAsync fromSet, CancellationToken token) + => DeserializeFromStringAsync(AsyncClient.PopItemWithLowestScoreFromSortedSetAsync(fromSet.Id, token)); + + ValueTask IRedisTypedClientAsync.PopItemWithHighestScoreFromSortedSetAsync(IRedisSortedSetAsync fromSet, CancellationToken token) + => DeserializeFromStringAsync(AsyncClient.PopItemWithHighestScoreFromSortedSetAsync(fromSet.Id, token)); + + ValueTask IRedisTypedClientAsync.SortedSetContainsItemAsync(IRedisSortedSetAsync set, T value, CancellationToken token) + => AsyncClient.SortedSetContainsItemAsync(set.Id, value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.IncrementItemInSortedSetAsync(IRedisSortedSetAsync set, T value, double incrementBy, CancellationToken token) + => AsyncClient.IncrementItemInSortedSetAsync(set.Id, value.SerializeToString(), incrementBy, token); + + ValueTask IRedisTypedClientAsync.GetItemIndexInSortedSetAsync(IRedisSortedSetAsync set, T value, CancellationToken token) + => AsyncClient.GetItemIndexInSortedSetAsync(set.Id, value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.GetItemIndexInSortedSetDescAsync(IRedisSortedSetAsync set, T value, CancellationToken token) + => AsyncClient.GetItemIndexInSortedSetDescAsync(set.Id, value.SerializeToString(), token); + + ValueTask> IRedisTypedClientAsync.GetAllItemsFromSortedSetAsync(IRedisSortedSetAsync set, CancellationToken token) + => AsyncClient.GetAllItemsFromSortedSetAsync(set.Id, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetAllItemsFromSortedSetDescAsync(IRedisSortedSetAsync set, CancellationToken token) + => AsyncClient.GetAllItemsFromSortedSetDescAsync(set.Id, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetAsync(IRedisSortedSetAsync set, int fromRank, int toRank, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetAsync(set.Id, fromRank, toRank, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetDescAsync(IRedisSortedSetAsync set, int fromRank, int toRank, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetDescAsync(set.Id, fromRank, toRank, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetAllWithScoresFromSortedSetAsync(IRedisSortedSetAsync set, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetAsync(set.Id, FirstElement, LastElement, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetAsync(IRedisSortedSetAsync set, int fromRank, int toRank, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetAsync(set.Id, fromRank, toRank, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetDescAsync(IRedisSortedSetAsync set, int fromRank, int toRank, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetDescAsync(set.Id, fromRank, toRank, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(set.Id, fromStringScore, toStringScore, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(set.Id, fromStringScore, toStringScore, skip, take, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(set.Id, fromScore, toScore, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(set.Id, fromScore, toScore, skip, take, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByLowestScoreAsync(set.Id, fromStringScore, toStringScore, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByLowestScoreAsync(set.Id, fromStringScore, toStringScore, skip, take, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByLowestScoreAsync(set.Id, fromScore, toScore, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByLowestScoreAsync(set.Id, fromScore, toScore, skip, take, token)); + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(set.Id, fromStringScore, toStringScore, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(set.Id, fromStringScore, toStringScore, skip, take, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(set.Id, fromScore, toScore, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByHighestScoreAsync(set.Id, fromScore, toScore, skip, take, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByHighestScoreAsync(set.Id, fromStringScore, toStringScore, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByHighestScoreAsync(set.Id, fromStringScore, toStringScore, skip, take, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByHighestScoreAsync(set.Id, fromScore, toScore, token)); + + ValueTask> IRedisTypedClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => CreateGenericMapAsync(AsyncClient.GetRangeWithScoresFromSortedSetByHighestScoreAsync(set.Id, fromScore, toScore, skip, take, token)); + + ValueTask IRedisTypedClientAsync.RemoveRangeFromSortedSetAsync(IRedisSortedSetAsync set, int minRank, int maxRank, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetAsync(set.Id, minRank, maxRank, token); + + ValueTask IRedisTypedClientAsync.RemoveRangeFromSortedSetByScoreAsync(IRedisSortedSetAsync set, double fromScore, double toScore, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetByScoreAsync(set.Id, fromScore, toScore, token); + + ValueTask IRedisTypedClientAsync.GetSortedSetCountAsync(IRedisSortedSetAsync set, CancellationToken token) + => AsyncClient.GetSortedSetCountAsync(set.Id, token); + + ValueTask IRedisTypedClientAsync.GetItemScoreInSortedSetAsync(IRedisSortedSetAsync set, T value, CancellationToken token) + => AsyncClient.GetItemScoreInSortedSetAsync(set.Id, value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.StoreIntersectFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, IRedisSortedSetAsync[] setIds, CancellationToken token) + => AsyncClient.StoreIntersectFromSortedSetsAsync(intoSetId.Id, setIds.Map(x => x.Id).ToArray(), token); + + ValueTask IRedisTypedClientAsync.StoreIntersectFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, IRedisSortedSetAsync[] setIds, string[] args, CancellationToken token) + => AsyncClient.StoreIntersectFromSortedSetsAsync(intoSetId.Id, setIds.Map(x => x.Id).ToArray(), args, token); + + ValueTask IRedisTypedClientAsync.StoreUnionFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, IRedisSortedSetAsync[] setIds, CancellationToken token) + => AsyncClient.StoreUnionFromSortedSetsAsync(intoSetId.Id, setIds.Map(x => x.Id).ToArray(), token); + + ValueTask IRedisTypedClientAsync.StoreUnionFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, IRedisSortedSetAsync[] setIds, string[] args, CancellationToken token) + => AsyncClient.StoreUnionFromSortedSetsAsync(intoSetId.Id, setIds.Map(x => x.Id).ToArray(), args, token); + + ValueTask IRedisTypedClientAsync.HashContainsEntryAsync(IRedisHashAsync hash, TKey key, CancellationToken token) + => AsyncClient.HashContainsEntryAsync(hash.Id, key.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.SetEntryInHashAsync(IRedisHashAsync hash, TKey key, T value, CancellationToken token) + => AsyncClient.SetEntryInHashAsync(hash.Id, key.SerializeToString(), value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.SetEntryInHashIfNotExistsAsync(IRedisHashAsync hash, TKey key, T value, CancellationToken token) + => AsyncClient.SetEntryInHashIfNotExistsAsync(hash.Id, key.SerializeToString(), value.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.SetRangeInHashAsync(IRedisHashAsync hash, IEnumerable> keyValuePairs, CancellationToken token) + { + var stringKeyValuePairs = keyValuePairs.ToList().ConvertAll( + x => new KeyValuePair(x.Key.SerializeToString(), x.Value.SerializeToString())); + + return AsyncClient.SetRangeInHashAsync(hash.Id, stringKeyValuePairs, token); + } + + ValueTask IRedisTypedClientAsync.GetValueFromHashAsync(IRedisHashAsync hash, TKey key, CancellationToken token) + => DeserializeFromStringAsync(AsyncClient.GetValueFromHashAsync(hash.Id, key.SerializeToString(), token)); + + ValueTask IRedisTypedClientAsync.RemoveEntryFromHashAsync(IRedisHashAsync hash, TKey key, CancellationToken token) + => AsyncClient.RemoveEntryFromHashAsync(hash.Id, key.SerializeToString(), token); + + ValueTask IRedisTypedClientAsync.GetHashCountAsync(IRedisHashAsync hash, CancellationToken token) + => AsyncClient.GetHashCountAsync(hash.Id, token); + + ValueTask> IRedisTypedClientAsync.GetHashKeysAsync(IRedisHashAsync hash, CancellationToken token) + => AsyncClient.GetHashKeysAsync(hash.Id, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetHashValuesAsync(IRedisHashAsync hash, CancellationToken token) + => AsyncClient.GetHashValuesAsync(hash.Id, token).ConvertEachToAsync(); + + ValueTask> IRedisTypedClientAsync.GetAllEntriesFromHashAsync(IRedisHashAsync hash, CancellationToken token) + => ConvertEachToAsync(AsyncClient.GetAllEntriesFromHashAsync(hash.Id, token)); + + async ValueTask IRedisTypedClientAsync.StoreRelatedEntitiesAsync(object parentId, List children, CancellationToken token) + { + var childRefKey = GetChildReferenceSetKey(parentId); + var childKeys = children.ConvertAll(x => client.UrnKey(x)); + + await using var trans = await AsyncClient.CreateTransactionAsync(token).ConfigureAwait(false); + //Ugly but need access to a generic constraint-free StoreAll method + trans.QueueCommand(c => ((RedisClient)c).StoreAllAsyncImpl(children, token)); + trans.QueueCommand(c => c.AddRangeToSetAsync(childRefKey, childKeys, token)); + + await trans.CommitAsync(token).ConfigureAwait(false); + } + + ValueTask IRedisTypedClientAsync.StoreRelatedEntitiesAsync(object parentId, TChild[] children, CancellationToken token) + => AsAsync().StoreRelatedEntitiesAsync(parentId, new List(children), token); + + ValueTask IRedisTypedClientAsync.DeleteRelatedEntitiesAsync(object parentId, CancellationToken token) + { + var childRefKey = GetChildReferenceSetKey(parentId); + return new ValueTask(AsyncClient.RemoveAsync(childRefKey, token)); + } + + ValueTask IRedisTypedClientAsync.DeleteRelatedEntityAsync(object parentId, object childId, CancellationToken token) + { + var childRefKey = GetChildReferenceSetKey(parentId); + return AsyncClient.RemoveItemFromSetAsync(childRefKey, TypeSerializer.SerializeToString(childId), token); + } + + async ValueTask> IRedisTypedClientAsync.GetRelatedEntitiesAsync(object parentId, CancellationToken token) + { + var childRefKey = GetChildReferenceSetKey(parentId); + var childKeys = (await AsyncClient.GetAllItemsFromSetAsync(childRefKey, token).ConfigureAwait(false)).ToList(); + + return await AsyncClient.As().GetValuesAsync(childKeys, token).ConfigureAwait(false); + } + + ValueTask IRedisTypedClientAsync.GetRelatedEntitiesCountAsync(object parentId, CancellationToken token) + { + var childRefKey = GetChildReferenceSetKey(parentId); + return AsyncClient.GetSetCountAsync(childRefKey, token); + } + + ValueTask IRedisTypedClientAsync.AddToRecentsListAsync(T value, CancellationToken token) + { + var key = client.UrnKey(value); + var nowScore = DateTime.UtcNow.ToUnixTime(); + return AsyncClient.AddItemToSortedSetAsync(RecentSortedSetKey, key, nowScore, token).Await(); + } + + async ValueTask> IRedisTypedClientAsync.GetLatestFromRecentsListAsync(int skip, int take, CancellationToken token) + { + var toRank = take - 1; + var keys = await AsyncClient.GetRangeFromSortedSetDescAsync(RecentSortedSetKey, skip, toRank, token).ConfigureAwait(false); + var values = await AsAsync().GetValuesAsync(keys, token).ConfigureAwait(false); + return values; + } + + async ValueTask> IRedisTypedClientAsync.GetEarliestFromRecentsListAsync(int skip, int take, CancellationToken token) + { + var toRank = take - 1; + var keys = await AsyncClient.GetRangeFromSortedSetAsync(RecentSortedSetKey, skip, toRank, token).ConfigureAwait(false); + var values = await AsAsync().GetValuesAsync(keys, token).ConfigureAwait(false); + return values; + } + + ValueTask IRedisTypedClientAsync.RemoveEntryAsync(params string[] args) + => AsAsync().RemoveEntryAsync(args, token: default); + + ValueTask IRedisTypedClientAsync.RemoveEntryAsync(params IHasStringId[] entities) + => AsAsync().RemoveEntryAsync(entities, token: default); + + ValueTask> IRedisTypedClientAsync.GetIntersectFromSetsAsync(params IRedisSetAsync[] sets) + => AsAsync().GetIntersectFromSetsAsync(sets, token: default); + + ValueTask IRedisTypedClientAsync.StoreIntersectFromSetsAsync(IRedisSetAsync intoSet, params IRedisSetAsync[] sets) + => AsAsync().StoreIntersectFromSetsAsync(intoSet, sets, token: default); + + ValueTask> IRedisTypedClientAsync.GetUnionFromSetsAsync(params IRedisSetAsync[] sets) + => AsAsync().GetUnionFromSetsAsync(sets, token: default); + + ValueTask IRedisTypedClientAsync.StoreUnionFromSetsAsync(IRedisSetAsync intoSet, params IRedisSetAsync[] sets) + => AsAsync().StoreUnionFromSetsAsync(intoSet, sets, token: default); + + ValueTask> IRedisTypedClientAsync.GetDifferencesFromSetAsync(IRedisSetAsync fromSet, params IRedisSetAsync[] withSets) + => AsAsync().GetDifferencesFromSetAsync(fromSet, withSets, token: default); + + ValueTask IRedisTypedClientAsync.StoreDifferencesFromSetAsync(IRedisSetAsync intoSet, IRedisSetAsync fromSet, params IRedisSetAsync[] withSets) + => AsAsync().StoreDifferencesFromSetAsync(intoSet, fromSet, withSets, token: default); + + ValueTask IRedisTypedClientAsync.StoreIntersectFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, params IRedisSortedSetAsync[] setIds) + => AsAsync().StoreIntersectFromSortedSetsAsync(intoSetId, setIds, token: default); + + ValueTask IRedisTypedClientAsync.StoreUnionFromSortedSetsAsync(IRedisSortedSetAsync intoSetId, params IRedisSortedSetAsync[] setIds) + => AsAsync().StoreUnionFromSortedSetsAsync(intoSetId, setIds, token: default); + + ValueTask IRedisTypedClientAsync.StoreRelatedEntitiesAsync(object parentId, params TChild[] children) + => AsAsync().StoreRelatedEntitiesAsync(parentId, children, token: default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient.cs index bfa10976..cf070975 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedClient.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -36,15 +36,9 @@ static RedisTypedClient() readonly ITypeSerializer serializer = new JsonSerializer(); private readonly RedisClient client; - public IRedisClient RedisClient - { - get { return client; } - } + public IRedisClient RedisClient => client; - public IRedisNativeClient NativeClient - { - get { return client; } - } + public IRedisNativeClient NativeClient => client; /// /// Use this to share the same redis connection with another @@ -69,7 +63,7 @@ public RedisTypedClient(RedisClient client) public IRedisTypedTransaction CreateTransaction() { - return new RedisTypedTransaction(this); + return new RedisTypedTransaction(this, false); } public IRedisTypedPipeline CreatePipeline() @@ -88,26 +82,14 @@ public IDisposable AcquireLock(TimeSpan timeOut) public IRedisTransactionBase Transaction { - get - { - return client.Transaction; - } - set - { - client.Transaction = value; - } + get => client.Transaction; + set => client.Transaction = value; } public IRedisPipelineShared Pipeline { - get - { - return client.Pipeline; - } - set - { - client.Pipeline = value; - } + get => client.Pipeline; + set => client.Pipeline = value; } public void Watch(params string[] keys) @@ -155,18 +137,14 @@ public string UrnKey(T entity) return client.UrnKey(entity); } - public IRedisSet TypeIdsSet - { - get - { - return new RedisClientSet(client, client.GetTypeIdsSetKey()); - } - } + public IRedisSet TypeIdsSet => TypeIdsSetRaw; + + private RedisClientSet TypeIdsSetRaw => new RedisClientSet(client, client.GetTypeIdsSetKey()); public T this[string key] { - get { return GetValue(key); } - set { SetValue(key, value); } + get => GetValue(key); + set => SetValue(key, value); } public byte[] SerializeValue(T value) @@ -184,7 +162,7 @@ public T DeserializeValue(byte[] value) public void SetValue(string key, T entity) { if (key == null) - throw new ArgumentNullException("key"); + throw new ArgumentNullException(nameof(key)); client.Set(key, SerializeValue(entity)); client.RegisterTypeId(entity); @@ -193,7 +171,7 @@ public void SetValue(string key, T entity) public void SetValue(string key, T entity, TimeSpan expireIn) { if (key == null) - throw new ArgumentNullException("key"); + throw new ArgumentNullException(nameof(key)); client.Set(key, SerializeValue(entity), expireIn); client.RegisterTypeId(entity); @@ -242,7 +220,7 @@ public bool RemoveEntry(params IHasStringId[] entities) { var ids = entities.Map(x => x.Id); var success = client.Del(ids.ToArray()) == RedisNativeClient.Success; - if (success) client.RemoveTypeIds(ids.ToArray()); + if (success) client.RemoveTypeIdsByValues(ids.ToArray()); return success; } @@ -343,6 +321,10 @@ public void FlushAll() public T[] SearchKeys(string pattern) { var strKeys = client.SearchKeys(pattern); + return SearchKeysParse(strKeys); + } + private T[] SearchKeysParse(List strKeys) + { var keysCount = strKeys.Count; var keys = new T[keysCount]; @@ -358,7 +340,10 @@ public List GetValues(List keys) if (keys.IsNullOrEmpty()) return new List(); var resultBytesArray = client.MGet(keys.ToArray()); - + return ProcessGetValues(resultBytesArray); + } + private List ProcessGetValues(byte[][] resultBytesArray) + { var results = new List(); foreach (var resultBytes in resultBytesArray) { @@ -424,29 +409,41 @@ public T Store(T entity, TimeSpan expireIn) public void StoreAll(IEnumerable entities) { - if (entities == null) return; + if (PrepareStoreAll(entities, out var keys, out var values, out var entitiesList)) + { + client.MSet(keys, values); + client.RegisterTypeIds(entitiesList); + } + } - var entitiesList = entities.ToList(); + private bool PrepareStoreAll(IEnumerable entities, out byte[][] keys, out byte[][] values, out List entitiesList) + { + if (entities == null) + { + keys = values = default; + entitiesList = default; + return false; + } + + entitiesList = entities.ToList(); var len = entitiesList.Count; - var keys = new byte[len][]; - var values = new byte[len][]; + keys = new byte[len][]; + values = new byte[len][]; for (var i = 0; i < len; i++) { keys[i] = client.UrnKey(entitiesList[i]).ToUtf8Bytes(); values[i] = Redis.RedisClient.SerializeToUtf8Bytes(entitiesList[i]); } - - client.MSet(keys, values); - client.RegisterTypeIds(entitiesList); + return true; } public void Delete(T entity) { var urnKey = client.UrnKey(entity); this.RemoveEntry(urnKey); - client.RemoveTypeIds(entity); + client.RemoveTypeIdsByValue(entity); } public void DeleteById(object id) @@ -454,31 +451,42 @@ public void DeleteById(object id) var urnKey = client.UrnKey(id); this.RemoveEntry(urnKey); - client.RemoveTypeIds(id.ToString()); + client.RemoveTypeIdsById(id.ToString()); } public void DeleteByIds(IEnumerable ids) { if (ids == null) return; - var urnKeys = ids.Map(t => client.UrnKey(t)); - if (urnKeys.Count > 0) + var idStrings = ids.Map(x => x.ToString()).ToArray(); + var urnKeys = idStrings.Select(t => client.UrnKey(t)).ToArray(); + if (urnKeys.Length > 0) { - this.RemoveEntry(urnKeys.ToArray()); - client.RemoveTypeIds(ids.Map(x => x.ToString()).ToArray()); + this.RemoveEntry(urnKeys); + client.RemoveTypeIdsByIds(idStrings); } } - public void DeleteAll() + private void DeleteAll(ulong cursor, int pageSize) { - var ids = client.GetAllItemsFromSet(this.TypeIdsSetKey); - var urnKeys = ids.Map(t => client.UrnKey(t)); - if (urnKeys.Count > 0) + do { - - this.RemoveEntry(urnKeys.ToArray()); - this.RemoveEntry(this.TypeIdsSetKey); - } + var scanResult = client.SScan(this.TypeIdsSetKey, cursor, pageSize); + cursor = scanResult.Cursor; + var ids = scanResult.Results.Select(x => Encoding.UTF8.GetString(x)).ToList(); + var urnKeys = ids.Map(t => client.UrnKey(t)); + if (urnKeys.Count > 0) + { + this.RemoveEntry(urnKeys.ToArray()); + } + } while (cursor != 0); + + this.RemoveEntry(this.TypeIdsSetKey); + } + + public void DeleteAll() + { + DeleteAll(0,RedisConfig.CommandKeysBatchSize); } #endregion diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_Hash.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_Hash.cs index e3c0b3bf..e60432d0 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedClient_Hash.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_Hash.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_List.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_List.Async.cs new file mode 100644 index 00000000..023590f3 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_List.Async.cs @@ -0,0 +1,33 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Generic +{ + public partial class RedisTypedClient + { + internal partial class RedisClientLists + : IHasNamed> + { + IRedisListAsync IHasNamed>.this[string listId] + { + get => new RedisClientList(client, listId); + set => throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_List.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_List.cs index 56ed9592..144099e2 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedClient_List.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_List.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using ServiceStack.Model; namespace ServiceStack.Redis.Generic @@ -24,7 +25,7 @@ public partial class RedisTypedClient public IHasNamed> Lists { get; set; } - internal class RedisClientLists + internal partial class RedisClientLists : IHasNamed> { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.Async.cs new file mode 100644 index 00000000..24889db6 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.Async.cs @@ -0,0 +1,30 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; + +namespace ServiceStack.Redis.Generic +{ + public partial class RedisTypedClient + { + internal partial class RedisClientSets + : IHasNamed> + { + IRedisSetAsync IHasNamed>.this[string setId] + { + get => new RedisClientSet(client, setId); + set => throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.cs index 15e607ef..d8a0510e 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_Set.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -27,7 +27,7 @@ public long Db set { client.Db = value; } } - internal class RedisClientSets + internal partial class RedisClientSets : IHasNamed> { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.Async.cs new file mode 100644 index 00000000..ab4ede2e --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.Async.cs @@ -0,0 +1,30 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; + +namespace ServiceStack.Redis.Generic +{ + public partial class RedisTypedClient + { + internal partial class RedisClientSortedSets + : IHasNamed> + { + IRedisSortedSetAsync IHasNamed>.this[string setId] + { + get => new RedisClientSortedSet(client, setId); + set => throw new NotSupportedException(); + } + } + } +} diff --git a/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.cs b/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.cs index 31113595..03155420 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedClient_SortedSet.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -21,7 +21,7 @@ public partial class RedisTypedClient { public IHasNamed> SortedSets { get; set; } - internal class RedisClientSortedSets + internal partial class RedisClientSortedSets : IHasNamed> { private readonly RedisTypedClient client; diff --git a/src/ServiceStack.Redis/Generic/RedisTypedCommandQueue.cs b/src/ServiceStack.Redis/Generic/RedisTypedCommandQueue.cs index 0128b55a..00989112 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedCommandQueue.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedCommandQueue.cs @@ -273,7 +273,7 @@ public void QueueCommand(Func, HashSet> command, Ac BeginQueuedCommand(new QueuedRedisTypedCommand { MultiStringReturnCommand = r => command(r).ToList(), - OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToHashSet()), + OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToSet()), OnErrorCallback = onErrorCallback }); command(RedisClient); @@ -294,7 +294,7 @@ public void QueueCommand(Func, HashSet> command, Action< BeginQueuedCommand(new QueuedRedisTypedCommand { MultiObjectReturnCommand = r => command(r).ToList(), - OnSuccessMultiTypeCallback = x => onSuccessCallback(x.ConvertAll(y => JsonSerializer.DeserializeFromString(y)).ToHashSet()), + OnSuccessMultiTypeCallback = x => onSuccessCallback(x.ConvertAll(JsonSerializer.DeserializeFromString).ToSet()), OnErrorCallback = onErrorCallback }); command(RedisClient); diff --git a/src/ServiceStack.Redis/Generic/RedisTypedPipeline.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedPipeline.Async.cs new file mode 100644 index 00000000..2b571235 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedPipeline.Async.cs @@ -0,0 +1,265 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Pipeline; +using ServiceStack.Text; + +namespace ServiceStack.Redis +{ + /// + /// Pipeline for redis typed client + /// + /// + public partial class RedisTypedPipeline + : IRedisTypedPipelineAsync + { + private IRedisTypedPipelineAsync AsAsync() => this; + void IRedisQueueCompletableOperationAsync.CompleteBytesQueuedCommandAsync(Func> bytesReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(bytesReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteDoubleQueuedCommandAsync(Func> doubleReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(doubleReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteIntQueuedCommandAsync(Func> intReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(intReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteLongQueuedCommandAsync(Func> longReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(longReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteMultiBytesQueuedCommandAsync(Func> multiBytesReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(multiBytesReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteMultiStringQueuedCommandAsync(Func>> multiStringReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(multiStringReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteRedisDataQueuedCommandAsync(Func> redisDataReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(redisDataReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteStringQueuedCommandAsync(Func> stringReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(stringReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteVoidQueuedCommandAsync(Func voidReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(voidReadCommand); + AddCurrentQueuedOperation(); + } + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + async ValueTask IRedisPipelineSharedAsync.FlushAsync(CancellationToken token) + { + try + { + // flush send buffers + await RedisClient.FlushSendBufferAsync(token).ConfigureAwait(false); + RedisClient.ResetSendBuffer(); + + //receive expected results + foreach (var queuedCommand in QueuedCommands) + { + await queuedCommand.ProcessResultAsync(token).ConfigureAwait(false); + } + + } + finally + { + ClosePipeline(); + await RedisClient.AddTypeIdsRegisteredDuringPipelineAsync(token).ConfigureAwait(false); + } + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessVoidCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessIntCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessLongCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessBoolCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessDoubleCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessBytesCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessStringCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessTypeCallback = x => onSuccessCallback(JsonSerializer.DeserializeFromString(x)), + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessMultiStringCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToSet()), + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(async r => + { + var result = await command(r).ConfigureAwait(false); + return result.ToList(); + })); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + void IRedisTypedQueueableOperationAsync.QueueCommand(Func, ValueTask>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisTypedCommand + { + OnSuccessMultiTypeCallback = x => onSuccessCallback(x.ConvertAll(JsonSerializer.DeserializeFromString)), + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + RedisAllPurposePipeline.AssertSync(command(RedisClient)); + } + + async ValueTask IRedisPipelineSharedAsync.ReplayAsync(CancellationToken token) + { + RedisClient.Pipeline = this; + // execute + foreach (var queuedCommand in QueuedCommands) + { + if (queuedCommand is QueuedRedisTypedCommand cmd) + await cmd.ExecuteAsync(RedisClient).ConfigureAwait(false); + } + await AsAsync().FlushAsync(token).ConfigureAwait(false); + return true; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisTypedPipeline.cs b/src/ServiceStack.Redis/Generic/RedisTypedPipeline.cs index 61336d98..86e42af1 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedPipeline.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedPipeline.cs @@ -7,7 +7,7 @@ namespace ServiceStack.Redis /// Pipeline for redis typed client /// /// - public class RedisTypedPipeline : RedisTypedCommandQueue, IRedisTypedPipeline + public partial class RedisTypedPipeline : RedisTypedCommandQueue, IRedisTypedPipeline { internal RedisTypedPipeline(RedisTypedClient redisClient) : base(redisClient) diff --git a/src/ServiceStack.Redis/Generic/RedisTypedTransaction.Async.cs b/src/ServiceStack.Redis/Generic/RedisTypedTransaction.Async.cs new file mode 100644 index 00000000..33375094 --- /dev/null +++ b/src/ServiceStack.Redis/Generic/RedisTypedTransaction.Async.cs @@ -0,0 +1,94 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Redis.Pipeline; + +namespace ServiceStack.Redis.Generic +{ + /// + /// Adds support for Redis Transactions (i.e. MULTI/EXEC/DISCARD operations). + /// + internal partial class RedisTypedTransaction + : IRedisTypedTransactionAsync, IRedisTransactionBaseAsync + { + async ValueTask IRedisTypedTransactionAsync.CommitAsync(CancellationToken token) + { + bool rc = true; + try + { + _numCommands = QueuedCommands.Count / 2; + + //insert multi command at beginning + QueuedCommands.Insert(0, new QueuedRedisCommand() + { + }.WithAsyncReturnCommand(VoidReturnCommandAsync: r => { Init(); return default; }) + .WithAsyncReadCommand(RedisClient.ExpectOkAsync)); + + //the first half of the responses will be "QUEUED", + // so insert reading of multiline after these responses + QueuedCommands.Insert(_numCommands + 1, new QueuedRedisOperation() + { + OnSuccessIntCallback = handleMultiDataResultCount + }.WithAsyncReadCommand(RedisClient.ReadMultiDataResultCountAsync)); + + // add Exec command at end (not queued) + QueuedCommands.Add(new RedisCommand() + { + }.WithAsyncReturnCommand(r => ExecAsync(token))); + + //execute transaction + await ExecAsync(token).ConfigureAwait(false); + + ///////////////////////////// + //receive expected results + foreach (var queuedCommand in QueuedCommands) + { + await queuedCommand.ProcessResultAsync(token).ConfigureAwait(false); + } + } + catch (RedisTransactionFailedException) + { + rc = false; + } + finally + { + RedisClient.Transaction = null; + ClosePipeline(); + await RedisClient.AddTypeIdsRegisteredDuringPipelineAsync(token).ConfigureAwait(false); + } + return rc; + } + + private ValueTask ExecAsync(CancellationToken token) + { + RedisClient.Exec(); + return RedisClient.FlushSendBufferAsync(token); + } + + ValueTask IRedisTypedTransactionAsync.RollbackAsync(CancellationToken token) + { + Rollback(); // no async bits needed + return default; + } + + partial void QueueExpectQueuedAsync() + { + QueuedCommands.Insert(0, new QueuedRedisOperation + { + }.WithAsyncReadCommand(RedisClient.ExpectQueuedAsync)); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Generic/RedisTypedTransaction.cs b/src/ServiceStack.Redis/Generic/RedisTypedTransaction.cs index 94902650..989ef1f4 100644 --- a/src/ServiceStack.Redis/Generic/RedisTypedTransaction.cs +++ b/src/ServiceStack.Redis/Generic/RedisTypedTransaction.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,14 +19,17 @@ namespace ServiceStack.Redis.Generic /// /// Adds support for Redis Transactions (i.e. MULTI/EXEC/DISCARD operations). /// - internal class RedisTypedTransaction + internal partial class RedisTypedTransaction : RedisTypedPipeline, IRedisTypedTransaction, IRedisTransactionBase { private int _numCommands = 0; - internal RedisTypedTransaction(RedisTypedClient redisClient) + private readonly bool _isAsync; + internal RedisTypedTransaction(RedisTypedClient redisClient, bool isAsync) : base(redisClient) { - + // if someone casts between sync/async: the sync-over-async or + // async-over-sync is entirely self-inflicted; I can't fix stupid + _isAsync = isAsync; } protected override void Init() @@ -59,7 +62,6 @@ private void Exec() { RedisClient.Exec(); RedisClient.FlushSendBuffer(); - } public bool Commit() @@ -76,7 +78,6 @@ public bool Commit() VoidReadCommand = RedisClient.ExpectOk, }); - //the first half of the responses will be "QUEUED", // so insert reading of multiline after these responses QueuedCommands.Insert(_numCommands + 1, new QueuedRedisOperation() @@ -174,8 +175,16 @@ public override void Dispose() protected override void AddCurrentQueuedOperation() { base.AddCurrentQueuedOperation(); - QueueExpectQueued(); + if (_isAsync) + { + QueueExpectQueuedAsync(); + } + else + { + QueueExpectQueued(); + } } #endregion + partial void QueueExpectQueuedAsync(); } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.Async.cs b/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.Async.cs new file mode 100644 index 00000000..fcadd82b --- /dev/null +++ b/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.Async.cs @@ -0,0 +1,51 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Generic; +using ServiceStack.Redis.Internal; + +namespace ServiceStack.Redis.Pipeline +{ + /// + /// A complete redis command, with method to send command, receive response, and run callback on success or failure + /// + internal partial class QueuedRedisCommand : RedisCommand + { + public override ValueTask ExecuteAsync(IRedisClientAsync client) + { + try + { + switch (AsyncReturnCommand) + { + case null: + ExecuteThrowIfSync(); + return default; + case Func VoidReturnCommandAsync: + return VoidReturnCommandAsync(client); + case Func> IntReturnCommandAsync: + return IntReturnCommandAsync(client).Await(); + case Func> LongReturnCommandAsync: + return LongReturnCommandAsync(client).Await(); + case Func> DoubleReturnCommandAsync: + return DoubleReturnCommandAsync(client).Await(); + case Func> BytesReturnCommandAsync: + return BytesReturnCommandAsync(client).Await(); + case Func> StringReturnCommandAsync: + return StringReturnCommandAsync(client).Await(); + case Func> MultiBytesReturnCommandAsync: + return MultiBytesReturnCommandAsync(client).Await(); + case Func>> MultiStringReturnCommandAsync: + return MultiStringReturnCommandAsync(client).Await(); + case object obj: + ExecuteThrowIfSync(); + // Execute only processes a limited number of patterns; we'll respect that here too + throw new InvalidOperationException("Command cannot be executed in this context: " + obj.GetType().FullName); + } + } + catch (Exception ex) + { + Log.Error(ex); + throw; + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.cs b/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.cs index 1acb90ec..238c9521 100644 --- a/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.cs +++ b/src/ServiceStack.Redis/Pipeline/QueuedRedisCommand.cs @@ -6,7 +6,7 @@ namespace ServiceStack.Redis.Pipeline /// /// A complete redis command, with method to send command, receive response, and run callback on success or failure /// - internal class QueuedRedisCommand : RedisCommand + internal partial class QueuedRedisCommand : RedisCommand { public override void Execute(IRedisClient client) { @@ -50,7 +50,10 @@ public override void Execute(IRedisClient client) else if (MultiStringReturnCommand != null) { MultiStringReturnCommand(client); - + } + else + { + ExecuteThrowIfAsync(); } } catch (Exception ex) @@ -58,7 +61,6 @@ public override void Execute(IRedisClient client) Log.Error(ex); throw; } - } } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.Async.cs b/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.Async.cs new file mode 100644 index 00000000..e1779605 --- /dev/null +++ b/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.Async.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Pipeline +{ + internal partial class QueuedRedisOperation + { + public virtual ValueTask ExecuteAsync(IRedisClientAsync client) => default; + + private Delegate _asyncReadCommand; + private QueuedRedisOperation SetAsyncReadCommand(Delegate value) + { + if (_asyncReadCommand is object && _asyncReadCommand != value) + throw new InvalidOperationException("Only a single async read command can be assigned"); + _asyncReadCommand = value; + return this; + } + + internal QueuedRedisOperation WithAsyncReadCommand(Func VoidReadCommandAsync) + => SetAsyncReadCommand(VoidReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> IntReadCommandAsync) + => SetAsyncReadCommand(IntReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> LongReadCommandAsync) + => SetAsyncReadCommand(LongReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> BoolReadCommandAsync) + => SetAsyncReadCommand(BoolReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> BytesReadCommandAsync) + => SetAsyncReadCommand(BytesReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> MultiBytesReadCommandAsync) + => SetAsyncReadCommand(MultiBytesReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> StringReadCommandAsync) + => SetAsyncReadCommand(StringReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func>> MultiStringReadCommandAsync) + => SetAsyncReadCommand(MultiStringReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func>> DictionaryStringReadCommandAsync) + => SetAsyncReadCommand(DictionaryStringReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> DoubleReadCommandAsync) + => SetAsyncReadCommand(DoubleReadCommandAsync); + internal QueuedRedisOperation WithAsyncReadCommand(Func> RedisDataReadCommandAsync) + => SetAsyncReadCommand(RedisDataReadCommandAsync); + + public async ValueTask ProcessResultAsync(CancellationToken token) + { + try + { + switch (_asyncReadCommand) + { + case null: + ProcessResultThrowIfSync(); + break; + case Func VoidReadCommandAsync: + await VoidReadCommandAsync(token).ConfigureAwait(false); + OnSuccessVoidCallback?.Invoke(); + break; + case Func> IntReadCommandAsync: + var i32 = await IntReadCommandAsync(token).ConfigureAwait(false); + OnSuccessIntCallback?.Invoke(i32); + OnSuccessLongCallback?.Invoke(i32); + OnSuccessBoolCallback?.Invoke(i32 == RedisNativeClient.Success); + OnSuccessVoidCallback?.Invoke(); + break; + case Func> LongReadCommandAsync: + var i64 = await LongReadCommandAsync(token).ConfigureAwait(false); + OnSuccessIntCallback?.Invoke((int)i64); + OnSuccessLongCallback?.Invoke(i64); + OnSuccessBoolCallback?.Invoke(i64 == RedisNativeClient.Success); + OnSuccessVoidCallback?.Invoke(); + break; + case Func> DoubleReadCommandAsync: + var f64 = await DoubleReadCommandAsync(token).ConfigureAwait(false); + OnSuccessDoubleCallback?.Invoke(f64); + break; + case Func> BytesReadCommandAsync: + var bytes = await BytesReadCommandAsync(token).ConfigureAwait(false); + if (bytes != null && bytes.Length == 0) bytes = null; + OnSuccessBytesCallback?.Invoke(bytes); + OnSuccessStringCallback?.Invoke(bytes != null ? Encoding.UTF8.GetString(bytes) : null); + OnSuccessTypeCallback?.Invoke(bytes != null ? Encoding.UTF8.GetString(bytes) : null); + OnSuccessIntCallback?.Invoke(bytes != null ? int.Parse(Encoding.UTF8.GetString(bytes)) : 0); + OnSuccessBoolCallback?.Invoke(bytes != null && Encoding.UTF8.GetString(bytes) == "OK"); + break; + case Func> StringReadCommandAsync: + var s = await StringReadCommandAsync(token).ConfigureAwait(false); + OnSuccessStringCallback?.Invoke(s); + OnSuccessTypeCallback?.Invoke(s); + break; + case Func> MultiBytesReadCommandAsync: + var multiBytes = await MultiBytesReadCommandAsync(token).ConfigureAwait(false); + OnSuccessMultiBytesCallback?.Invoke(multiBytes); + OnSuccessMultiStringCallback?.Invoke(multiBytes?.ToStringList()); + OnSuccessMultiTypeCallback?.Invoke(multiBytes.ToStringList()); + OnSuccessDictionaryStringCallback?.Invoke(multiBytes.ToStringDictionary()); + break; + case Func>> MultiStringReadCommandAsync: + var multiString = await MultiStringReadCommandAsync(token).ConfigureAwait(false); + OnSuccessMultiStringCallback?.Invoke(multiString); + break; + case Func> RedisDataReadCommandAsync: + var data = await RedisDataReadCommandAsync(token).ConfigureAwait(false); + OnSuccessRedisTextCallback?.Invoke(data.ToRedisText()); + OnSuccessRedisDataCallback?.Invoke(data); + break; + case Func> BoolReadCommandAsync: + var b = await BoolReadCommandAsync(token).ConfigureAwait(false); + OnSuccessBoolCallback?.Invoke(b); + break; + case Func>> DictionaryStringReadCommandAsync: + var dict = await DictionaryStringReadCommandAsync(token).ConfigureAwait(false); + OnSuccessDictionaryStringCallback?.Invoke(dict); + break; + default: + ProcessResultThrowIfSync(); + break; + } + } + catch (Exception ex) + { + Log.Error(ex); + + if (OnErrorCallback != null) + { + OnErrorCallback(ex); + } + else + { + throw; + } + } + } + + partial void OnProcessResultThrowIfAsync() + { + if (_asyncReadCommand is object) + { + throw new InvalidOperationException("An async read command was present, but the queued operation is being processed synchronously"); + } + } + private void ProcessResultThrowIfSync() + { + if (VoidReadCommand is object + || IntReadCommand is object + || LongReadCommand is object + || BoolReadCommand is object + || BytesReadCommand is object + || MultiBytesReadCommand is object + || StringReadCommand is object + || MultiBytesReadCommand is object + || DictionaryStringReadCommand is object + || DoubleReadCommand is object + || RedisDataReadCommand is object) + { + throw new InvalidOperationException("A sync read command was present, but the queued operation is being processed asynchronously"); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.cs b/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.cs index 58838a94..3a45d25b 100644 --- a/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.cs +++ b/src/ServiceStack.Redis/Pipeline/QueuedRedisOperation.cs @@ -5,7 +5,7 @@ namespace ServiceStack.Redis.Pipeline { - internal class QueuedRedisOperation + internal partial class QueuedRedisOperation { protected static readonly ILog Log = LogManager.GetLogger(typeof(QueuedRedisOperation)); @@ -51,60 +51,28 @@ public void ProcessResult() if (VoidReadCommand != null) { VoidReadCommand(); - if (OnSuccessVoidCallback != null) - { - OnSuccessVoidCallback(); - } + OnSuccessVoidCallback?.Invoke(); } else if (IntReadCommand != null) { var result = IntReadCommand(); - if (OnSuccessIntCallback != null) - { - OnSuccessIntCallback(result); - } - if (OnSuccessLongCallback != null) - { - OnSuccessLongCallback(result); - } - if (OnSuccessBoolCallback != null) - { - var success = result == RedisNativeClient.Success; - OnSuccessBoolCallback(success); - } - if (OnSuccessVoidCallback != null) - { - OnSuccessVoidCallback(); - } + OnSuccessIntCallback?.Invoke(result); + OnSuccessLongCallback?.Invoke(result); + OnSuccessBoolCallback?.Invoke(result == RedisNativeClient.Success); + OnSuccessVoidCallback?.Invoke(); } else if (LongReadCommand != null) { var result = LongReadCommand(); - if (OnSuccessIntCallback != null) - { - OnSuccessIntCallback((int)result); - } - if (OnSuccessLongCallback != null) - { - OnSuccessLongCallback(result); - } - if (OnSuccessBoolCallback != null) - { - var success = result == RedisNativeClient.Success; - OnSuccessBoolCallback(success); - } - if (OnSuccessVoidCallback != null) - { - OnSuccessVoidCallback(); - } + OnSuccessIntCallback?.Invoke((int)result); + OnSuccessLongCallback?.Invoke(result); + OnSuccessBoolCallback?.Invoke(result == RedisNativeClient.Success); + OnSuccessVoidCallback?.Invoke(); } else if (DoubleReadCommand != null) { var result = DoubleReadCommand(); - if (OnSuccessDoubleCallback != null) - { - OnSuccessDoubleCallback(result); - } + OnSuccessDoubleCallback?.Invoke(result); } else if (BytesReadCommand != null) { @@ -112,78 +80,50 @@ public void ProcessResult() if (result != null && result.Length == 0) result = null; - if (OnSuccessBytesCallback != null) - { - OnSuccessBytesCallback(result); - } - if (OnSuccessStringCallback != null) - { - OnSuccessStringCallback(result != null ? Encoding.UTF8.GetString(result) : null); - } - if (OnSuccessTypeCallback != null) - { - OnSuccessTypeCallback(result != null ? Encoding.UTF8.GetString(result) : null); - } - if (OnSuccessIntCallback != null) - { - OnSuccessIntCallback(result != null ? int.Parse(Encoding.UTF8.GetString(result)) : 0); - } - if (OnSuccessBoolCallback != null) - { - OnSuccessBoolCallback(result != null && Encoding.UTF8.GetString(result) == "OK"); - } + OnSuccessBytesCallback?.Invoke(result); + OnSuccessStringCallback?.Invoke(result != null ? Encoding.UTF8.GetString(result) : null); + OnSuccessTypeCallback?.Invoke(result != null ? Encoding.UTF8.GetString(result) : null); + OnSuccessIntCallback?.Invoke(result != null ? int.Parse(Encoding.UTF8.GetString(result)) : 0); + OnSuccessBoolCallback?.Invoke(result != null && Encoding.UTF8.GetString(result) == "OK"); } else if (StringReadCommand != null) { var result = StringReadCommand(); - if (OnSuccessStringCallback != null) - { - OnSuccessStringCallback(result); - } - if (OnSuccessTypeCallback != null) - { - OnSuccessTypeCallback(result); - } + OnSuccessStringCallback?.Invoke(result); + OnSuccessTypeCallback?.Invoke(result); } else if (MultiBytesReadCommand != null) { var result = MultiBytesReadCommand(); - if (OnSuccessMultiBytesCallback != null) - { - OnSuccessMultiBytesCallback(result); - } - if (OnSuccessMultiStringCallback != null) - { - OnSuccessMultiStringCallback(result != null ? result.ToStringList() : null); - } - if (OnSuccessMultiTypeCallback != null) - { - OnSuccessMultiTypeCallback(result.ToStringList()); - } - if (OnSuccessDictionaryStringCallback != null) - { - OnSuccessDictionaryStringCallback(result.ToStringDictionary()); - } + OnSuccessMultiBytesCallback?.Invoke(result); + OnSuccessMultiStringCallback?.Invoke(result != null ? result.ToStringList() : null); + OnSuccessMultiTypeCallback?.Invoke(result.ToStringList()); + OnSuccessDictionaryStringCallback?.Invoke(result.ToStringDictionary()); } else if (MultiStringReadCommand != null) { var result = MultiStringReadCommand(); - if (OnSuccessMultiStringCallback != null) - { - OnSuccessMultiStringCallback(result); - } + OnSuccessMultiStringCallback?.Invoke(result); } else if (RedisDataReadCommand != null) { var data = RedisDataReadCommand(); - if (OnSuccessRedisTextCallback != null) - { - OnSuccessRedisTextCallback(data.ToRedisText()); - } - if (OnSuccessRedisDataCallback != null) - { - OnSuccessRedisDataCallback(data); - } + OnSuccessRedisTextCallback?.Invoke(data.ToRedisText()); + OnSuccessRedisDataCallback?.Invoke(data); + } + else if (BoolReadCommand != null) + { + var result = BoolReadCommand(); + OnSuccessBoolCallback?.Invoke(result); + } + else if (DictionaryStringReadCommand != null) + { + var result = DictionaryStringReadCommand(); + OnSuccessDictionaryStringCallback?.Invoke(result); + } + else + { + ProcessResultThrowIfAsync(); } } catch (Exception ex) @@ -201,5 +141,7 @@ public void ProcessResult() } } + protected void ProcessResultThrowIfAsync() => OnProcessResultThrowIfAsync(); + partial void OnProcessResultThrowIfAsync(); } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.Async.cs b/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.Async.cs new file mode 100644 index 00000000..992bb480 --- /dev/null +++ b/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.Async.cs @@ -0,0 +1,330 @@ +using ServiceStack.Redis.Pipeline; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + + public partial class RedisAllPurposePipeline : IRedisPipelineAsync + { + private IRedisPipelineAsync AsAsync() => this; + + private protected virtual async ValueTask ReplayAsync(CancellationToken token) + { + Init(); + await ExecuteAsync().ConfigureAwait(false); + await AsAsync().FlushAsync(token).ConfigureAwait(false); + return true; + } + + protected async ValueTask ExecuteAsync() + { + int count = QueuedCommands.Count; + for (int i = 0; i < count; ++i) + { + var op = QueuedCommands[0]; + QueuedCommands.RemoveAt(0); + await op.ExecuteAsync(RedisClient).ConfigureAwait(false); + QueuedCommands.Add(op); + } + } + + ValueTask IRedisPipelineSharedAsync.ReplayAsync(CancellationToken token) + => ReplayAsync(token); + + async ValueTask IRedisPipelineSharedAsync.FlushAsync(CancellationToken token) + { + // flush send buffers + await RedisClient.FlushSendBufferAsync(token).ConfigureAwait(false); + RedisClient.ResetSendBuffer(); + + try + { + //receive expected results + foreach (var queuedCommand in QueuedCommands) + { + await queuedCommand.ProcessResultAsync(token).ConfigureAwait(false); + } + } + catch (Exception) + { + // The connection cannot be reused anymore. All queued commands have been sent to redis. Even if a new command is executed, the next response read from the + // network stream can be the response of one of the queued commands, depending on when the exception occurred. This response would be invalid for the new command. + RedisClient.DisposeConnection(); + throw; + } + + ClosePipeline(); + } + + private protected virtual ValueTask DisposeAsync() + { + // don't need to send anything; just clean up + Dispose(); + return default; + } + + ValueTask IAsyncDisposable.DisposeAsync() => DisposeAsync(); + + internal static void AssertSync(ValueTask command) + { + if (!command.IsCompleted) + { + _ = ObserveAsync(command.AsTask()); + throw new InvalidOperationException($"The operations provided to {nameof(IRedisQueueableOperationAsync.QueueCommand)} should not perform asynchronous operations internally"); + } + // this serves two purposes: 1) surface any fault, and + // 2) ensure that if pooled (IValueTaskSource), it is reclaimed + _ = command.Result; + } + + internal static void AssertSync(ValueTask command) + { + if (!command.IsCompleted) + { + _ = ObserveAsync(command.AsTask()); + throw new InvalidOperationException($"The operations provided to {nameof(IRedisQueueableOperationAsync.QueueCommand)} should not perform asynchronous operations internally"); + } + // this serves two purposes: 1) surface any fault, and + // 2) ensure that if pooled (IValueTaskSource), it is reclaimed + command.GetAwaiter().GetResult(); + } + + static async Task ObserveAsync(Task task) // semantically this is "async void", but: some sync-contexts explode on that + { + // we've already thrown an exception via AssertSync; this + // just ensures that an "unobserved exception" doesn't fire + // as well + try { await task.ConfigureAwait(false); } + catch { } + } + + void IRedisQueueableOperationAsync.QueueCommand(Func command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessVoidCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessIntCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessLongCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessBoolCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessDoubleCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessBytesCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessMultiBytesCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessStringCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessMultiStringCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToSet()), + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(async r => + { + var result = await command(r).ConfigureAwait(false); + return result.ToList(); + })); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func>> command, Action> onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessDictionaryStringCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessRedisDataCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueableOperationAsync.QueueCommand(Func> command, Action onSuccessCallback, Action onErrorCallback) + { + BeginQueuedCommand(new QueuedRedisCommand + { + OnSuccessRedisTextCallback = onSuccessCallback, + OnErrorCallback = onErrorCallback + }.WithAsyncReturnCommand(command)); + AssertSync(command(RedisClient)); + } + + void IRedisQueueCompletableOperationAsync.CompleteMultiBytesQueuedCommandAsync(Func> multiBytesReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(multiBytesReadCommand); + AddCurrentQueuedOperation(); + } + + + void IRedisQueueCompletableOperationAsync.CompleteLongQueuedCommandAsync(Func> longReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(longReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteBytesQueuedCommandAsync(Func> bytesReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(bytesReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteVoidQueuedCommandAsync(Func voidReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(voidReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteStringQueuedCommandAsync(Func> stringReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(stringReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteDoubleQueuedCommandAsync(Func> doubleReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(doubleReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteIntQueuedCommandAsync(Func> intReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(intReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteMultiStringQueuedCommandAsync(Func>> multiStringReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(multiStringReadCommand); + AddCurrentQueuedOperation(); + } + + void IRedisQueueCompletableOperationAsync.CompleteRedisDataQueuedCommandAsync(Func> redisDataReadCommand) + { + //AssertCurrentOperation(); + // this can happen when replaying pipeline/transaction + if (CurrentQueuedOperation == null) return; + + CurrentQueuedOperation.WithAsyncReadCommand(redisDataReadCommand); + AddCurrentQueuedOperation(); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.cs b/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.cs index 9b08b858..19b8fd1b 100644 --- a/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.cs +++ b/src/ServiceStack.Redis/Pipeline/RedisAllPurposePipeline.cs @@ -1,10 +1,10 @@ -using System; using ServiceStack.Redis.Pipeline; +using System; namespace ServiceStack.Redis { - public class RedisAllPurposePipeline : RedisCommandQueue, IRedisPipeline + public partial class RedisAllPurposePipeline : RedisCommandQueue, IRedisPipeline { /// /// General purpose pipeline diff --git a/src/ServiceStack.Redis/Pipeline/RedisCommand.Async.cs b/src/ServiceStack.Redis/Pipeline/RedisCommand.Async.cs new file mode 100644 index 00000000..0485af6f --- /dev/null +++ b/src/ServiceStack.Redis/Pipeline/RedisCommand.Async.cs @@ -0,0 +1,118 @@ +using ServiceStack.Redis.Internal; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + /// + /// Redis command that does not get queued + /// + internal partial class RedisCommand + { + private Delegate _asyncReturnCommand; + protected Delegate AsyncReturnCommand => _asyncReturnCommand; + private RedisCommand SetAsyncReturnCommand(Delegate value) + { + if (_asyncReturnCommand is object && _asyncReturnCommand != value) + throw new InvalidOperationException("Only a single async return command can be assigned"); + _asyncReturnCommand = value; + return this; + } + internal RedisCommand WithAsyncReturnCommand(Func VoidReturnCommandAsync) + => SetAsyncReturnCommand(VoidReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> IntReturnCommandAsync) + => SetAsyncReturnCommand(IntReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> LongReturnCommandAsync) + => SetAsyncReturnCommand(LongReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> BoolReturnCommandAsync) + => SetAsyncReturnCommand(BoolReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> BytesReturnCommandAsync) + => SetAsyncReturnCommand(BytesReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> MultiBytesReturnCommandAsync) + => SetAsyncReturnCommand(MultiBytesReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> StringReturnCommandAsync) + => SetAsyncReturnCommand(StringReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func>> MultiStringReturnCommandAsync) + => SetAsyncReturnCommand(MultiStringReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func>> DictionaryStringReturnCommandAsync) + => SetAsyncReturnCommand(DictionaryStringReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> RedisDataReturnCommandAsync) + => SetAsyncReturnCommand(RedisDataReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> RedisTextReturnCommandAsync) + => SetAsyncReturnCommand(RedisTextReturnCommandAsync); + internal RedisCommand WithAsyncReturnCommand(Func> DoubleReturnCommandAsync) + => SetAsyncReturnCommand(DoubleReturnCommandAsync); + + public override ValueTask ExecuteAsync(IRedisClientAsync client) + { + try + { + switch (_asyncReturnCommand) + { + case null: + ExecuteThrowIfSync(); + return default; + case Func VoidReturnCommandAsync: + return VoidReturnCommandAsync(client); + case Func> IntReturnCommandAsync: + return IntReturnCommandAsync(client).Await(); + case Func> LongReturnCommandAsync: + return LongReturnCommandAsync(client).Await(); + case Func> DoubleReturnCommandAsync: + return DoubleReturnCommandAsync(client).Await(); + case Func> BytesReturnCommandAsync: + return BytesReturnCommandAsync(client).Await(); + case Func> StringReturnCommandAsync: + return StringReturnCommandAsync(client).Await(); + case Func> MultiBytesReturnCommandAsync: + return MultiBytesReturnCommandAsync(client).Await(); + case Func>> MultiStringReturnCommandAsync: + return MultiStringReturnCommandAsync(client).Await(); + case Func>> DictionaryStringReturnCommandAsync: + return DictionaryStringReturnCommandAsync(client).Await(); + case Func> RedisDataReturnCommandAsync: + return RedisDataReturnCommandAsync(client).Await(); + case Func> RedisTextReturnCommandAsync: + return RedisTextReturnCommandAsync(client).Await(); + case Func> BoolReturnCommandAsync: + return BoolReturnCommandAsync(client).Await(); + case object obj: + ExecuteThrowIfSync(); + return default; + } + } + catch (Exception ex) + { + Log.Error(ex); + return default; // RedisCommand.Execute swallows here; we'll do the same + } + } + + partial void OnExecuteThrowIfAsync() + { + if (_asyncReturnCommand is object) + { + throw new InvalidOperationException("An async return command was present, but the queued operation is being processed synchronously"); + } + } + protected void ExecuteThrowIfSync() + { + if (VoidReturnCommand is object + || IntReturnCommand is object + || LongReturnCommand is object + || BoolReturnCommand is object + || BytesReturnCommand is object + || MultiBytesReturnCommand is object + || StringReturnCommand is object + || MultiStringReturnCommand is object + || DictionaryStringReturnCommand is object + || RedisDataReturnCommand is object + || RedisTextReturnCommand is object + || DoubleReturnCommand is object) + { + throw new InvalidOperationException("A sync return command was present, but the queued operation is being processed asynchronously"); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/RedisCommand.cs b/src/ServiceStack.Redis/Pipeline/RedisCommand.cs index ab64a2c9..abe09a35 100644 --- a/src/ServiceStack.Redis/Pipeline/RedisCommand.cs +++ b/src/ServiceStack.Redis/Pipeline/RedisCommand.cs @@ -9,7 +9,7 @@ namespace ServiceStack.Redis /// /// Redis command that does not get queued /// - internal class RedisCommand : QueuedRedisOperation + internal partial class RedisCommand : QueuedRedisOperation { public Action VoidReturnCommand { get; set; } public Func IntReturnCommand { get; set; } @@ -79,11 +79,22 @@ public override void Execute(IRedisClient client) { RedisTextReturnCommand(client); } + else if (BoolReturnCommand != null) + { + BoolReturnCommand(client); + } + else + { + ExecuteThrowIfAsync(); + } } catch (Exception ex) { Log.Error(ex); } } + + protected void ExecuteThrowIfAsync() => OnExecuteThrowIfAsync(); + partial void OnExecuteThrowIfAsync(); } } diff --git a/src/ServiceStack.Redis/Pipeline/RedisCommandQueue.cs b/src/ServiceStack.Redis/Pipeline/RedisCommandQueue.cs index 0a5f3abf..e21d42ab 100644 --- a/src/ServiceStack.Redis/Pipeline/RedisCommandQueue.cs +++ b/src/ServiceStack.Redis/Pipeline/RedisCommandQueue.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -242,7 +242,7 @@ public virtual void QueueCommand(Func> command, Ac BeginQueuedCommand(new QueuedRedisCommand { MultiStringReturnCommand = r => command(r).ToList(), - OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToHashSet()), + OnSuccessMultiStringCallback = list => onSuccessCallback(list.ToSet()), OnErrorCallback = onErrorCallback }); command(RedisClient); diff --git a/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.Async.cs b/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.Async.cs new file mode 100644 index 00000000..bea7be2f --- /dev/null +++ b/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.Async.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Pipeline +{ + partial class RedisPipelineCommand + { + internal async ValueTask> ReadAllAsIntsAsync(CancellationToken token) + { + var results = new List(); + while (cmdCount-- > 0) + { + results.Add(await client.ReadLongAsync(token).ConfigureAwait(false)); + } + + return results; + } + internal async ValueTask ReadAllAsIntsHaveSuccessAsync(CancellationToken token) + { + var allResults = await ReadAllAsIntsAsync(token).ConfigureAwait(false); + return allResults.All(x => x == RedisNativeClient.Success); + } + + internal ValueTask FlushAsync(CancellationToken token) + { + Flush(); + return default; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.cs b/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.cs index 6c2830dd..67b4cb69 100644 --- a/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.cs +++ b/src/ServiceStack.Redis/Pipeline/RedisPipelineCommand.cs @@ -3,7 +3,7 @@ namespace ServiceStack.Redis.Pipeline { - public class RedisPipelineCommand + public partial class RedisPipelineCommand { private readonly RedisNativeClient client; private int cmdCount; diff --git a/src/ServiceStack.Redis/Pipeline/RedisQueueCompletableOperation.cs b/src/ServiceStack.Redis/Pipeline/RedisQueueCompletableOperation.cs index 8a399cac..92b10529 100644 --- a/src/ServiceStack.Redis/Pipeline/RedisQueueCompletableOperation.cs +++ b/src/ServiceStack.Redis/Pipeline/RedisQueueCompletableOperation.cs @@ -7,7 +7,7 @@ namespace ServiceStack.Redis /// /// Redis operation (transaction/pipeline) that allows queued commands to be completed /// - public class RedisQueueCompletableOperation + public partial class RedisQueueCompletableOperation { internal readonly List QueuedCommands = new List(); diff --git a/src/ServiceStack.Redis/PooledRedisClientManager.Async.cs b/src/ServiceStack.Redis/PooledRedisClientManager.Async.cs new file mode 100644 index 00000000..79734a35 --- /dev/null +++ b/src/ServiceStack.Redis/PooledRedisClientManager.Async.cs @@ -0,0 +1,283 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Caching; +using ServiceStack.Redis.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.AsyncEx; + +namespace ServiceStack.Redis +{ + public partial class PooledRedisClientManager + : IRedisClientsManagerAsync + { + /// + /// Use previous client resolving behavior + /// + public static bool UseGetClientBlocking = false; + + ValueTask IRedisClientsManagerAsync.GetCacheClientAsync(CancellationToken token) + => new RedisClientManagerCacheClient(this).AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetClientAsync(CancellationToken token) => UseGetClientBlocking + ? GetClientBlocking().AsValueTaskResult() + : GetClientAsync(); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyCacheClientAsync(CancellationToken token) + => new RedisClientManagerCacheClient(this) { ReadOnly = true }.AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyClientAsync(CancellationToken token) => UseGetClientBlocking + ? GetReadOnlyClientBlocking().AsValueTaskResult() + : GetReadOnlyClientAsync(); + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + private AsyncManualResetEvent readAsyncEvent; + partial void PulseAllReadAsync() + { + readAsyncEvent?.Set(); + readAsyncEvent?.Reset(); + } + + private AsyncManualResetEvent writeAsyncEvent; + partial void PulseAllWriteAsync() + { + writeAsyncEvent?.Set(); + writeAsyncEvent?.Reset(); + } + + private async Task WaitForWriter(int msTimeout) + { + // If we're not doing async, no need to create this till we need it. + writeAsyncEvent ??= new AsyncManualResetEvent(false); + var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(msTimeout)); + try + { + await writeAsyncEvent.WaitAsync(cts.Token); + } + catch (OperationCanceledException) { return false; } + return true; + } + + private async ValueTask GetClientAsync() + { + try + { + var inactivePoolIndex = -1; + do + { + lock (writeClients) + { + AssertValidReadWritePool(); + + // If it's -1, then we want to try again after a delay of some kind. So if it's NOT negative one, process it... + if ((inactivePoolIndex = GetInActiveWriteClient(out var inActiveClient)) != -1) + { + //inActiveClient != null only for Valid InActive Clients + if (inActiveClient != null) + { + WritePoolIndex++; + inActiveClient.Activate(); + + InitClient(inActiveClient); + + return inActiveClient; + } + else + { + // Still need to be in lock for this! + break; + } + } + } + + if (PoolTimeout.HasValue) + { + // We have a timeout value set - so try to not wait longer than this. + if (!await WaitForWriter(PoolTimeout.Value)) + { + throw new TimeoutException(PoolTimeoutError); + } + } + else + { + // Wait forever, so just retry till we get one. + await WaitForWriter(RecheckPoolAfterMs); + } + } while (true); // Just keep repeating until we get a slot. + + //Reaches here when there's no Valid InActive Clients, but we have a slot for one! + try + { + //inactivePoolIndex = index of reservedSlot || index of invalid client + var existingClient = writeClients[inactivePoolIndex]; + if (existingClient != null && existingClient != reservedSlot && existingClient.HadExceptions) + { + RedisState.DeactivateClient(existingClient); + } + + var newClient = InitNewClient(RedisResolver.CreateMasterClient(inactivePoolIndex)); + + //Put all blocking I/O or potential Exceptions before lock + lock (writeClients) + { + //If existingClient at inactivePoolIndex changed (failover) return new client outside of pool + if (writeClients[inactivePoolIndex] != existingClient) + { + if (Log.IsDebugEnabled) + Log.Debug("writeClients[inactivePoolIndex] != existingClient: {0}".Fmt(writeClients[inactivePoolIndex])); + + return newClient; //return client outside of pool + } + + WritePoolIndex++; + writeClients[inactivePoolIndex] = newClient; + + return !AssertAccessOnlyOnSameThread + ? newClient + : newClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace); + } + } + catch + { + //Revert free-slot for any I/O exceptions that can throw (before lock) + lock (writeClients) + { + writeClients[inactivePoolIndex] = null; //free slot + } + throw; + } + } + finally + { + RedisState.DisposeExpiredClients(); + } + } + + private async Task WaitForReader(int msTimeout) + { + // If we're not doing async, no need to create this till we need it. + readAsyncEvent ??= new AsyncManualResetEvent(false); + var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(msTimeout)); + try + { + await readAsyncEvent.WaitAsync(cts.Token); + } + catch (OperationCanceledException) { return false; } + return true; + } + + private async ValueTask GetReadOnlyClientAsync() + { + try + { + var inactivePoolIndex = -1; + do + { + lock (readClients) + { + AssertValidReadOnlyPool(); + + // If it's -1, then we want to try again after a delay of some kind. So if it's NOT negative one, process it... + if ((inactivePoolIndex = GetInActiveReadClient(out var inActiveClient)) != -1) + { + //inActiveClient != null only for Valid InActive Clients + if (inActiveClient != null) + { + ReadPoolIndex++; + inActiveClient.Activate(); + + InitClient(inActiveClient); + + return inActiveClient; + } + else + { + // Still need to be in lock for this! + break; + } + } + } + + if (PoolTimeout.HasValue) + { + // We have a timeout value set - so try to not wait longer than this. + if (!await WaitForReader(PoolTimeout.Value)) + { + throw new TimeoutException(PoolTimeoutError); + } + } + else + { + // Wait forever, so just retry till we get one. + await WaitForReader(RecheckPoolAfterMs); + } + } while (true); // Just keep repeating until we get a slot. + + //Reaches here when there's no Valid InActive Clients + try + { + //inactivePoolIndex = index of reservedSlot || index of invalid client + var existingClient = readClients[inactivePoolIndex]; + if (existingClient != null && existingClient != reservedSlot && existingClient.HadExceptions) + { + RedisState.DeactivateClient(existingClient); + } + + var newClient = InitNewClient(RedisResolver.CreateSlaveClient(inactivePoolIndex)); + + //Put all blocking I/O or potential Exceptions before lock + lock (readClients) + { + //If existingClient at inactivePoolIndex changed (failover) return new client outside of pool + if (readClients[inactivePoolIndex] != existingClient) + { + if (Log.IsDebugEnabled) + Log.Debug("readClients[inactivePoolIndex] != existingClient: {0}".Fmt(readClients[inactivePoolIndex])); + + Interlocked.Increment(ref RedisState.TotalClientsCreatedOutsidePool); + + //Don't handle callbacks for new client outside pool + newClient.ClientManager = null; + return newClient; //return client outside of pool + } + + ReadPoolIndex++; + readClients[inactivePoolIndex] = newClient; + return newClient; + } + } + catch + { + //Revert free-slot for any I/O exceptions that can throw + lock (readClients) + { + readClients[inactivePoolIndex] = null; //free slot + } + throw; + } + } + finally + { + RedisState.DisposeExpiredClients(); + } + } + + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/PooledRedisClientManager.cs b/src/ServiceStack.Redis/PooledRedisClientManager.cs index 770be3bb..481db23a 100644 --- a/src/ServiceStack.Redis/PooledRedisClientManager.cs +++ b/src/ServiceStack.Redis/PooledRedisClientManager.cs @@ -5,14 +5,13 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using ServiceStack.Caching; @@ -23,8 +22,8 @@ namespace ServiceStack.Redis { /// /// Provides thread-safe pooling of redis client connections. - /// Allows load-balancing of master-write and read-slave hosts, ideal for - /// 1 master and multiple replicated read slaves. + /// Allows load-balancing of master-write and read-replica hosts, ideal for + /// 1 master and multiple replicated read replicas. /// public partial class PooledRedisClientManager : IRedisClientsManager, IRedisFailover, IHandleClientDispose, IHasRedisResolver @@ -42,7 +41,7 @@ public partial class PooledRedisClientManager public int? SocketReceiveTimeout { get; set; } public int? IdleTimeOutSecs { get; set; } public bool AssertAccessOnlyOnSameThread { get; set; } - + /// /// Gets or sets object key prefix. /// @@ -104,8 +103,8 @@ public PooledRedisClientManager( public PooledRedisClientManager( IEnumerable readWriteHosts, IEnumerable readOnlyHosts, - long initalDb) - : this(readWriteHosts, readOnlyHosts, null, initalDb, null, null) + long initialDb) + : this(readWriteHosts, readOnlyHosts, null, initialDb, null, null) { } @@ -113,25 +112,25 @@ public PooledRedisClientManager( IEnumerable readWriteHosts, IEnumerable readOnlyHosts, RedisClientManagerConfig config, - long? initalDb, + long? initialDb, int? poolSizeMultiplier, int? poolTimeOutSeconds) { this.Db = config != null - ? config.DefaultDb ?? initalDb - : initalDb; + ? config.DefaultDb ?? initialDb + : initialDb; var masters = (readWriteHosts ?? TypeConstants.EmptyStringArray).ToArray(); - var slaves = (readOnlyHosts ?? TypeConstants.EmptyStringArray).ToArray(); + var replicas = (readOnlyHosts ?? TypeConstants.EmptyStringArray).ToArray(); - RedisResolver = new RedisResolver(masters, slaves); + RedisResolver = new RedisResolver(masters, replicas); - this.PoolSizeMultiplier = poolSizeMultiplier ?? 20; + this.PoolSizeMultiplier = poolSizeMultiplier ?? RedisConfig.DefaultPoolSizeMultiplier; this.Config = config ?? new RedisClientManagerConfig { MaxWritePoolSize = RedisConfig.DefaultMaxPoolSize ?? masters.Length * PoolSizeMultiplier, - MaxReadPoolSize = RedisConfig.DefaultMaxPoolSize ?? slaves.Length * PoolSizeMultiplier, + MaxReadPoolSize = RedisConfig.DefaultMaxPoolSize ?? replicas.Length * PoolSizeMultiplier, }; this.OnFailover = new List>(); @@ -160,6 +159,11 @@ public void FailoverTo(IEnumerable readWriteHosts, IEnumerable r { Interlocked.Increment(ref RedisState.TotalFailovers); + var masters = readWriteHosts.ToList(); + var replicas = readOnlyHosts.ToList(); + + Log.Info($"FailoverTo: {string.Join(",", masters)} : {string.Join(",", replicas)} Total: {RedisState.TotalFailovers}"); + lock (readClients) { for (var i = 0; i < readClients.Length; i++) @@ -170,7 +174,7 @@ public void FailoverTo(IEnumerable readWriteHosts, IEnumerable r readClients[i] = null; } - RedisResolver.ResetSlaves(readOnlyHosts); + RedisResolver.ResetSlaves(replicas); } lock (writeClients) @@ -183,7 +187,7 @@ public void FailoverTo(IEnumerable readWriteHosts, IEnumerable r writeClients[i] = null; } - RedisResolver.ResetMasters(readWriteHosts); + RedisResolver.ResetMasters(masters); } if (this.OnFailover != null) @@ -211,7 +215,9 @@ protected virtual void OnStart() /// Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts /// /// - public IRedisClient GetClient() + public IRedisClient GetClient() => GetClientBlocking(); + + private RedisClient GetClientBlocking() { try { @@ -243,11 +249,11 @@ public IRedisClient GetClient() if (inActiveClient != null) { WritePoolIndex++; - inActiveClient.Active = true; + inActiveClient.Activate(); InitClient(inActiveClient); - return !AssertAccessOnlyOnSameThread + return (!AssertAccessOnlyOnSameThread) ? inActiveClient : inActiveClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace); } @@ -283,7 +289,7 @@ public IRedisClient GetClient() WritePoolIndex++; writeClients[inactivePoolIndex] = newClient; - return !AssertAccessOnlyOnSameThread + return (!AssertAccessOnlyOnSameThread) ? newClient : newClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace); } @@ -323,7 +329,7 @@ public override void Dispose() {} private int GetInActiveWriteClient(out RedisClient inactiveClient) { //this will loop through all hosts in readClients once even though there are 2 for loops - //both loops are used to try to get the prefered host according to the round robin algorithm + //both loops are used to try to get the preferred host according to the round robin algorithm var readWriteTotal = RedisResolver.ReadWriteHostsCount; var desiredIndex = WritePoolIndex % writeClients.Length; for (int x = 0; x < readWriteTotal; x++) @@ -359,7 +365,9 @@ private int GetInActiveWriteClient(out RedisClient inactiveClient) /// Returns a ReadOnly client using the hosts defined in ReadOnlyHosts. /// /// - public virtual IRedisClient GetReadOnlyClient() + public virtual IRedisClient GetReadOnlyClient() => GetReadOnlyClientBlocking(); + + private RedisClient GetReadOnlyClientBlocking() { try { @@ -391,7 +399,7 @@ public virtual IRedisClient GetReadOnlyClient() if (inActiveClient != null) { ReadPoolIndex++; - inActiveClient.Active = true; + inActiveClient.Activate(); InitClient(inActiveClient); @@ -459,7 +467,7 @@ private int GetInActiveReadClient(out RedisClient inactiveClient) { var desiredIndex = ReadPoolIndex % readClients.Length; //this will loop through all hosts in readClients once even though there are 2 for loops - //both loops are used to try to get the prefered host according to the round robin algorithm + //both loops are used to try to get the preferred host according to the round robin algorithm var readOnlyTotal = RedisResolver.ReadOnlyHostsCount; for (int x = 0; x < readOnlyTotal; x++) { @@ -493,7 +501,7 @@ private int GetInActiveReadClient(out RedisClient inactiveClient) private RedisClient InitNewClient(RedisClient client) { client.Id = Interlocked.Increment(ref RedisClientCounter); - client.Active = true; + client.Activate(newClient:true); client.ClientManager = this; client.ConnectionFilter = ConnectionFilter; if (NamespacePrefix != null) @@ -519,6 +527,20 @@ private RedisClient InitClient(RedisClient client) return client; } + partial void PulseAllReadAsync(); + private void PulseAllRead() + { + PulseAllReadAsync(); + Monitor.PulseAll(readClients); + } + + partial void PulseAllWriteAsync(); + private void PulseAllWrite() + { + PulseAllWriteAsync(); + Monitor.PulseAll(writeClients); + } + public void DisposeClient(RedisNativeClient client) { lock (readClients) @@ -534,10 +556,10 @@ public void DisposeClient(RedisNativeClient client) else { client.TrackThread = null; - client.Active = false; + client.Deactivate(); } - Monitor.PulseAll(readClients); + PulseAllRead(); return; } } @@ -555,19 +577,24 @@ public void DisposeClient(RedisNativeClient client) else { client.TrackThread = null; - client.Active = false; + client.Deactivate(); } - Monitor.PulseAll(writeClients); + PulseAllWrite(); return; } } //Client not found in any pool, pulse both pools. lock (readClients) - Monitor.PulseAll(readClients); + { + PulseAllRead(); + } + lock (writeClients) - Monitor.PulseAll(writeClients); + { + PulseAllWrite(); + } } /// @@ -578,8 +605,8 @@ public void DisposeReadOnlyClient(RedisNativeClient client) { lock (readClients) { - client.Active = false; - Monitor.PulseAll(readClients); + client.Deactivate(); + PulseAllRead(); } } @@ -591,8 +618,8 @@ public void DisposeWriteClient(RedisNativeClient client) { lock (writeClients) { - client.Active = false; - Monitor.PulseAll(writeClients); + client.Deactivate(); + PulseAllWrite(); } } @@ -770,9 +797,7 @@ protected void Dispose(RedisClient redisClient) } catch (Exception ex) { - Log.Error(string.Format( - "Error when trying to dispose of RedisClient to host {0}:{1}", - redisClient.Host, redisClient.Port), ex); + Log.Error($"Error when trying to dispose of RedisClient to host {redisClient.Host}:{redisClient.Port}", ex); } } @@ -812,7 +837,7 @@ public DisposablePooledClient(PooledRedisClientManager clientManager) /// /// access the wrapped client /// - public T Client { get { return client; } } + public T Client => client; /// /// release the wrapped client back to the pool diff --git a/src/ServiceStack.Redis/Properties/AssemblyInfo.cs b/src/ServiceStack.Redis/Properties/AssemblyInfo.cs index d58a4563..4ce9daba 100644 --- a/src/ServiceStack.Redis/Properties/AssemblyInfo.cs +++ b/src/ServiceStack.Redis/Properties/AssemblyInfo.cs @@ -1,36 +1,5 @@ -using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("ServiceStack.Redis")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("ServiceStack.Redis")] -[assembly: AssemblyCopyright("Copyright (c) ServiceStack 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("70a33fa7-9f81-418d-bb25-6a4be6648ae4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("5.0.0.0")] -[assembly: AssemblyFileVersion("5.0.0.0")] +[assembly: System.Reflection.AssemblyVersion("6.0.0.0")] \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClient.Async.cs b/src/ServiceStack.Redis/RedisClient.Async.cs new file mode 100644 index 00000000..6648a224 --- /dev/null +++ b/src/ServiceStack.Redis/RedisClient.Async.cs @@ -0,0 +1,1558 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis/ +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Caching; +using ServiceStack.Data; +using ServiceStack.Model; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Internal; +using ServiceStack.Redis.Pipeline; +using ServiceStack.Text; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + partial class RedisClient : IRedisClientAsync, IRemoveByPatternAsync, ICacheClientAsync, IAsyncDisposable + { + /// + /// Access this instance for async usage + /// + public IRedisClientAsync AsAsync() => this; + + // the typed client implements this for us + IRedisTypedClientAsync IRedisClientAsync.As() => (IRedisTypedClientAsync)As(); + + // convenience since we're not saturating the public API; this makes it easy to call + // the explicit interface implementations; the JIT should make this a direct call + private IRedisNativeClientAsync NativeAsync => this; + + IHasNamed IRedisClientAsync.Lists => Lists as IHasNamed ?? throw new NotSupportedException($"The provided Lists ({Lists?.GetType().FullName}) does not support IRedisListAsync"); + IHasNamed IRedisClientAsync.Sets => Sets as IHasNamed ?? throw new NotSupportedException($"The provided Sets ({Sets?.GetType().FullName})does not support IRedisSetAsync"); + IHasNamed IRedisClientAsync.SortedSets => SortedSets as IHasNamed ?? throw new NotSupportedException($"The provided SortedSets ({SortedSets?.GetType().FullName})does not support IRedisSortedSetAsync"); + IHasNamed IRedisClientAsync.Hashes => Hashes as IHasNamed ?? throw new NotSupportedException($"The provided Hashes ({Hashes?.GetType().FullName})does not support IRedisHashAsync"); + + internal ValueTask RegisterTypeIdAsync(T value, CancellationToken token) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + var id = value.GetId().ToString(); + + return RegisterTypeIdAsync(typeIdsSetKey, id, token); + } + internal ValueTask RegisterTypeIdAsync(string typeIdsSetKey, string id, CancellationToken token) + { + if (this.Pipeline != null) + { + var registeredTypeIdsWithinPipeline = GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey); + registeredTypeIdsWithinPipeline.Add(id); + return default; + } + else + { + return AsAsync().AddItemToSetAsync(typeIdsSetKey, id, token); + } + } + + // Called just after original Pipeline is closed. + internal async ValueTask AddTypeIdsRegisteredDuringPipelineAsync(CancellationToken token) + { + foreach (var entry in registeredTypeIdsWithinPipelineMap) + { + await AsAsync().AddRangeToSetAsync(entry.Key, entry.Value.ToList(), token).ConfigureAwait(false); + } + registeredTypeIdsWithinPipelineMap = new Dictionary>(); + } + + + ValueTask IRedisClientAsync.GetServerTimeAsync(CancellationToken token) + => NativeAsync.TimeAsync(token).Await(parts => ParseTimeResult(parts)); + + IRedisPipelineAsync IRedisClientAsync.CreatePipeline() + => new RedisAllPurposePipeline(this); + + ValueTask IRedisClientAsync.CreateTransactionAsync(CancellationToken token) + { + AssertServerVersionNumber(); // pre-fetch call to INFO before transaction if needed + return new RedisTransaction(this, true).AsValueTaskResult(); // note that the MULTI here will be held and flushed async + } + + ValueTask IRedisClientAsync.RemoveEntryAsync(string[] keys, CancellationToken token) + => keys.Length == 0 ? default : NativeAsync.DelAsync(keys, token).IsSuccessAsync(); + + private async ValueTask ExecAsync(Func action) + { + using (JsConfig.With(new Text.Config { ExcludeTypeInfo = false })) + { + await action(this).ConfigureAwait(false); + } + } + + private async ValueTask ExecAsync(Func> action) + { + using (JsConfig.With(new Text.Config { ExcludeTypeInfo = false })) + { + var ret = await action(this).ConfigureAwait(false); + return ret; + } + } + + ValueTask IRedisClientAsync.SetValueAsync(string key, string value, CancellationToken token) + { + var bytesValue = value?.ToUtf8Bytes(); + return NativeAsync.SetAsync(key, bytesValue, token: token); + } + + ValueTask IRedisClientAsync.GetValueAsync(string key, CancellationToken token) + => NativeAsync.GetAsync(key, token).FromUtf8BytesAsync(); + + Task ICacheClientAsync.GetAsync(string key, CancellationToken token) + { + return ExecAsync(async r => { + if (typeof(T) == typeof(byte[])) + { + var ret = await ((IRedisNativeClientAsync) r).GetAsync(key, token).ConfigureAwait(false); + return (T) (object) ret; + } + else + { + var val = await r.GetValueAsync(key, token).ConfigureAwait(false); + var ret = JsonSerializer.DeserializeFromString(val); + return ret; + } + }).AsTask(); + } + + async ValueTask> IRedisClientAsync.SearchKeysAsync(string pattern, CancellationToken token) + { + var list = new List(); + await foreach (var value in ((IRedisClientAsync)this).ScanAllKeysAsync(pattern, token: token).WithCancellation(token).ConfigureAwait(false)) + { + list.Add(value); + } + return list; + } + + async IAsyncEnumerable IRedisClientAsync.ScanAllKeysAsync(string pattern, int pageSize, [EnumeratorCancellation] CancellationToken token) + { + ScanResult ret = default; + while (true) + { + ret = await (pattern != null // note ConfigureAwait is handled below + ? NativeAsync.ScanAsync(ret?.Cursor ?? 0, pageSize, match: pattern, token: token) + : NativeAsync.ScanAsync(ret?.Cursor ?? 0, pageSize, token: token) + ).ConfigureAwait(false); + + foreach (var key in ret.Results) + { + yield return key.FromUtf8Bytes(); + } + + if (ret.Cursor == 0) break; + } + } + + ValueTask IRedisClientAsync.GetEntryTypeAsync(string key, CancellationToken token) + => NativeAsync.TypeAsync(key, token).Await((val, state) => state.ParseEntryType(val), this); + + ValueTask IRedisClientAsync.AddItemToSetAsync(string setId, string item, CancellationToken token) + => NativeAsync.SAddAsync(setId, item.ToUtf8Bytes(), token).Await(); + + ValueTask IRedisClientAsync.AddItemToListAsync(string listId, string value, CancellationToken token) + => NativeAsync.RPushAsync(listId, value.ToUtf8Bytes(), token).Await(); + + ValueTask IRedisClientAsync.AddItemToSortedSetAsync(string setId, string value, CancellationToken token) + => ((IRedisClientAsync)this).AddItemToSortedSetAsync(setId, value, GetLexicalScore(value), token); + + ValueTask IRedisClientAsync.AddItemToSortedSetAsync(string setId, string value, double score, CancellationToken token) + => NativeAsync.ZAddAsync(setId, score, value.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask IRedisClientAsync.SetEntryInHashAsync(string hashId, string key, string value, CancellationToken token) + => NativeAsync.HSetAsync(hashId, key.ToUtf8Bytes(), value.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask IRedisClientAsync.SetAllAsync(IDictionary map, CancellationToken token) + => GetSetAllBytes(map, out var keyBytes, out var valBytes) ? NativeAsync.MSetAsync(keyBytes, valBytes, token) : default; + + ValueTask IRedisClientAsync.SetAllAsync(IEnumerable keys, IEnumerable values, CancellationToken token) + => GetSetAllBytes(keys, values, out var keyBytes, out var valBytes) ? NativeAsync.MSetAsync(keyBytes, valBytes, token) : default; + + Task ICacheClientAsync.SetAllAsync(IDictionary values, CancellationToken token) + { + if (values.Count != 0) + { + return ExecAsync(r => + { + // need to do this inside Exec for the JSON config bits + GetSetAllBytesTyped(values, out var keys, out var valBytes); + return ((IRedisNativeClientAsync)r).MSetAsync(keys, valBytes, token); + }).AsTask(); + } + else + { + return Task.CompletedTask; + } + } + + ValueTask IRedisClientAsync.RenameKeyAsync(string fromName, string toName, CancellationToken token) + => NativeAsync.RenameAsync(fromName, toName, token); + + ValueTask IRedisClientAsync.ContainsKeyAsync(string key, CancellationToken token) + => NativeAsync.ExistsAsync(key, token).IsSuccessAsync(); + + + ValueTask IRedisClientAsync.GetRandomKeyAsync(CancellationToken token) + => NativeAsync.RandomKeyAsync(token); + + ValueTask IRedisClientAsync.SelectAsync(long db, CancellationToken token) + => NativeAsync.SelectAsync(db, token); + + ValueTask IRedisClientAsync.ExpireEntryInAsync(string key, TimeSpan expireIn, CancellationToken token) + => UseMillisecondExpiration(expireIn) + ? NativeAsync.PExpireAsync(key, (long)expireIn.TotalMilliseconds, token) + : NativeAsync.ExpireAsync(key, (int)expireIn.TotalSeconds, token); + + ValueTask IRedisClientAsync.ExpireEntryAtAsync(string key, DateTime expireAt, CancellationToken token) + => AssertServerVersionNumber() >= 2600 + ? NativeAsync.PExpireAtAsync(key, ConvertToServerDate(expireAt).ToUnixTimeMs(), token) + : NativeAsync.ExpireAtAsync(key, ConvertToServerDate(expireAt).ToUnixTime(), token); + + Task ICacheClientAsync.GetTimeToLiveAsync(string key, CancellationToken token) + => NativeAsync.TtlAsync(key, token).Await(ParseTimeToLiveResult).AsTask(); + + ValueTask IRedisClientAsync.PingAsync(CancellationToken token) + => NativeAsync.PingAsync(token); + + ValueTask IRedisClientAsync.EchoAsync(string text, CancellationToken token) + => NativeAsync.EchoAsync(text, token); + + ValueTask IRedisClientAsync.ForegroundSaveAsync(CancellationToken token) + => NativeAsync.SaveAsync(token); + + ValueTask IRedisClientAsync.BackgroundSaveAsync(CancellationToken token) + => NativeAsync.BgSaveAsync(token); + + ValueTask IRedisClientAsync.ShutdownAsync(CancellationToken token) + => NativeAsync.ShutdownAsync(false, token); + + ValueTask IRedisClientAsync.ShutdownNoSaveAsync(CancellationToken token) + => NativeAsync.ShutdownAsync(true, token); + + ValueTask IRedisClientAsync.BackgroundRewriteAppendOnlyFileAsync(CancellationToken token) + => NativeAsync.BgRewriteAofAsync(token); + + ValueTask IRedisClientAsync.FlushDbAsync(CancellationToken token) + => NativeAsync.FlushDbAsync(token); + + ValueTask> IRedisClientAsync.GetValuesAsync(List keys, CancellationToken token) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Count == 0) return new List().AsValueTaskResult(); + + return NativeAsync.MGetAsync(keys.ToArray(), token).Await(ParseGetValuesResult); + } + + ValueTask> IRedisClientAsync.GetValuesAsync(List keys, CancellationToken token) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Count == 0) return new List().AsValueTaskResult(); + + return NativeAsync.MGetAsync(keys.ToArray(), token).Await(ParseGetValuesResult); + } + + ValueTask> IRedisClientAsync.GetValuesMapAsync(List keys, CancellationToken token) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Count == 0) return new Dictionary().AsValueTaskResult(); + + var keysArray = keys.ToArray(); + return NativeAsync.MGetAsync(keysArray, token).Await((resultBytesArray, state) => ParseGetValuesMapResult(state, resultBytesArray), keysArray); + } + + ValueTask> IRedisClientAsync.GetValuesMapAsync(List keys, CancellationToken token) + { + if (keys == null) throw new ArgumentNullException(nameof(keys)); + if (keys.Count == 0) return new Dictionary().AsValueTaskResult(); + + var keysArray = keys.ToArray(); + return NativeAsync.MGetAsync(keysArray, token).Await((resultBytesArray, state) => ParseGetValuesMapResult(state, resultBytesArray), keysArray); + } + + ValueTask IRedisClientAsync.AcquireLockAsync(string key, TimeSpan? timeOut, CancellationToken token) + => RedisLock.CreateAsync(this, key, timeOut, token).Await(value => value); + + ValueTask IRedisClientAsync.SetValueAsync(string key, string value, TimeSpan expireIn, CancellationToken token) + { + var bytesValue = value?.ToUtf8Bytes(); + + if (AssertServerVersionNumber() >= 2610) + { + PickTime(expireIn, out var seconds, out var milliseconds); + return NativeAsync.SetAsync(key, bytesValue, expirySeconds: seconds, + expiryMilliseconds: milliseconds, token: token); + } + else + { + return NativeAsync.SetExAsync(key, (int)expireIn.TotalSeconds, bytesValue, token); + } + } + + static void PickTime(TimeSpan? value, out long expirySeconds, out long expiryMilliseconds) + { + expirySeconds = expiryMilliseconds = 0; + if (value.HasValue) + { + var expireIn = value.GetValueOrDefault(); + if (expireIn.Milliseconds > 0) + { + expiryMilliseconds = (long)expireIn.TotalMilliseconds; + } + else + { + expirySeconds = (long)expireIn.TotalSeconds; + } + } + } + ValueTask IRedisClientAsync.SetValueIfNotExistsAsync(string key, string value, TimeSpan? expireIn, CancellationToken token) + { + var bytesValue = value?.ToUtf8Bytes(); + PickTime(expireIn, out var seconds, out var milliseconds); + return NativeAsync.SetAsync(key, bytesValue, false, seconds, milliseconds, token); + } + + ValueTask IRedisClientAsync.SetValueIfExistsAsync(string key, string value, TimeSpan? expireIn, CancellationToken token) + { + var bytesValue = value?.ToUtf8Bytes(); + PickTime(expireIn, out var seconds, out var milliseconds); + return NativeAsync.SetAsync(key, bytesValue, true, seconds, milliseconds, token); + } + + ValueTask IRedisClientAsync.WatchAsync(string[] keys, CancellationToken token) + => NativeAsync.WatchAsync(keys, token); + + ValueTask IRedisClientAsync.UnWatchAsync(CancellationToken token) + => NativeAsync.UnWatchAsync(token); + + ValueTask IRedisClientAsync.AppendToValueAsync(string key, string value, CancellationToken token) + => NativeAsync.AppendAsync(key, value.ToUtf8Bytes(), token); + + async ValueTask IRedisClientAsync.StoreObjectAsync(object entity, CancellationToken token) + { + if (entity == null) throw new ArgumentNullException(nameof(entity)); + + var id = entity.GetObjectId(); + var entityType = entity.GetType(); + var urnKey = UrnKey(entityType, id); + var valueString = JsonSerializer.SerializeToString(entity); + + await ((IRedisClientAsync)this).SetValueAsync(urnKey, valueString, token).ConfigureAwait(false); + + await RegisterTypeIdAsync(GetTypeIdsSetKey(entityType), id.ToString(), token).ConfigureAwait(false); + + return entity; + } + + ValueTask IRedisClientAsync.PopItemFromSetAsync(string setId, CancellationToken token) + => NativeAsync.SPopAsync(setId, token).FromUtf8BytesAsync(); + + ValueTask> IRedisClientAsync.PopItemsFromSetAsync(string setId, int count, CancellationToken token) + => NativeAsync.SPopAsync(setId, count, token).ToStringListAsync(); + + ValueTask IRedisClientAsync.SlowlogResetAsync(CancellationToken token) + => NativeAsync.SlowlogResetAsync(token); + + ValueTask IRedisClientAsync.GetSlowlogAsync(int? numberOfRecords, CancellationToken token) + => NativeAsync.SlowlogGetAsync(numberOfRecords, token).Await(ParseSlowlog); + + + Task ICacheClientAsync.SetAsync(string key, T value, CancellationToken token) + => ExecAsync(r => ((IRedisNativeClientAsync)r).SetAsync(key, ToBytes(value), token: token)).AwaitAsTrueTask(); + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + ValueTask IRedisClientAsync.GetSortedSetCountAsync(string setId, CancellationToken token) + => NativeAsync.ZCardAsync(setId, token); + + ValueTask IRedisClientAsync.GetSortedSetCountAsync(string setId, string fromStringScore, string toStringScore, CancellationToken token) + { + var fromScore = GetLexicalScore(fromStringScore); + var toScore = GetLexicalScore(toStringScore); + return AsAsync().GetSortedSetCountAsync(setId, fromScore, toScore, token); + } + + ValueTask IRedisClientAsync.GetSortedSetCountAsync(string setId, double fromScore, double toScore, CancellationToken token) + => NativeAsync.ZCountAsync(setId, fromScore, toScore, token); + + ValueTask IRedisClientAsync.GetSortedSetCountAsync(string setId, long fromScore, long toScore, CancellationToken token) + => NativeAsync.ZCountAsync(setId, fromScore, toScore, token); + + ValueTask IRedisClientAsync.GetItemScoreInSortedSetAsync(string setId, string value, CancellationToken token) + => NativeAsync.ZScoreAsync(setId, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.CustomAsync(object[] cmdWithArgs, CancellationToken token) + => RawCommandAsync(token, cmdWithArgs).Await(result => result.ToRedisText()); + + ValueTask IRedisClientAsync.SetValuesAsync(IDictionary map, CancellationToken token) + => ((IRedisClientAsync)this).SetAllAsync(map, token); + + Task ICacheClientAsync.SetAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + AssertNotInTransaction(); + return ExecAsync(async r => + { + await r.SetAsync(key, value, token).ConfigureAwait(false); + await r.ExpireEntryAtAsync(key, ConvertToServerDate(expiresAt), token).ConfigureAwait(false); + }).AwaitAsTrueTask(); + } + Task ICacheClientAsync.SetAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + if (AssertServerVersionNumber() >= 2600) + { + return ExecAsync(r => ((IRedisNativeClientAsync)r) + .SetAsync(key, ToBytes(value), 0, expiryMilliseconds: (long)expiresIn.TotalMilliseconds, token)).AwaitAsTrueTask(); + } + else + { + return ExecAsync(r => ((IRedisNativeClientAsync)r) + .SetExAsync(key, (int)expiresIn.TotalSeconds, ToBytes(value), token)).AwaitAsTrueTask(); + } + } + + Task ICacheClientAsync.FlushAllAsync(CancellationToken token) + => NativeAsync.FlushAllAsync(token).AsTask(); + + Task> ICacheClientAsync.GetAllAsync(IEnumerable keys, CancellationToken token) + { + return ExecAsync(r => + { + var keysArray = keys.ToArray(); + + return ((IRedisNativeClientAsync)r).MGetAsync(keysArray, token).Await((keyValues, state) => ProcessGetAllResult(state, keyValues), keysArray); + }).AsTask(); + } + + Task ICacheClientAsync.RemoveAsync(string key, CancellationToken token) + => NativeAsync.DelAsync(key, token).IsSuccessTaskAsync(); + + IAsyncEnumerable ICacheClientAsync.GetKeysByPatternAsync(string pattern, CancellationToken token) + => AsAsync().ScanAllKeysAsync(pattern, token: token); + + Task ICacheClientAsync.RemoveExpiredEntriesAsync(CancellationToken token) + { + //Redis automatically removed expired Cache Entries + return Task.CompletedTask; + } + + async Task IRemoveByPatternAsync.RemoveByPatternAsync(string pattern, CancellationToken token) + { + List buffer = null; + const int BATCH_SIZE = 1024; + await foreach (var key in AsAsync().ScanAllKeysAsync(pattern, token: token).WithCancellation(token).ConfigureAwait(false)) + { + (buffer ??= new List()).Add(key); + if (buffer.Count == BATCH_SIZE) + { + await NativeAsync.DelAsync(buffer.ToArray(), token).ConfigureAwait(false); + buffer.Clear(); + } + } + if (buffer is object && buffer.Count != 0) + { + await NativeAsync.DelAsync(buffer.ToArray(), token).ConfigureAwait(false); + } + } + + Task IRemoveByPatternAsync.RemoveByRegexAsync(string regex, CancellationToken token) + => AsAsync().RemoveByPatternAsync(RegexToGlob(regex), token); + + Task ICacheClientAsync.RemoveAllAsync(IEnumerable keys, CancellationToken token) + => ExecAsync(r => r.RemoveEntryAsync(keys.ToArray(), token)).AsTask(); + + Task ICacheClientAsync.IncrementAsync(string key, uint amount, CancellationToken token) + => ExecAsync(r => r.IncrementValueByAsync(key, (int)amount, token)).AsTask(); + + Task ICacheClientAsync.DecrementAsync(string key, uint amount, CancellationToken token) + => ExecAsync(r => r.DecrementValueByAsync(key, (int)amount, token)).AsTask(); + + + Task ICacheClientAsync.AddAsync(string key, T value, CancellationToken token) + => ExecAsync(r => ((IRedisNativeClientAsync)r).SetAsync(key, ToBytes(value), exists: false, token: token)).AsTask(); + + Task ICacheClientAsync.ReplaceAsync(string key, T value, CancellationToken token) + => ExecAsync(r => ((IRedisNativeClientAsync)r).SetAsync(key, ToBytes(value), exists: true, token: token)).AsTask(); + + Task ICacheClientAsync.AddAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + AssertNotInTransaction(); + + return ExecAsync(async r => + { + if (await r.AddAsync(key, value, token).ConfigureAwait(false)) + { + await r.ExpireEntryAtAsync(key, ConvertToServerDate(expiresAt), token).ConfigureAwait(false); + return true; + } + return false; + }).AsTask(); + } + + Task ICacheClientAsync.ReplaceAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + AssertNotInTransaction(); + + return ExecAsync(async r => + { + if (await r.ReplaceAsync(key, value, token).ConfigureAwait(false)) + { + await r.ExpireEntryAtAsync(key, ConvertToServerDate(expiresAt), token).ConfigureAwait(false); + return true; + } + return false; + }).AsTask(); + } + + Task ICacheClientAsync.AddAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + => ExecAsync(r => ((IRedisNativeClientAsync)r).SetAsync(key, ToBytes(value), exists: false, token: token)).AsTask(); + + Task ICacheClientAsync.ReplaceAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + => ExecAsync(r => ((IRedisNativeClientAsync)r).SetAsync(key, ToBytes(value), exists: true, token: token)).AsTask(); + + ValueTask IRedisClientAsync.DbSizeAsync(CancellationToken token) + => NativeAsync.DbSizeAsync(token); + + ValueTask> IRedisClientAsync.InfoAsync(CancellationToken token) + => NativeAsync.InfoAsync(token); + + ValueTask IRedisClientAsync.LastSaveAsync(CancellationToken token) + => NativeAsync.LastSaveAsync(token); + + async Task IEntityStoreAsync.GetByIdAsync(object id, CancellationToken token) + { + var key = UrnKey(id); + var valueString = await AsAsync().GetValueAsync(key, token).ConfigureAwait(false); + var value = JsonSerializer.DeserializeFromString(valueString); + return value; + } + + async Task> IEntityStoreAsync.GetByIdsAsync(ICollection ids, CancellationToken token) + { + if (ids == null || ids.Count == 0) + return new List(); + + var urnKeys = ids.Cast().Map(UrnKey); + return await AsAsync().GetValuesAsync(urnKeys, token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.StoreAsync(T entity, CancellationToken token) + { + var urnKey = UrnKey(entity); + var valueString = JsonSerializer.SerializeToString(entity); + + await AsAsync().SetValueAsync(urnKey, valueString, token).ConfigureAwait(false); + await RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + + return entity; + } + + Task IEntityStoreAsync.StoreAllAsync(IEnumerable entities, CancellationToken token) + => StoreAllAsyncImpl(entities, token).AsTask(); + + internal async ValueTask StoreAllAsyncImpl(IEnumerable entities, CancellationToken token) + { + if (PrepareStoreAll(entities, out var keys, out var values, out var entitiesList)) + { + await NativeAsync.MSetAsync(keys, values, token).ConfigureAwait(false); + await RegisterTypeIdsAsync(entitiesList, token).ConfigureAwait(false); + } + } + + internal ValueTask RegisterTypeIdsAsync(IEnumerable values, CancellationToken token) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + var ids = values.Map(x => x.GetId().ToString()); + + if (this.Pipeline != null) + { + var registeredTypeIdsWithinPipeline = GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey); + ids.ForEach(x => registeredTypeIdsWithinPipeline.Add(x)); + return default; + } + else + { + return AsAsync().AddRangeToSetAsync(typeIdsSetKey, ids, token); + } + } + + internal ValueTask RemoveTypeIdsByValueAsync(T value, CancellationToken token) => + RemoveTypeIdsByIdAsync(value.GetId().ToString(), token); + internal async ValueTask RemoveTypeIdsByValuesAsync(T[] values, CancellationToken token) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + if (this.Pipeline != null) + { + var registeredTypeIdsWithinPipeline = GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey); + values.Each(x => registeredTypeIdsWithinPipeline.Remove(x.GetId().ToString())); + } + else + { + foreach (var x in values) + { + await AsAsync().RemoveItemFromSetAsync(typeIdsSetKey, x.GetId().ToString(), token).ConfigureAwait(false); + } + } + } + + internal async ValueTask RemoveTypeIdsByIdAsync(string id, CancellationToken token) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + if (this.Pipeline != null) + GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey).Remove(id); + else + { + await AsAsync().RemoveItemFromSetAsync(typeIdsSetKey, id, token).ConfigureAwait(false); + } + } + + internal async ValueTask RemoveTypeIdsByIdsAsync(string[] ids, CancellationToken token) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + if (this.Pipeline != null) + { + var registeredTypeIdsWithinPipeline = GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey); + ids.Each(x => registeredTypeIdsWithinPipeline.Remove(x)); + } + else + { + foreach (var x in ids) + { + await AsAsync().RemoveItemFromSetAsync(typeIdsSetKey, x, token).ConfigureAwait(false); + } + } + } + + async Task IEntityStoreAsync.DeleteAsync(T entity, CancellationToken token) + { + var urnKey = UrnKey(entity); + await AsAsync().RemoveAsync(urnKey, token).ConfigureAwait(false); + await this.RemoveTypeIdsByValueAsync(entity, token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.DeleteByIdAsync(object id, CancellationToken token) + { + var urnKey = UrnKey(id); + await AsAsync().RemoveAsync(urnKey, token).ConfigureAwait(false); + await this.RemoveTypeIdsByIdAsync(id.ToString(), token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.DeleteByIdsAsync(ICollection ids, CancellationToken token) + { + if (ids == null || ids.Count == 0) return; + + var idStrings = ids.Cast().Select(x => x.ToString()).ToArray(); + var urnKeys = idStrings.Select(UrnKey).ToArray(); + await AsAsync().RemoveEntryAsync(urnKeys, token).ConfigureAwait(false); + await this.RemoveTypeIdsByIdsAsync(idStrings, token).ConfigureAwait(false); + } + + async Task IEntityStoreAsync.DeleteAllAsync(CancellationToken token) + { + await DeleteAllAsync(0, RedisConfig.CommandKeysBatchSize, token).ConfigureAwait(false); + } + + private async Task DeleteAllAsync(ulong cursor, int batchSize, CancellationToken token) + { + var typeIdsSetKey = this.GetTypeIdsSetKey(); + var asyncClient = AsAsync(); + do + { + var scanResult = await NativeAsync.SScanAsync(typeIdsSetKey, cursor, batchSize, token: token).ConfigureAwait(false); + cursor = scanResult.Cursor; + var urnKeys = scanResult.Results.Select(id => UrnKey(id.FromUtf8Bytes())).ToArray(); + if (urnKeys.Length > 0) + { + await asyncClient.RemoveEntryAsync(urnKeys, token).ConfigureAwait(false); + } + } while (cursor != 0); + await asyncClient.RemoveEntryAsync(new[] { typeIdsSetKey }, token).ConfigureAwait(false); + } + + ValueTask> IRedisClientAsync.SearchSortedSetAsync(string setId, string start, string end, int? skip, int? take, CancellationToken token) + { + start = GetSearchStart(start); + end = GetSearchEnd(end); + + return NativeAsync.ZRangeByLexAsync(setId, start, end, skip, take, token).ToStringListAsync(); + } + + ValueTask IRedisClientAsync.SearchSortedSetCountAsync(string setId, string start, string end, CancellationToken token) + => NativeAsync.ZLexCountAsync(setId, GetSearchStart(start), GetSearchEnd(end), token); + + ValueTask IRedisClientAsync.RemoveRangeFromSortedSetBySearchAsync(string setId, string start, string end, CancellationToken token) + => NativeAsync.ZRemRangeByLexAsync(setId, GetSearchStart(start), GetSearchEnd(end), token); + + ValueTask IRedisClientAsync.TypeAsync(string key, CancellationToken token) + => NativeAsync.TypeAsync(key, token); + + ValueTask IRedisClientAsync.GetStringCountAsync(string key, CancellationToken token) + => NativeAsync.StrLenAsync(key, token); + + ValueTask IRedisClientAsync.GetSetCountAsync(string setId, CancellationToken token) + => NativeAsync.SCardAsync(setId, token); + + ValueTask IRedisClientAsync.GetListCountAsync(string listId, CancellationToken token) + => NativeAsync.LLenAsync(listId, token); + + ValueTask IRedisClientAsync.GetHashCountAsync(string hashId, CancellationToken token) + => NativeAsync.HLenAsync(hashId, token); + + async ValueTask IRedisClientAsync.ExecCachedLuaAsync(string scriptBody, Func> scriptSha1, CancellationToken token) + { + if (!CachedLuaSha1Map.TryGetValue(scriptBody, out var sha1)) + CachedLuaSha1Map[scriptBody] = sha1 = await AsAsync().LoadLuaScriptAsync(scriptBody, token).ConfigureAwait(false); + + try + { + return await scriptSha1(sha1).ConfigureAwait(false); + } + catch (RedisResponseException ex) + { + if (!ex.Message.StartsWith("NOSCRIPT")) + throw; + + CachedLuaSha1Map[scriptBody] = sha1 = await AsAsync().LoadLuaScriptAsync(scriptBody, token).ConfigureAwait(false); + return await scriptSha1(sha1).ConfigureAwait(false); + } + } + + ValueTask IRedisClientAsync.ExecLuaAsync(string luaBody, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalCommandAsync(luaBody, keys?.Length ?? 0, MergeAndConvertToBytes(keys, args), token).Await(data => data.ToRedisText()); + + ValueTask IRedisClientAsync.ExecLuaShaAsync(string sha1, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalShaCommandAsync(sha1, keys?.Length ?? 0, MergeAndConvertToBytes(keys, args), token).Await(data => data.ToRedisText()); + + ValueTask IRedisClientAsync.ExecLuaAsStringAsync(string luaBody, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalStrAsync(luaBody, keys?.Length ?? 0, MergeAndConvertToBytes(keys, args), token); + + ValueTask IRedisClientAsync.ExecLuaShaAsStringAsync(string sha1, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalShaStrAsync(sha1, keys?.Length ?? 0, MergeAndConvertToBytes(keys, args), token); + + ValueTask IRedisClientAsync.LoadLuaScriptAsync(string body, CancellationToken token) + => NativeAsync.ScriptLoadAsync(body, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.WriteAllAsync(IEnumerable entities, CancellationToken token) + => PrepareWriteAll(entities, out var keys, out var values) ? NativeAsync.MSetAsync(keys, values, token) : default; + + async ValueTask> IRedisClientAsync.GetAllItemsFromSetAsync(string setId, CancellationToken token) + { + var multiDataList = await NativeAsync.SMembersAsync(setId, token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + async ValueTask IRedisClientAsync.AddRangeToSetAsync(string setId, List items, CancellationToken token) + { + if (await AddRangeToSetNeedsSendAsync(setId, items).ConfigureAwait(false)) + { + var uSetId = setId.ToUtf8Bytes(); + var pipeline = CreatePipelineCommand(); + foreach (var item in items) + { + pipeline.WriteCommand(Commands.SAdd, uSetId, item.ToUtf8Bytes()); + } + await pipeline.FlushAsync(token).ConfigureAwait(false); + + //the number of items after + _ = await pipeline.ReadAllAsIntsAsync(token).ConfigureAwait(false); + } + } + + async ValueTask AddRangeToSetNeedsSendAsync(string setId, List items) + { + if (setId.IsNullOrEmpty()) + throw new ArgumentNullException("setId"); + if (items == null) + throw new ArgumentNullException("items"); + if (items.Count == 0) + return false; + + if (this.Transaction is object || this.PipelineAsync is object) + { + var queueable = this.Transaction as IRedisQueueableOperationAsync + ?? this.Pipeline as IRedisQueueableOperationAsync; + + if (queueable == null) + throw new NotSupportedException("Cannot AddRangeToSetAsync() when Transaction is: " + this.Transaction.GetType().Name); + + //Complete the first QueuedCommand() + await AsAsync().AddItemToSetAsync(setId, items[0]).ConfigureAwait(false); + + //Add subsequent queued commands + for (var i = 1; i < items.Count; i++) + { + var item = items[i]; + queueable.QueueCommand(c => c.AddItemToSetAsync(setId, item)); + } + return false; + } + else + { + return true; + } + } + + ValueTask IRedisClientAsync.RemoveItemFromSetAsync(string setId, string item, CancellationToken token) + => NativeAsync.SRemAsync(setId, item.ToUtf8Bytes(), token).Await(); + + ValueTask IRedisClientAsync.IncrementValueByAsync(string key, int count, CancellationToken token) + => NativeAsync.IncrByAsync(key, count, token); + + ValueTask IRedisClientAsync.IncrementValueByAsync(string key, long count, CancellationToken token) + => NativeAsync.IncrByAsync(key, count, token); + + ValueTask IRedisClientAsync.IncrementValueByAsync(string key, double count, CancellationToken token) + => NativeAsync.IncrByFloatAsync(key, count, token); + ValueTask IRedisClientAsync.IncrementValueAsync(string key, CancellationToken token) + => NativeAsync.IncrAsync(key, token); + + ValueTask IRedisClientAsync.DecrementValueAsync(string key, CancellationToken token) + => NativeAsync.DecrAsync(key, token); + + ValueTask IRedisClientAsync.DecrementValueByAsync(string key, int count, CancellationToken token) + => NativeAsync.DecrByAsync(key, count, token); + + async ValueTask IRedisClientAsync.GetServerRoleAsync(CancellationToken token) + { + if (AssertServerVersionNumber() >= 2812) + { + var text = await NativeAsync.RoleAsync(token).ConfigureAwait(false); + var roleName = text.Children[0].Text; + return ToServerRole(roleName); + } + + var info = await AsAsync().InfoAsync(token).ConfigureAwait(false); + info.TryGetValue("role", out var role); + return ToServerRole(role); + } + + ValueTask IRedisClientAsync.GetServerRoleInfoAsync(CancellationToken token) + => NativeAsync.RoleAsync(token); + + async ValueTask IRedisClientAsync.GetConfigAsync(string configItem, CancellationToken token) + { + var byteArray = await NativeAsync.ConfigGetAsync(configItem, token).ConfigureAwait(false); + return GetConfigParse(byteArray); + } + + ValueTask IRedisClientAsync.SetConfigAsync(string configItem, string value, CancellationToken token) + => NativeAsync.ConfigSetAsync(configItem, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.SaveConfigAsync(CancellationToken token) + => NativeAsync.ConfigRewriteAsync(token); + + ValueTask IRedisClientAsync.ResetInfoStatsAsync(CancellationToken token) + => NativeAsync.ConfigResetStatAsync(token); + + ValueTask IRedisClientAsync.GetClientAsync(CancellationToken token) + => NativeAsync.ClientGetNameAsync(token); + + ValueTask IRedisClientAsync.SetClientAsync(string name, CancellationToken token) + => NativeAsync.ClientSetNameAsync(name, token); + + ValueTask IRedisClientAsync.KillClientAsync(string address, CancellationToken token) + => NativeAsync.ClientKillAsync(address, token); + + ValueTask IRedisClientAsync.KillClientsAsync(string fromAddress, string withId, RedisClientType? ofType, bool? skipMe, CancellationToken token) + { + var typeString = ofType?.ToString().ToLower(); + var skipMeString = skipMe.HasValue ? (skipMe.Value ? "yes" : "no") : null; + return NativeAsync.ClientKillAsync(addr: fromAddress, id: withId, type: typeString, skipMe: skipMeString, token); + } + + async ValueTask>> IRedisClientAsync.GetClientsInfoAsync(CancellationToken token) + => GetClientsInfoParse(await NativeAsync.ClientListAsync(token).ConfigureAwait(false)); + + ValueTask IRedisClientAsync.PauseAllClientsAsync(TimeSpan duration, CancellationToken token) + => NativeAsync.ClientPauseAsync((int)duration.TotalMilliseconds, token); + + ValueTask> IRedisClientAsync.GetAllKeysAsync(CancellationToken token) + => AsAsync().SearchKeysAsync("*", token); + + ValueTask IRedisClientAsync.GetAndSetValueAsync(string key, string value, CancellationToken token) + => NativeAsync.GetSetAsync(key, value.ToUtf8Bytes(), token).FromUtf8BytesAsync(); + + async ValueTask IRedisClientAsync.GetFromHashAsync(object id, CancellationToken token) + { + var key = UrnKey(id); + return (await AsAsync().GetAllEntriesFromHashAsync(key, token).ConfigureAwait(false)).ToJson().FromJson(); + } + + async ValueTask IRedisClientAsync.StoreAsHashAsync(T entity, CancellationToken token) + { + var key = UrnKey(entity); + var hash = ConvertToHashFn(entity); + await AsAsync().SetRangeInHashAsync(key, hash, token).ConfigureAwait(false); + await RegisterTypeIdAsync(entity, token).ConfigureAwait(false); + } + + ValueTask> IRedisClientAsync.GetSortedEntryValuesAsync(string setId, int startingFrom, int endingAt, CancellationToken token) + { + var sortOptions = new SortOptions { Skip = startingFrom, Take = endingAt, }; + return NativeAsync.SortAsync(setId, sortOptions, token).ToStringListAsync(); + } + + async IAsyncEnumerable IRedisClientAsync.ScanAllSetItemsAsync(string setId, string pattern, int pageSize, [EnumeratorCancellation] CancellationToken token) + { + var ret = new ScanResult(); + while (true) + { + ret = await (pattern != null // note ConfigureAwait is handled below + ? NativeAsync.SScanAsync(setId, ret.Cursor, pageSize, match: pattern, token: token) + : NativeAsync.SScanAsync(setId, ret.Cursor, pageSize, token: token) + ).ConfigureAwait(false); + + foreach (var key in ret.Results) + { + yield return key.FromUtf8Bytes(); + } + + if (ret.Cursor == 0) break; + } + } + + async IAsyncEnumerable> IRedisClientAsync.ScanAllSortedSetItemsAsync(string setId, string pattern, int pageSize, [EnumeratorCancellation] CancellationToken token) + { + var ret = new ScanResult(); + while (true) + { + ret = await (pattern != null // note ConfigureAwait is handled below + ? NativeAsync.ZScanAsync(setId, ret.Cursor, pageSize, match: pattern, token: token) + : NativeAsync.ZScanAsync(setId, ret.Cursor, pageSize, token: token) + ).ConfigureAwait(false); + + foreach (var entry in ret.AsItemsWithScores()) + { + yield return entry; + } + + if (ret.Cursor == 0) break; + } + } + + async IAsyncEnumerable> IRedisClientAsync.ScanAllHashEntriesAsync(string hashId, string pattern, int pageSize, [EnumeratorCancellation] CancellationToken token) + { + var ret = new ScanResult(); + while (true) + { + ret = await (pattern != null // note ConfigureAwait is handled below + ? NativeAsync.HScanAsync(hashId, ret.Cursor, pageSize, match: pattern, token: token) + : NativeAsync.HScanAsync(hashId, ret.Cursor, pageSize, token: token) + ).ConfigureAwait(false); + + foreach (var entry in ret.AsKeyValues()) + { + yield return entry; + } + + if (ret.Cursor == 0) break; + } + } + + ValueTask IRedisClientAsync.AddToHyperLogAsync(string key, string[] elements, CancellationToken token) + => NativeAsync.PfAddAsync(key, elements.Map(x => x.ToUtf8Bytes()).ToArray(), token); + + ValueTask IRedisClientAsync.CountHyperLogAsync(string key, CancellationToken token) + => NativeAsync.PfCountAsync(key, token); + + ValueTask IRedisClientAsync.MergeHyperLogsAsync(string toKey, string[] fromKeys, CancellationToken token) + => NativeAsync.PfMergeAsync(toKey, fromKeys, token); + + ValueTask IRedisClientAsync.AddGeoMemberAsync(string key, double longitude, double latitude, string member, CancellationToken token) + => NativeAsync.GeoAddAsync(key, longitude, latitude, member, token); + + ValueTask IRedisClientAsync.AddGeoMembersAsync(string key, RedisGeo[] geoPoints, CancellationToken token) + => NativeAsync.GeoAddAsync(key, geoPoints, token); + + ValueTask IRedisClientAsync.CalculateDistanceBetweenGeoMembersAsync(string key, string fromMember, string toMember, string unit, CancellationToken token) + => NativeAsync.GeoDistAsync(key, fromMember, toMember, unit, token); + + ValueTask IRedisClientAsync.GetGeohashesAsync(string key, string[] members, CancellationToken token) + => NativeAsync.GeoHashAsync(key, members, token); + + ValueTask> IRedisClientAsync.GetGeoCoordinatesAsync(string key, string[] members, CancellationToken token) + => NativeAsync.GeoPosAsync(key, members, token); + + async ValueTask IRedisClientAsync.FindGeoMembersInRadiusAsync(string key, double longitude, double latitude, double radius, string unit, CancellationToken token) + { + var results = await NativeAsync.GeoRadiusAsync(key, longitude, latitude, radius, unit, token: token).ConfigureAwait(false); + return ParseFindGeoMembersResult(results); + } + + ValueTask> IRedisClientAsync.FindGeoResultsInRadiusAsync(string key, double longitude, double latitude, double radius, string unit, int? count, bool? sortByNearest, CancellationToken token) + => NativeAsync.GeoRadiusAsync(key, longitude, latitude, radius, unit, withCoords: true, withDist: true, withHash: true, count: count, asc: sortByNearest, token: token); + + async ValueTask IRedisClientAsync.FindGeoMembersInRadiusAsync(string key, string member, double radius, string unit, CancellationToken token) + { + var results = await NativeAsync.GeoRadiusByMemberAsync(key, member, radius, unit, token: token).ConfigureAwait(false); + return ParseFindGeoMembersResult(results); + } + + ValueTask> IRedisClientAsync.FindGeoResultsInRadiusAsync(string key, string member, double radius, string unit, int? count, bool? sortByNearest, CancellationToken token) + => NativeAsync.GeoRadiusByMemberAsync(key, member, radius, unit, withCoords: true, withDist: true, withHash: true, count: count, asc: sortByNearest, token: token); + + ValueTask IRedisClientAsync.CreateSubscriptionAsync(CancellationToken token) + => new RedisSubscription(this).AsValueTaskResult(); + + ValueTask IRedisClientAsync.PublishMessageAsync(string toChannel, string message, CancellationToken token) + => NativeAsync.PublishAsync(toChannel, message.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.MoveBetweenSetsAsync(string fromSetId, string toSetId, string item, CancellationToken token) + => NativeAsync.SMoveAsync(fromSetId, toSetId, item.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.SetContainsItemAsync(string setId, string item, CancellationToken token) + => NativeAsync.SIsMemberAsync(setId, item.ToUtf8Bytes(), token).IsSuccessAsync(); + + async ValueTask> IRedisClientAsync.GetIntersectFromSetsAsync(string[] setIds, CancellationToken token) + { + if (setIds.Length == 0) + return new HashSet(); + + var multiDataList = await NativeAsync.SInterAsync(setIds, token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisClientAsync.StoreIntersectFromSetsAsync(string intoSetId, string[] setIds, CancellationToken token) + { + if (setIds.Length == 0) return default; + + return NativeAsync.SInterStoreAsync(intoSetId, setIds, token); + } + + async ValueTask> IRedisClientAsync.GetUnionFromSetsAsync(string[] setIds, CancellationToken token) + { + if (setIds.Length == 0) + return new HashSet(); + + var multiDataList = await NativeAsync.SUnionAsync(setIds, token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisClientAsync.StoreUnionFromSetsAsync(string intoSetId, string[] setIds, CancellationToken token) + { + if (setIds.Length == 0) return default; + + return NativeAsync.SUnionStoreAsync(intoSetId, setIds, token); + } + + async ValueTask> IRedisClientAsync.GetDifferencesFromSetAsync(string fromSetId, string[] withSetIds, CancellationToken token) + { + if (withSetIds.Length == 0) + return new HashSet(); + + var multiDataList = await NativeAsync.SDiffAsync(fromSetId, withSetIds, token).ConfigureAwait(false); + return CreateHashSet(multiDataList); + } + + ValueTask IRedisClientAsync.StoreDifferencesFromSetAsync(string intoSetId, string fromSetId, string[] withSetIds, CancellationToken token) + { + if (withSetIds.Length == 0) return default; + + return NativeAsync.SDiffStoreAsync(intoSetId, fromSetId, withSetIds, token); + } + + ValueTask IRedisClientAsync.GetRandomItemFromSetAsync(string setId, CancellationToken token) + => NativeAsync.SRandMemberAsync(setId, token).FromUtf8BytesAsync(); + + ValueTask> IRedisClientAsync.GetAllItemsFromListAsync(string listId, CancellationToken token) + => NativeAsync.LRangeAsync(listId, FirstElement, LastElement, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromListAsync(string listId, int startingFrom, int endingAt, CancellationToken token) + => NativeAsync.LRangeAsync(listId, startingFrom, endingAt, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromSortedListAsync(string listId, int startingFrom, int endingAt, CancellationToken token) + { + var sortOptions = new SortOptions { Skip = startingFrom, Take = endingAt, SortAlpha = true }; + return AsAsync().GetSortedItemsFromListAsync(listId, sortOptions, token); + } + + ValueTask> IRedisClientAsync.GetSortedItemsFromListAsync(string listId, SortOptions sortOptions, CancellationToken token) + => NativeAsync.SortAsync(listId, sortOptions, token).ToStringListAsync(); + + async ValueTask IRedisClientAsync.AddRangeToListAsync(string listId, List values, CancellationToken token) + { + var pipeline = AddRangeToListPrepareNonFlushed(listId, values); + await pipeline.FlushAsync(token).ConfigureAwait(false); + + //the number of items after + _ = await pipeline.ReadAllAsIntsAsync(token).ConfigureAwait(false); + } + + ValueTask IRedisClientAsync.PrependItemToListAsync(string listId, string value, CancellationToken token) + => NativeAsync.LPushAsync(listId, value.ToUtf8Bytes(), token).Await(); + + async ValueTask IRedisClientAsync.PrependRangeToListAsync(string listId, List values, CancellationToken token) + { + var pipeline = PrependRangeToListPrepareNonFlushed(listId, values); + await pipeline.FlushAsync(token).ConfigureAwait(false); + + //the number of items after + _ = await pipeline.ReadAllAsIntsAsync(token).ConfigureAwait(false); + } + + ValueTask IRedisClientAsync.RemoveAllFromListAsync(string listId, CancellationToken token) + => NativeAsync.LTrimAsync(listId, LastElement, FirstElement, token); + + ValueTask IRedisClientAsync.RemoveStartFromListAsync(string listId, CancellationToken token) + => NativeAsync.LPopAsync(listId, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.BlockingRemoveStartFromListAsync(string listId, TimeSpan? timeOut, CancellationToken token) + => NativeAsync.BLPopValueAsync(listId, (int)timeOut.GetValueOrDefault().TotalSeconds, token).FromUtf8BytesAsync(); + + async ValueTask IRedisClientAsync.BlockingRemoveStartFromListsAsync(string[] listIds, TimeSpan? timeOut, CancellationToken token) + { + var value = await NativeAsync.BLPopValueAsync(listIds, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + if (value == null) + return null; + return new ItemRef { Id = value[0].FromUtf8Bytes(), Item = value[1].FromUtf8Bytes() }; + } + + ValueTask IRedisClientAsync.RemoveEndFromListAsync(string listId, CancellationToken token) + => NativeAsync.RPopAsync(listId, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.TrimListAsync(string listId, int keepStartingFrom, int keepEndingAt, CancellationToken token) + => NativeAsync.LTrimAsync(listId, keepStartingFrom, keepEndingAt, token); + + ValueTask IRedisClientAsync.RemoveItemFromListAsync(string listId, string value, CancellationToken token) + => NativeAsync.LRemAsync(listId, 0, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.RemoveItemFromListAsync(string listId, string value, int noOfMatches, CancellationToken token) + => NativeAsync.LRemAsync(listId, 0, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.GetItemFromListAsync(string listId, int listIndex, CancellationToken token) + => NativeAsync.LIndexAsync(listId, listIndex, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.SetItemInListAsync(string listId, int listIndex, string value, CancellationToken token) + => NativeAsync.LSetAsync(listId, listIndex, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.EnqueueItemOnListAsync(string listId, string value, CancellationToken token) + => NativeAsync.LPushAsync(listId, value.ToUtf8Bytes(), token).Await(); + + ValueTask IRedisClientAsync.DequeueItemFromListAsync(string listId, CancellationToken token) + => NativeAsync.RPopAsync(listId, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.BlockingDequeueItemFromListAsync(string listId, TimeSpan? timeOut, CancellationToken token) + => NativeAsync.BRPopValueAsync(listId, (int)timeOut.GetValueOrDefault().TotalSeconds, token).FromUtf8BytesAsync(); + + async ValueTask IRedisClientAsync.BlockingDequeueItemFromListsAsync(string[] listIds, TimeSpan? timeOut, CancellationToken token) + { + var value = await NativeAsync.BRPopValueAsync(listIds, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + if (value == null) + return null; + return new ItemRef { Id = value[0].FromUtf8Bytes(), Item = value[1].FromUtf8Bytes() }; + } + + ValueTask IRedisClientAsync.PushItemToListAsync(string listId, string value, CancellationToken token) + => NativeAsync.RPushAsync(listId, value.ToUtf8Bytes(), token).Await(); + + ValueTask IRedisClientAsync.PopItemFromListAsync(string listId, CancellationToken token) + => NativeAsync.RPopAsync(listId, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.BlockingPopItemFromListAsync(string listId, TimeSpan? timeOut, CancellationToken token) + => NativeAsync.BRPopValueAsync(listId, (int)timeOut.GetValueOrDefault().TotalSeconds, token).FromUtf8BytesAsync(); + + async ValueTask IRedisClientAsync.BlockingPopItemFromListsAsync(string[] listIds, TimeSpan? timeOut, CancellationToken token) + { + var value = await NativeAsync.BRPopValueAsync(listIds, (int)timeOut.GetValueOrDefault().TotalSeconds, token).ConfigureAwait(false); + if (value == null) + return null; + return new ItemRef { Id = value[0].FromUtf8Bytes(), Item = value[1].FromUtf8Bytes() }; + } + + ValueTask IRedisClientAsync.PopAndPushItemBetweenListsAsync(string fromListId, string toListId, CancellationToken token) + => NativeAsync.RPopLPushAsync(fromListId, toListId, token).FromUtf8BytesAsync(); + + ValueTask IRedisClientAsync.BlockingPopAndPushItemBetweenListsAsync(string fromListId, string toListId, TimeSpan? timeOut, CancellationToken token) + => NativeAsync.BRPopLPushAsync(fromListId, toListId, (int)timeOut.GetValueOrDefault().TotalSeconds, token).FromUtf8BytesAsync(); + + async ValueTask IRedisClientAsync.AddRangeToSortedSetAsync(string setId, List values, double score, CancellationToken token) + { + var pipeline = AddRangeToSortedSetPrepareNonFlushed(setId, values, score.ToFastUtf8Bytes()); + await pipeline.FlushAsync(token).ConfigureAwait(false); + + return await pipeline.ReadAllAsIntsHaveSuccessAsync(token).ConfigureAwait(false); + } + + async ValueTask IRedisClientAsync.AddRangeToSortedSetAsync(string setId, List values, long score, CancellationToken token) + { + var pipeline = AddRangeToSortedSetPrepareNonFlushed(setId, values, score.ToUtf8Bytes()); + await pipeline.FlushAsync(token).ConfigureAwait(false); + + return await pipeline.ReadAllAsIntsHaveSuccessAsync(token).ConfigureAwait(false); + } + + ValueTask IRedisClientAsync.RemoveItemFromSortedSetAsync(string setId, string value, CancellationToken token) + => NativeAsync.ZRemAsync(setId, value.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask IRedisClientAsync.RemoveItemsFromSortedSetAsync(string setId, List values, CancellationToken token) + => NativeAsync.ZRemAsync(setId, values.Map(x => x.ToUtf8Bytes()).ToArray(), token); + + async ValueTask IRedisClientAsync.PopItemWithLowestScoreFromSortedSetAsync(string setId, CancellationToken token) + { + //TODO: this should be atomic + var topScoreItemBytes = await NativeAsync.ZRangeAsync(setId, FirstElement, 1, token).ConfigureAwait(false); + if (topScoreItemBytes.Length == 0) return null; + + await NativeAsync.ZRemAsync(setId, topScoreItemBytes[0], token).ConfigureAwait(false); + return topScoreItemBytes[0].FromUtf8Bytes(); + } + + async ValueTask IRedisClientAsync.PopItemWithHighestScoreFromSortedSetAsync(string setId, CancellationToken token) + { + //TODO: this should be atomic + var topScoreItemBytes = await NativeAsync.ZRevRangeAsync(setId, FirstElement, 1, token).ConfigureAwait(false); + if (topScoreItemBytes.Length == 0) return null; + + await NativeAsync.ZRemAsync(setId, topScoreItemBytes[0], token).ConfigureAwait(false); + return topScoreItemBytes[0].FromUtf8Bytes(); + } + + ValueTask IRedisClientAsync.SortedSetContainsItemAsync(string setId, string value, CancellationToken token) + => NativeAsync.ZRankAsync(setId, value.ToUtf8Bytes(), token).Await(val => val != -1); + + ValueTask IRedisClientAsync.IncrementItemInSortedSetAsync(string setId, string value, double incrementBy, CancellationToken token) + => NativeAsync.ZIncrByAsync(setId, incrementBy, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.IncrementItemInSortedSetAsync(string setId, string value, long incrementBy, CancellationToken token) + => NativeAsync.ZIncrByAsync(setId, incrementBy, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.GetItemIndexInSortedSetAsync(string setId, string value, CancellationToken token) + => NativeAsync.ZRankAsync(setId, value.ToUtf8Bytes(), token); + + ValueTask IRedisClientAsync.GetItemIndexInSortedSetDescAsync(string setId, string value, CancellationToken token) + => NativeAsync.ZRevRankAsync(setId, value.ToUtf8Bytes(), token); + + ValueTask> IRedisClientAsync.GetAllItemsFromSortedSetAsync(string setId, CancellationToken token) + => NativeAsync.ZRangeAsync(setId, FirstElement, LastElement, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetAllItemsFromSortedSetDescAsync(string setId, CancellationToken token) + => NativeAsync.ZRevRangeAsync(setId, FirstElement, LastElement, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetAsync(string setId, int fromRank, int toRank, CancellationToken token) + => NativeAsync.ZRangeAsync(setId, fromRank, toRank, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetDescAsync(string setId, int fromRank, int toRank, CancellationToken token) + => NativeAsync.ZRevRangeAsync(setId, fromRank, toRank, token).ToStringListAsync(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ValueTask> CreateSortedScoreMapAsync(ValueTask pending) + { + return pending.IsCompletedSuccessfully ? CreateSortedScoreMap(pending.Result).AsValueTaskResult() : Awaited(pending); + static async ValueTask> Awaited(ValueTask pending) + => CreateSortedScoreMap(await pending.ConfigureAwait(false)); + } + + ValueTask> IRedisClientAsync.GetAllWithScoresFromSortedSetAsync(string setId, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRangeWithScoresAsync(setId, FirstElement, LastElement, token)); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetAsync(string setId, int fromRank, int toRank, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRangeWithScoresAsync(setId, fromRank, toRank, token)); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetDescAsync(string setId, int fromRank, int toRank, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRevRangeWithScoresAsync(setId, fromRank, toRank, token)); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, string fromStringScore, string toStringScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByLowestScoreAsync(setId, fromStringScore, toStringScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + { + var fromScore = GetLexicalScore(fromStringScore); + var toScore = GetLexicalScore(toStringScore); + return AsAsync().GetRangeFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, skip, take, token); + } + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => NativeAsync.ZRangeByScoreAsync(setId, fromScore, toScore, skip, take, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByLowestScoreAsync(string setId, long fromScore, long toScore, int? skip, int? take, CancellationToken token) + => NativeAsync.ZRangeByScoreAsync(setId, fromScore, toScore, skip, take, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, string fromStringScore, string toStringScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByLowestScoreAsync(setId, fromStringScore, toStringScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + { + var fromScore = GetLexicalScore(fromStringScore); + var toScore = GetLexicalScore(toStringScore); + return AsAsync().GetRangeWithScoresFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, skip, take, token); + } + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRangeByScoreWithScoresAsync(setId, fromScore, toScore, skip, take, token)); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByLowestScoreAsync(string setId, long fromScore, long toScore, int? skip, int? take, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRangeByScoreWithScoresAsync(setId, fromScore, toScore, skip, take, token)); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, string fromStringScore, string toStringScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByHighestScoreAsync(setId, fromStringScore, toStringScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + { + var fromScore = GetLexicalScore(fromStringScore); + var toScore = GetLexicalScore(toStringScore); + return AsAsync().GetRangeFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, skip, take, token); + } + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + => AsAsync().GetRangeFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => NativeAsync.ZRevRangeByScoreAsync(setId, fromScore, toScore, skip, take, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeFromSortedSetByHighestScoreAsync(string setId, long fromScore, long toScore, int? skip, int? take, CancellationToken token) + => NativeAsync.ZRevRangeByScoreAsync(setId, fromScore, toScore, skip, take, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, string fromStringScore, string toStringScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByHighestScoreAsync(setId, fromStringScore, toStringScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + { + var fromScore = GetLexicalScore(fromStringScore); + var toScore = GetLexicalScore(toStringScore); + return AsAsync().GetRangeWithScoresFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, skip, take, token); + } + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + => AsAsync().GetRangeWithScoresFromSortedSetByHighestScoreAsync(setId, fromScore, toScore, null, null, token); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRevRangeByScoreWithScoresAsync(setId, fromScore, toScore, skip, take, token)); + + ValueTask> IRedisClientAsync.GetRangeWithScoresFromSortedSetByHighestScoreAsync(string setId, long fromScore, long toScore, int? skip, int? take, CancellationToken token) + => CreateSortedScoreMapAsync(NativeAsync.ZRevRangeByScoreWithScoresAsync(setId, fromScore, toScore, skip, take, token)); + + ValueTask IRedisClientAsync.RemoveRangeFromSortedSetAsync(string setId, int minRank, int maxRank, CancellationToken token) + => NativeAsync.ZRemRangeByRankAsync(setId, minRank, maxRank, token); + + ValueTask IRedisClientAsync.RemoveRangeFromSortedSetByScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + => NativeAsync.ZRemRangeByScoreAsync(setId, fromScore, toScore, token); + + ValueTask IRedisClientAsync.RemoveRangeFromSortedSetByScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + => NativeAsync.ZRemRangeByScoreAsync(setId, fromScore, toScore, token); + + ValueTask IRedisClientAsync.StoreIntersectFromSortedSetsAsync(string intoSetId, string[] setIds, CancellationToken token) + => NativeAsync.ZInterStoreAsync(intoSetId, setIds, token); + + ValueTask IRedisClientAsync.StoreIntersectFromSortedSetsAsync(string intoSetId, string[] setIds, string[] args, CancellationToken token) + => base.ZInterStoreAsync(intoSetId, setIds, args, token); + + ValueTask IRedisClientAsync.StoreUnionFromSortedSetsAsync(string intoSetId, string[] setIds, CancellationToken token) + => NativeAsync.ZUnionStoreAsync(intoSetId, setIds, token); + + ValueTask IRedisClientAsync.StoreUnionFromSortedSetsAsync(string intoSetId, string[] setIds, string[] args, CancellationToken token) + => base.ZUnionStoreAsync(intoSetId, setIds, args, token); + + ValueTask IRedisClientAsync.HashContainsEntryAsync(string hashId, string key, CancellationToken token) + => NativeAsync.HExistsAsync(hashId, key.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask IRedisClientAsync.SetEntryInHashIfNotExistsAsync(string hashId, string key, string value, CancellationToken token) + => NativeAsync.HSetNXAsync(hashId, key.ToUtf8Bytes(), value.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask IRedisClientAsync.SetRangeInHashAsync(string hashId, IEnumerable> keyValuePairs, CancellationToken token) + => SetRangeInHashPrepare(keyValuePairs, out var keys, out var values) ? NativeAsync.HMSetAsync(hashId, keys, values, token) : default; + + ValueTask IRedisClientAsync.IncrementValueInHashAsync(string hashId, string key, int incrementBy, CancellationToken token) + => NativeAsync.HIncrbyAsync(hashId, key.ToUtf8Bytes(), incrementBy, token); + + ValueTask IRedisClientAsync.IncrementValueInHashAsync(string hashId, string key, double incrementBy, CancellationToken token) + => NativeAsync.HIncrbyFloatAsync(hashId, key.ToUtf8Bytes(), incrementBy, token); + + ValueTask IRedisClientAsync.GetValueFromHashAsync(string hashId, string key, CancellationToken token) + => NativeAsync.HGetAsync(hashId, key.ToUtf8Bytes(), token).FromUtf8BytesAsync(); + + ValueTask> IRedisClientAsync.GetValuesFromHashAsync(string hashId, string[] keys, CancellationToken token) + { + if (keys.Length == 0) return new List().AsValueTaskResult(); + var keyBytes = ConvertToBytes(keys); + return NativeAsync.HMGetAsync(hashId, keyBytes, token).ToStringListAsync(); + } + + ValueTask IRedisClientAsync.RemoveEntryFromHashAsync(string hashId, string key, CancellationToken token) + => NativeAsync.HDelAsync(hashId, key.ToUtf8Bytes(), token).IsSuccessAsync(); + + ValueTask> IRedisClientAsync.GetHashKeysAsync(string hashId, CancellationToken token) + => NativeAsync.HKeysAsync(hashId, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetHashValuesAsync(string hashId, CancellationToken token) + => NativeAsync.HValsAsync(hashId, token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.GetAllEntriesFromHashAsync(string hashId, CancellationToken token) + => NativeAsync.HGetAllAsync(hashId, token).Await(ret => ret.ToStringDictionary()); + + ValueTask IRedisClientAsync.ExecLuaAsync(string body, string[] args, CancellationToken token) + => NativeAsync.EvalCommandAsync(body, 0, args.ToMultiByteArray(), token).Await(ret => ret.ToRedisText()); + + ValueTask IRedisClientAsync.ExecLuaShaAsync(string sha1, string[] args, CancellationToken token) + => NativeAsync.EvalShaCommandAsync(sha1, 0, args.ToMultiByteArray(), token).Await(ret => ret.ToRedisText()); + + ValueTask IRedisClientAsync.ExecLuaAsStringAsync(string body, string[] args, CancellationToken token) + => NativeAsync.EvalStrAsync(body, 0, args.ToMultiByteArray(), token); + + ValueTask IRedisClientAsync.ExecLuaShaAsStringAsync(string sha1, string[] args, CancellationToken token) + => NativeAsync.EvalShaStrAsync(sha1, 0, args.ToMultiByteArray(), token); + + ValueTask IRedisClientAsync.ExecLuaAsIntAsync(string body, string[] args, CancellationToken token) + => NativeAsync.EvalIntAsync(body, 0, args.ToMultiByteArray(), token); + + ValueTask IRedisClientAsync.ExecLuaAsIntAsync(string body, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalIntAsync(body, keys.Length, MergeAndConvertToBytes(keys, args), token); + + ValueTask IRedisClientAsync.ExecLuaShaAsIntAsync(string sha1, string[] args, CancellationToken token) + => NativeAsync.EvalShaIntAsync(sha1, 0, args.ToMultiByteArray(), token); + + ValueTask IRedisClientAsync.ExecLuaShaAsIntAsync(string sha1, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalShaIntAsync(sha1, keys.Length, MergeAndConvertToBytes(keys, args), token); + + ValueTask> IRedisClientAsync.ExecLuaAsListAsync(string body, string[] args, CancellationToken token) + => NativeAsync.EvalAsync(body, 0, args.ToMultiByteArray(), token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.ExecLuaAsListAsync(string body, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalAsync(body, keys.Length, MergeAndConvertToBytes(keys, args), token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.ExecLuaShaAsListAsync(string sha1, string[] args, CancellationToken token) + => NativeAsync.EvalShaAsync(sha1, 0, args.ToMultiByteArray(), token).ToStringListAsync(); + + ValueTask> IRedisClientAsync.ExecLuaShaAsListAsync(string sha1, string[] keys, string[] args, CancellationToken token) + => NativeAsync.EvalShaAsync(sha1, keys.Length, MergeAndConvertToBytes(keys, args), token).ToStringListAsync(); + + ValueTask IRedisClientAsync.CalculateSha1Async(string luaBody, CancellationToken token) + => CalculateSha1(luaBody).AsValueTaskResult(); + + async ValueTask IRedisClientAsync.HasLuaScriptAsync(string sha1Ref, CancellationToken token) + { + var map = await AsAsync().WhichLuaScriptsExistsAsync(new[] { sha1Ref }, token).ConfigureAwait(false); + return map[sha1Ref]; + } + + async ValueTask> IRedisClientAsync.WhichLuaScriptsExistsAsync(string[] sha1Refs, CancellationToken token) + { + var intFlags = await NativeAsync.ScriptExistsAsync(sha1Refs.ToMultiByteArray()).ConfigureAwait(false); + return WhichLuaScriptsExistsParseResult(sha1Refs, intFlags); + } + + ValueTask IRedisClientAsync.RemoveAllLuaScriptsAsync(CancellationToken token) + => NativeAsync.ScriptFlushAsync(token); + + ValueTask IRedisClientAsync.KillRunningLuaScriptAsync(CancellationToken token) + => NativeAsync.ScriptKillAsync(token); + + ValueTask IRedisClientAsync.CustomAsync(params object[] cmdWithArgs) + => AsAsync().CustomAsync(cmdWithArgs, token: default); + + ValueTask IRedisClientAsync.RemoveEntryAsync(params string[] args) + => AsAsync().RemoveEntryAsync(args, token: default); + + ValueTask IRedisClientAsync.AddToHyperLogAsync(string key, params string[] elements) + => AsAsync().AddToHyperLogAsync(key, elements, token: default); + + ValueTask IRedisClientAsync.MergeHyperLogsAsync(string toKey, params string[] fromKeys) + => AsAsync().MergeHyperLogsAsync(toKey, fromKeys, token: default); + + ValueTask IRedisClientAsync.AddGeoMembersAsync(string key, params RedisGeo[] geoPoints) + => AsAsync().AddGeoMembersAsync(key, geoPoints, token: default); + + ValueTask IRedisClientAsync.GetGeohashesAsync(string key, params string[] members) + => AsAsync().GetGeohashesAsync(key, members, token: default); + + ValueTask> IRedisClientAsync.GetGeoCoordinatesAsync(string key, params string[] members) + => AsAsync().GetGeoCoordinatesAsync(key, members, token: default); + + ValueTask IRedisClientAsync.WatchAsync(params string[] keys) + => AsAsync().WatchAsync(keys, token: default); + + ValueTask> IRedisClientAsync.GetIntersectFromSetsAsync(params string[] setIds) + => AsAsync().GetIntersectFromSetsAsync(setIds, token: default); + + ValueTask IRedisClientAsync.StoreIntersectFromSetsAsync(string intoSetId, params string[] setIds) + => AsAsync().StoreIntersectFromSetsAsync(intoSetId, setIds, token: default); + + ValueTask> IRedisClientAsync.GetUnionFromSetsAsync(params string[] setIds) + => AsAsync().GetUnionFromSetsAsync(setIds, token: default); + + ValueTask IRedisClientAsync.StoreUnionFromSetsAsync(string intoSetId, params string[] setIds) + => AsAsync().StoreUnionFromSetsAsync(intoSetId, setIds, token: default); + + ValueTask> IRedisClientAsync.GetDifferencesFromSetAsync(string fromSetId, params string[] withSetIds) + => AsAsync().GetDifferencesFromSetAsync(fromSetId, withSetIds, token: default); + + ValueTask IRedisClientAsync.StoreDifferencesFromSetAsync(string intoSetId, string fromSetId, params string[] withSetIds) + => AsAsync().StoreDifferencesFromSetAsync(intoSetId, fromSetId, withSetIds, token: default); + + ValueTask IRedisClientAsync.StoreIntersectFromSortedSetsAsync(string intoSetId, params string[] setIds) + => AsAsync().StoreIntersectFromSortedSetsAsync(intoSetId, setIds, token: default); + + ValueTask IRedisClientAsync.StoreUnionFromSortedSetsAsync(string intoSetId, params string[] setIds) + => AsAsync().StoreUnionFromSortedSetsAsync(intoSetId, setIds, token: default); + + ValueTask> IRedisClientAsync.GetValuesFromHashAsync(string hashId, params string[] keys) + => AsAsync().GetValuesFromHashAsync(hashId, keys, token: default); + + ValueTask IRedisClientAsync.ExecLuaAsync(string body, params string[] args) + => AsAsync().ExecLuaAsync(body, args, token: default); + + ValueTask IRedisClientAsync.ExecLuaShaAsync(string sha1, params string[] args) + => AsAsync().ExecLuaShaAsync(sha1, args, token: default); + + ValueTask IRedisClientAsync.ExecLuaAsStringAsync(string luaBody, params string[] args) + => AsAsync().ExecLuaAsStringAsync(luaBody, args, token: default); + + ValueTask IRedisClientAsync.ExecLuaShaAsStringAsync(string sha1, params string[] args) + => AsAsync().ExecLuaShaAsStringAsync(sha1, args, token: default); + + ValueTask IRedisClientAsync.ExecLuaAsIntAsync(string luaBody, params string[] args) + => AsAsync().ExecLuaAsIntAsync(luaBody, args, token: default); + + ValueTask IRedisClientAsync.ExecLuaShaAsIntAsync(string sha1, params string[] args) + => AsAsync().ExecLuaShaAsIntAsync(sha1, args, token: default); + + ValueTask> IRedisClientAsync.ExecLuaAsListAsync(string luaBody, params string[] args) + => AsAsync().ExecLuaAsListAsync(luaBody, args, token: default); + + ValueTask> IRedisClientAsync.ExecLuaShaAsListAsync(string sha1, params string[] args) + => AsAsync().ExecLuaShaAsListAsync(sha1, args, token: default); + + ValueTask> IRedisClientAsync.WhichLuaScriptsExistsAsync(params string[] sha1Refs) + => AsAsync().WhichLuaScriptsExistsAsync(sha1Refs, token: default); + } +} diff --git a/src/ServiceStack.Redis/RedisClient.ICacheClient.cs b/src/ServiceStack.Redis/RedisClient.ICacheClient.cs index 26ff6681..21bd3372 100644 --- a/src/ServiceStack.Redis/RedisClient.ICacheClient.cs +++ b/src/ServiceStack.Redis/RedisClient.ICacheClient.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -24,7 +24,7 @@ public partial class RedisClient { public T Exec(Func action) { - using (JsConfig.With(excludeTypeInfo: false)) + using (JsConfig.With(new Text.Config { ExcludeTypeInfo = false })) { return action(this); } @@ -32,7 +32,7 @@ public T Exec(Func action) public void Exec(Action action) { - using (JsConfig.With(excludeTypeInfo: false)) + using (JsConfig.With(new Text.Config { ExcludeTypeInfo = false })) { action(this); } @@ -172,60 +172,74 @@ public IDictionary GetAll(IEnumerable keys) { var keysArray = keys.ToArray(); var keyValues = r.MGet(keysArray); - var results = new Dictionary(); - var isBytes = typeof(T) == typeof(byte[]); - var i = 0; - foreach (var keyValue in keyValues) + return ProcessGetAllResult(keysArray, keyValues); + }); + } + + private static IDictionary ProcessGetAllResult(string[] keysArray, byte[][] keyValues) + { + var results = new Dictionary(); + var isBytes = typeof(T) == typeof(byte[]); + + var i = 0; + foreach (var keyValue in keyValues) + { + var key = keysArray[i++]; + + if (keyValue == null) { - var key = keysArray[i++]; - - if (keyValue == null) - { - results[key] = default(T); - continue; - } - - if (isBytes) - { - results[key] = (T)(object)keyValue; - } - else - { - var keyValueString = Encoding.UTF8.GetString(keyValue); - results[key] = JsonSerializer.DeserializeFromString(keyValueString); - } + results[key] = default(T); + continue; } - return results; - }); + + if (isBytes) + { + results[key] = (T)(object)keyValue; + } + else + { + var keyValueString = Encoding.UTF8.GetString(keyValue); + results[key] = JsonSerializer.DeserializeFromString(keyValueString); + } + } + return results; } public void SetAll(IDictionary values) { - Exec(r => + if (values.Count != 0) { - var keys = values.Keys.ToArray(); - var valBytes = new byte[values.Count][]; - var isBytes = typeof(T) == typeof(byte[]); + Exec(r => + { + // need to do this inside Exec for the JSON config bits + GetSetAllBytesTyped(values, out var keys, out var valBytes); + r.MSet(keys, valBytes); + }); + } + } - var i = 0; - foreach (var value in values.Values) + private static void GetSetAllBytesTyped(IDictionary values, out string[] keys, out byte[][] valBytes) + { + keys = values.Keys.ToArray(); + valBytes = new byte[values.Count][]; + var isBytes = typeof(T) == typeof(byte[]); + + var i = 0; + foreach (var value in values.Values) + { + if (!isBytes) { - if (!isBytes) - { - var t = JsonSerializer.SerializeToString(value); - if (t != null) - valBytes[i] = t.ToUtf8Bytes(); - else - valBytes[i] = new byte[] { }; - } + var t = JsonSerializer.SerializeToString(value); + if (t != null) + valBytes[i] = t.ToUtf8Bytes(); else - valBytes[i] = (byte[])(object)value ?? new byte[] { }; - i++; + valBytes[i] = new byte[] { }; } - - r.MSet(keys, valBytes); - }); + else + valBytes[i] = (byte[])(object)value ?? new byte[] { }; + i++; + } } } diff --git a/src/ServiceStack.Redis/RedisClient.cs b/src/ServiceStack.Redis/RedisClient.cs index d6a90d0a..1908f322 100644 --- a/src/ServiceStack.Redis/RedisClient.cs +++ b/src/ServiceStack.Redis/RedisClient.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -197,32 +197,54 @@ public void SetValues(Dictionary map) public void SetAll(IEnumerable keys, IEnumerable values) { - if (keys == null || values == null) return; + if (GetSetAllBytes(keys, values, out var keyBytes, out var valBytes)) + { + base.MSet(keyBytes, valBytes); + } + } + + bool GetSetAllBytes(IEnumerable keys, IEnumerable values, out byte[][] keyBytes, out byte[][] valBytes) + { + keyBytes = valBytes = default; + if (keys == null || values == null) return false; var keyArray = keys.ToArray(); var valueArray = values.ToArray(); if (keyArray.Length != valueArray.Length) throw new Exception("Key length != Value Length. {0}/{1}".Fmt(keyArray.Length, valueArray.Length)); - if (keyArray.Length == 0) return; + if (keyArray.Length == 0) return false; - var keyBytes = new byte[keyArray.Length][]; - var valBytes = new byte[keyArray.Length][]; + keyBytes = new byte[keyArray.Length][]; + valBytes = new byte[keyArray.Length][]; for (int i = 0; i < keyArray.Length; i++) { keyBytes[i] = keyArray[i].ToUtf8Bytes(); valBytes[i] = valueArray[i].ToUtf8Bytes(); } - base.MSet(keyBytes, valBytes); + return true; } public void SetAll(Dictionary map) { - if (map == null || map.Count == 0) return; + if (GetSetAllBytes(map, out var keyBytes, out var valBytes)) + { + base.MSet(keyBytes, valBytes); + } + } + + private static bool GetSetAllBytes(IDictionary map, out byte[][] keyBytes, out byte[][] valBytes) + { + if (map == null || map.Count == 0) + { + keyBytes = null; + valBytes = null; + return false; + } - var keyBytes = new byte[map.Count][]; - var valBytes = new byte[map.Count][]; + keyBytes = new byte[map.Count][]; + valBytes = new byte[map.Count][]; var i = 0; foreach (var key in map.Keys) @@ -232,8 +254,7 @@ public void SetAll(Dictionary map) valBytes[i] = val.ToUtf8Bytes(); i++; } - - base.MSet(keyBytes, valBytes); + return true; } public string GetValue(string key) @@ -321,25 +342,23 @@ public string GetRandomKey() public bool ExpireEntryIn(string key, TimeSpan expireIn) { - if (AssertServerVersionNumber() >= 2600) + if (UseMillisecondExpiration(expireIn)) { - if (expireIn.Milliseconds > 0) - { - return PExpire(key, (long)expireIn.TotalMilliseconds); - } + return PExpire(key, (long)expireIn.TotalMilliseconds); } return Expire(key, (int)expireIn.TotalSeconds); } + private bool UseMillisecondExpiration(TimeSpan value) + + => AssertServerVersionNumber() >= 2600 && value.Milliseconds > 0; + public bool ExpireEntryIn(byte[] key, TimeSpan expireIn) { - if (AssertServerVersionNumber() >= 2600) + if (UseMillisecondExpiration(expireIn)) { - if (expireIn.Milliseconds > 0) - { - return PExpire(key, (long)expireIn.TotalMilliseconds); - } + return PExpire(key, (long)expireIn.TotalMilliseconds); } return Expire(key, (int)expireIn.TotalSeconds); @@ -358,8 +377,10 @@ public bool ExpireEntryAt(string key, DateTime expireAt) } public TimeSpan? GetTimeToLive(string key) + => ParseTimeToLiveResult(Ttl(key)); + + private static TimeSpan? ParseTimeToLiveResult(long ttlSecs) { - var ttlSecs = Ttl(key); if (ttlSecs == -1) return TimeSpan.MaxValue; //no expiry set @@ -369,6 +390,11 @@ public bool ExpireEntryAt(string key, DateTime expireAt) return TimeSpan.FromSeconds(ttlSecs); } + public void RemoveExpiredEntries() + { + //Redis automatically removed expired Cache Entries + } + public IRedisTypedClient As() { try @@ -396,12 +422,12 @@ public IDisposable AcquireLock(string key, TimeSpan timeOut) public IRedisTransaction CreateTransaction() { AssertServerVersionNumber(); // pre-fetch call to INFO before transaction if needed - return new RedisTransaction(this); + return new RedisTransaction(this, false); } public void AssertNotInTransaction() { - if (Transaction != null) + if (Transaction != null || Pipeline != null) throw new NotSupportedException("Only atomic redis-server operations are supported in a transaction"); } @@ -412,8 +438,8 @@ public IRedisPipeline CreatePipeline() public List SearchKeys(string pattern) { - var multiDataList = Keys(pattern); - return multiDataList.ToStringList(); + var multiDataList = ScanAllKeys(pattern); + return multiDataList.ToList(); } public List GetValues(List keys) @@ -421,9 +447,11 @@ public List GetValues(List keys) if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Count == 0) return new List(); - var resultBytesArray = MGet(keys.ToArray()); - - var results = new List(); + return ParseGetValuesResult(MGet(keys.ToArray())); + } + private static List ParseGetValuesResult(byte[][] resultBytesArray) + { + var results = new List(resultBytesArray.Length); foreach (var resultBytes in resultBytesArray) { if (resultBytes == null) continue; @@ -440,9 +468,12 @@ public List GetValues(List keys) if (keys == null) throw new ArgumentNullException(nameof(keys)); if (keys.Count == 0) return new List(); - var resultBytesArray = MGet(keys.ToArray()); + return ParseGetValuesResult(MGet(keys.ToArray())); + } - var results = new List(); + private static List ParseGetValuesResult(byte[][] resultBytesArray) + { + var results = new List(resultBytesArray.Length); foreach (var resultBytes in resultBytesArray) { if (resultBytes == null) continue; @@ -463,6 +494,11 @@ public Dictionary GetValuesMap(List keys) var keysArray = keys.ToArray(); var resultBytesArray = MGet(keysArray); + return ParseGetValuesMapResult(keysArray, resultBytesArray); + } + + private static Dictionary ParseGetValuesMapResult(string[] keysArray, byte[][] resultBytesArray) + { var results = new Dictionary(); for (var i = 0; i < resultBytesArray.Length; i++) { @@ -491,6 +527,11 @@ public Dictionary GetValuesMap(List keys) var keysArray = keys.ToArray(); var resultBytesArray = MGet(keysArray); + return ParseGetValuesMapResult(keysArray, resultBytesArray); + } + + private static Dictionary ParseGetValuesMapResult(string[] keysArray, byte[][] resultBytesArray) + { var results = new Dictionary(); for (var i = 0; i < resultBytesArray.Length; i++) { @@ -573,7 +614,16 @@ internal void RegisterTypeIds(IEnumerable values) } } - internal void RemoveTypeIds(params string[] ids) + internal void RemoveTypeIdsById(string id) + { + var typeIdsSetKey = GetTypeIdsSetKey(); + if (this.Pipeline != null) + GetRegisteredTypeIdsWithinPipeline(typeIdsSetKey).Remove(id); + else + this.RemoveItemFromSet(typeIdsSetKey, id); + } + + internal void RemoveTypeIdsByIds(IEnumerable ids) { var typeIdsSetKey = GetTypeIdsSetKey(); if (this.Pipeline != null) @@ -587,7 +637,9 @@ internal void RemoveTypeIds(params string[] ids) } } - internal void RemoveTypeIds(params T[] values) + internal void RemoveTypeIdsByValue(T value) => RemoveTypeIdsById(value.GetId().ToString()); + + internal void RemoveTypeIdsByValues(IEnumerable values) { var typeIdsSetKey = GetTypeIdsSetKey(); if (this.Pipeline != null) @@ -694,42 +746,69 @@ public void StoreAsHash(T entity) //Without the Generic Constraints internal void _StoreAll(IEnumerable entities) { - if (entities == null) return; + if (PrepareStoreAll(entities, out var keys, out var values, out var entitiesList)) + { + base.MSet(keys, values); + RegisterTypeIds(entitiesList); + } + } + + private bool PrepareStoreAll(IEnumerable entities, out byte[][] keys, out byte[][] values, out List entitiesList) + { + if (entities == null) + { + entitiesList = default; + keys = values = default; + return false; + } - var entitiesList = entities.ToList(); + entitiesList = entities.ToList(); var len = entitiesList.Count; - if (len == 0) return; + if (len == 0) + { + keys = values = default; + return false; + } - var keys = new byte[len][]; - var values = new byte[len][]; + keys = new byte[len][]; + values = new byte[len][]; for (var i = 0; i < len; i++) { keys[i] = UrnKey(entitiesList[i]).ToUtf8Bytes(); values[i] = SerializeToUtf8Bytes(entitiesList[i]); } - - base.MSet(keys, values); - RegisterTypeIds(entitiesList); + return true; } public void WriteAll(IEnumerable entities) { - if (entities == null) return; + if (PrepareWriteAll(entities, out var keys, out var values)) + { + base.MSet(keys, values); + } + } + + private bool PrepareWriteAll(IEnumerable entities, out byte[][] keys, out byte[][] values) + { + if (entities == null) + { + keys = values = default; + return false; + } var entitiesList = entities.ToList(); var len = entitiesList.Count; - var keys = new byte[len][]; - var values = new byte[len][]; + keys = new byte[len][]; + values = new byte[len][]; for (var i = 0; i < len; i++) { keys[i] = UrnKey(entitiesList[i]).ToUtf8Bytes(); values[i] = SerializeToUtf8Bytes(entitiesList[i]); } - - base.MSet(keys, values); + return true; } public static byte[] SerializeToUtf8Bytes(T value) @@ -741,14 +820,14 @@ public void Delete(T entity) { var urnKey = UrnKey(entity); this.Remove(urnKey); - this.RemoveTypeIds(entity); + this.RemoveTypeIdsByValue(entity); } public void DeleteById(object id) { var urnKey = UrnKey(id); this.Remove(urnKey); - this.RemoveTypeIds(id.ToString()); + this.RemoveTypeIdsById(id.ToString()); } public void DeleteByIds(ICollection ids) @@ -756,32 +835,38 @@ public void DeleteByIds(ICollection ids) if (ids == null || ids.Count == 0) return; var idsList = ids.Cast(); - var urnKeys = idsList.Map(UrnKey); - this.RemoveEntry(urnKeys.ToArray()); - this.RemoveTypeIds(idsList.Map(x => x.ToString()).ToArray()); + var urnKeys = idsList.Select(UrnKey).ToArray(); + this.RemoveEntry(urnKeys); + this.RemoveTypeIdsByIds(ids.Map(x => x.ToString()).ToArray()); } public void DeleteAll() { - var typeIdsSetKey = this.GetTypeIdsSetKey(); - var ids = this.GetAllItemsFromSet(typeIdsSetKey); - if (ids.Count > 0) - { - var urnKeys = ids.ToList().ConvertAll(UrnKey); - this.RemoveEntry(urnKeys.ToArray()); - this.Remove(typeIdsSetKey); - } + DeleteAll(0,RedisConfig.CommandKeysBatchSize); } - public RedisClient CloneClient() + private void DeleteAll(ulong cursor, int batchSize) { - return new RedisClient(Host, Port, Password, Db) + var typeIdsSetKey = this.GetTypeIdsSetKey(); + do { - SendTimeout = SendTimeout, - ReceiveTimeout = ReceiveTimeout - }; + var scanResult = this.SScan(typeIdsSetKey, cursor, batchSize); + cursor = scanResult.Cursor; + var urnKeys = scanResult.Results.Select(id => UrnKey(id.FromUtf8Bytes())).ToArray(); + if (urnKeys.Length > 0) + { + this.RemoveEntry(urnKeys); + } + } while (cursor != 0); + + this.RemoveEntry(typeIdsSetKey); } + public RedisClient CloneClient() => new(Host, Port, Password, Db) { + SendTimeout = SendTimeout, + ReceiveTimeout = ReceiveTimeout + }; + /// /// Returns key with automatic object id detection in provided value with generic type. /// @@ -818,8 +903,7 @@ public string UrnKey(Type type, object id) #region LUA EVAL - static readonly ConcurrentDictionary CachedLuaSha1Map = - new ConcurrentDictionary(); + static readonly ConcurrentDictionary CachedLuaSha1Map = new(); public T ExecCachedLua(string scriptBody, Func scriptSha1) { @@ -933,6 +1017,10 @@ public bool HasLuaScript(string sha1Ref) public Dictionary WhichLuaScriptsExists(params string[] sha1Refs) { var intFlags = base.ScriptExists(sha1Refs.ToMultiByteArray()); + return WhichLuaScriptsExistsParseResult(sha1Refs, intFlags); + } + static Dictionary WhichLuaScriptsExistsParseResult(string[] sha1Refs, byte[][] intFlags) + { var map = new Dictionary(); for (int i = 0; i < sha1Refs.Length; i++) { @@ -961,15 +1049,18 @@ public string LoadLuaScript(string body) public void RemoveByPattern(string pattern) { - var keys = Keys(pattern).ToStringArray(); + var keys = ScanAllKeys(pattern).ToArray(); if (keys.Length > 0) Del(keys); } public void RemoveByRegex(string pattern) { - RemoveByPattern(pattern.Replace(".*", "*").Replace(".+", "?")); + RemoveByPattern(RegexToGlob(pattern)); } + + private static string RegexToGlob(string regex) + => regex.Replace(".*", "*").Replace(".+", "?"); public IEnumerable ScanAllKeys(string pattern = null, int pageSize = 1000) { diff --git a/src/ServiceStack.Redis/RedisClientExtensions.cs b/src/ServiceStack.Redis/RedisClientExtensions.cs index 5f7ef45b..79e655ed 100644 --- a/src/ServiceStack.Redis/RedisClientExtensions.cs +++ b/src/ServiceStack.Redis/RedisClientExtensions.cs @@ -1,6 +1,6 @@ namespace ServiceStack.Redis { - public static class RedisClientExtensions + public static partial class RedisClientExtensions { public static string GetHostString(this IRedisClient redis) { diff --git a/src/ServiceStack.Redis/RedisClientHash.Async.cs b/src/ServiceStack.Redis/RedisClientHash.Async.cs new file mode 100644 index 00000000..c1bab49f --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientHash.Async.cs @@ -0,0 +1,55 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + internal partial class RedisClientHash + : IRedisHashAsync + { + private IRedisClientAsync AsyncClient => client; + + ValueTask IRedisHashAsync.AddAsync(KeyValuePair item, CancellationToken token) + => AsyncClient.SetEntryInHashAsync(hashId, item.Key, item.Value, token).Await(); + + ValueTask IRedisHashAsync.AddAsync(string key, string value, CancellationToken token) + => AsyncClient.SetEntryInHashAsync(hashId, key, value, token).Await(); + + ValueTask IRedisHashAsync.AddIfNotExistsAsync(KeyValuePair item, CancellationToken token) + => AsyncClient.SetEntryInHashIfNotExistsAsync(hashId, item.Key, item.Value, token); + + ValueTask IRedisHashAsync.AddRangeAsync(IEnumerable> items, CancellationToken token) + => AsyncClient.SetRangeInHashAsync(hashId, items, token); + + ValueTask IRedisHashAsync.ClearAsync(CancellationToken token) + => new ValueTask(AsyncClient.RemoveAsync(hashId, token)); + + ValueTask IRedisHashAsync.ContainsKeyAsync(string key, CancellationToken token) + => AsyncClient.HashContainsEntryAsync(hashId, key, token); + + ValueTask IRedisHashAsync.CountAsync(CancellationToken token) + => AsyncClient.GetHashCountAsync(hashId, token).AsInt32(); + + IAsyncEnumerator> IAsyncEnumerable>.GetAsyncEnumerator(CancellationToken token) + => AsyncClient.ScanAllHashEntriesAsync(hashId).GetAsyncEnumerator(token); // note: we're using HSCAN here, not HGETALL + + ValueTask IRedisHashAsync.IncrementValueAsync(string key, int incrementBy, CancellationToken token) + => AsyncClient.IncrementValueInHashAsync(hashId, key, incrementBy, token); + + ValueTask IRedisHashAsync.RemoveAsync(string key, CancellationToken token) + => AsyncClient.RemoveEntryFromHashAsync(hashId, key, token); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientHash.cs b/src/ServiceStack.Redis/RedisClientHash.cs index dd9efd76..5aad5911 100644 --- a/src/ServiceStack.Redis/RedisClientHash.cs +++ b/src/ServiceStack.Redis/RedisClientHash.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -20,7 +20,7 @@ namespace ServiceStack.Redis /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientHash + internal partial class RedisClientHash : IRedisHash { private readonly RedisClient client; diff --git a/src/ServiceStack.Redis/RedisClientList.Async.cs b/src/ServiceStack.Redis/RedisClientList.Async.cs new file mode 100644 index 00000000..a995081b --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientList.Async.cs @@ -0,0 +1,161 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + internal partial class RedisClientList + : IRedisListAsync + { + private IRedisClientAsync AsyncClient => client; + private IRedisListAsync AsAsync() => this; + + ValueTask IRedisListAsync.AppendAsync(string value, CancellationToken token) + => AsyncClient.AddItemToListAsync(listId, value, token); + + ValueTask IRedisListAsync.BlockingDequeueAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingDequeueItemFromListAsync(listId, timeOut, token); + + ValueTask IRedisListAsync.BlockingPopAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingPopItemFromListAsync(listId, timeOut, token); + + ValueTask IRedisListAsync.BlockingRemoveStartAsync(TimeSpan? timeOut, CancellationToken token) + => AsyncClient.BlockingRemoveStartFromListAsync(listId, timeOut, token); + + ValueTask IRedisListAsync.CountAsync(CancellationToken token) + => AsyncClient.GetListCountAsync(listId, token).AsInt32(); + + ValueTask IRedisListAsync.DequeueAsync(CancellationToken token) + => AsyncClient.DequeueItemFromListAsync(listId, token); + + ValueTask IRedisListAsync.EnqueueAsync(string value, CancellationToken token) + => AsyncClient.EnqueueItemOnListAsync(listId, value, token); + + ValueTask> IRedisListAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromListAsync(listId, token); + + + async IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + { + var count = await AsAsync().CountAsync(token).ConfigureAwait(false); + if (count <= PageLimit) + { + var all = await AsyncClient.GetAllItemsFromListAsync(listId, token).ConfigureAwait(false); + foreach (var item in all) + { + yield return item; + } + } + else + { + // from GetPagingEnumerator() + var skip = 0; + List pageResults; + do + { + pageResults = await AsyncClient.GetRangeFromListAsync(listId, skip, skip + PageLimit - 1, token).ConfigureAwait(false); + foreach (var result in pageResults) + { + yield return result; + } + skip += PageLimit; + } while (pageResults.Count == PageLimit); + } + } + + ValueTask> IRedisListAsync.GetRangeAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.GetRangeFromListAsync(listId, startingFrom, endingAt, token); + + ValueTask> IRedisListAsync.GetRangeFromSortedListAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.GetRangeFromSortedListAsync(listId, startingFrom, endingAt, token); + + ValueTask IRedisListAsync.PopAndPushAsync(IRedisListAsync toList, CancellationToken token) + => AsyncClient.PopAndPushItemBetweenListsAsync(listId, toList.Id, token); + + ValueTask IRedisListAsync.PopAsync(CancellationToken token) + => AsyncClient.PopItemFromListAsync(listId, token); + + ValueTask IRedisListAsync.PrependAsync(string value, CancellationToken token) + => AsyncClient.PrependItemToListAsync(listId, value, token); + + ValueTask IRedisListAsync.PushAsync(string value, CancellationToken token) + => AsyncClient.PushItemToListAsync(listId, value, token); + + ValueTask IRedisListAsync.RemoveAllAsync(CancellationToken token) + => AsyncClient.RemoveAllFromListAsync(listId, token); + + ValueTask IRedisListAsync.RemoveEndAsync(CancellationToken token) + => AsyncClient.RemoveEndFromListAsync(listId, token); + + ValueTask IRedisListAsync.RemoveStartAsync(CancellationToken token) + => AsyncClient.RemoveStartFromListAsync(listId, token); + + ValueTask IRedisListAsync.RemoveValueAsync(string value, CancellationToken token) + => AsyncClient.RemoveItemFromListAsync(listId, value, token); + + ValueTask IRedisListAsync.RemoveValueAsync(string value, int noOfMatches, CancellationToken token) + => AsyncClient.RemoveItemFromListAsync(listId, value, noOfMatches, token); + + ValueTask IRedisListAsync.TrimAsync(int keepStartingFrom, int keepEndingAt, CancellationToken token) + => AsyncClient.TrimListAsync(listId, keepStartingFrom, keepEndingAt, token); + + async ValueTask IRedisListAsync.RemoveAsync(string value, CancellationToken token) + => (await AsyncClient.RemoveItemFromListAsync(listId, value, token).ConfigureAwait(false)) > 0; + + ValueTask IRedisListAsync.AddAsync(string value, CancellationToken token) + => AsyncClient.AddItemToListAsync(listId, value, token); + + async ValueTask IRedisListAsync.RemoveAtAsync(int index, CancellationToken token) + { + //TODO: replace with native implementation when one exists + var markForDelete = Guid.NewGuid().ToString(); + await AsyncClient.SetItemInListAsync(listId, index, markForDelete, token).ConfigureAwait(false); + await AsyncClient.RemoveItemFromListAsync(listId, markForDelete, token).ConfigureAwait(false); + } + + async ValueTask IRedisListAsync.ContainsAsync(string value, CancellationToken token) + { + //TODO: replace with native implementation when exists + await foreach (var existingItem in this.ConfigureAwait(false).WithCancellation(token)) + { + if (existingItem == value) return true; + } + return false; + } + + ValueTask IRedisListAsync.ClearAsync(CancellationToken token) + => AsyncClient.RemoveAllFromListAsync(listId, token); + + async ValueTask IRedisListAsync.IndexOfAsync(string value, CancellationToken token) + { + //TODO: replace with native implementation when exists + var i = 0; + await foreach (var existingItem in this.ConfigureAwait(false).WithCancellation(token)) + { + if (existingItem == value) return i; + i++; + } + return -1; + } + + ValueTask IRedisListAsync.ElementAtAsync(int index, CancellationToken token) + => AsyncClient.GetItemFromListAsync(listId, index, token); + + ValueTask IRedisListAsync.SetValueAsync(int index, string value, CancellationToken token) + => AsyncClient.SetItemInListAsync(listId, index, value, token); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientList.cs b/src/ServiceStack.Redis/RedisClientList.cs index 8d80b101..1777723e 100644 --- a/src/ServiceStack.Redis/RedisClientList.cs +++ b/src/ServiceStack.Redis/RedisClientList.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -19,7 +19,7 @@ namespace ServiceStack.Redis /// /// Wrap the common redis list operations under a IList[string] interface. /// - internal class RedisClientList + internal partial class RedisClientList : IRedisList { private readonly RedisClient client; diff --git a/src/ServiceStack.Redis/RedisClientManagerCacheClient.Async.cs b/src/ServiceStack.Redis/RedisClientManagerCacheClient.Async.cs new file mode 100644 index 00000000..f77928b3 --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientManagerCacheClient.Async.cs @@ -0,0 +1,165 @@ +using ServiceStack.Caching; +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + partial class RedisClientManagerCacheClient : ICacheClientAsync, IRemoveByPatternAsync, IAsyncDisposable + { + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + private ValueTask GetClientAsync(in CancellationToken token) + { + AssertNotReadOnly(); + return redisManager.GetClientAsync(token); + } + + async Task ICacheClientAsync.GetAsync(string key, CancellationToken token) + { + await using var client = await redisManager.GetReadOnlyClientAsync(token).ConfigureAwait(false); + return await client.GetAsync(key, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.SetAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.FlushAllAsync(CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + await client.FlushAllAsync(token).ConfigureAwait(false); + } + + async Task> ICacheClientAsync.GetAllAsync(IEnumerable keys, CancellationToken token) + { + await using var client = await redisManager.GetReadOnlyClientAsync(token).ConfigureAwait(false); + return await client.GetAllAsync(keys, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.SetAllAsync(IDictionary values, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + await client.SetAllAsync(values, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.RemoveAsync(string key, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.RemoveAsync(key, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.GetTimeToLiveAsync(string key, CancellationToken token) + { + await using var client = await redisManager.GetReadOnlyClientAsync(token).ConfigureAwait(false); + return await client.GetTimeToLiveAsync(key, token).ConfigureAwait(false); + } + + async IAsyncEnumerable ICacheClientAsync.GetKeysByPatternAsync(string pattern, [EnumeratorCancellation] CancellationToken token) + { + await using var client = await redisManager.GetReadOnlyClientAsync(token).ConfigureAwait(false); + await foreach (var key in client.GetKeysByPatternAsync(pattern, token).ConfigureAwait(false).WithCancellation(token)) + { + yield return key; + } + } + + Task ICacheClientAsync.RemoveExpiredEntriesAsync(CancellationToken token) + { + //Redis automatically removed expired Cache Entries + return Task.CompletedTask; + } + + async Task IRemoveByPatternAsync.RemoveByPatternAsync(string pattern, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + if (client is IRemoveByPatternAsync redisClient) + { + await redisClient.RemoveByPatternAsync(pattern, token).ConfigureAwait(false); + } + } + + async Task IRemoveByPatternAsync.RemoveByRegexAsync(string regex, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + if (client is IRemoveByPatternAsync redisClient) + { + await redisClient.RemoveByRegexAsync(regex, token).ConfigureAwait(false); + } + } + + async Task ICacheClientAsync.RemoveAllAsync(IEnumerable keys, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + await client.RemoveAllAsync(keys, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.IncrementAsync(string key, uint amount, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.IncrementAsync(key, amount, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.DecrementAsync(string key, uint amount, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.DecrementAsync(key, amount, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, DateTime expiresAt, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, expiresAt, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.AddAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.AddAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + + async Task ICacheClientAsync.ReplaceAsync(string key, T value, TimeSpan expiresIn, CancellationToken token) + { + await using var client = await GetClientAsync(token).ConfigureAwait(false); + return await client.ReplaceAsync(key, value, expiresIn, token).ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientManagerCacheClient.cs b/src/ServiceStack.Redis/RedisClientManagerCacheClient.cs index 4de16c2e..6557bb46 100644 --- a/src/ServiceStack.Redis/RedisClientManagerCacheClient.cs +++ b/src/ServiceStack.Redis/RedisClientManagerCacheClient.cs @@ -6,14 +6,14 @@ namespace ServiceStack.Redis { /// - /// For interoperabilty GetCacheClient() and GetReadOnlyCacheClient() + /// For interoperability GetCacheClient() and GetReadOnlyCacheClient() /// return an ICacheClient wrapper around the redis manager which has the affect of calling /// GetClient() for all write operations and GetReadOnlyClient() for the read ones. /// - /// This works well for master-slave replication scenarios where you have - /// 1 master that replicates to multiple read slaves. + /// This works well for master-replica replication scenarios where you have + /// 1 master that replicates to multiple read replicas. /// - public class RedisClientManagerCacheClient : ICacheClient, IRemoveByPattern, ICacheClientExtended + public partial class RedisClientManagerCacheClient : ICacheClient, IRemoveByPattern, ICacheClientExtended { private readonly IRedisClientsManager redisManager; @@ -31,18 +31,14 @@ public void Dispose() { } public T Get(string key) { - using (var client = redisManager.GetReadOnlyClient()) - { - return client.Get(key); - } + using var client = redisManager.GetReadOnlyClient(); + return client.Get(key); } public IDictionary GetAll(IEnumerable keys) { - using (var client = redisManager.GetReadOnlyClient()) - { - return client.GetAll(keys); - } + using var client = redisManager.GetReadOnlyClient(); + return client.GetAll(keys); } private void AssertNotReadOnly() @@ -59,167 +55,132 @@ public ICacheClient GetClient() public bool Remove(string key) { - using (var client = GetClient()) - { - return client.Remove(key); - } + using var client = GetClient(); + return client.Remove(key); } public void RemoveAll(IEnumerable keys) { - using (var client = GetClient()) - { - client.RemoveAll(keys); - } + using var client = GetClient(); + client.RemoveAll(keys); } public long Increment(string key, uint amount) { - using (var client = GetClient()) - { - return client.Increment(key, amount); - } + using var client = GetClient(); + return client.Increment(key, amount); } public long Decrement(string key, uint amount) { - using (var client = GetClient()) - { - return client.Decrement(key, amount); - } + using var client = GetClient(); + return client.Decrement(key, amount); } public bool Add(string key, T value) { - using (var client = GetClient()) - { - return client.Add(key, value); - } + using var client = GetClient(); + return client.Add(key, value); } public bool Set(string key, T value) { - using (var client = GetClient()) - { - return client.Set(key, value); - } + using var client = GetClient(); + return client.Set(key, value); } public bool Replace(string key, T value) { - using (var client = GetClient()) - { - return client.Replace(key, value); - } + using var client = GetClient(); + return client.Replace(key, value); } public bool Add(string key, T value, DateTime expiresAt) { - using (var client = GetClient()) - { - return client.Add(key, value, expiresAt); - } + using var client = GetClient(); + return client.Add(key, value, expiresAt); } public bool Set(string key, T value, DateTime expiresAt) { - using (var client = GetClient()) - { - return client.Set(key, value, expiresAt); - } + using var client = GetClient(); + return client.Set(key, value, expiresAt); } public bool Replace(string key, T value, DateTime expiresAt) { - using (var client = GetClient()) - { - return client.Replace(key, value, expiresAt); - } + using var client = GetClient(); + return client.Replace(key, value, expiresAt); } public bool Add(string key, T value, TimeSpan expiresIn) { - using (var client = GetClient()) - { - return client.Set(key, value, expiresIn); - } + using var client = GetClient(); + return client.Set(key, value, expiresIn); } public bool Set(string key, T value, TimeSpan expiresIn) { - using (var client = GetClient()) - { - return client.Set(key, value, expiresIn); - } + using var client = GetClient(); + return client.Set(key, value, expiresIn); } public bool Replace(string key, T value, TimeSpan expiresIn) { - using (var client = GetClient()) - { - return client.Replace(key, value, expiresIn); - } + using var client = GetClient(); + return client.Replace(key, value, expiresIn); } public void FlushAll() { - using (var client = GetClient()) - { - client.FlushAll(); - } + using var client = GetClient(); + client.FlushAll(); } public void SetAll(IDictionary values) { - using (var client = GetClient()) - { - client.SetAll(values); - } + using var client = GetClient(); + client.SetAll(values); } public void RemoveByPattern(string pattern) { - using (var client = GetClient()) + using var client = GetClient(); + if (client is IRemoveByPattern redisClient) { - var redisClient = client as IRemoveByPattern; - if (redisClient != null) - { - redisClient.RemoveByPattern(pattern); - } + redisClient.RemoveByPattern(pattern); } } public void RemoveByRegex(string pattern) { - using (var client = GetClient()) + using var client = GetClient(); + if (client is IRemoveByPattern redisClient) { - var redisClient = client as IRemoveByPattern; - if (redisClient != null) - { - redisClient.RemoveByRegex(pattern); - } + redisClient.RemoveByRegex(pattern); } } public TimeSpan? GetTimeToLive(string key) { - using (var client = GetClient()) + using var client = GetClient(); + if (client is ICacheClientExtended redisClient) { - var redisClient = client as ICacheClientExtended; - if (redisClient != null) - { - return redisClient.GetTimeToLive(key); - } + return redisClient.GetTimeToLive(key); } + return null; } public IEnumerable GetKeysByPattern(string pattern) { - using (var client = (ICacheClientExtended)GetClient()) - { - return client.GetKeysByPattern(pattern).ToList(); - } + using var client = (ICacheClientExtended)GetClient(); + return client.GetKeysByPattern(pattern).ToList(); + } + + public void RemoveExpiredEntries() + { + //Redis automatically removed expired Cache Entries } } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientManagerConfig.cs b/src/ServiceStack.Redis/RedisClientManagerConfig.cs index 8ca2a21e..e3052805 100644 --- a/src/ServiceStack.Redis/RedisClientManagerConfig.cs +++ b/src/ServiceStack.Redis/RedisClientManagerConfig.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // diff --git a/src/ServiceStack.Redis/RedisClientSet.Async.cs b/src/ServiceStack.Redis/RedisClientSet.Async.cs new file mode 100644 index 00000000..b8d2d55f --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientSet.Async.cs @@ -0,0 +1,118 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + internal partial class RedisClientSet + : IRedisSetAsync + { + private IRedisSetAsync AsAsync() => this; + private IRedisClientAsync AsyncClient => client; + + ValueTask IRedisSetAsync.AddAsync(string item, CancellationToken token) + => AsyncClient.AddItemToSetAsync(setId, item, token); + + ValueTask IRedisSetAsync.ClearAsync(CancellationToken token) + => new ValueTask(AsyncClient.RemoveAsync(setId, token)); + + ValueTask IRedisSetAsync.ContainsAsync(string item, CancellationToken token) + => AsyncClient.SetContainsItemAsync(setId, item, token); + + ValueTask IRedisSetAsync.CountAsync(CancellationToken token) + => AsyncClient.GetSetCountAsync(setId, token).AsInt32(); + + ValueTask> IRedisSetAsync.DiffAsync(IRedisSetAsync[] withSets, CancellationToken token) + { + var withSetIds = withSets.ToList().ConvertAll(x => x.Id).ToArray(); + return AsyncClient.GetDifferencesFromSetAsync(setId, withSetIds, token); + } + + ValueTask> IRedisSetAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromSetAsync(setId, token); + + IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + => AsyncClient.ScanAllSetItemsAsync(setId, token: token).GetAsyncEnumerator(token); // uses SSCAN + + ValueTask IRedisSetAsync.GetRandomEntryAsync(CancellationToken token) + => AsyncClient.GetRandomItemFromSetAsync(setId, token); + + ValueTask> IRedisSetAsync.GetRangeFromSortedSetAsync(int startingFrom, int endingAt, CancellationToken token) + => AsyncClient.GetSortedEntryValuesAsync(setId, startingFrom, endingAt, token); + + ValueTask> IRedisSetAsync.IntersectAsync(IRedisSetAsync[] withSets, CancellationToken token) + { + var allSetIds = MergeSetIds(withSets); + return AsyncClient.GetIntersectFromSetsAsync(allSetIds.ToArray(), token); + } + + ValueTask> IRedisSetAsync.IntersectAsync(params IRedisSetAsync[] withSets) + => AsAsync().IntersectAsync(withSets, token: default); + + private List MergeSetIds(IRedisSetAsync[] withSets) + { + var allSetIds = new List { setId }; + allSetIds.AddRange(withSets.ToList().ConvertAll(x => x.Id)); + return allSetIds; + } + + ValueTask IRedisSetAsync.MoveAsync(string value, IRedisSetAsync toSet, CancellationToken token) + => AsyncClient.MoveBetweenSetsAsync(setId, toSet.Id, value, token); + + ValueTask IRedisSetAsync.PopAsync(CancellationToken token) + => AsyncClient.PopItemFromSetAsync(setId, token); + + ValueTask IRedisSetAsync.RemoveAsync(string item, CancellationToken token) + => AsyncClient.RemoveItemFromSetAsync(setId, item, token).AwaitAsTrue(); // see Remove for why true + + ValueTask IRedisSetAsync.StoreDiffAsync(IRedisSetAsync fromSet, IRedisSetAsync[] withSets, CancellationToken token) + { + var withSetIds = withSets.ToList().ConvertAll(x => x.Id).ToArray(); + return AsyncClient.StoreDifferencesFromSetAsync(setId, fromSet.Id, withSetIds, token); + } + + ValueTask IRedisSetAsync.StoreDiffAsync(IRedisSetAsync fromSet, params IRedisSetAsync[] withSets) + => AsAsync().StoreDiffAsync(fromSet, withSets, token: default); + + ValueTask IRedisSetAsync.StoreIntersectAsync(IRedisSetAsync[] withSets, CancellationToken token) + { + var withSetIds = withSets.ToList().ConvertAll(x => x.Id).ToArray(); + return AsyncClient.StoreIntersectFromSetsAsync(setId, withSetIds, token); + } + + ValueTask IRedisSetAsync.StoreIntersectAsync(params IRedisSetAsync[] withSets) + => AsAsync().StoreIntersectAsync(withSets, token: default); + + ValueTask IRedisSetAsync.StoreUnionAsync(IRedisSetAsync[] withSets, CancellationToken token) + { + var withSetIds = withSets.ToList().ConvertAll(x => x.Id).ToArray(); + return AsyncClient.StoreUnionFromSetsAsync(setId, withSetIds, token); + } + + ValueTask IRedisSetAsync.StoreUnionAsync(params IRedisSetAsync[] withSets) + => AsAsync().StoreUnionAsync(withSets, token: default); + + ValueTask> IRedisSetAsync.UnionAsync(IRedisSetAsync[] withSets, CancellationToken token) + { + var allSetIds = MergeSetIds(withSets); + return AsyncClient.GetUnionFromSetsAsync(allSetIds.ToArray(), token); + } + + ValueTask> IRedisSetAsync.UnionAsync(params IRedisSetAsync[] withSets) + => AsAsync().UnionAsync(withSets, token: default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientSet.cs b/src/ServiceStack.Redis/RedisClientSet.cs index dc96b566..daa58653 100644 --- a/src/ServiceStack.Redis/RedisClientSet.cs +++ b/src/ServiceStack.Redis/RedisClientSet.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -20,7 +20,7 @@ namespace ServiceStack.Redis /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientSet + internal partial class RedisClientSet : IRedisSet { private readonly RedisClient client; diff --git a/src/ServiceStack.Redis/RedisClientSortedSet.Async.cs b/src/ServiceStack.Redis/RedisClientSortedSet.Async.cs new file mode 100644 index 00000000..a7afd56e --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientSortedSet.Async.cs @@ -0,0 +1,102 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Redis.Internal; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + internal partial class RedisClientSortedSet + : IRedisSortedSetAsync + { + private IRedisClientAsync AsyncClient => client; + + ValueTask IRedisSortedSetAsync.AddAsync(string value, CancellationToken token) + => AsyncClient.AddItemToSortedSetAsync(setId, value, token).Await(); + + private IRedisSortedSetAsync AsAsync() => this; + + ValueTask IRedisSortedSetAsync.ClearAsync(CancellationToken token) + => new ValueTask(AsyncClient.RemoveAsync(setId, token)); + + ValueTask IRedisSortedSetAsync.ContainsAsync(string value, CancellationToken token) + => AsyncClient.SortedSetContainsItemAsync(setId, value, token); + + ValueTask IRedisSortedSetAsync.CountAsync(CancellationToken token) + => AsyncClient.GetSortedSetCountAsync(setId, token).AsInt32(); + + ValueTask> IRedisSortedSetAsync.GetAllAsync(CancellationToken token) + => AsyncClient.GetAllItemsFromSortedSetAsync(setId, token); + + async IAsyncEnumerator IAsyncEnumerable.GetAsyncEnumerator(CancellationToken token) + { + // uses ZSCAN + await foreach (var pair in AsyncClient.ScanAllSortedSetItemsAsync(setId, token: token).ConfigureAwait(false)) + { + yield return pair.Key; + } + } + + ValueTask IRedisSortedSetAsync.GetItemIndexAsync(string value, CancellationToken token) + => AsyncClient.GetItemIndexInSortedSetAsync(setId, value, token); + + ValueTask IRedisSortedSetAsync.GetItemScoreAsync(string value, CancellationToken token) + => AsyncClient.GetItemScoreInSortedSetAsync(setId, value, token); + + ValueTask> IRedisSortedSetAsync.GetRangeAsync(int startingRank, int endingRank, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetAsync(setId, startingRank, endingRank, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByScoreAsync(string fromStringScore, string toStringScore, CancellationToken token) + => AsAsync().GetRangeByScoreAsync(fromStringScore, toStringScore, null, null, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByScoreAsync(string fromStringScore, string toStringScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(setId, fromStringScore, toStringScore, skip, take, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByScoreAsync(double fromScore, double toScore, CancellationToken token) + => AsAsync().GetRangeByScoreAsync(fromScore, toScore, null, null, token); + + ValueTask> IRedisSortedSetAsync.GetRangeByScoreAsync(double fromScore, double toScore, int? skip, int? take, CancellationToken token) + => AsyncClient.GetRangeFromSortedSetByLowestScoreAsync(setId, fromScore, toScore, skip, take, token); + + ValueTask IRedisSortedSetAsync.IncrementItemScoreAsync(string value, double incrementByScore, CancellationToken token) + => AsyncClient.IncrementItemInSortedSetAsync(setId, value, incrementByScore, token).Await(); + + ValueTask IRedisSortedSetAsync.PopItemWithHighestScoreAsync(CancellationToken token) + => AsyncClient.PopItemWithHighestScoreFromSortedSetAsync(setId, token); + + ValueTask IRedisSortedSetAsync.PopItemWithLowestScoreAsync(CancellationToken token) + => AsyncClient.PopItemWithLowestScoreFromSortedSetAsync(setId, token); + + ValueTask IRedisSortedSetAsync.RemoveAsync(string value, CancellationToken token) + => AsyncClient.RemoveItemFromSortedSetAsync(setId, value, token).AwaitAsTrue(); // see Remove() for why "true" + + ValueTask IRedisSortedSetAsync.RemoveRangeAsync(int fromRank, int toRank, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetAsync(setId, fromRank, toRank, token).Await(); + + ValueTask IRedisSortedSetAsync.RemoveRangeByScoreAsync(double fromScore, double toScore, CancellationToken token) + => AsyncClient.RemoveRangeFromSortedSetByScoreAsync(setId, fromScore, toScore, token).Await(); + + ValueTask IRedisSortedSetAsync.StoreFromIntersectAsync(IRedisSortedSetAsync[] ofSets, CancellationToken token) + => AsyncClient.StoreIntersectFromSortedSetsAsync(setId, ofSets.GetIds(), token).Await(); + + ValueTask IRedisSortedSetAsync.StoreFromIntersectAsync(params IRedisSortedSetAsync[] ofSets) + => AsAsync().StoreFromIntersectAsync(ofSets, token: default); + + ValueTask IRedisSortedSetAsync.StoreFromUnionAsync(IRedisSortedSetAsync[] ofSets, CancellationToken token) + => AsyncClient.StoreUnionFromSortedSetsAsync(setId, ofSets.GetIds(), token).Await(); + + ValueTask IRedisSortedSetAsync.StoreFromUnionAsync(params IRedisSortedSetAsync[] ofSets) + => AsAsync().StoreFromUnionAsync(ofSets, token: default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientSortedSet.cs b/src/ServiceStack.Redis/RedisClientSortedSet.cs index 78086aec..adde5d12 100644 --- a/src/ServiceStack.Redis/RedisClientSortedSet.cs +++ b/src/ServiceStack.Redis/RedisClientSortedSet.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -20,7 +20,7 @@ namespace ServiceStack.Redis /// /// Wrap the common redis set operations under a ICollection[string] interface. /// - internal class RedisClientSortedSet + internal partial class RedisClientSortedSet : IRedisSortedSet { private readonly RedisClient client; diff --git a/src/ServiceStack.Redis/RedisClient_Admin.cs b/src/ServiceStack.Redis/RedisClient_Admin.cs index 0dc7e3f1..804df5f1 100644 --- a/src/ServiceStack.Redis/RedisClient_Admin.cs +++ b/src/ServiceStack.Redis/RedisClient_Admin.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -32,8 +32,13 @@ public RedisText GetServerRoleInfo() public string GetConfig(string configItem) { - var sb = StringBuilderCache.Allocate(); var byteArray = base.ConfigGet(configItem); + return GetConfigParse(byteArray); + } + + static string GetConfigParse(byte[][] byteArray) + { + var sb = StringBuilderCache.Allocate(); const int startAt = 1; //skip repeating config name for (var i = startAt; i < byteArray.Length; i++) { @@ -80,7 +85,11 @@ public long KillClients(string fromAddress = null, string withId = null, RedisCl public List> GetClientsInfo() { - var clientList = base.ClientList().FromUtf8Bytes(); + return GetClientsInfoParse(ClientList()); + } + private static List> GetClientsInfoParse(byte[] rawResult) + { + var clientList = rawResult.FromUtf8Bytes(); var results = new List>(); var lines = clientList.Split('\n'); @@ -108,6 +117,11 @@ public void PauseAllClients(TimeSpan duration) public DateTime GetServerTime() { var parts = base.Time(); + return ParseTimeResult(parts); + } + + private static DateTime ParseTimeResult(byte[][] parts) + { var unixTime = long.Parse(parts[0].FromUtf8Bytes()); var microSecs = long.Parse(parts[1].FromUtf8Bytes()); var ticks = microSecs / 1000 * TimeSpan.TicksPerMillisecond; diff --git a/src/ServiceStack.Redis/RedisClient_Hash.Async.cs b/src/ServiceStack.Redis/RedisClient_Hash.Async.cs new file mode 100644 index 00000000..9832b5d4 --- /dev/null +++ b/src/ServiceStack.Redis/RedisClient_Hash.Async.cs @@ -0,0 +1,30 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; + +namespace ServiceStack.Redis +{ + public partial class RedisClient + { + internal partial class RedisClientHashes + : IHasNamed + { + IRedisHashAsync IHasNamed.this[string hashId] + { + get => new RedisClientHash(client, hashId); + set => throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClient_Hash.cs b/src/ServiceStack.Redis/RedisClient_Hash.cs index 66ea80fd..afc71b60 100644 --- a/src/ServiceStack.Redis/RedisClient_Hash.cs +++ b/src/ServiceStack.Redis/RedisClient_Hash.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -23,7 +23,7 @@ public partial class RedisClient { public IHasNamed Hashes { get; set; } - internal class RedisClientHashes + internal partial class RedisClientHashes : IHasNamed { private readonly RedisClient client; @@ -59,12 +59,23 @@ public bool SetEntryInHashIfNotExists(string hashId, string key, string value) } public void SetRangeInHash(string hashId, IEnumerable> keyValuePairs) + { + if (SetRangeInHashPrepare(keyValuePairs, out var keys, out var values)) + { + base.HMSet(hashId, keys, values); + } + } + bool SetRangeInHashPrepare(IEnumerable> keyValuePairs, out byte[][] keys, out byte[][] values) { var keyValuePairsList = keyValuePairs.ToList(); - if (keyValuePairsList.Count == 0) return; + if (keyValuePairsList.Count == 0) + { + keys = values = default; + return false; + } - var keys = new byte[keyValuePairsList.Count][]; - var values = new byte[keyValuePairsList.Count][]; + keys = new byte[keyValuePairsList.Count][]; + values = new byte[keyValuePairsList.Count][]; for (var i = 0; i < keyValuePairsList.Count; i++) { @@ -72,8 +83,7 @@ public void SetRangeInHash(string hashId, IEnumerable + { + IRedisListAsync IHasNamed.this[string listId] + { + get => new RedisClientList(client, listId); + set => throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClient_List.cs b/src/ServiceStack.Redis/RedisClient_List.cs index 77f7a0fd..d723b975 100644 --- a/src/ServiceStack.Redis/RedisClient_List.cs +++ b/src/ServiceStack.Redis/RedisClient_List.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using ServiceStack.Model; +using ServiceStack.Redis.Pipeline; using ServiceStack.Text; namespace ServiceStack.Redis @@ -26,7 +27,7 @@ public partial class RedisClient public IHasNamed Lists { get; set; } - internal class RedisClientLists + internal partial class RedisClientLists : IHasNamed { private readonly RedisClient client; @@ -81,6 +82,15 @@ public void AddItemToList(string listId, string value) } public void AddRangeToList(string listId, List values) + { + var pipeline = AddRangeToListPrepareNonFlushed(listId, values); + pipeline.Flush(); + + //the number of items after + var intResults = pipeline.ReadAllAsInts(); + } + + private RedisPipelineCommand AddRangeToListPrepareNonFlushed(string listId, List values) { var uListId = listId.ToUtf8Bytes(); @@ -89,10 +99,7 @@ public void AddRangeToList(string listId, List values) { pipeline.WriteCommand(Commands.RPush, uListId, value.ToUtf8Bytes()); } - pipeline.Flush(); - - //the number of items after - var intResults = pipeline.ReadAllAsInts(); + return pipeline; } public void PrependItemToList(string listId, string value) @@ -101,6 +108,15 @@ public void PrependItemToList(string listId, string value) } public void PrependRangeToList(string listId, List values) + { + var pipeline = PrependRangeToListPrepareNonFlushed(listId, values); + pipeline.Flush(); + + //the number of items after + var intResults = pipeline.ReadAllAsInts(); + } + + private RedisPipelineCommand PrependRangeToListPrepareNonFlushed(string listId, List values) { var uListId = listId.ToUtf8Bytes(); @@ -111,10 +127,7 @@ public void PrependRangeToList(string listId, List values) var value = values[i]; pipeline.WriteCommand(Commands.LPush, uListId, value.ToUtf8Bytes()); } - pipeline.Flush(); - - //the number of items after - var intResults = pipeline.ReadAllAsInts(); + return pipeline; } public void RemoveAllFromList(string listId) diff --git a/src/ServiceStack.Redis/RedisClient_Set.Async.cs b/src/ServiceStack.Redis/RedisClient_Set.Async.cs new file mode 100644 index 00000000..3e6f3b21 --- /dev/null +++ b/src/ServiceStack.Redis/RedisClient_Set.Async.cs @@ -0,0 +1,30 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; + +namespace ServiceStack.Redis +{ + public partial class RedisClient + { + internal partial class RedisClientSets + : IHasNamed + { + IRedisSetAsync IHasNamed.this[string setId] + { + get => new RedisClientSet(client, setId); + set => throw new NotSupportedException(); + } + } + } +} diff --git a/src/ServiceStack.Redis/RedisClient_Set.cs b/src/ServiceStack.Redis/RedisClient_Set.cs index 8b18f169..c99a00c4 100644 --- a/src/ServiceStack.Redis/RedisClient_Set.cs +++ b/src/ServiceStack.Redis/RedisClient_Set.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using ServiceStack.Common; using ServiceStack.Model; using ServiceStack.Redis.Generic; @@ -26,7 +27,7 @@ public partial class RedisClient { public IHasNamed Sets { get; set; } - internal class RedisClientSets + internal partial class RedisClientSets : IHasNamed { private readonly RedisClient client; @@ -96,6 +97,11 @@ public List GetGeoCoordinates(string key, params string[] members) public string[] FindGeoMembersInRadius(string key, double longitude, double latitude, double radius, string unit) { var results = base.GeoRadius(key, longitude, latitude, radius, unit); + return ParseFindGeoMembersResult(results); + } + + private static string[] ParseFindGeoMembersResult(List results) + { var to = new string[results.Count]; for (var i = 0; i < results.Count; i++) { @@ -113,12 +119,7 @@ public List FindGeoResultsInRadius(string key, double longitude, public string[] FindGeoMembersInRadius(string key, string member, double radius, string unit) { var results = base.GeoRadiusByMember(key, member, radius, unit); - var to = new string[results.Count]; - for (var i = 0; i < results.Count; i++) - { - to[i] = results[i].Member; - } - return to; + return ParseFindGeoMembersResult(results); } public List FindGeoResultsInRadius(string key, string member, double radius, string unit, int? count = null, bool? sortByNearest = null) @@ -138,13 +139,30 @@ public void AddItemToSet(string setId, string item) } public void AddRangeToSet(string setId, List items) + { + if (AddRangeToSetNeedsSend(setId, items)) + { + var uSetId = setId.ToUtf8Bytes(); + var pipeline = CreatePipelineCommand(); + foreach (var item in items) + { + pipeline.WriteCommand(Commands.SAdd, uSetId, item.ToUtf8Bytes()); + } + pipeline.Flush(); + + //the number of items after + _ = pipeline.ReadAllAsInts(); + } + } + + bool AddRangeToSetNeedsSend(string setId, List items) { if (setId.IsNullOrEmpty()) throw new ArgumentNullException("setId"); if (items == null) throw new ArgumentNullException("items"); if (items.Count == 0) - return; + return false; if (this.Transaction != null || this.Pipeline != null) { @@ -163,19 +181,11 @@ public void AddRangeToSet(string setId, List items) var item = items[i]; queueable.QueueCommand(c => c.AddItemToSet(setId, item)); } + return false; } else { - var uSetId = setId.ToUtf8Bytes(); - var pipeline = CreatePipelineCommand(); - foreach (var item in items) - { - pipeline.WriteCommand(Commands.SAdd, uSetId, item.ToUtf8Bytes()); - } - pipeline.Flush(); - - //the number of items after - var intResults = pipeline.ReadAllAsInts(); + return true; } } diff --git a/src/ServiceStack.Redis/RedisClient_Slowlog.cs b/src/ServiceStack.Redis/RedisClient_Slowlog.cs index eb36f3b9..ecde5244 100644 --- a/src/ServiceStack.Redis/RedisClient_Slowlog.cs +++ b/src/ServiceStack.Redis/RedisClient_Slowlog.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -24,6 +24,11 @@ public partial class RedisClient public IEnumerable GetSlowlog(int? numberOfRecords = null) { var data = Slowlog(numberOfRecords); + return ParseSlowlog(data); + } + + private static SlowlogItem[] ParseSlowlog(object[] data) + { var list = new SlowlogItem[data.Length]; for (int i = 0; i < data.Length; i++) { @@ -47,20 +52,4 @@ public IEnumerable GetSlowlog(int? numberOfRecords = null) } - - public class SlowlogItem - { - public SlowlogItem(int id, DateTime timeStamp, int duration, string[] arguments) - { - Id = id; - Timestamp = timeStamp; - Duration = duration; - Arguments = arguments; - } - - public int Id { get; private set; } - public int Duration { get; private set; } - public DateTime Timestamp { get; private set; } - public string[] Arguments { get; private set; } - } } diff --git a/src/ServiceStack.Redis/RedisClient_SortedSet.Async.cs b/src/ServiceStack.Redis/RedisClient_SortedSet.Async.cs new file mode 100644 index 00000000..b3a89ff3 --- /dev/null +++ b/src/ServiceStack.Redis/RedisClient_SortedSet.Async.cs @@ -0,0 +1,30 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using ServiceStack.Model; +using System; + +namespace ServiceStack.Redis +{ + public partial class RedisClient : IRedisClient + { + internal partial class RedisClientSortedSets + : IHasNamed + { + IRedisSortedSetAsync IHasNamed.this[string setId] + { + get => new RedisClientSortedSet(client, setId); + set => throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClient_SortedSet.cs b/src/ServiceStack.Redis/RedisClient_SortedSet.cs index fd909c57..55549114 100644 --- a/src/ServiceStack.Redis/RedisClient_SortedSet.cs +++ b/src/ServiceStack.Redis/RedisClient_SortedSet.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -15,6 +15,7 @@ using System.Globalization; using System.Linq; using ServiceStack.Model; +using ServiceStack.Redis.Pipeline; using ServiceStack.Redis.Support; using ServiceStack.Text; @@ -24,7 +25,7 @@ public partial class RedisClient : IRedisClient { public IHasNamed SortedSets { get; set; } - internal class RedisClientSortedSets + internal partial class RedisClientSortedSets : IHasNamed { private readonly RedisClient client; @@ -87,36 +88,29 @@ public bool AddItemToSortedSet(string setId, string value, long score) public bool AddRangeToSortedSet(string setId, List values, double score) { - var pipeline = CreatePipelineCommand(); - var uSetId = setId.ToUtf8Bytes(); - var uScore = score.ToFastUtf8Bytes(); - - foreach (var value in values) - { - pipeline.WriteCommand(Commands.ZAdd, uSetId, uScore, value.ToUtf8Bytes()); - } - + var pipeline = AddRangeToSortedSetPrepareNonFlushed(setId, values, score.ToFastUtf8Bytes()); pipeline.Flush(); - var success = pipeline.ReadAllAsIntsHaveSuccess(); - return success; + return pipeline.ReadAllAsIntsHaveSuccess(); } public bool AddRangeToSortedSet(string setId, List values, long score) + { + var pipeline = AddRangeToSortedSetPrepareNonFlushed(setId, values, score.ToUtf8Bytes()); + pipeline.Flush(); + + return pipeline.ReadAllAsIntsHaveSuccess(); + } + RedisPipelineCommand AddRangeToSortedSetPrepareNonFlushed(string setId, List values, byte[] uScore) { var pipeline = CreatePipelineCommand(); var uSetId = setId.ToUtf8Bytes(); - var uScore = score.ToUtf8Bytes(); foreach (var value in values) { pipeline.WriteCommand(Commands.ZAdd, uSetId, uScore, value.ToUtf8Bytes()); } - - pipeline.Flush(); - - var success = pipeline.ReadAllAsIntsHaveSuccess(); - return success; + return pipeline; } public bool RemoveItemFromSortedSet(string setId, string value) diff --git a/src/ServiceStack.Redis/RedisClientsManagerExtensions.Async.cs b/src/ServiceStack.Redis/RedisClientsManagerExtensions.Async.cs new file mode 100644 index 00000000..123396ac --- /dev/null +++ b/src/ServiceStack.Redis/RedisClientsManagerExtensions.Async.cs @@ -0,0 +1,120 @@ +using ServiceStack.Caching; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Internal; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + /// + /// Useful wrapper IRedisClientsManager to cut down the boiler plate of most IRedisClient access + /// + public static partial class RedisClientsManagerExtensions + { + ///// + ///// Creates a PubSubServer that uses a background thread to listen and process for + ///// Redis Pub/Sub messages published to the specified channel. + ///// Use optional callbacks to listen for message, error and life-cycle events. + ///// Callbacks can be assigned later, then call Start() for PubSubServer to start listening for messages + ///// + //public static IRedisPubSubServer CreatePubSubServer(this IRedisClientsManager redisManager, + // string channel, + // Action onMessage = null, + // Action onError = null, + // Action onInit = null, + // Action onStart = null, + // Action onStop = null) + //{ + // return new RedisPubSubServer(redisManager, channel) + // { + // OnMessage = onMessage, + // OnError = onError, + // OnInit = onInit, + // OnStart = onStart, + // OnStop = onStop, + // }; + //} + + private static T InvalidAsyncClient(IRedisClientsManager manager, string method) where T : class + => throw new NotSupportedException($"The client returned from '{manager?.GetType().FullName ?? "(null)"}.{method}()' does not implement {typeof(T).Name}"); + + public static ValueTask GetClientAsync(this IRedisClientsManager redisManager, CancellationToken token = default) + { + return redisManager is IRedisClientsManagerAsync asyncManager + ? asyncManager.GetClientAsync(token) + : (redisManager.GetClient() as IRedisClientAsync ?? InvalidAsyncClient(redisManager, nameof(redisManager.GetClient))).AsValueTaskResult(); + } + + public static ValueTask GetReadOnlyClientAsync(this IRedisClientsManager redisManager, CancellationToken token = default) + { + return redisManager is IRedisClientsManagerAsync asyncManager + ? asyncManager.GetReadOnlyClientAsync(token) + : (redisManager.GetReadOnlyClient() as IRedisClientAsync ?? InvalidAsyncClient(redisManager, nameof(redisManager.GetReadOnlyClient))).AsValueTaskResult(); + } + + public static ValueTask GetCacheClientAsync(this IRedisClientsManager redisManager, CancellationToken token = default) + { + return redisManager is IRedisClientsManagerAsync asyncManager + ? asyncManager.GetCacheClientAsync(token) + : (redisManager.GetCacheClient() as ICacheClientAsync ?? InvalidAsyncClient(redisManager, nameof(redisManager.GetCacheClient))).AsValueTaskResult(); + } + + public static ValueTask GetReadOnlyCacheClientAsync(this IRedisClientsManager redisManager, CancellationToken token = default) + { + return redisManager is IRedisClientsManagerAsync asyncManager + ? asyncManager.GetReadOnlyCacheClientAsync(token) + : (redisManager.GetReadOnlyCacheClient() as ICacheClientAsync ?? InvalidAsyncClient(redisManager, nameof(redisManager.GetCacheClient))).AsValueTaskResult(); + } + + + public static async ValueTask ExecAsync(this IRedisClientsManager redisManager, Func lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + await lambda(redis).ConfigureAwait(false); + } + + public static async ValueTask ExecAsync(this IRedisClientsManager redisManager, Func> lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + return await lambda(redis).ConfigureAwait(false); + } + + //public static void ExecTrans(this IRedisClientsManager redisManager, Action lambda) + //{ + // using (var redis = redisManager.GetClient()) + // using (var trans = redis.CreateTransaction()) + // { + // lambda(trans); + + // trans.Commit(); + // } + //} + + public static async ValueTask ExecAsAsync(this IRedisClientsManager redisManager, Func, ValueTask> lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + await lambda(redis.As()).ConfigureAwait(false); + } + + public static async ValueTask ExecAsAsync(this IRedisClientsManager redisManager, Func, ValueTask> lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + return await lambda(redis.As()).ConfigureAwait(false); + } + + public static async ValueTask> ExecAsAsync(this IRedisClientsManager redisManager, Func, ValueTask>> lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + return await lambda(redis.As()).ConfigureAwait(false); + } + + public static async ValueTask> ExecAsAsync(this IRedisClientsManager redisManager, Func, ValueTask>> lambda) + { + await using var redis = await redisManager.GetClientAsync().ConfigureAwait(false); + return await lambda(redis.As()).ConfigureAwait(false); + } + } + +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisClientsManagerExtensions.cs b/src/ServiceStack.Redis/RedisClientsManagerExtensions.cs index 9d4f8ce6..6f62b84b 100644 --- a/src/ServiceStack.Redis/RedisClientsManagerExtensions.cs +++ b/src/ServiceStack.Redis/RedisClientsManagerExtensions.cs @@ -7,7 +7,7 @@ namespace ServiceStack.Redis /// /// Useful wrapper IRedisClientsManager to cut down the boiler plate of most IRedisClient access /// - public static class RedisClientsManagerExtensions + public static partial class RedisClientsManagerExtensions { /// /// Creates a PubSubServer that uses a background thread to listen and process for diff --git a/src/ServiceStack.Redis/RedisConfig.cs b/src/ServiceStack.Redis/RedisConfig.cs index 4d3d8130..da28ffb5 100644 --- a/src/ServiceStack.Redis/RedisConfig.cs +++ b/src/ServiceStack.Redis/RedisConfig.cs @@ -52,6 +52,11 @@ public class RedisConfig /// public static int? DefaultMaxPoolSize; + /// + /// The default pool size multiplier if no pool size is specified (default 50) + /// + public static int DefaultPoolSizeMultiplier = 50; + /// /// The BackOff multiplier failed Auto Retries starts from (default 10ms) /// @@ -67,11 +72,22 @@ public class RedisConfig /// public static int BufferPoolMaxSize = 500000; + /// + /// Batch size of keys to include in a single Redis Command (e.g. DEL k1 k2...) + /// + public static int CommandKeysBatchSize = 10000; + /// /// Whether Connections to Master hosts should be verified they're still master instances (default true) /// public static bool VerifyMasterConnections = true; + /// + /// Whether to retry re-connecting on same connection if not a master instance (default true) + /// For Managed Services (e.g. AWS ElastiCache) which eventually restores master instances on same host + /// + public static bool RetryReconnectOnFailedMasters = true; + /// /// The ConnectTimeout on clients used to find the next available host (default 200ms) /// @@ -83,15 +99,22 @@ public class RedisConfig public static int? AssumeServerVersion; /// - /// How long to hold deactivated clients for before disposing their connection (default 1 min) + /// How long to hold deactivated clients for before disposing their connection (default 0 seconds) /// Dispose of deactivated Clients immediately with TimeSpan.Zero /// - public static TimeSpan DeactivatedClientsExpiry = TimeSpan.FromMinutes(1); + public static TimeSpan DeactivatedClientsExpiry = TimeSpan.Zero; /// /// Whether Debug Logging should log detailed Redis operations (default false) /// - public static bool DisableVerboseLogging = false; + public static bool EnableVerboseLogging = false; + + [Obsolete("Use EnableVerboseLogging")] + public static bool DisableVerboseLogging + { + get => !EnableVerboseLogging; + set => EnableVerboseLogging = !value; + } //Example at: http://msdn.microsoft.com/en-us/library/office/dd633677(v=exchg.80).aspx public static LocalCertificateSelectionCallback CertificateSelectionCallback { get; set; } @@ -118,11 +141,13 @@ public static void Reset() DefaultMaxPoolSize = null; BackOffMultiplier = 10; BufferPoolMaxSize = 500000; + CommandKeysBatchSize = 10000; VerifyMasterConnections = true; + RetryReconnectOnFailedMasters = true; HostLookupTimeoutMs = 200; AssumeServerVersion = null; - DeactivatedClientsExpiry = TimeSpan.FromMinutes(1); - DisableVerboseLogging = false; + DeactivatedClientsExpiry = TimeSpan.Zero; + EnableVerboseLogging = false; CertificateSelectionCallback = null; CertificateValidationCallback = null; AssertAccessOnlyOnSameThread = false; diff --git a/src/ServiceStack.Redis/RedisEndpoint.cs b/src/ServiceStack.Redis/RedisEndpoint.cs index 4ec125d2..b43da7da 100644 --- a/src/ServiceStack.Redis/RedisEndpoint.cs +++ b/src/ServiceStack.Redis/RedisEndpoint.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Security.Authentication; using System.Text; using ServiceStack.IO; using ServiceStack.Text; @@ -34,6 +35,7 @@ public RedisEndpoint(string host, int port, string password = null, long db = Re public string Host { get; set; } public int Port { get; set; } public bool Ssl { get; set; } + public SslProtocols? SslProtocols {get; set;} public int ConnectTimeout { get; set; } public int SendTimeout { get; set; } public int ReceiveTimeout { get; set; } @@ -59,6 +61,8 @@ public override string ToString() args.Add("Db=" + Db); if (Ssl) args.Add("Ssl=true"); + if (SslProtocols != null) + args.Add("SslProtocols=" + SslProtocols.ToString()); if (ConnectTimeout != RedisConfig.DefaultConnectTimeout) args.Add("ConnectTimeout=" + ConnectTimeout); if (SendTimeout != RedisConfig.DefaultSendTimeout) @@ -83,6 +87,7 @@ protected bool Equals(RedisEndpoint other) return string.Equals(Host, other.Host) && Port == other.Port && Ssl.Equals(other.Ssl) + && SslProtocols.Equals(other.SslProtocols) && ConnectTimeout == other.ConnectTimeout && SendTimeout == other.SendTimeout && ReceiveTimeout == other.ReceiveTimeout @@ -109,6 +114,7 @@ public override int GetHashCode() var hashCode = (Host != null ? Host.GetHashCode() : 0); hashCode = (hashCode * 397) ^ Port; hashCode = (hashCode * 397) ^ Ssl.GetHashCode(); + hashCode = (hashCode * 397) ^ SslProtocols.GetHashCode(); hashCode = (hashCode * 397) ^ ConnectTimeout; hashCode = (hashCode * 397) ^ SendTimeout; hashCode = (hashCode * 397) ^ ReceiveTimeout; diff --git a/src/ServiceStack.Redis/RedisExtensions.cs b/src/ServiceStack.Redis/RedisExtensions.cs index f3193ebe..b4ba79f7 100644 --- a/src/ServiceStack.Redis/RedisExtensions.cs +++ b/src/ServiceStack.Redis/RedisExtensions.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -15,6 +15,7 @@ using System.Globalization; using System.Linq; using System.Net.Sockets; +using System.Security.Authentication; using ServiceStack.Model; using ServiceStack.Text; @@ -76,6 +77,12 @@ public static RedisEndpoint ToRedisEndpoint(this string connectionString, int? d if (useDefaultPort) endpoint.Port = RedisConfig.DefaultPortSsl; break; + case "sslprotocols": + SslProtocols protocols; + value = value?.Replace("|", ","); + if (!Enum.TryParse(value, true, out protocols)) throw new ArgumentOutOfRangeException("Keyword '" + name + "' requires an SslProtocol value (multiple values separated by '|')."); + endpoint.SslProtocols = protocols; + break; case "client": endpoint.Client = value; break; diff --git a/src/ServiceStack.Redis/RedisLock.Async.cs b/src/ServiceStack.Redis/RedisLock.Async.cs new file mode 100644 index 00000000..8466c11f --- /dev/null +++ b/src/ServiceStack.Redis/RedisLock.Async.cs @@ -0,0 +1,93 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Redis.Internal; +using ServiceStack.Text; + +namespace ServiceStack.Redis +{ + public partial class RedisLock + : IAsyncDisposable + { + internal static ValueTask CreateAsync(IRedisClientAsync redisClient, string key, + TimeSpan? timeOut = default, CancellationToken token = default) + { + var obj = new RedisLock(redisClient, key); + return obj.AcquireAsync(timeOut, token).Await(obj); + } + + // async version of ExecUtils.RetryUntilTrue + private static async ValueTask RetryUntilTrue(Func> action, + TimeSpan? timeOut = null, CancellationToken token = default) + { + var i = 0; + var firstAttempt = DateTime.UtcNow; + + while (timeOut == null || DateTime.UtcNow - firstAttempt < timeOut.Value) + { + token.ThrowIfCancellationRequested(); + i++; + if (await action(token).ConfigureAwait(false)) + { + return; + } + await Task.Delay(ExecUtils.CalculateFullJitterBackOffDelay(i)).ConfigureAwait(false); + } + + throw new TimeoutException($"Exceeded timeout of {timeOut.Value}"); + } + + + private async ValueTask AcquireAsync(TimeSpan? timeOut, CancellationToken token) + { + var redisClient = (IRedisClientAsync)untypedClient; + await RetryUntilTrue( // .ConfigureAwait(false) is below + async ct => + { + //This pattern is taken from the redis command for SETNX http://redis.io/commands/setnx + + //Calculate a unix time for when the lock should expire + var realSpan = timeOut ?? new TimeSpan(365, 0, 0, 0); //if nothing is passed in the timeout hold for a year + var expireTime = DateTime.UtcNow.Add(realSpan); + var lockString = (expireTime.ToUnixTimeMs() + 1).ToString(); + + //Try to set the lock, if it does not exist this will succeed and the lock is obtained + var nx = await redisClient.SetValueIfNotExistsAsync(key, lockString, token: ct).ConfigureAwait(false); + if (nx) + return true; + + //If we've gotten here then a key for the lock is present. This could be because the lock is + //correctly acquired or it could be because a client that had acquired the lock crashed (or didn't release it properly). + //Therefore we need to get the value of the lock to see when it should expire + + await redisClient.WatchAsync(new[] { key }, ct).ConfigureAwait(false); + var lockExpireString = await redisClient.GetValueAsync(key, ct).ConfigureAwait(false); + if (!long.TryParse(lockExpireString, out var lockExpireTime)) + { + await redisClient.UnWatchAsync(ct).ConfigureAwait(false); // since the client is scoped externally + return false; + } + + //If the expire time is greater than the current time then we can't let the lock go yet + if (lockExpireTime > DateTime.UtcNow.ToUnixTimeMs()) + { + await redisClient.UnWatchAsync(ct).ConfigureAwait(false); // since the client is scoped externally + return false; + } + + //If the expire time is less than the current time then it wasn't released properly and we can attempt to + //acquire the lock. The above call to Watch(_lockKey) enrolled the key in monitoring, so if it changes + //before we call Commit() below, the Commit will fail and return false, which means that another thread + //was able to acquire the lock before we finished processing. + await using var trans = await redisClient.CreateTransactionAsync(ct).ConfigureAwait(false); + trans.QueueCommand(r => r.SetValueAsync(key, lockString)); + return await trans.CommitAsync(ct).ConfigureAwait(false); //returns false if Transaction failed + }, + timeOut, token + ).ConfigureAwait(false); + } + + ValueTask IAsyncDisposable.DisposeAsync() + => new ValueTask(((IRedisClientAsync)untypedClient).RemoveAsync(key)); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisLock.cs b/src/ServiceStack.Redis/RedisLock.cs index 021aba20..abf3a9d9 100644 --- a/src/ServiceStack.Redis/RedisLock.cs +++ b/src/ServiceStack.Redis/RedisLock.cs @@ -1,20 +1,23 @@ -using System; -using ServiceStack.Common; using ServiceStack.Text; +using System; namespace ServiceStack.Redis { - public class RedisLock + public partial class RedisLock : IDisposable { - private readonly IRedisClient redisClient; + private readonly object untypedClient; private readonly string key; - public RedisLock(IRedisClient redisClient, string key, TimeSpan? timeOut) + private RedisLock(object redisClient, string key) { - this.redisClient = redisClient; + this.untypedClient = redisClient; this.key = key; + } + public RedisLock(IRedisClient redisClient, string key, TimeSpan? timeOut) + : this(redisClient, key) + { ExecUtils.RetryUntilTrue( () => { @@ -65,7 +68,7 @@ public RedisLock(IRedisClient redisClient, string key, TimeSpan? timeOut) public void Dispose() { - redisClient.Remove(key); + ((IRedisClient)untypedClient).Remove(key); } } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisManagerPool.Async.cs b/src/ServiceStack.Redis/RedisManagerPool.Async.cs new file mode 100644 index 00000000..25406123 --- /dev/null +++ b/src/ServiceStack.Redis/RedisManagerPool.Async.cs @@ -0,0 +1,33 @@ +//Copyright (c) ServiceStack, Inc. All Rights Reserved. +//License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using ServiceStack.Caching; +using ServiceStack.Redis.Internal; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + public partial class RedisManagerPool + : IRedisClientsManagerAsync + { + ValueTask IRedisClientsManagerAsync.GetCacheClientAsync(CancellationToken token) + => new RedisClientManagerCacheClient(this).AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetClientAsync(CancellationToken token) + => GetClient(true).AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyCacheClientAsync(CancellationToken token) + => new RedisClientManagerCacheClient(this) { ReadOnly = true }.AsValueTaskResult(); + + ValueTask IRedisClientsManagerAsync.GetReadOnlyClientAsync(CancellationToken token) + => GetClient(true).AsValueTaskResult(); + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisManagerPool.cs b/src/ServiceStack.Redis/RedisManagerPool.cs index d57a16b1..6394f037 100644 --- a/src/ServiceStack.Redis/RedisManagerPool.cs +++ b/src/ServiceStack.Redis/RedisManagerPool.cs @@ -1,4 +1,4 @@ -//Copyright (c) Service Stack LLC. All Rights Reserved. +//Copyright (c) ServiceStack, Inc. All Rights Reserved. //License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt using System; @@ -28,13 +28,13 @@ public RedisPoolConfig() } /// - /// Maximum ammount of s created by the . + /// Maximum amount of s created by the . /// public int MaxPoolSize { get; set; } } /// - /// Provides thread-safe pooling of redis client connections. All connections are treaded as read and write hosts. + /// Provides thread-safe pooling of redis client connections. All connections are treated as read and write hosts. /// public partial class RedisManagerPool : IRedisClientsManager, IRedisFailover, IHandleClientDispose, IHasRedisResolver, IRedisClientCacheManager @@ -94,6 +94,8 @@ public void FailoverTo(params string[] readWriteHosts) { Interlocked.Increment(ref RedisState.TotalFailovers); + Log.Info($"FailoverTo: {string.Join(",", readWriteHosts)} Total: {RedisState.TotalFailovers}"); + lock (clients) { for (var i = 0; i < clients.Length; i++) @@ -127,12 +129,13 @@ public void FailoverTo(IEnumerable readWriteHosts, IEnumerable r { FailoverTo(readWriteHosts.ToArray()); //only use readWriteHosts } - + /// /// Returns a Read/Write client (The default) using the hosts defined in ReadWriteHosts /// /// - public IRedisClient GetClient() + public IRedisClient GetClient() => GetClient(false); + private RedisClient GetClient(bool forAsync) { try { @@ -148,7 +151,7 @@ public IRedisClient GetClient() if (inActiveClient != null) { poolIndex++; - inActiveClient.Active = true; + inActiveClient.Activate(); return !AssertAccessOnlyOnSameThread ? inActiveClient @@ -175,14 +178,14 @@ public IRedisClient GetClient() lock (clients) { //Create new client outside of pool when max pool size exceeded - //Reverting free-slot not needed when -1 since slwo wasn't reserved or - //when existingClient changed (failover) since no longer reserver + //Reverting free-slot not needed when -1 since slow wasn't reserved or + //when existingClient changed (failover) since no longer reserved var stillReserved = inactivePoolIndex >= 0 && inactivePoolIndex < clients.Length && clients[inactivePoolIndex] == existingClient; if (inactivePoolIndex == -1 || !stillReserved) { if (Log.IsDebugEnabled) - Log.Debug("clients[inactivePoolIndex] != existingClient: {0}".Fmt(!stillReserved ? "!stillReserved" : "-1")); + Log.Debug($"POOL clients[inactivePoolIndex] != existingClient: {(!stillReserved ? "!stillReserved" : "-1")}"); Interlocked.Increment(ref RedisState.TotalClientsCreatedOutsidePool); @@ -194,7 +197,7 @@ public IRedisClient GetClient() poolIndex++; clients[inactivePoolIndex] = newClient; - return !AssertAccessOnlyOnSameThread + return (!AssertAccessOnlyOnSameThread || forAsync) ? newClient : newClient.LimitAccessToThread(Thread.CurrentThread.ManagedThreadId, Environment.StackTrace); } @@ -220,7 +223,7 @@ public IRedisClient GetClient() public IRedisClient GetReadOnlyClient() { - return GetClient(); + return GetClient(false); } class ReservedClient : RedisClient @@ -243,7 +246,7 @@ public override void Dispose() { } private int GetInActiveClient(out RedisClient inactiveClient) { //this will loop through all hosts in readClients once even though there are 2 for loops - //both loops are used to try to get the prefered host according to the round robin algorithm + //both loops are used to try to get the preferred host according to the round robin algorithm var readWriteTotal = RedisResolver.ReadWriteHostsCount; var desiredIndex = poolIndex % clients.Length; for (int x = 0; x < readWriteTotal; x++) @@ -278,7 +281,7 @@ private int GetInActiveClient(out RedisClient inactiveClient) private RedisClient InitNewClient(RedisClient client) { client.Id = Interlocked.Increment(ref RedisClientCounter); - client.Active = true; + client.Activate(newClient:true); client.ClientManager = this; client.ConnectionFilter = ConnectionFilter; @@ -300,7 +303,7 @@ public void DisposeClient(RedisNativeClient client) else { client.TrackThread = null; - client.Active = false; + client.Deactivate(); } Monitor.PulseAll(clients); @@ -317,7 +320,7 @@ public void DisposeWriteClient(RedisNativeClient client) { lock (clients) { - client.Active = false; + client.Deactivate(); } } @@ -407,9 +410,9 @@ protected virtual void Dispose(bool disposing) try { // get rid of unmanaged resources - for (var i = 0; i < clients.Length; i++) + foreach (var client in clients) { - Dispose(clients[i]); + Dispose(client); } } catch (Exception ex) @@ -431,9 +434,7 @@ protected void Dispose(RedisClient redisClient) } catch (Exception ex) { - Log.Error(string.Format( - "Error when trying to dispose of RedisClient to host {0}:{1}", - redisClient.Host, redisClient.Port), ex); + Log.Error($"Error when trying to dispose of RedisClient to host {redisClient.Host}:{redisClient.Port}", ex); } } diff --git a/src/ServiceStack.Redis/RedisNativeClient.Async.cs b/src/ServiceStack.Redis/RedisNativeClient.Async.cs new file mode 100644 index 00000000..d265c0ae --- /dev/null +++ b/src/ServiceStack.Redis/RedisNativeClient.Async.cs @@ -0,0 +1,1487 @@ +using ServiceStack.Redis.Internal; +using ServiceStack.Redis.Pipeline; +using ServiceStack.Text; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + partial class RedisNativeClient + : IRedisNativeClientAsync + { + internal IRedisPipelineSharedAsync PipelineAsync + => (IRedisPipelineSharedAsync)pipeline; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static void AssertNotNull(object obj, string name = "key") + { + if (obj is null) Throw(name); + static void Throw(string name) => throw new ArgumentNullException(name); + } + + private IRedisNativeClientAsync AsAsync() => this; + + ValueTask IAsyncDisposable.DisposeAsync() + { + Dispose(); + return default; + } + + ValueTask IRedisNativeClientAsync.TimeAsync(CancellationToken token) + => SendExpectMultiDataAsync(token, Commands.Time); + + ValueTask IRedisNativeClientAsync.ExistsAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Exists, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SetAsync(string key, byte[] value, bool exists, long expirySeconds, long expiryMilliseconds, CancellationToken token) + { + AssertNotNull(key); + value ??= TypeConstants.EmptyByteArray; + + if (value.Length > OneGb) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + var entryExists = exists ? Commands.Xx : Commands.Nx; + byte[][] args; + if (expiryMilliseconds != 0) + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value, Commands.Px, expiryMilliseconds.ToUtf8Bytes(), entryExists }; + } + else if (expirySeconds != 0) + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value, Commands.Ex, expirySeconds.ToUtf8Bytes(), entryExists }; + } + else + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value, entryExists }; + } + + return IsString(SendExpectStringAsync(token, args), OK); + } + ValueTask IRedisNativeClientAsync.SetAsync(string key, byte[] value, long expirySeconds, long expiryMilliseconds, CancellationToken token) + { + AssertNotNull(key); + value ??= TypeConstants.EmptyByteArray; + + if (value.Length > OneGb) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + byte[][] args; + if (expiryMilliseconds != 0) + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value, Commands.Px, expiryMilliseconds.ToUtf8Bytes() }; + } + else if (expirySeconds != 0) + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value, Commands.Ex, expirySeconds.ToUtf8Bytes() }; + } + else + { + args = new[] { Commands.Set, key.ToUtf8Bytes(), value }; + } + + return SendExpectSuccessAsync(token, args); + } + + ValueTask IRedisNativeClientAsync.GetAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectDataAsync(token, Commands.Get, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.DelAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Del, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ScanAsync(ulong cursor, int count, string match, CancellationToken token) + { + if (match == null) + return SendExpectScanResultAsync(token, Commands.Scan, cursor.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + + return SendExpectScanResultAsync(token, Commands.Scan, cursor.ToUtf8Bytes(), + Commands.Match, match.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.TypeAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectCodeAsync(token, Commands.Type, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.RPushAsync(string listId, byte[] value, CancellationToken token) + { + AssertListIdAndValue(listId, value); + + return SendExpectLongAsync(token, Commands.RPush, listId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.SAddAsync(string setId, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + + return SendExpectLongAsync(token, Commands.SAdd, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZAddAsync(string setId, double score, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + + return SendExpectLongAsync(token, Commands.ZAdd, setId.ToUtf8Bytes(), score.ToFastUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZAddAsync(string setId, long score, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + + return SendExpectLongAsync(token, Commands.ZAdd, setId.ToUtf8Bytes(), score.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.HSetAsync(string hashId, byte[] key, byte[] value, CancellationToken token) + => HSetAsync(hashId.ToUtf8Bytes(), key, value, token); + + internal ValueTask HSetAsync(byte[] hashId, byte[] key, byte[] value, CancellationToken token = default) + { + AssertHashIdAndKey(hashId, key); + + return SendExpectLongAsync(token, Commands.HSet, hashId, key, value); + } + + ValueTask IRedisNativeClientAsync.RandomKeyAsync(CancellationToken token) + => SendExpectDataAsync(token, Commands.RandomKey).FromUtf8BytesAsync(); + + ValueTask IRedisNativeClientAsync.RenameAsync(string oldKeyName, string newKeyName, CancellationToken token) + { + CheckRenameKeys(oldKeyName, newKeyName); + return SendExpectSuccessAsync(token, Commands.Rename, oldKeyName.ToUtf8Bytes(), newKeyName.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.RenameNxAsync(string oldKeyName, string newKeyName, CancellationToken token) + { + CheckRenameKeys(oldKeyName, newKeyName); + return SendExpectLongAsync(token, Commands.RenameNx, oldKeyName.ToUtf8Bytes(), newKeyName.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.MSetAsync(byte[][] keys, byte[][] values, CancellationToken token) + { + var keysAndValues = MergeCommandWithKeysAndValues(Commands.MSet, keys, values); + + return SendExpectSuccessAsync(token, keysAndValues); + } + + + ValueTask IRedisNativeClientAsync.MSetAsync(string[] keys, byte[][] values, CancellationToken token) + => ((IRedisNativeClientAsync)this).MSetAsync(keys.ToMultiByteArray(), values, token); + + ValueTask IRedisNativeClientAsync.SelectAsync(long db, CancellationToken token) + { + this.db = db; + return SendExpectSuccessAsync(token, Commands.Select, db.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.DelAsync(string[] keys, CancellationToken token) + { + AssertNotNull(keys, nameof(keys)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.Del, keys); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.ExpireAsync(string key, int seconds, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Expire, key.ToUtf8Bytes(), seconds.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.PExpireAsync(string key, long ttlMs, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.PExpire, key.ToUtf8Bytes(), ttlMs.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.ExpireAtAsync(string key, long unixTime, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.ExpireAt, key.ToUtf8Bytes(), unixTime.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.PExpireAtAsync(string key, long unixTimeMs, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.PExpireAt, key.ToUtf8Bytes(), unixTimeMs.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.TtlAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Ttl, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.PTtlAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.PTtl, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.PingAsync(CancellationToken token) + => IsString(SendExpectCodeAsync(token, Commands.Ping), "PONG"); + + private static ValueTask IsString(ValueTask pending, string expected) + { + return pending.IsCompletedSuccessfully ? (pending.Result == expected).AsValueTaskResult() + : Awaited(pending, expected); + + static async ValueTask Awaited(ValueTask pending, string expected) + => await pending.ConfigureAwait(false) == expected; + } + + ValueTask IRedisNativeClientAsync.EchoAsync(string text, CancellationToken token) + => SendExpectDataAsync(token, Commands.Echo, text.ToUtf8Bytes()).FromUtf8BytesAsync(); + + ValueTask IRedisNativeClientAsync.DbSizeAsync(CancellationToken token) + => SendExpectLongAsync(token, Commands.DbSize); + + ValueTask IRedisNativeClientAsync.LastSaveAsync(CancellationToken token) + => SendExpectLongAsync(token, Commands.LastSave).Await(t => t.FromUnixTime()); + + ValueTask IRedisNativeClientAsync.SaveAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Save); + + ValueTask IRedisNativeClientAsync.BgSaveAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.BgSave); + + ValueTask IRedisNativeClientAsync.ShutdownAsync(bool noSave, CancellationToken token) + => noSave + ? SendWithoutReadAsync(token, Commands.Shutdown, Commands.NoSave) + : SendWithoutReadAsync(token, Commands.Shutdown); + + ValueTask IRedisNativeClientAsync.BgRewriteAofAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.BgRewriteAof); + + ValueTask IRedisNativeClientAsync.QuitAsync(CancellationToken token) + => SendWithoutReadAsync(token, Commands.Quit); + + ValueTask IRedisNativeClientAsync.FlushDbAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.FlushDb); + + ValueTask IRedisNativeClientAsync.FlushAllAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.FlushAll); + + ValueTask IRedisNativeClientAsync.SlaveOfAsync(string hostname, int port, CancellationToken token) + => SendExpectSuccessAsync(token, Commands.SlaveOf, hostname.ToUtf8Bytes(), port.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.SlaveOfNoOneAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.SlaveOf, Commands.No, Commands.One); + + ValueTask IRedisNativeClientAsync.KeysAsync(string pattern, CancellationToken token) + { + AssertNotNull(pattern, nameof(pattern)); + return SendExpectMultiDataAsync(token, Commands.Keys, pattern.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.MGetAsync(string[] keys, CancellationToken token) + { + AssertNotNull(keys, nameof(keys)); + if (keys.Length == 0) + throw new ArgumentException("keys"); + + var cmdWithArgs = MergeCommandWithArgs(Commands.MGet, keys); + + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SetExAsync(string key, int expireInSeconds, byte[] value, CancellationToken token) + { + AssertNotNull(key); + value ??= TypeConstants.EmptyByteArray; + + if (value.Length > OneGb) + throw new ArgumentException("value exceeds 1G", nameof(value)); + + return SendExpectSuccessAsync(token, Commands.SetEx, key.ToUtf8Bytes(), expireInSeconds.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.WatchAsync(string[] keys, CancellationToken token) + { + AssertNotNull(keys, nameof(keys)); + if (keys.Length == 0) + throw new ArgumentException("keys"); + + var cmdWithArgs = MergeCommandWithArgs(Commands.Watch, keys); + + return SendExpectCodeAsync(token, cmdWithArgs).Await(); + } + + ValueTask IRedisNativeClientAsync.UnWatchAsync(CancellationToken token) + => SendExpectCodeAsync(token, Commands.UnWatch).Await(); + + ValueTask IRedisNativeClientAsync.AppendAsync(string key, byte[] value, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Append, key.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.GetRangeAsync(string key, int fromIndex, int toIndex, CancellationToken token) + { + AssertNotNull(key); + return SendExpectDataAsync(token, Commands.GetRange, key.ToUtf8Bytes(), fromIndex.ToUtf8Bytes(), toIndex.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SetRangeAsync(string key, int offset, byte[] value, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.SetRange, key.ToUtf8Bytes(), offset.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.GetBitAsync(string key, int offset, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.GetBit, key.ToUtf8Bytes(), offset.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SetBitAsync(string key, int offset, int value, CancellationToken token) + { + AssertNotNull(key); + if (value > 1 || value < 0) + throw new ArgumentOutOfRangeException(nameof(value), "value is out of range"); + return SendExpectLongAsync(token, Commands.SetBit, key.ToUtf8Bytes(), offset.ToUtf8Bytes(), value.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.PersistAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Persist, key.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.PSetExAsync(string key, long expireInMs, byte[] value, CancellationToken token) + { + AssertNotNull(key); + return SendExpectSuccessAsync(token, Commands.PSetEx, key.ToUtf8Bytes(), expireInMs.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.SetNXAsync(string key, byte[] value, CancellationToken token) + { + AssertNotNull(key); + value ??= TypeConstants.EmptyByteArray; + + if (value.Length > OneGb) + throw new ArgumentException("value exceeds 1G", "value"); + + return SendExpectLongAsync(token, Commands.SetNx, key.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.SPopAsync(string setId, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectDataAsync(token, Commands.SPop, setId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SPopAsync(string setId, int count, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectMultiDataAsync(token, Commands.SPop, setId.ToUtf8Bytes(), count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SlowlogResetAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Slowlog, "RESET".ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.SlowlogGetAsync(int? top, CancellationToken token) + { + if (top.HasValue) + return SendExpectDeeplyNestedMultiDataAsync(token, Commands.Slowlog, Commands.Get, top.Value.ToUtf8Bytes()); + else + return SendExpectDeeplyNestedMultiDataAsync(token, Commands.Slowlog, Commands.Get); + } + + ValueTask IRedisNativeClientAsync.ZCardAsync(string setId, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.ZCard, setId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZCountAsync(string setId, double min, double max, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.ZCount, setId.ToUtf8Bytes(), min.ToUtf8Bytes(), max.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZScoreAsync(string setId, byte[] value, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectDoubleAsync(token, Commands.ZScore, setId.ToUtf8Bytes(), value); + } + + protected ValueTask RawCommandAsync(CancellationToken token, params object[] cmdWithArgs) + { + var byteArgs = new List(); + + foreach (var arg in cmdWithArgs) + { + if (arg == null) + { + byteArgs.Add(TypeConstants.EmptyByteArray); + continue; + } + + if (arg is byte[] bytes) + { + byteArgs.Add(bytes); + } + else if (arg.GetType().IsUserType()) + { + var json = arg.ToJson(); + byteArgs.Add(json.ToUtf8Bytes()); + } + else + { + var str = arg.ToString(); + byteArgs.Add(str.ToUtf8Bytes()); + } + } + + return SendExpectComplexResponseAsync(token, byteArgs.ToArray()); + } + + ValueTask> IRedisNativeClientAsync.InfoAsync(CancellationToken token) + => SendExpectStringAsync(token, Commands.Info).Await(ParseInfoResult); + + ValueTask IRedisNativeClientAsync.ZRangeByLexAsync(string setId, string min, string max, int? skip, int? take, CancellationToken token) + => SendExpectMultiDataAsync(token, GetZRangeByLexArgs(setId, min, max, skip, take)); + + ValueTask IRedisNativeClientAsync.ZLexCountAsync(string setId, string min, string max, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + + return SendExpectLongAsync(token, + Commands.ZLexCount, setId.ToUtf8Bytes(), min.ToUtf8Bytes(), max.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZRemRangeByLexAsync(string setId, string min, string max, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + + return SendExpectLongAsync(token, + Commands.ZRemRangeByLex, setId.ToUtf8Bytes(), min.ToUtf8Bytes(), max.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.CalculateSha1Async(string luaBody, CancellationToken token) + { + AssertNotNull(luaBody, nameof(luaBody)); + + byte[] buffer = Encoding.UTF8.GetBytes(luaBody); + return BitConverter.ToString(buffer.ToSha1Hash()).Replace("-", "").AsValueTaskResult(); + } + + ValueTask IRedisNativeClientAsync.ScriptExistsAsync(byte[][] sha1Refs, CancellationToken token) + { + var keysAndValues = MergeCommandWithArgs(Commands.Script, Commands.Exists, sha1Refs); + return SendExpectMultiDataAsync(token, keysAndValues); + } + + ValueTask IRedisNativeClientAsync.ScriptFlushAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Script, Commands.Flush); + + ValueTask IRedisNativeClientAsync.ScriptKillAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Script, Commands.Kill); + + ValueTask IRedisNativeClientAsync.ScriptLoadAsync(string body, CancellationToken token) + { + AssertNotNull(body, nameof(body)); + + var cmdArgs = MergeCommandWithArgs(Commands.Script, Commands.Load, body.ToUtf8Bytes()); + return SendExpectDataAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.StrLenAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.StrLen, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.LLenAsync(string listId, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectLongAsync(token, Commands.LLen, listId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.SCardAsync(string setId, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.SCard, setId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HLenAsync(string hashId, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + return SendExpectLongAsync(token, Commands.HLen, hashId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.EvalCommandAsync(string luaBody, int numberKeysInArgs, byte[][] keys, CancellationToken token) + { + AssertNotNull(luaBody, nameof(luaBody)); + + var cmdArgs = MergeCommandWithArgs(Commands.Eval, luaBody.ToUtf8Bytes(), keys.PrependInt(numberKeysInArgs)); + return RawCommandAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalShaCommandAsync(string sha1, int numberKeysInArgs, byte[][] keys, CancellationToken token) + { + AssertNotNull(sha1, nameof(sha1)); + + var cmdArgs = MergeCommandWithArgs(Commands.EvalSha, sha1.ToUtf8Bytes(), keys.PrependInt(numberKeysInArgs)); + return RawCommandAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalAsync(string luaBody, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(luaBody, nameof(luaBody)); + + var cmdArgs = MergeCommandWithArgs(Commands.Eval, luaBody.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectMultiDataAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalShaAsync(string sha1, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(sha1, nameof(sha1)); + + var cmdArgs = MergeCommandWithArgs(Commands.EvalSha, sha1.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectMultiDataAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalIntAsync(string luaBody, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(luaBody, nameof(luaBody)); + + var cmdArgs = MergeCommandWithArgs(Commands.Eval, luaBody.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectLongAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalShaIntAsync(string sha1, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(sha1, nameof(sha1)); + + var cmdArgs = MergeCommandWithArgs(Commands.EvalSha, sha1.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectLongAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.EvalStrAsync(string luaBody, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(luaBody, nameof(luaBody)); + + var cmdArgs = MergeCommandWithArgs(Commands.Eval, luaBody.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectDataAsync(token, cmdArgs).FromUtf8BytesAsync(); + } + + ValueTask IRedisNativeClientAsync.EvalShaStrAsync(string sha1, int numberOfKeys, byte[][] keysAndArgs, CancellationToken token) + { + AssertNotNull(sha1, nameof(sha1)); + + var cmdArgs = MergeCommandWithArgs(Commands.EvalSha, sha1.ToUtf8Bytes(), keysAndArgs.PrependInt(numberOfKeys)); + return SendExpectDataAsync(token, cmdArgs).FromUtf8BytesAsync(); + } + + ValueTask IRedisNativeClientAsync.SMembersAsync(string setId, CancellationToken token) + => SendExpectMultiDataAsync(token, Commands.SMembers, setId.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.SAddAsync(string setId, byte[][] values, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + AssertNotNull(values, nameof(values)); + if (values.Length == 0) + throw new ArgumentException(nameof(values)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.SAdd, setId.ToUtf8Bytes(), values); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SRemAsync(string setId, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectLongAsync(token, Commands.SRem, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.IncrByAsync(string key, long count, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.IncrBy, key.ToUtf8Bytes(), count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.IncrByFloatAsync(string key, double incrBy, CancellationToken token) + { + AssertNotNull(key); + return SendExpectDoubleAsync(token, Commands.IncrByFloat, key.ToUtf8Bytes(), incrBy.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.IncrAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Incr, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.DecrAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Decr, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.DecrByAsync(string key, long count, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.DecrBy, key.ToUtf8Bytes(), count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ConfigGetAsync(string pattern, CancellationToken token) + => SendExpectMultiDataAsync(token, Commands.Config, Commands.Get, pattern.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.ConfigSetAsync(string item, byte[] value, CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Config, Commands.Set, item.ToUtf8Bytes(), value); + + ValueTask IRedisNativeClientAsync.ConfigResetStatAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Config, Commands.ResetStat); + + ValueTask IRedisNativeClientAsync.ConfigRewriteAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Config, Commands.Rewrite); + + ValueTask IRedisNativeClientAsync.DebugSegfaultAsync(CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Debug, Commands.Segfault); + + ValueTask IRedisNativeClientAsync.DumpAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectDataAsync(token, Commands.Dump, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.RestoreAsync(string key, long expireMs, byte[] dumpValue, CancellationToken token) + { + AssertNotNull(key); + return SendExpectDataAsync(token, Commands.Restore, key.ToUtf8Bytes(), expireMs.ToUtf8Bytes(), dumpValue); + } + + ValueTask IRedisNativeClientAsync.MigrateAsync(string host, int port, string key, int destinationDb, long timeoutMs, CancellationToken token) + { + AssertNotNull(key); + return SendExpectSuccessAsync(token, Commands.Migrate, host.ToUtf8Bytes(), port.ToUtf8Bytes(), key.ToUtf8Bytes(), destinationDb.ToUtf8Bytes(), timeoutMs.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.MoveAsync(string key, int db, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Move, key.ToUtf8Bytes(), db.ToUtf8Bytes()).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.ObjectIdleTimeAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.Object, Commands.IdleTime, key.ToUtf8Bytes()); + } + + async ValueTask IRedisNativeClientAsync.RoleAsync(CancellationToken token) + => (await SendExpectComplexResponseAsync(token, Commands.Role).ConfigureAwait(false)).ToRedisText(); + + ValueTask IRedisNativeClientAsync.RawCommandAsync(object[] cmdWithArgs, CancellationToken token) + => SendExpectComplexResponseAsync(token, PrepareRawCommand(cmdWithArgs)); + + ValueTask IRedisNativeClientAsync.RawCommandAsync(byte[][] cmdWithBinaryArgs, CancellationToken token) + => SendExpectComplexResponseAsync(token, cmdWithBinaryArgs); + + ValueTask IRedisNativeClientAsync.ClientGetNameAsync(CancellationToken token) + => SendExpectStringAsync(token, Commands.Client, Commands.GetName); + + ValueTask IRedisNativeClientAsync.ClientSetNameAsync(string name, CancellationToken token) + { + ClientValidateName(name); + return SendExpectSuccessAsync(token, Commands.Client, Commands.SetName, name.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ClientKillAsync(string clientAddr, CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Client, Commands.Kill, clientAddr.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.ClientKillAsync(string addr, string id, string type, string skipMe, CancellationToken token) + => SendExpectLongAsync(token, ClientKillPrepareArgs(addr, id, type, skipMe)); + + ValueTask IRedisNativeClientAsync.ClientListAsync(CancellationToken token) + => SendExpectDataAsync(token, Commands.Client, Commands.List); + + ValueTask IRedisNativeClientAsync.ClientPauseAsync(int timeOutMs, CancellationToken token) + => SendExpectSuccessAsync(token, Commands.Client, Commands.Pause, timeOutMs.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.MSetNxAsync(byte[][] keys, byte[][] values, CancellationToken token) + { + var keysAndValues = MergeCommandWithKeysAndValues(Commands.MSet, keys, values); + return SendExpectLongAsync(token, keysAndValues).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.MSetNxAsync(string[] keys, byte[][] values, CancellationToken token) + => AsAsync().MSetNxAsync(keys.ToMultiByteArray(), values, token); + + ValueTask IRedisNativeClientAsync.GetSetAsync(string key, byte[] value, CancellationToken token) + { + GetSetAssertArgs(key, ref value); + return SendExpectDataAsync(token, Commands.GetSet, key.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.MGetAsync(byte[][] keys, CancellationToken token) + => SendExpectMultiDataAsync(token, MGetPrepareArgs(keys)); + + ValueTask IRedisNativeClientAsync.SScanAsync(string setId, ulong cursor, int count, string match, CancellationToken token) + { + if (match == null) + { + return SendExpectScanResultAsync(token, Commands.SScan, + setId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + return SendExpectScanResultAsync(token, Commands.SScan, + setId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Match, match.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZScanAsync(string setId, ulong cursor, int count, string match, CancellationToken token) + { + if (match == null) + { + return SendExpectScanResultAsync(token, Commands.ZScan, + setId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + return SendExpectScanResultAsync(token, Commands.ZScan, + setId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Match, match.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HScanAsync(string hashId, ulong cursor, int count, string match, CancellationToken token) + { + if (match == null) + { + return SendExpectScanResultAsync(token, Commands.HScan, + hashId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + return SendExpectScanResultAsync(token, Commands.HScan, + hashId.ToUtf8Bytes(), cursor.ToUtf8Bytes(), + Commands.Match, match.ToUtf8Bytes(), + Commands.Count, count.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.PfAddAsync(string key, byte[][] elements, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.PfAdd, key.ToUtf8Bytes(), elements); + return SendExpectLongAsync(token, cmdWithArgs).IsSuccessAsync(); + } + + ValueTask IRedisNativeClientAsync.PfCountAsync(string key, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.PfCount, key.ToUtf8Bytes()); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.PfMergeAsync(string toKeyId, string[] fromKeys, CancellationToken token) + { + var fromKeyBytes = fromKeys.Map(x => x.ToUtf8Bytes()).ToArray(); + var cmdWithArgs = MergeCommandWithArgs(Commands.PfMerge, toKeyId.ToUtf8Bytes(), fromKeyBytes); + return SendExpectSuccessAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SortAsync(string listOrSetId, SortOptions sortOptions, CancellationToken token) + => SendExpectMultiDataAsync(token, SortPrepareArgs(listOrSetId, sortOptions)); + + ValueTask IRedisNativeClientAsync.LRangeAsync(string listId, int startingFrom, int endingAt, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectMultiDataAsync(token, Commands.LRange, listId.ToUtf8Bytes(), startingFrom.ToUtf8Bytes(), endingAt.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.RPushXAsync(string listId, byte[] value, CancellationToken token) + { + AssertListIdAndValue(listId, value); + return SendExpectLongAsync(token, Commands.RPush, listId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.LPushAsync(string listId, byte[] value, CancellationToken token) + { + AssertListIdAndValue(listId, value); + return SendExpectLongAsync(token, Commands.LPush, listId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.LPushXAsync(string listId, byte[] value, CancellationToken token) + { + AssertListIdAndValue(listId, value); + return SendExpectLongAsync(token, Commands.LPushX, listId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.LTrimAsync(string listId, int keepStartingFrom, int keepEndingAt, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectSuccessAsync(token, Commands.LTrim, listId.ToUtf8Bytes(), keepStartingFrom.ToUtf8Bytes(), keepEndingAt.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.LRemAsync(string listId, int removeNoOfMatches, byte[] value, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectLongAsync(token, Commands.LRem, listId.ToUtf8Bytes(), removeNoOfMatches.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.LIndexAsync(string listId, int listIndex, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectDataAsync(token, Commands.LIndex, listId.ToUtf8Bytes(), listIndex.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.LInsertAsync(string listId, bool insertBefore, byte[] pivot, byte[] value, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + var position = insertBefore ? Commands.Before : Commands.After; + return SendExpectSuccessAsync(token, Commands.LInsert, listId.ToUtf8Bytes(), position, pivot, value); + } + + ValueTask IRedisNativeClientAsync.LSetAsync(string listId, int listIndex, byte[] value, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectSuccessAsync(token, Commands.LSet, listId.ToUtf8Bytes(), listIndex.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.LPopAsync(string listId, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectDataAsync(token, Commands.LPop, listId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.RPopAsync(string listId, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectDataAsync(token, Commands.RPop, listId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.BLPopAsync(string listId, int timeOutSecs, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectMultiDataAsync(token, Commands.BLPop, listId.ToUtf8Bytes(), timeOutSecs.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.BLPopAsync(string[] listIds, int timeOutSecs, CancellationToken token) + { + AssertNotNull(listIds, nameof(listIds)); + var args = new List { Commands.BLPop }; + args.AddRange(listIds.Select(listId => listId.ToUtf8Bytes())); + args.Add(timeOutSecs.ToUtf8Bytes()); + return SendExpectMultiDataAsync(token, args.ToArray()); + } + + async ValueTask IRedisNativeClientAsync.BLPopValueAsync(string listId, int timeOutSecs, CancellationToken token) + { + var blockingResponse = await AsAsync().BLPopAsync(new[] { listId }, timeOutSecs, token).ConfigureAwait(false); + return blockingResponse.Length == 0 + ? null + : blockingResponse[1]; + } + + async ValueTask IRedisNativeClientAsync.BLPopValueAsync(string[] listIds, int timeOutSecs, CancellationToken token) + { + var blockingResponse = await AsAsync().BLPopAsync(listIds, timeOutSecs, token).ConfigureAwait(false); + return blockingResponse.Length == 0 + ? null + : blockingResponse; + } + + ValueTask IRedisNativeClientAsync.BRPopAsync(string listId, int timeOutSecs, CancellationToken token) + { + AssertNotNull(listId, nameof(listId)); + return SendExpectMultiDataAsync(token, Commands.BRPop, listId.ToUtf8Bytes(), timeOutSecs.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.BRPopAsync(string[] listIds, int timeOutSecs, CancellationToken token) + { + AssertNotNull(listIds, nameof(listIds)); + var args = new List { Commands.BRPop }; + args.AddRange(listIds.Select(listId => listId.ToUtf8Bytes())); + args.Add(timeOutSecs.ToUtf8Bytes()); + return SendExpectMultiDataAsync(token, args.ToArray()); + } + + ValueTask IRedisNativeClientAsync.RPopLPushAsync(string fromListId, string toListId, CancellationToken token) + { + AssertNotNull(fromListId, nameof(fromListId)); + AssertNotNull(toListId, nameof(toListId)); + return SendExpectDataAsync(token, Commands.RPopLPush, fromListId.ToUtf8Bytes(), toListId.ToUtf8Bytes()); + } + + async ValueTask IRedisNativeClientAsync.BRPopValueAsync(string listId, int timeOutSecs, CancellationToken token) + { + var blockingResponse = await AsAsync().BRPopAsync(new[] { listId }, timeOutSecs, token).ConfigureAwait(false); + return blockingResponse.Length == 0 + ? null + : blockingResponse[1]; + } + + async ValueTask IRedisNativeClientAsync.BRPopValueAsync(string[] listIds, int timeOutSecs, CancellationToken token) + { + var blockingResponse = await AsAsync().BRPopAsync(listIds, timeOutSecs, token).ConfigureAwait(false); + return blockingResponse.Length == 0 + ? null + : blockingResponse; + } + + async ValueTask IRedisNativeClientAsync.BRPopLPushAsync(string fromListId, string toListId, int timeOutSecs, CancellationToken token) + { + AssertNotNull(fromListId, nameof(fromListId)); + AssertNotNull(toListId, nameof(toListId)); + byte[][] result = await SendExpectMultiDataAsync(token, Commands.BRPopLPush, fromListId.ToUtf8Bytes(), toListId.ToUtf8Bytes(), timeOutSecs.ToUtf8Bytes()); + return result.Length == 0 ? null : result[1]; + } + + ValueTask IRedisNativeClientAsync.SMoveAsync(string fromSetId, string toSetId, byte[] value, CancellationToken token) + { + AssertNotNull(fromSetId, nameof(fromSetId)); + AssertNotNull(toSetId, nameof(toSetId)); + return SendExpectSuccessAsync(token, Commands.SMove, fromSetId.ToUtf8Bytes(), toSetId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.SIsMemberAsync(string setId, byte[] value, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.SIsMember, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.SInterAsync(string[] setIds, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.SInter, setIds); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SInterStoreAsync(string intoSetId, string[] setIds, CancellationToken token) + { + var setIdsList = new List(setIds); + setIdsList.Insert(0, intoSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.SInterStore, setIdsList.ToArray()); + return SendExpectSuccessAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SUnionAsync(string[] setIds, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.SUnion, setIds); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SUnionStoreAsync(string intoSetId, string[] setIds, CancellationToken token) + { + var setIdsList = new List(setIds); + setIdsList.Insert(0, intoSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.SUnionStore, setIdsList.ToArray()); + return SendExpectSuccessAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SDiffAsync(string fromSetId, string[] withSetIds, CancellationToken token) + { + var setIdsList = new List(withSetIds); + setIdsList.Insert(0, fromSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.SDiff, setIdsList.ToArray()); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SDiffStoreAsync(string intoSetId, string fromSetId, string[] withSetIds, CancellationToken token) + { + var setIdsList = new List(withSetIds); + setIdsList.Insert(0, fromSetId); + setIdsList.Insert(0, intoSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.SDiffStore, setIdsList.ToArray()); + return SendExpectSuccessAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.SRandMemberAsync(string setId, CancellationToken token) + => SendExpectDataAsync(token, Commands.SRandMember, setId.ToUtf8Bytes()); + + ValueTask IRedisNativeClientAsync.ZRemAsync(string setId, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectLongAsync(token, Commands.ZRem, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZRemAsync(string setId, byte[][] values, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + AssertNotNull(values, nameof(values)); + if (values.Length == 0) + throw new ArgumentException("values"); + + var cmdWithArgs = MergeCommandWithArgs(Commands.ZRem, setId.ToUtf8Bytes(), values); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.ZIncrByAsync(string setId, double incrBy, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectDoubleAsync(token, Commands.ZIncrBy, setId.ToUtf8Bytes(), incrBy.ToFastUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZIncrByAsync(string setId, long incrBy, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectDoubleAsync(token, Commands.ZIncrBy, setId.ToUtf8Bytes(), incrBy.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZRankAsync(string setId, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectLongAsync(token, Commands.ZRank, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZRevRankAsync(string setId, byte[] value, CancellationToken token) + { + AssertSetIdAndValue(setId, value); + return SendExpectLongAsync(token, Commands.ZRevRank, setId.ToUtf8Bytes(), value); + } + + ValueTask IRedisNativeClientAsync.ZRangeAsync(string setId, int min, int max, CancellationToken token) + => SendExpectMultiDataAsync(token, Commands.ZRange, setId.ToUtf8Bytes(), min.ToUtf8Bytes(), max.ToUtf8Bytes()); + + private ValueTask GetRangeAsync(byte[] commandBytes, string setId, int min, int max, bool withScores, CancellationToken token) + { + var args = GetRangeArgs(commandBytes, setId, min, max, withScores); + return SendExpectMultiDataAsync(token, args); + } + + ValueTask IRedisNativeClientAsync.ZRangeWithScoresAsync(string setId, int min, int max, CancellationToken token) + => GetRangeAsync(Commands.ZRange, setId, min, max, true, token); + + ValueTask IRedisNativeClientAsync.ZRevRangeAsync(string setId, int min, int max, CancellationToken token) + => GetRangeAsync(Commands.ZRevRange, setId, min, max, false, token); + + ValueTask IRedisNativeClientAsync.ZRevRangeWithScoresAsync(string setId, int min, int max, CancellationToken token) + => GetRangeAsync(Commands.ZRevRange, setId, min, max, true, token); + + private ValueTask GetRangeByScoreAsync(byte[] commandBytes, + string setId, double min, double max, int? skip, int? take, bool withScores, CancellationToken token) + { + var args = GetRangeByScoreArgs(commandBytes, setId, min, max, skip, take, withScores); + return SendExpectMultiDataAsync(token, args); + } + + ValueTask IRedisNativeClientAsync.ZRangeByScoreAsync(string setId, double min, double max, int? skip, int? take, CancellationToken token) + => GetRangeByScoreAsync(Commands.ZRangeByScore, setId, min, max, skip, take, false, token); + + ValueTask IRedisNativeClientAsync.ZRangeByScoreAsync(string setId, long min, long max, int? skip, int? take, CancellationToken token) + => GetRangeByScoreAsync(Commands.ZRangeByScore, setId, min, max, skip, take, false, token); + + ValueTask IRedisNativeClientAsync.ZRangeByScoreWithScoresAsync(string setId, double min, double max, int? skip, int? take, CancellationToken token) + => GetRangeByScoreAsync(Commands.ZRangeByScore, setId, min, max, skip, take, true, token); + + ValueTask IRedisNativeClientAsync.ZRangeByScoreWithScoresAsync(string setId, long min, long max, int? skip, int? take, CancellationToken token) + => GetRangeByScoreAsync(Commands.ZRangeByScore, setId, min, max, skip, take, true, token); + + ValueTask IRedisNativeClientAsync.ZRevRangeByScoreAsync(string setId, double min, double max, int? skip, int? take, CancellationToken token) + { + //Note: http://redis.io/commands/zrevrangebyscore has max, min in the wrong other + return GetRangeByScoreAsync(Commands.ZRevRangeByScore, setId, max, min, skip, take, false, token); + } + + ValueTask IRedisNativeClientAsync.ZRevRangeByScoreAsync(string setId, long min, long max, int? skip, int? take, CancellationToken token) + { + //Note: http://redis.io/commands/zrevrangebyscore has max, min in the wrong other + return GetRangeByScoreAsync(Commands.ZRevRangeByScore, setId, max, min, skip, take, false, token); + } + + ValueTask IRedisNativeClientAsync.ZRevRangeByScoreWithScoresAsync(string setId, double min, double max, int? skip, int? take, CancellationToken token) + { + //Note: http://redis.io/commands/zrevrangebyscore has max, min in the wrong other + return GetRangeByScoreAsync(Commands.ZRevRangeByScore, setId, max, min, skip, take, true, token); + } + + ValueTask IRedisNativeClientAsync.ZRevRangeByScoreWithScoresAsync(string setId, long min, long max, int? skip, int? take, CancellationToken token) + { + //Note: http://redis.io/commands/zrevrangebyscore has max, min in the wrong other + return GetRangeByScoreAsync(Commands.ZRevRangeByScore, setId, max, min, skip, take, true, token); + } + + ValueTask IRedisNativeClientAsync.ZRemRangeByRankAsync(string setId, int min, int max, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.ZRemRangeByRank, setId.ToUtf8Bytes(), + min.ToUtf8Bytes(), max.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZRemRangeByScoreAsync(string setId, double fromScore, double toScore, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.ZRemRangeByScore, setId.ToUtf8Bytes(), + fromScore.ToFastUtf8Bytes(), toScore.ToFastUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZRemRangeByScoreAsync(string setId, long fromScore, long toScore, CancellationToken token) + { + AssertNotNull(setId, nameof(setId)); + return SendExpectLongAsync(token, Commands.ZRemRangeByScore, setId.ToUtf8Bytes(), + fromScore.ToUtf8Bytes(), toScore.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.ZUnionStoreAsync(string intoSetId, string[] setIds, CancellationToken token) + { + var setIdsList = new List(setIds); + setIdsList.Insert(0, setIds.Length.ToString()); + setIdsList.Insert(0, intoSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.ZUnionStore, setIdsList.ToArray()); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.ZInterStoreAsync(string intoSetId, string[] setIds, CancellationToken token) + { + var setIdsList = new List(setIds); + setIdsList.Insert(0, setIds.Length.ToString()); + setIdsList.Insert(0, intoSetId); + + var cmdWithArgs = MergeCommandWithArgs(Commands.ZInterStore, setIdsList.ToArray()); + return SendExpectLongAsync(token, cmdWithArgs); + } + + internal ValueTask ZInterStoreAsync(string intoSetId, string[] setIds, string[] args, CancellationToken token) + { + var totalArgs = new List(setIds); + totalArgs.Insert(0, setIds.Length.ToString()); + totalArgs.Insert(0, intoSetId); + totalArgs.AddRange(args); + + var cmdWithArgs = MergeCommandWithArgs(Commands.ZInterStore, totalArgs.ToArray()); + return SendExpectLongAsync(token, cmdWithArgs); + } + + internal ValueTask ZUnionStoreAsync(string intoSetId, string[] setIds, string[] args, CancellationToken token) + { + var totalArgs = new List(setIds); + totalArgs.Insert(0, setIds.Length.ToString()); + totalArgs.Insert(0, intoSetId); + totalArgs.AddRange(args); + + var cmdWithArgs = MergeCommandWithArgs(Commands.ZUnionStore, totalArgs.ToArray()); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.HMSetAsync(string hashId, byte[][] keys, byte[][] values, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + var cmdArgs = MergeCommandWithKeysAndValues(Commands.HMSet, hashId.ToUtf8Bytes(), keys, values); + return SendExpectSuccessAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.HSetNXAsync(string hashId, byte[] key, byte[] value, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectLongAsync(token, Commands.HSetNx, hashId.ToUtf8Bytes(), key, value); + } + + ValueTask IRedisNativeClientAsync.HIncrbyAsync(string hashId, byte[] key, int incrementBy, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectLongAsync(token, Commands.HIncrBy, hashId.ToUtf8Bytes(), key, incrementBy.ToString().ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HIncrbyFloatAsync(string hashId, byte[] key, double incrementBy, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectDoubleAsync(token, Commands.HIncrByFloat, hashId.ToUtf8Bytes(), key, incrementBy.ToString(CultureInfo.InvariantCulture).ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HGetAsync(string hashId, byte[] key, CancellationToken token) + => HGetAsync(hashId.ToUtf8Bytes(), key, token); + + private ValueTask HGetAsync(byte[] hashId, byte[] key, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectDataAsync(token, Commands.HGet, hashId, key); + } + + ValueTask IRedisNativeClientAsync.HMGetAsync(string hashId, byte[][] keys, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + if (keys.Length == 0) + throw new ArgumentNullException(nameof(keys)); + + var cmdArgs = MergeCommandWithArgs(Commands.HMGet, hashId.ToUtf8Bytes(), keys); + return SendExpectMultiDataAsync(token, cmdArgs); + } + + ValueTask IRedisNativeClientAsync.HDelAsync(string hashId, byte[] key, CancellationToken token) + => HDelAsync(hashId.ToUtf8Bytes(), key, token); + + private ValueTask HDelAsync(byte[] hashId, byte[] key, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectLongAsync(token, Commands.HDel, hashId, key); + } + + ValueTask IRedisNativeClientAsync.HExistsAsync(string hashId, byte[] key, CancellationToken token) + { + AssertHashIdAndKey(hashId, key); + return SendExpectLongAsync(token, Commands.HExists, hashId.ToUtf8Bytes(), key); + } + + ValueTask IRedisNativeClientAsync.HKeysAsync(string hashId, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + return SendExpectMultiDataAsync(token, Commands.HKeys, hashId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HValsAsync(string hashId, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + return SendExpectMultiDataAsync(token, Commands.HVals, hashId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.HGetAllAsync(string hashId, CancellationToken token) + { + AssertNotNull(hashId, nameof(hashId)); + return SendExpectMultiDataAsync(token, Commands.HGetAll, hashId.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.GeoAddAsync(string key, double longitude, double latitude, string member, CancellationToken token) + { + AssertNotNull(key, nameof(key)); + AssertNotNull(member, nameof(member)); + return SendExpectLongAsync(token, Commands.GeoAdd, key.ToUtf8Bytes(), longitude.ToUtf8Bytes(), latitude.ToUtf8Bytes(), member.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.GeoAddAsync(string key, RedisGeo[] geoPoints, CancellationToken token) + { + var cmdWithArgs = GeoAddPrepareArgs(key, geoPoints); + return SendExpectLongAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.GeoDistAsync(string key, string fromMember, string toMember, string unit, CancellationToken token) + { + AssertNotNull(key, nameof(key)); + + return unit == null + ? SendExpectDoubleAsync(token, Commands.GeoDist, key.ToUtf8Bytes(), fromMember.ToUtf8Bytes(), toMember.ToUtf8Bytes()) + : SendExpectDoubleAsync(token, Commands.GeoDist, key.ToUtf8Bytes(), fromMember.ToUtf8Bytes(), toMember.ToUtf8Bytes(), unit.ToUtf8Bytes()); + } + + async ValueTask IRedisNativeClientAsync.GeoHashAsync(string key, string[] members, CancellationToken token) + { + AssertNotNull(key, nameof(key)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.GeoHash, key.ToUtf8Bytes(), members.Map(x => x.ToUtf8Bytes()).ToArray()); + var result = await SendExpectMultiDataAsync(token, cmdWithArgs).ConfigureAwait(false); + return result.ToStringArray(); + } + + async ValueTask> IRedisNativeClientAsync.GeoPosAsync(string key, string[] members, CancellationToken token) + { + AssertNotNull(key, nameof(key)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.GeoPos, key.ToUtf8Bytes(), members.Map(x => x.ToUtf8Bytes()).ToArray()); + var data = await SendExpectComplexResponseAsync(token, cmdWithArgs).ConfigureAwait(false); + return GeoPosParseResult(members, data); + } + + async ValueTask> IRedisNativeClientAsync.GeoRadiusAsync(string key, double longitude, double latitude, double radius, string unit, bool withCoords, bool withDist, bool withHash, int? count, bool? asc, CancellationToken token) + { + var cmdWithArgs = GeoRadiusPrepareArgs(key, longitude, latitude, radius, unit, + withCoords, withDist, withHash, count, asc); + + var to = new List(); + + if (!(withCoords || withDist || withHash)) + { + var members = (await SendExpectMultiDataAsync(token, cmdWithArgs).ConfigureAwait(false)).ToStringArray(); + foreach (var member in members) + { + to.Add(new RedisGeoResult { Member = member }); + } + } + else + { + var data = await SendExpectComplexResponseAsync(token, cmdWithArgs).ConfigureAwait(false); + GetRadiusParseResult(unit, withCoords, withDist, withHash, to, data); + } + + return to; + } + + async ValueTask> IRedisNativeClientAsync.GeoRadiusByMemberAsync(string key, string member, double radius, string unit, bool withCoords, bool withDist, bool withHash, int? count, bool? asc, CancellationToken token) + { + var cmdWithArgs = GeoRadiusByMemberPrepareArgs(key, member, radius, unit, withCoords, withDist, withHash, count, asc); + + var to = new List(); + + if (!(withCoords || withDist || withHash)) + { + var members = (await SendExpectMultiDataAsync(token, cmdWithArgs).ConfigureAwait(false)).ToStringArray(); + foreach (var x in members) + { + to.Add(new RedisGeoResult { Member = x }); + } + } + else + { + var data = await SendExpectComplexResponseAsync(token, cmdWithArgs).ConfigureAwait(false); + GeoRadiusByMemberParseResult(unit, withCoords, withDist, withHash, to, data); + } + + return to; + } + + ValueTask IRedisNativeClientAsync.PublishAsync(string toChannel, byte[] message, CancellationToken token) + => SendExpectLongAsync(token, Commands.Publish, toChannel.ToUtf8Bytes(), message); + + ValueTask IRedisNativeClientAsync.SubscribeAsync(string[] toChannels, CancellationToken token) + { + if (toChannels.Length == 0) + throw new ArgumentNullException(nameof(toChannels)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.Subscribe, toChannels); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.UnSubscribeAsync(string[] fromChannels, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.UnSubscribe, fromChannels); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.PSubscribeAsync(string[] toChannelsMatchingPatterns, CancellationToken token) + { + if (toChannelsMatchingPatterns.Length == 0) + throw new ArgumentNullException(nameof(toChannelsMatchingPatterns)); + + var cmdWithArgs = MergeCommandWithArgs(Commands.PSubscribe, toChannelsMatchingPatterns); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.PUnSubscribeAsync(string[] fromChannelsMatchingPatterns, CancellationToken token) + { + var cmdWithArgs = MergeCommandWithArgs(Commands.PUnSubscribe, fromChannelsMatchingPatterns); + return SendExpectMultiDataAsync(token, cmdWithArgs); + } + + ValueTask IRedisNativeClientAsync.ReceiveMessagesAsync(CancellationToken token) + => ReadMultiDataAsync(token); + + ValueTask IRedisNativeClientAsync.CreateSubscriptionAsync(CancellationToken token) + => new RedisSubscription(this).AsValueTaskResult(); + + ValueTask IRedisNativeClientAsync.BitCountAsync(string key, CancellationToken token) + { + AssertNotNull(key); + return SendExpectLongAsync(token, Commands.BitCount, key.ToUtf8Bytes()); + } + + ValueTask IRedisNativeClientAsync.DelAsync(params string[] keys) + => AsAsync().DelAsync(keys, default); + + ValueTask IRedisNativeClientAsync.SInterStoreAsync(string intoSetId, params string[] setIds) + => AsAsync().SInterStoreAsync(intoSetId, setIds, default); + + ValueTask IRedisNativeClientAsync.SUnionAsync(params string[] setIds) + => AsAsync().SUnionAsync(setIds, default); + + ValueTask IRedisNativeClientAsync.WatchAsync(params string[] keys) + => AsAsync().WatchAsync(keys, default); + + ValueTask IRedisNativeClientAsync.SubscribeAsync(params string[] toChannels) + => AsAsync().SubscribeAsync(toChannels, default); + + ValueTask IRedisNativeClientAsync.UnSubscribeAsync(params string[] toChannels) + => AsAsync().UnSubscribeAsync(toChannels, default); + + ValueTask IRedisNativeClientAsync.PSubscribeAsync(params string[] toChannelsMatchingPatterns) + => AsAsync().PSubscribeAsync(toChannelsMatchingPatterns, default); + + ValueTask IRedisNativeClientAsync.PUnSubscribeAsync(params string[] toChannelsMatchingPatterns) + => AsAsync().PUnSubscribeAsync(toChannelsMatchingPatterns, default); + + ValueTask IRedisNativeClientAsync.SInterAsync(params string[] setIds) + => AsAsync().SInterAsync(setIds, default); + + ValueTask IRedisNativeClientAsync.SDiffAsync(string fromSetId, params string[] withSetIds) + => AsAsync().SDiffAsync(fromSetId, withSetIds, default); + + ValueTask IRedisNativeClientAsync.SDiffStoreAsync(string intoSetId, string fromSetId, params string[] withSetIds) + => AsAsync().SDiffStoreAsync(intoSetId, fromSetId, withSetIds, default); + + ValueTask IRedisNativeClientAsync.ZUnionStoreAsync(string intoSetId, params string[] setIds) + => AsAsync().ZUnionStoreAsync(intoSetId, setIds, default); + + ValueTask IRedisNativeClientAsync.ZInterStoreAsync(string intoSetId, params string[] setIds) + => AsAsync().ZInterStoreAsync(intoSetId, setIds, default); + + ValueTask IRedisNativeClientAsync.EvalCommandAsync(string luaBody, int numberKeysInArgs, params byte[][] keys) + => AsAsync().EvalCommandAsync(luaBody, numberKeysInArgs, keys, default); + + ValueTask IRedisNativeClientAsync.EvalShaCommandAsync(string sha1, int numberKeysInArgs, params byte[][] keys) + => AsAsync().EvalShaCommandAsync(sha1, numberKeysInArgs, keys, default); + + ValueTask IRedisNativeClientAsync.EvalAsync(string luaBody, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalAsync(luaBody, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.EvalShaAsync(string sha1, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalShaAsync(sha1, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.EvalIntAsync(string luaBody, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalIntAsync(luaBody, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.EvalShaIntAsync(string sha1, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalShaIntAsync(sha1, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.EvalStrAsync(string luaBody, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalStrAsync(luaBody, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.EvalShaStrAsync(string sha1, int numberOfKeys, params byte[][] keysAndArgs) + => AsAsync().EvalShaStrAsync(sha1, numberOfKeys, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.RawCommandAsync(params object[] cmdWithArgs) + => AsAsync().RawCommandAsync(cmdWithArgs, default); + + ValueTask IRedisNativeClientAsync.RawCommandAsync(params byte[][] cmdWithBinaryArgs) + => AsAsync().RawCommandAsync(cmdWithBinaryArgs, default); + + ValueTask IRedisNativeClientAsync.MGetAsync(params string[] keys) + => AsAsync().MGetAsync(keys, default); + + ValueTask IRedisNativeClientAsync.PfAddAsync(string key, params byte[][] elements) + => AsAsync().PfAddAsync(key, elements, default); + + ValueTask IRedisNativeClientAsync.HMGetAsync(string hashId, params byte[][] keysAndArgs) + => AsAsync().HMGetAsync(hashId, keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.MGetAsync(params byte[][] keysAndArgs) + => AsAsync().MGetAsync(keysAndArgs, default); + + ValueTask IRedisNativeClientAsync.SUnionStoreAsync(string intoSetId, params string[] setIds) + => AsAsync().SUnionStoreAsync(intoSetId, setIds, default); + + ValueTask IRedisNativeClientAsync.ScriptExistsAsync(params byte[][] sha1Refs) + => AsAsync().ScriptExistsAsync(sha1Refs, default); + + ValueTask IRedisNativeClientAsync.PfMergeAsync(string toKeyId, params string[] fromKeys) + => AsAsync().PfMergeAsync(toKeyId, fromKeys, default); + + ValueTask IRedisNativeClientAsync.GeoAddAsync(string key, params RedisGeo[] geoPoints) + => AsAsync().GeoAddAsync(key, geoPoints, default); + + ValueTask IRedisNativeClientAsync.GeoHashAsync(string key, params string[] members) + => AsAsync().GeoHashAsync(key, members, default); + + ValueTask> IRedisNativeClientAsync.GeoPosAsync(string key, params string[] members) + => AsAsync().GeoPosAsync(key, members, default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisNativeClient.cs b/src/ServiceStack.Redis/RedisNativeClient.cs index 1d8787e9..220f0405 100644 --- a/src/ServiceStack.Redis/RedisNativeClient.cs +++ b/src/ServiceStack.Redis/RedisNativeClient.cs @@ -21,6 +21,7 @@ using ServiceStack.Logging; using ServiceStack.Redis.Pipeline; using ServiceStack.Text; +using System.Security.Authentication; namespace ServiceStack.Redis { @@ -63,9 +64,12 @@ public DateTime? DeactivatedAt public bool HadExceptions => deactivatedAtTicks > 0; protected Socket socket; + [Obsolete("The direct stream is no longer directly available", true)] // API BREAKING CHANGE since exposed protected BufferedStream Bstream; protected SslStream sslStream; + private BufferedReader bufferedReader; + private IRedisTransactionBase transaction; private IRedisPipelineShared pipeline; @@ -79,7 +83,7 @@ public DateTime? DeactivatedAt internal bool Active { get => Interlocked.CompareExchange(ref active, 0, 0) == YES; - set => Interlocked.Exchange(ref active, value ? YES : NO); + private set => Interlocked.Exchange(ref active, value ? YES : NO); } internal IHandleClientDispose ClientManager { get; set; } @@ -91,6 +95,7 @@ internal bool Active public string Host { get; private set; } public int Port { get; private set; } public bool Ssl { get; private set; } + public SslProtocols? SslProtocols { get; private set; } /// /// Gets or sets object key prefix. @@ -177,8 +182,10 @@ private void Init(RedisEndpoint config) Client = config.Client; Db = config.Db; Ssl = config.Ssl; + SslProtocols = config.SslProtocols; IdleTimeOutSecs = config.IdleTimeOutSecs; ServerVersionNumber = RedisConfig.AssumeServerVersion.GetValueOrDefault(); + LogPrefix = "#" + ClientId + " "; JsConfig.InitStatics(); } @@ -203,6 +210,7 @@ public long Db } } + public void ChangeDb(long db) { this.db = db; @@ -222,22 +230,23 @@ public DateTime LastSave public Dictionary Info { - get - { - var lines = SendExpectString(Commands.Info); - var info = new Dictionary(); + get => ParseInfoResult(SendExpectString(Commands.Info)); + } - foreach (var line in lines - .Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) - { - var p = line.IndexOf(':'); - if (p == -1) continue; + private static Dictionary ParseInfoResult(string lines) + { + var info = new Dictionary(); - info.Add(line.Substring(0, p), line.Substring(p + 1)); - } + foreach (var line in lines + .Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) + { + var p = line.IndexOf(':'); + if (p == -1) continue; - return info; + info[line.Substring(0, p)] = line.Substring(p + 1); } + + return info; } public string ServerVersion @@ -250,6 +259,11 @@ public string ServerVersion } public RedisData RawCommand(params object[] cmdWithArgs) + { + return SendExpectComplexResponse(PrepareRawCommand(cmdWithArgs)); + } + + private static byte[][] PrepareRawCommand(object[] cmdWithArgs) { var byteArgs = new List(); @@ -276,9 +290,7 @@ public RedisData RawCommand(params object[] cmdWithArgs) byteArgs.Add(str.ToUtf8Bytes()); } } - - var data = SendExpectComplexResponse(byteArgs.ToArray()); - return data; + return byteArgs.ToArray(); } public RedisData RawCommand(params byte[][] cmdWithBinaryArgs) @@ -390,8 +402,10 @@ public string Type(string key) } public RedisKeyType GetEntryType(string key) + => ParseEntryType(Type(key)); + + private protected RedisKeyType ParseEntryType(string type) { - var type = Type(key); switch (type) { case "none": @@ -569,6 +583,12 @@ public byte[] GetBytes(string key) } public byte[] GetSet(string key, byte[] value) + { + GetSetAssertArgs(key, ref value); + return SendExpectData(Commands.GetSet, key.ToUtf8Bytes(), value); + } + + private static void GetSetAssertArgs(string key, ref byte[] value) { if (key == null) throw new ArgumentNullException("key"); @@ -577,8 +597,6 @@ public byte[] GetSet(string key, byte[] value) if (value.Length > OneGb) throw new ArgumentException("value exceeds 1G", "value"); - - return SendExpectData(Commands.GetSet, key.ToUtf8Bytes(), value); } public long Exists(string key) @@ -716,21 +734,21 @@ public string RandomKey() public void Rename(string oldKeyname, string newKeyname) { - if (oldKeyname == null) - throw new ArgumentNullException("oldKeyname"); - if (newKeyname == null) - throw new ArgumentNullException("newKeyname"); - + CheckRenameKeys(oldKeyname, newKeyname); SendExpectSuccess(Commands.Rename, oldKeyname.ToUtf8Bytes(), newKeyname.ToUtf8Bytes()); } - public bool RenameNx(string oldKeyname, string newKeyname) + private protected static void CheckRenameKeys(string oldKeyname, string newKeyname) { if (oldKeyname == null) throw new ArgumentNullException("oldKeyname"); if (newKeyname == null) throw new ArgumentNullException("newKeyname"); + } + public bool RenameNx(string oldKeyname, string newKeyname) + { + CheckRenameKeys(oldKeyname, newKeyname); return SendExpectLong(Commands.RenameNx, oldKeyname.ToUtf8Bytes(), newKeyname.ToUtf8Bytes()) == Success; } @@ -848,14 +866,18 @@ public string ClientGetName() } public void ClientSetName(string name) + { + ClientValidateName(name); + SendExpectSuccess(Commands.Client, Commands.SetName, name.ToUtf8Bytes()); + } + + private static void ClientValidateName(string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name cannot be null or empty"); if (name.Contains(" ")) throw new ArgumentException("Name cannot contain spaces"); - - SendExpectSuccess(Commands.Client, Commands.SetName, name.ToUtf8Bytes()); } public void ClientPause(int timeOutMs) @@ -874,6 +896,11 @@ public void ClientKill(string clientAddr) } public long ClientKill(string addr = null, string id = null, string type = null, string skipMe = null) + { + return SendExpectLong(ClientKillPrepareArgs(addr, id, type, skipMe)); + } + + static byte[][] ClientKillPrepareArgs(string addr, string id, string type, string skipMe) { var cmdWithArgs = new List { @@ -903,8 +930,7 @@ public long ClientKill(string addr = null, string id = null, string type = null, cmdWithArgs.Add(Commands.SkipMe); cmdWithArgs.Add(skipMe.ToUtf8Bytes()); } - - return SendExpectLong(cmdWithArgs.ToArray()); + return cmdWithArgs.ToArray(); } public byte[][] Keys(string pattern) @@ -916,15 +942,18 @@ public byte[][] Keys(string pattern) } public byte[][] MGet(params byte[][] keys) + { + return SendExpectMultiData(MGetPrepareArgs(keys)); + } + + private static byte[][] MGetPrepareArgs(byte[][] keys) { if (keys == null) throw new ArgumentNullException("keys"); if (keys.Length == 0) throw new ArgumentException("keys"); - var cmdWithArgs = MergeCommandWithArgs(Commands.MGet, keys); - - return SendExpectMultiData(cmdWithArgs); + return MergeCommandWithArgs(Commands.MGet, keys); } public byte[][] MGet(params string[] keys) @@ -1034,6 +1063,10 @@ internal ScanResult SendExpectScanResult(byte[] cmd, params byte[][] args) { var cmdWithArgs = MergeCommandWithArgs(cmd, args); var multiData = SendExpectDeeplyNestedMultiData(cmdWithArgs); + return ParseScanResult(multiData); + } + internal static ScanResult ParseScanResult(object[] multiData) + { var counterBytes = (byte[])multiData[0]; var ret = new ScanResult @@ -1235,11 +1268,16 @@ public byte[][] LRange(string listId, int startingFrom, int endingAt) } public byte[][] Sort(string listOrSetId, SortOptions sortOptions) + { + return SendExpectMultiData(SortPrepareArgs(listOrSetId, sortOptions)); + } + + private static byte[][] SortPrepareArgs(string listOrSetId, SortOptions sortOptions) { var cmdWithArgs = new List - { - Commands.Sort, listOrSetId.ToUtf8Bytes() - }; + { + Commands.Sort, listOrSetId.ToUtf8Bytes() + }; if (sortOptions.SortPattern != null) { @@ -1275,8 +1313,7 @@ public byte[][] Sort(string listOrSetId, SortOptions sortOptions) cmdWithArgs.Add(Commands.Store); cmdWithArgs.Add(sortOptions.StoreAtKey.ToUtf8Bytes()); } - - return SendExpectMultiData(cmdWithArgs.ToArray()); + return cmdWithArgs.ToArray(); } public long RPush(string listId, byte[] value) @@ -1674,6 +1711,12 @@ public long ZRevRank(string setId, byte[] value) } private byte[][] GetRange(byte[] commandBytes, string setId, int min, int max, bool withScores) + { + var args = GetRangeArgs(commandBytes, setId, min, max, withScores); + return SendExpectMultiData(args); + } + + private static byte[][] GetRangeArgs(byte[] commandBytes, string setId, int min, int max, bool withScores) { if (string.IsNullOrEmpty(setId)) throw new ArgumentNullException("setId"); @@ -1687,8 +1730,7 @@ private byte[][] GetRange(byte[] commandBytes, string setId, int min, int max, b { cmdWithArgs.Add(Commands.WithScores); } - - return SendExpectMultiData(cmdWithArgs.ToArray()); + return cmdWithArgs.ToArray(); } public byte[][] ZRange(string setId, int min, int max) @@ -1713,6 +1755,13 @@ public byte[][] ZRevRangeWithScores(string setId, int min, int max) private byte[][] GetRangeByScore(byte[] commandBytes, string setId, double min, double max, int? skip, int? take, bool withScores) + { + var args = GetRangeByScoreArgs(commandBytes, setId, min, max, skip, take, withScores); + return SendExpectMultiData(args); + } + + private static byte[][] GetRangeByScoreArgs(byte[] commandBytes, + string setId, double min, double max, int? skip, int? take, bool withScores) { if (setId == null) throw new ArgumentNullException("setId"); @@ -1733,34 +1782,14 @@ private byte[][] GetRangeByScore(byte[] commandBytes, { cmdWithArgs.Add(Commands.WithScores); } - - return SendExpectMultiData(cmdWithArgs.ToArray()); + return cmdWithArgs.ToArray(); } private byte[][] GetRangeByScore(byte[] commandBytes, string setId, long min, long max, int? skip, int? take, bool withScores) { - if (setId == null) - throw new ArgumentNullException("setId"); - - var cmdWithArgs = new List - { - commandBytes, setId.ToUtf8Bytes(), min.ToUtf8Bytes(), max.ToUtf8Bytes() - }; - - if (skip.HasValue || take.HasValue) - { - cmdWithArgs.Add(Commands.Limit); - cmdWithArgs.Add(skip.GetValueOrDefault(0).ToUtf8Bytes()); - cmdWithArgs.Add(take.GetValueOrDefault(0).ToUtf8Bytes()); - } - - if (withScores) - { - cmdWithArgs.Add(Commands.WithScores); - } - - return SendExpectMultiData(cmdWithArgs.ToArray()); + var args = GetRangeByScoreArgs(commandBytes, setId, min, max, skip, take, withScores); + return SendExpectMultiData(args); } public byte[][] ZRangeByScore(string setId, double min, double max, int? skip, int? take) @@ -1908,7 +1937,7 @@ public long ZInterStore(string intoSetId, string[] setIds, string[] args) return SendExpectLong(cmdWithArgs); } - public byte[][] ZRangeByLex(string setId, string min, string max, int? skip = null, int? take = null) + static byte[][] GetZRangeByLexArgs(string setId, string min, string max, int? skip, int? take) { if (setId == null) throw new ArgumentNullException("setId"); @@ -1924,9 +1953,10 @@ public byte[][] ZRangeByLex(string setId, string min, string max, int? skip = nu cmdWithArgs.Add(skip.GetValueOrDefault(0).ToUtf8Bytes()); cmdWithArgs.Add(take.GetValueOrDefault(0).ToUtf8Bytes()); } - - return SendExpectMultiData(cmdWithArgs.ToArray()); + return cmdWithArgs.ToArray(); } + public byte[][] ZRangeByLex(string setId, string min, string max, int? skip = null, int? take = null) + => SendExpectMultiData(GetZRangeByLexArgs(setId, min, max, skip, take)); public long ZLexCount(string setId, string min, string max) { @@ -2162,6 +2192,12 @@ public long GeoAdd(string key, double longitude, double latitude, string member) } public long GeoAdd(string key, params RedisGeo[] geoPoints) + { + var cmdWithArgs = GeoAddPrepareArgs(key, geoPoints); + return SendExpectLong(cmdWithArgs); + } + + private static byte[][] GeoAddPrepareArgs(string key, RedisGeo[] geoPoints) { if (key == null) throw new ArgumentNullException(nameof(key)); @@ -2175,8 +2211,7 @@ public long GeoAdd(string key, params RedisGeo[] geoPoints) members[i * 3 + 2] = geoPoint.Member.ToUtf8Bytes(); } - var cmdWithArgs = MergeCommandWithArgs(Commands.GeoAdd, key.ToUtf8Bytes(), members); - return SendExpectLong(cmdWithArgs); + return MergeCommandWithArgs(Commands.GeoAdd, key.ToUtf8Bytes(), members); } public double GeoDist(string key, string fromMember, string toMember, string unit = null) @@ -2205,6 +2240,10 @@ public List GeoPos(string key, params string[] members) var cmdWithArgs = MergeCommandWithArgs(Commands.GeoPos, key.ToUtf8Bytes(), members.Map(x => x.ToUtf8Bytes()).ToArray()); var data = SendExpectComplexResponse(cmdWithArgs); + return GeoPosParseResult(members, data); + } + private static List GeoPosParseResult(string[] members, RedisData data) + { var to = new List(); for (var i = 0; i < members.Length; i++) @@ -2231,6 +2270,53 @@ public List GeoPos(string key, params string[] members) public List GeoRadius(string key, double longitude, double latitude, double radius, string unit, bool withCoords = false, bool withDist = false, bool withHash = false, int? count = null, bool? asc = null) + { + var cmdWithArgs = GeoRadiusPrepareArgs(key, longitude, latitude, radius, unit, + withCoords, withDist, withHash, count, asc); + + var to = new List(); + + if (!(withCoords || withDist || withHash)) + { + var members = SendExpectMultiData(cmdWithArgs).ToStringArray(); + foreach (var member in members) + { + to.Add(new RedisGeoResult { Member = member }); + } + } + else + { + var data = SendExpectComplexResponse(cmdWithArgs); + GetRadiusParseResult(unit, withCoords, withDist, withHash, to, data); + } + + return to; + } + + private static void GetRadiusParseResult(string unit, bool withCoords, bool withDist, bool withHash, List to, RedisData data) + { + foreach (var child in data.Children) + { + var i = 0; + var result = new RedisGeoResult { Unit = unit, Member = child.Children[i++].Data.FromUtf8Bytes() }; + + if (withDist) result.Distance = child.Children[i++].ToDouble(); + + if (withHash) result.Hash = child.Children[i++].ToInt64(); + + if (withCoords) + { + var children = child.Children[i].Children; + result.Longitude = children[0].ToDouble(); + result.Latitude = children[1].ToDouble(); + } + + to.Add(result); + } + } + + private static byte[][] GeoRadiusPrepareArgs(string key, double longitude, double latitude, double radius, string unit, + bool withCoords, bool withDist, bool withHash, int? count, bool? asc) { if (key == null) throw new ArgumentNullException(nameof(key)); @@ -2261,47 +2347,57 @@ public List GeoRadius(string key, double longitude, double latit else if (asc == false) args.Add(Commands.Desc); - var cmdWithArgs = MergeCommandWithArgs(Commands.GeoRadius, key.ToUtf8Bytes(), args.ToArray()); + return MergeCommandWithArgs(Commands.GeoRadius, key.ToUtf8Bytes(), args.ToArray()); + } + + public List GeoRadiusByMember(string key, string member, double radius, string unit, + bool withCoords = false, bool withDist = false, bool withHash = false, int? count = null, bool? asc = null) + { + var cmdWithArgs = GeoRadiusByMemberPrepareArgs(key, member, radius, unit, withCoords, withDist, withHash, count, asc); var to = new List(); if (!(withCoords || withDist || withHash)) { var members = SendExpectMultiData(cmdWithArgs).ToStringArray(); - foreach (var member in members) + foreach (var x in members) { - to.Add(new RedisGeoResult { Member = member }); + to.Add(new RedisGeoResult { Member = x }); } } else { var data = SendExpectComplexResponse(cmdWithArgs); + GeoRadiusByMemberParseResult(unit, withCoords, withDist, withHash, to, data); + } - foreach (var child in data.Children) - { - var i = 0; - var result = new RedisGeoResult { Unit = unit, Member = child.Children[i++].Data.FromUtf8Bytes() }; + return to; + } - if (withDist) result.Distance = child.Children[i++].ToDouble(); + private static void GeoRadiusByMemberParseResult(string unit, bool withCoords, bool withDist, bool withHash, List to, RedisData data) + { + foreach (var child in data.Children) + { + var i = 0; + var result = new RedisGeoResult { Unit = unit, Member = child.Children[i++].Data.FromUtf8Bytes() }; - if (withHash) result.Hash = child.Children[i++].ToInt64(); + if (withDist) result.Distance = child.Children[i++].ToDouble(); - if (withCoords) - { - var children = child.Children[i].Children; - result.Longitude = children[0].ToDouble(); - result.Latitude = children[1].ToDouble(); - } + if (withHash) result.Hash = child.Children[i++].ToInt64(); - to.Add(result); + if (withCoords) + { + var children = child.Children[i].Children; + result.Longitude = children[0].ToDouble(); + result.Latitude = children[1].ToDouble(); } - } - return to; + to.Add(result); + } } - public List GeoRadiusByMember(string key, string member, double radius, string unit, - bool withCoords = false, bool withDist = false, bool withHash = false, int? count = null, bool? asc = null) + static byte[][] GeoRadiusByMemberPrepareArgs(string key, string member, double radius, string unit, + bool withCoords, bool withDist, bool withHash, int? count, bool? asc) { if (key == null) throw new ArgumentNullException(nameof(key)); @@ -2331,43 +2427,7 @@ public List GeoRadiusByMember(string key, string member, double else if (asc == false) args.Add(Commands.Desc); - var cmdWithArgs = MergeCommandWithArgs(Commands.GeoRadiusByMember, key.ToUtf8Bytes(), args.ToArray()); - - var to = new List(); - - if (!(withCoords || withDist || withHash)) - { - var members = SendExpectMultiData(cmdWithArgs).ToStringArray(); - foreach (var x in members) - { - to.Add(new RedisGeoResult { Member = x }); - } - } - else - { - var data = SendExpectComplexResponse(cmdWithArgs); - - foreach (var child in data.Children) - { - var i = 0; - var result = new RedisGeoResult { Unit = unit, Member = child.Children[i++].Data.FromUtf8Bytes() }; - - if (withDist) result.Distance = child.Children[i++].ToDouble(); - - if (withHash) result.Hash = child.Children[i++].ToInt64(); - - if (withCoords) - { - var children = child.Children[i].Children; - result.Longitude = children[0].ToDouble(); - result.Latitude = children[1].ToDouble(); - } - - to.Add(result); - } - } - - return to; + return MergeCommandWithArgs(Commands.GeoRadiusByMember, key.ToUtf8Bytes(), args.ToArray()); } #endregion @@ -2428,7 +2488,7 @@ private void SafeConnectionClose() try { // workaround for a .net bug: http://support.microsoft.com/kb/821625 - Bstream?.Close(); + bufferedReader?.Close(); } catch { } try @@ -2442,7 +2502,7 @@ private void SafeConnectionClose() } catch { } - Bstream = null; + bufferedReader = null; sslStream = null; socket = null; } diff --git a/src/ServiceStack.Redis/RedisNativeClient_Utils.Async.cs b/src/ServiceStack.Redis/RedisNativeClient_Utils.Async.cs new file mode 100644 index 00000000..966dbbf1 --- /dev/null +++ b/src/ServiceStack.Redis/RedisNativeClient_Utils.Async.cs @@ -0,0 +1,552 @@ +using ServiceStack.Redis.Internal; +using ServiceStack.Redis.Pipeline; +using ServiceStack.Text; +using ServiceStack.Text.Pools; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + partial class RedisNativeClient + { + private async ValueTask SendExpectMultiDataAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return (await SendReceiveAsync(cmdWithBinaryArgs, ReadMultiDataAsync, token, + PipelineAsync != null ? PipelineAsync.CompleteMultiBytesQueuedCommandAsync : (Action>>)null).ConfigureAwait(false)) + ?? TypeConstants.EmptyByteArrayArray; + } + + protected ValueTask SendWithoutReadAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + => SendReceiveAsync(cmdWithBinaryArgs, null, token, null, sendWithoutRead: true).Await(); + + private ValueTask SendExpectLongAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return SendReceiveAsync(cmdWithBinaryArgs, ReadLongAsync, token, + PipelineAsync != null ? PipelineAsync.CompleteLongQueuedCommandAsync : (Action>>)null); + } + + private ValueTask SendExpectDoubleAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return SendReceiveAsync(cmdWithBinaryArgs, ReadDoubleAsync, token, + PipelineAsync != null ? PipelineAsync.CompleteDoubleQueuedCommandAsync : (Action>>)null); + } + protected ValueTask SendExpectStringAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + => SendExpectDataAsync(token, cmdWithBinaryArgs).FromUtf8BytesAsync(); + + private ValueTask SendExpectSuccessAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + //Turn Action into Func Hack + Action>> completePipelineFn = null; + if (PipelineAsync != null) completePipelineFn = f => { PipelineAsync.CompleteVoidQueuedCommandAsync(ct => f(ct).Await()); }; + + return SendReceiveAsync(cmdWithBinaryArgs, ExpectSuccessFnAsync, token, completePipelineFn).Await(); + } + + private ValueTask SendExpectDataAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return SendReceiveAsync(cmdWithBinaryArgs, ReadDataAsync, token, PipelineAsync != null ? PipelineAsync.CompleteBytesQueuedCommandAsync : (Action>>)null); + } + + private ValueTask SendExpectCodeAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return SendReceiveAsync(cmdWithBinaryArgs, ExpectCodeAsync, token, PipelineAsync != null ? PipelineAsync.CompleteStringQueuedCommandAsync : (Action>>)null); + } + + private ValueTask SendExpectScanResultAsync(CancellationToken token, byte[] cmd, params byte[][] args) + { + var cmdWithArgs = MergeCommandWithArgs(cmd, args); + return SendExpectDeeplyNestedMultiDataAsync(token, cmdWithArgs).Await(multiData => ParseScanResult(multiData)); + } + + private ValueTask SendExpectDeeplyNestedMultiDataAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + => SendReceiveAsync(cmdWithBinaryArgs, ReadDeeplyNestedMultiDataAsync, token); + + private ValueTask ReadDeeplyNestedMultiDataAsync(CancellationToken token) + => ReadDeeplyNestedMultiDataItemAsync(token).Await(result => (object[])result); + + private async ValueTask ReadDeeplyNestedMultiDataItemAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + if (log.IsDebugEnabled) + Log("R: {0}", s); + + switch (c) + { + case '$': + return await ParseSingleLineAsync(string.Concat(char.ToString((char)c), s), token).ConfigureAwait(false); + + case '-': + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + + case '*': + if (int.TryParse(s, out var count)) + { + var array = new object[count]; + for (int i = 0; i < count; i++) + { + array[i] = await ReadDeeplyNestedMultiDataItemAsync(token).ConfigureAwait(false); + } + + return array; + } + break; + + default: + return s; + } + + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix + } + + protected ValueTask SendExpectComplexResponseAsync(CancellationToken token, params byte[][] cmdWithBinaryArgs) + { + return SendReceiveAsync(cmdWithBinaryArgs, ReadComplexResponseAsync, token, + PipelineAsync != null ? PipelineAsync.CompleteRedisDataQueuedCommandAsync : (Action>>)null); + } + + private async ValueTask ReadComplexResponseAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + if (log.IsDebugEnabled) + Log("R: {0}", s); + + switch (c) + { + case '$': + return new RedisData + { + Data = await ParseSingleLineAsync(string.Concat(char.ToString((char)c), s), token).ConfigureAwait(false) + }; + + case '-': + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + + case '*': + if (int.TryParse(s, out var count)) + { + var ret = new RedisData { Children = new List() }; + for (var i = 0; i < count; i++) + { + ret.Children.Add(await ReadComplexResponseAsync(token).ConfigureAwait(false)); + } + + return ret; + } + break; + + default: + return new RedisData { Data = s.ToUtf8Bytes() }; + } + + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix + } + + private async ValueTask SendReceiveAsync(byte[][] cmdWithBinaryArgs, + Func> fn, + CancellationToken token, + Action>> completePipelineFn = null, + bool sendWithoutRead = false) + { + //if (TrackThread != null) + //{ + // if (TrackThread.Value.ThreadId != Thread.CurrentThread.ManagedThreadId) + // throw new InvalidAccessException(TrackThread.Value.ThreadId, TrackThread.Value.StackTrace); + //} + + var i = 0; + var didWriteToBuffer = false; + Exception originalEx = null; + + var firstAttempt = DateTime.UtcNow; + + while (true) + { + // this is deliberately *before* the try, so we never retry + // if we've been cancelled + token.ThrowIfCancellationRequested(); + try + { + if (TryConnectIfNeeded()) // TODO: asyncify + didWriteToBuffer = false; + + if (socket == null) + throw new RedisRetryableException("Socket is not connected"); + + if (!didWriteToBuffer) //only write to buffer once + { + WriteCommandToSendBuffer(cmdWithBinaryArgs); + didWriteToBuffer = true; + } + + if (PipelineAsync == null) //pipeline will handle flush if in pipeline + { + await FlushSendBufferAsync(token).ConfigureAwait(false); + } + else if (!sendWithoutRead) + { + if (completePipelineFn == null) + throw new NotSupportedException("Pipeline is not supported."); + + completePipelineFn(fn); + return default; + } + + var result = default(T); + if (fn != null) + result = await fn(token).ConfigureAwait(false); + + if (Pipeline == null) + ResetSendBuffer(); + + if (i > 0) + Interlocked.Increment(ref RedisState.TotalRetrySuccess); + + Interlocked.Increment(ref RedisState.TotalCommandsSent); + + return result; + } + catch (Exception outerEx) + { + if (log.IsDebugEnabled) + logDebug("SendReceive Exception: " + outerEx.Message); + + var retryableEx = outerEx as RedisRetryableException; + if (retryableEx == null && outerEx is RedisException + || outerEx is LicenseException) + { + ResetSendBuffer(); + throw; + } + + var ex = retryableEx ?? GetRetryableException(outerEx); + if (ex == null) + throw CreateConnectionError(originalEx ?? outerEx); + + if (originalEx == null) + originalEx = ex; + + var retry = DateTime.UtcNow - firstAttempt < retryTimeout; + if (!retry) + { + if (Pipeline == null) + ResetSendBuffer(); + + Interlocked.Increment(ref RedisState.TotalRetryTimedout); + throw CreateRetryTimeoutException(retryTimeout, originalEx); + } + + Interlocked.Increment(ref RedisState.TotalRetryCount); + await Task.Delay(GetBackOffMultiplier(++i), token).ConfigureAwait(false); + } + } + } + + internal ValueTask FlushSendBufferAsync(CancellationToken token) + { + if (currentBufferIndex > 0) + PushCurrentBuffer(); + + if (cmdBuffer.Count > 0) + { + OnBeforeFlush?.Invoke(); + + if (!Env.IsMono && sslStream == null) + { + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) + { + var sb = StringBuilderCache.Allocate(); + foreach (var cmd in cmdBuffer) + { + if (sb.Length > 50) + break; + + sb.Append(Encoding.UTF8.GetString(cmd.Array, cmd.Offset, cmd.Count)); + } + logDebug("socket.Send: " + StringBuilderCache.ReturnAndFree(sb.Replace("\r\n", " ")).SafeSubstring(0, 50)); + } + + return new ValueTask(socket.SendAsync(cmdBuffer, SocketFlags.None)); + } + else + { + //Sending IList Throws 'Message to Large' SocketException in Mono + if (sslStream == null) + { + foreach (var segment in cmdBuffer) + { // TODO: what is modern Mono behavior here? + socket.Send(segment.Array, segment.Offset, segment.Count, SocketFlags.None); + } + } + else + { + return WriteAsync(sslStream, cmdBuffer, token); + } + } + } + + return default; + + static async ValueTask WriteAsync(Stream destination, List> buffer, CancellationToken token) + { + foreach (var segment in buffer) + { +#if ASYNC_MEMORY + await destination.WriteAsync(new ReadOnlyMemory(segment.Array, segment.Offset, segment.Count), token).ConfigureAwait(false); +#else + await destination.WriteAsync(segment.Array, segment.Offset, segment.Count, token).ConfigureAwait(false); +#endif + } + } + } + + + private ValueTask SafeReadByteAsync(in CancellationToken token, [CallerMemberName]string name = null) + { + AssertNotDisposed(); + + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) + logDebug(name + "()"); + + return bufferedReader.ReadByteAsync(token); + } + + private async ValueTask ReadLineAsync(CancellationToken token) + { + AssertNotDisposed(); + + var sb = StringBuilderCache.Allocate(); + + int c; + while ((c = await bufferedReader.ReadByteAsync(token).ConfigureAwait(false)) != -1) + { + if (c == '\r') + continue; + if (c == '\n') + break; + sb.Append((char)c); + } + return StringBuilderCache.ReturnAndFree(sb); + } + + private async ValueTask ParseSingleLineAsync(string r, CancellationToken token) + { + if (log.IsDebugEnabled) + Log("R: {0}", r); + if (r.Length == 0) + throw CreateResponseError("Zero length response"); + + char c = r[0]; + if (c == '-') + throw CreateResponseError(r.StartsWith("-ERR") ? r.Substring(5) : r.Substring(1)); + + if (c == '$') + { + if (r == "$-1") + return null; + + if (int.TryParse(r.Substring(1), out var count)) + { + var retbuf = new byte[count]; + + var offset = 0; + while (count > 0) + { + var readCount = await bufferedReader.ReadAsync(retbuf, offset, count, token).ConfigureAwait(false); + if (readCount <= 0) + throw CreateResponseError("Unexpected end of Stream"); + + offset += readCount; + count -= readCount; + } + + if (await bufferedReader.ReadByteAsync(token).ConfigureAwait(false) != '\r' + || await bufferedReader.ReadByteAsync(token).ConfigureAwait(false) != '\n') + throw CreateResponseError("Invalid termination"); + + return retbuf; + } + throw CreateResponseError("Invalid length"); + } + + if (c == ':' || c == '+') + { + //match the return value + return r.Substring(1).ToUtf8Bytes(); + } + throw CreateResponseError("Unexpected reply: " + r); + } + + private ValueTask ReadDataAsync(CancellationToken token) + { + var pending = ReadLineAsync(token); + return pending.IsCompletedSuccessfully + ? ParseSingleLineAsync(pending.Result, token) + : Awaited(this, pending, token); + + static async ValueTask Awaited(RedisNativeClient @this, ValueTask pending, CancellationToken token) + { + var r = await pending.ConfigureAwait(false); + return await @this.ParseSingleLineAsync(r, token).ConfigureAwait(false); + } + } + + private async ValueTask ExpectCodeAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + + if (log.IsDebugEnabled) + Log((char)c + s); + + if (c == '-') + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + + return s; + } + + private async ValueTask ReadMultiDataAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + if (log.IsDebugEnabled) + Log("R: {0}", s); + + switch (c) + { + // Some commands like BRPOPLPUSH may return Bulk Reply instead of Multi-bulk + case '$': + var t = new byte[2][]; + t[1] = await ParseSingleLineAsync(string.Concat(char.ToString((char)c), s), token).ConfigureAwait(false); + return t; + + case '-': + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + + case '*': + if (int.TryParse(s, out var count)) + { + if (count == -1) + { + //redis is in an invalid state + return TypeConstants.EmptyByteArrayArray; + } + + var result = new byte[count][]; + + for (int i = 0; i < count; i++) + result[i] = await ReadDataAsync(token).ConfigureAwait(false); + + return result; + } + break; + } + + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix + } + + internal async ValueTask ReadLongAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + return ParseLong(c, await ReadLineAsync(token).ConfigureAwait(false)); + } + + private ValueTask ReadDoubleAsync(CancellationToken token) + => ReadDataAsync(token).Await(bytes => bytes == null ? double.NaN : ParseDouble(bytes)); + + internal ValueTask ExpectOkAsync(CancellationToken token) + => ExpectWordAsync(OK, token); + + internal ValueTask ExpectQueuedAsync(CancellationToken token) + => ExpectWordAsync(QUEUED, token); + + internal ValueTask ExpectSuccessFnAsync(CancellationToken token) + { + var pending = ExpectSuccessAsync(token); + return pending.IsCompletedSuccessfully ? default : Awaited(pending); + + static async ValueTask Awaited(ValueTask pending) + { + await pending.ConfigureAwait(false); + return 0; + } + } + + internal async ValueTask ExpectSuccessAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + + if (log.IsDebugEnabled) + Log((char)c + s); + + if (c == '-') + throw CreateResponseError(s.StartsWith("ERR") && s.Length >= 4 ? s.Substring(4) : s); + } + + + private async ValueTask ExpectWordAsync(string word, CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + + if (log.IsDebugEnabled) + Log((char)c + s); + + if (c == '-') + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + + if (s != word) + throw CreateResponseError($"Expected '{word}' got '{s}'"); + } + + internal async ValueTask ReadMultiDataResultCountAsync(CancellationToken token) + { + int c = await SafeReadByteAsync(token).ConfigureAwait(false); + if (c == -1) + throw CreateNoMoreDataError(); + + var s = await ReadLineAsync(token).ConfigureAwait(false); + if (log.IsDebugEnabled) + Log("R: {0}", s); + if (c == '-') + throw CreateResponseError(s.StartsWith("ERR") ? s.Substring(4) : s); + if (c == '*') + { + if (int.TryParse(s, out var count)) + { + return count; + } + } + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisNativeClient_Utils.cs b/src/ServiceStack.Redis/RedisNativeClient_Utils.cs index 29587cdd..c8b5d19c 100644 --- a/src/ServiceStack.Redis/RedisNativeClient_Utils.cs +++ b/src/ServiceStack.Redis/RedisNativeClient_Utils.cs @@ -18,7 +18,10 @@ using System.Net; using System.Net.Security; using System.Net.Sockets; +using System.Runtime.CompilerServices; +using System.Security.Authentication; using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -39,6 +42,14 @@ public partial class RedisNativeClient private const int Unknown = -1; public static int ServerVersionNumber { get; set; } + private static long IdCounter = 0; + public long ClientId { get; } = Interlocked.Increment(ref IdCounter); + + private string LogPrefix = string.Empty; + private void logDebug(object message) => log.Debug(LogPrefix + message); + private void logError(object message) => log.Error(LogPrefix + message); + private void logError(object message, Exception ex) => log.Error(LogPrefix + message, ex); + public int AssertServerVersionNumber() { if (ServerVersionNumber == 0) @@ -79,31 +90,46 @@ private void Connect() SendTimeout = SendTimeout, ReceiveTimeout = ReceiveTimeout }; +#if DEBUG + // allow sync commands during connect (we're OK with sync for connect; the + // DebugAllowSync feature being used here only impacts tests) + var oldDebugAllowSync = DebugAllowSync; + DebugAllowSync = true; +#endif try { -#if NETSTANDARD2_0 - var addresses = Dns.GetHostAddressesAsync(Host).Result; - socket.Connect(addresses.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork), Port); -#else + if (log.IsDebugEnabled) + { + var type = ConnectTimeout <= 0 ? "sync" : "async"; + logDebug($"Attempting {type} connection to '{Host}:{Port}' (SEND {SendTimeout}, RECV {ReceiveTimeout} Timeouts)..."); + } + if (ConnectTimeout <= 0) { socket.Connect(Host, Port); } else { - var connectResult = socket.BeginConnect(Host, Port, null, null); + var connectResult = IPAddress.TryParse(Host, out var ip) + ? socket.BeginConnect(ip, Port, null, null) + : socket.BeginConnect(Host, Port, null, null); connectResult.AsyncWaitHandle.WaitOne(ConnectTimeout, true); } -#endif if (!socket.Connected) { + if (log.IsDebugEnabled) + logDebug($"Socket failed connect to '{Host}:{Port}' (ConnectTimeout {ConnectTimeout})"); + socket.Close(); socket = null; DeactivatedAt = DateTime.UtcNow; return; } + if (log.IsDebugEnabled) + logDebug($"Socket connected to '{Host}:{Port}'"); + Stream networkStream = new NetworkStream(socket); if (Ssl) @@ -118,7 +144,7 @@ private void Connect() } else { -#if NETSTANDARD2_0 +#if NETSTANDARD || NET472 sslStream = new SslStream(networkStream, leaveInnerStreamOpen: false, userCertificateValidationCallback: RedisConfig.CertificateValidationCallback, @@ -141,19 +167,35 @@ private void Connect() #endif } -#if NETSTANDARD2_0 - sslStream.AuthenticateAsClientAsync(Host).Wait(); +#if NETSTANDARD || NET472 + var task = sslStream.AuthenticateAsClientAsync(Host); + if (ConnectTimeout > 0) + { + task.Wait(ConnectTimeout); + } + else + { + task.Wait(); + } #else - sslStream.AuthenticateAsClient(Host); + if (SslProtocols != null) + { + sslStream.AuthenticateAsClient(Host, new X509CertificateCollection(), + SslProtocols ?? System.Security.Authentication.SslProtocols.None, checkCertificateRevocation: true); + } + else + { + sslStream.AuthenticateAsClient(Host); + } #endif if (!sslStream.IsEncrypted) - throw new Exception("Could not establish an encrypted connection to " + Host); + throw new Exception($"Could not establish an encrypted connection to '{Host}:{Port}'"); networkStream = sslStream; } - Bstream = new BufferedStream(networkStream, 16 * 1024); + bufferedReader = new BufferedReader(networkStream, 16 * 1024); if (!string.IsNullOrEmpty(Password)) SendUnmanagedExpectSuccess(Commands.Auth, Password.ToUtf8Bytes()); @@ -203,9 +245,15 @@ private void Connect() } catch (SocketException) { - log.Error(ErrorConnect.Fmt(Host, Port)); + logError(ErrorConnect.Fmt(Host, Port)); throw; } + finally + { +#if DEBUG + DebugAllowSync = oldDebugAllowSync; +#endif + } } public static string ErrorConnect = "Could not connect to redis Instance at {0}:{1}"; @@ -216,10 +264,13 @@ public virtual void OnConnected() protected string ReadLine() { + AssertNotDisposed(); + AssertNotAsyncOnly(); + var sb = StringBuilderCache.Allocate(); int c; - while ((c = Bstream.ReadByte()) != -1) + while ((c = bufferedReader.ReadByte()) != -1) { if (c == '\r') continue; @@ -251,7 +302,7 @@ internal bool AssertConnectedSocket() } catch (SocketException ex) { - log.Error(ErrorConnect.Fmt(Host, Port)); + logError(ErrorConnect.Fmt(Host, Port)); socket?.Close(); @@ -260,13 +311,14 @@ internal bool AssertConnectedSocket() DeactivatedAt = DateTime.UtcNow; var message = Host + ":" + Port; var throwEx = new RedisException(message, ex); - log.Error(throwEx.Message, ex); + logError(throwEx.Message, ex); throw throwEx; } } - private void TryConnectIfNeeded() + private bool TryConnectIfNeeded() { + bool didConnect = false; if (LastConnectedAtTimestamp > 0) { var now = Stopwatch.GetTimestamp(); @@ -275,6 +327,7 @@ private void TryConnectIfNeeded() if (socket == null || (elapsedSecs > IdleTimeOutSecs && !socket.IsConnected())) { Reconnect(); + didConnect = true; } LastConnectedAtTimestamp = now; } @@ -282,17 +335,16 @@ private void TryConnectIfNeeded() if (socket == null) { Connect(); + didConnect = true; } + + return didConnect; } private bool Reconnect() { - var previousDb = db; - SafeConnectionClose(); - Connect(); //sets db to 0 - - if (previousDb != RedisConfig.DefaultDb) this.Db = previousDb; + Connect(); //sets db return socket != null; } @@ -301,7 +353,7 @@ private RedisResponseException CreateResponseError(string error) { DeactivatedAt = DateTime.UtcNow; - if (!RedisConfig.DisableVerboseLogging) + if (RedisConfig.EnableVerboseLogging) { var safeLastCommand = string.IsNullOrEmpty(Password) ? lastCommand @@ -312,7 +364,7 @@ private RedisResponseException CreateResponseError(string error) } var throwEx = new RedisResponseException(error); - log.Error(error); + logError(error); return throwEx; } @@ -328,7 +380,7 @@ private RedisRetryableException CreateRetryableResponseError(string error) var throwEx = new RedisRetryableException( $"[{DateTime.UtcNow:HH:mm:ss.fff}] {error}, sPort: {clientPort}, LastCommand: {safeLastCommand}"); - log.Error(throwEx.Message); + logError(throwEx.Message); throw throwEx; } @@ -338,7 +390,7 @@ private RedisException CreateConnectionError(Exception originalEx) var throwEx = new RedisException( $"[{DateTime.UtcNow:HH:mm:ss.fff}] Unable to Connect: sPort: {clientPort}{(originalEx != null ? ", Error: " + originalEx.Message + "\n" + originalEx.StackTrace : "")}", originalEx ?? lastSocketException); - log.Error(throwEx.Message); + logError(throwEx.Message); throw throwEx; } @@ -360,7 +412,7 @@ private static byte[] GetCmdBytes(char cmdPrefix, int noOfLines) } /// - /// Command to set multuple binary safe arguments + /// Command to set multiple binary safe arguments /// /// /// @@ -373,7 +425,7 @@ protected void WriteCommandToSendBuffer(params byte[][] cmdWithBinaryArgs) LicenseUtils.AssertValidUsage(LicenseFeature.Redis, QuotaType.RequestsPerHour, __requestsPerHour); } - if (log.IsDebugEnabled && !RedisConfig.DisableVerboseLogging) + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) CmdLog(cmdWithBinaryArgs); //Total command lines count @@ -392,8 +444,11 @@ protected void SendUnmanagedExpectSuccess(params byte[][] cmdWithBinaryArgs) { bytes = bytes.Combine(GetCmdBytes('$', safeBinaryValue.Length), safeBinaryValue, endData); } + + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) + logDebug("stream.Write: " + Encoding.UTF8.GetString(bytes, 0, Math.Min(bytes.Length, 50)).Replace("\r\n"," ").SafeSubstring(0,50)); - Bstream.Write(bytes, 0, bytes.Length); + SendDirectToSocket(new ArraySegment(bytes, 0, bytes.Length)); ExpectSuccess(); } @@ -410,7 +465,9 @@ public void WriteAllToSendBuffer(params byte[][] cmdWithBinaryArgs) } } - readonly IList> cmdBuffer = new List>(); + // trated as List rather than IList to avoid allocs during foreach + readonly List> cmdBuffer = new List>(); + byte[] currentBuffer = BufferPool.GetBuffer(); int currentBufferIndex; @@ -471,27 +528,69 @@ internal void FlushSendBuffer() if (!Env.IsMono && sslStream == null) { + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) + { + var sb = StringBuilderCache.Allocate(); + foreach (var cmd in cmdBuffer) + { + if (sb.Length > 50) + break; + + sb.Append(Encoding.UTF8.GetString(cmd.Array, cmd.Offset, cmd.Count)); + } + logDebug("socket.Send: " + StringBuilderCache.ReturnAndFree(sb.Replace("\r\n", " ")).SafeSubstring(0,50)); + } + socket.Send(cmdBuffer); //Optimized for Windows } else { - //Sendling IList Throws 'Message to Large' SocketException in Mono + //Sending IList Throws 'Message to Large' SocketException in Mono foreach (var segment in cmdBuffer) { - var buffer = segment.Array; - if (sslStream == null) - { - socket.Send(buffer, segment.Offset, segment.Count, SocketFlags.None); - } - else - { - sslStream.Write(buffer, segment.Offset, segment.Count); - } + SendDirectToSocket(segment); } } } } + private void SendDirectToSocket(ArraySegment segment) + { + if (sslStream == null) + { + socket.Send(segment.Array, segment.Offset, segment.Count, SocketFlags.None); + } + else + { + sslStream.Write(segment.Array, segment.Offset, segment.Count); + } + } + + /// + /// Called before returning pooled client/socket + /// + internal void Activate(bool newClient=false) + { + if (!newClient) + { + //Drain any existing buffers + ResetSendBuffer(); + bufferedReader?.Reset(); + if (socket?.Available > 0) + { + logDebug($"Draining existing socket of {socket.Available} bytes"); + var buff = new byte[socket.Available]; + socket.Receive(buff, SocketFlags.None); + } + } + Active = true; + } + + internal void Deactivate() + { + Active = false; + } + /// /// reset buffer index in send buffer /// @@ -506,18 +605,44 @@ public void ResetSendBuffer() } } - private int SafeReadByte() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void AssertNotDisposed() { - return Bstream.ReadByte(); + if (bufferedReader == null) + throw new ObjectDisposedException($"Redis Client {ClientId} is Disposed"); } + + private int SafeReadByte(string name) + { + AssertNotDisposed(); + AssertNotAsyncOnly(); + + if (log.IsDebugEnabled && RedisConfig.EnableVerboseLogging) + logDebug(name + "()"); + return bufferedReader.ReadByte(); + } + internal TrackThread? TrackThread; - + + partial void AssertNotAsyncOnly([CallerMemberName] string caller = default); +#if DEBUG + public bool DebugAllowSync { get; set; } = true; + partial void AssertNotAsyncOnly(string caller) + { + // for unit tests only; asserts that we're not meant to be in an async context + if (!DebugAllowSync) + throw new InvalidOperationException("Unexpected synchronous operation detected from '" + caller + "'"); + } +#endif + + protected T SendReceive(byte[][] cmdWithBinaryArgs, Func fn, Action> completePipelineFn = null, bool sendWithoutRead = false) { + if (Pipeline is null) AssertNotAsyncOnly(); if (TrackThread != null) { if (TrackThread.Value.ThreadId != Thread.CurrentThread.ManagedThreadId) @@ -525,6 +650,7 @@ protected T SendReceive(byte[][] cmdWithBinaryArgs, } var i = 0; + var didWriteToBuffer = false; Exception originalEx = null; var firstAttempt = DateTime.UtcNow; @@ -533,13 +659,17 @@ protected T SendReceive(byte[][] cmdWithBinaryArgs, { try { - TryConnectIfNeeded(); + if (TryConnectIfNeeded()) + didWriteToBuffer = false; if (socket == null) throw new RedisRetryableException("Socket is not connected"); - if (i == 0) //only write to buffer once + if (!didWriteToBuffer) //only write to buffer once + { WriteCommandToSendBuffer(cmdWithBinaryArgs); + didWriteToBuffer = true; + } if (Pipeline == null) //pipeline will handle flush if in pipeline { @@ -570,6 +700,9 @@ protected T SendReceive(byte[][] cmdWithBinaryArgs, } catch (Exception outerEx) { + if (log.IsDebugEnabled) + logDebug("SendReceive Exception: " + outerEx.Message); + var retryableEx = outerEx as RedisRetryableException; if (retryableEx == null && outerEx is RedisException || outerEx is LicenseException) @@ -605,7 +738,7 @@ private RedisException CreateRetryTimeoutException(TimeSpan retryTimeout, Except { DeactivatedAt = DateTime.UtcNow; var message = "Exceeded timeout of {0}".Fmt(retryTimeout); - log.Error(message); + logError(message); return new RedisException(message, originalEx); } @@ -618,7 +751,7 @@ private Exception GetRetryableException(Exception outerEx) if (socketEx == null) return null; - log.Error("SocketException in SendReceive, retrying...", socketEx); + logError("SocketException in SendReceive, retrying...", socketEx); lastSocketException = socketEx; socket?.Close(); @@ -734,10 +867,10 @@ protected string SendExpectString(params byte[][] cmdWithBinaryArgs) protected void Log(string fmt, params object[] args) { - if (RedisConfig.DisableVerboseLogging) + if (!RedisConfig.EnableVerboseLogging) return; - log.DebugFormat("{0}", string.Format(fmt, args).Trim()); + log.DebugFormat(LogPrefix + "{0}", string.Format(fmt, args).Trim()); } protected void CmdLog(byte[][] args) @@ -762,7 +895,7 @@ protected void CmdLog(byte[][] args) this.lastCommand = this.lastCommand.Substring(0, 100) + "..."; } - log.Debug("S: " + this.lastCommand); + logDebug("S: " + this.lastCommand); } //Turn Action into Func Hack @@ -774,7 +907,7 @@ protected long ExpectSuccessFn() protected void ExpectSuccess() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ExpectSuccess)); if (c == -1) throw CreateNoMoreDataError(); @@ -789,7 +922,7 @@ protected void ExpectSuccess() private void ExpectWord(string word) { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ExpectWord)); if (c == -1) throw CreateNoMoreDataError(); @@ -807,7 +940,7 @@ private void ExpectWord(string word) private string ExpectCode() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ExpectCode)); if (c == -1) throw CreateNoMoreDataError(); @@ -834,12 +967,15 @@ internal void ExpectQueued() public long ReadLong() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ReadLong)); if (c == -1) throw CreateNoMoreDataError(); - var s = ReadLine(); + return ParseLong(c, ReadLine()); + } + private long ParseLong(int c, string s) + { if (log.IsDebugEnabled) Log("R: {0}", s); @@ -852,7 +988,7 @@ public long ReadLong() if (long.TryParse(s, out i)) return i; } - throw CreateResponseError("Unknown reply on integer response: " + c + s); + throw CreateResponseError("Unknown reply on integer response: " + ((char)c) + s); // c here is the protocol prefix } public double ReadDouble() @@ -897,7 +1033,7 @@ private byte[] ParseSingleLine(string r) var offset = 0; while (count > 0) { - var readCount = Bstream.Read(retbuf, offset, count); + var readCount = bufferedReader.Read(retbuf, offset, count); if (readCount <= 0) throw CreateResponseError("Unexpected end of Stream"); @@ -905,7 +1041,7 @@ private byte[] ParseSingleLine(string r) count -= readCount; } - if (Bstream.ReadByte() != '\r' || Bstream.ReadByte() != '\n') + if (bufferedReader.ReadByte() != '\r' || bufferedReader.ReadByte() != '\n') throw CreateResponseError("Invalid termination"); return retbuf; @@ -923,7 +1059,7 @@ private byte[] ParseSingleLine(string r) private byte[][] ReadMultiData() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ReadMultiData)); if (c == -1) throw CreateNoMoreDataError(); @@ -961,7 +1097,7 @@ private byte[][] ReadMultiData() break; } - throw CreateResponseError("Unknown reply on multi-request: " + c + s); + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix } private object[] ReadDeeplyNestedMultiData() @@ -972,7 +1108,7 @@ private object[] ReadDeeplyNestedMultiData() private object ReadDeeplyNestedMultiDataItem() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ReadDeeplyNestedMultiDataItem)); if (c == -1) throw CreateNoMoreDataError(); @@ -1005,12 +1141,12 @@ private object ReadDeeplyNestedMultiDataItem() return s; } - throw CreateResponseError("Unknown reply on multi-request: " + c + s); + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix } internal RedisData ReadComplexResponse() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ReadComplexResponse)); if (c == -1) throw CreateNoMoreDataError(); @@ -1046,12 +1182,12 @@ internal RedisData ReadComplexResponse() return new RedisData { Data = s.ToUtf8Bytes() }; } - throw CreateResponseError("Unknown reply on multi-request: " + c + s); + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix } internal int ReadMultiDataResultCount() { - int c = SafeReadByte(); + int c = SafeReadByte(nameof(ReadMultiDataResultCount)); if (c == -1) throw CreateNoMoreDataError(); @@ -1067,7 +1203,7 @@ internal int ReadMultiDataResultCount() return count; } } - throw CreateResponseError("Unknown reply on multi-request: " + c + s); + throw CreateResponseError("Unknown reply on multi-request: " + ((char)c) + s); // c here is the protocol prefix } private static void AssertListIdAndValue(string listId, byte[] value) diff --git a/src/ServiceStack.Redis/RedisPubSubServer.cs b/src/ServiceStack.Redis/RedisPubSubServer.cs index c5abe43f..a1d4fd17 100644 --- a/src/ServiceStack.Redis/RedisPubSubServer.cs +++ b/src/ServiceStack.Redis/RedisPubSubServer.cs @@ -1,8 +1,6 @@ using System; using System.Diagnostics; -using System.Text; using System.Threading; -using System.Threading.Tasks; using ServiceStack.Logging; using ServiceStack.Text; @@ -30,9 +28,11 @@ public class RedisPubSubServer : IRedisPubSubServer /// Callback fired on each message received, handle with (channel, msg) => ... /// public Action OnMessage { get; set; } + public Action OnMessageBytes { get; set; } public Action OnControlCommand { get; set; } public Action OnUnSubscribe { get; set; } + public Action OnEvent { get; set; } public Action OnError { get; set; } public Action OnFailover { get; set; } public bool IsSentinelSubscription { get; set; } @@ -55,11 +55,11 @@ public class RedisPubSubServer : IRedisPubSubServer private int autoRestart = YES; public bool AutoRestart { - get { return Interlocked.CompareExchange(ref autoRestart, 0, 0) == YES; } - set { Interlocked.CompareExchange(ref autoRestart, value ? YES : NO, autoRestart); } + get => Interlocked.CompareExchange(ref autoRestart, 0, 0) == YES; + set => Interlocked.CompareExchange(ref autoRestart, value ? YES : NO, autoRestart); } - public DateTime CurrentServerTime => new DateTime(serverTimeAtStart.Ticks + startedAt.ElapsedTicks, DateTimeKind.Utc); + public DateTime CurrentServerTime => new DateTime(serverTimeAtStart.Ticks + startedAt.Elapsed.Ticks, DateTimeKind.Utc); public long BgThreadCount => Interlocked.CompareExchange(ref bgThreadCount, 0, 0); @@ -73,6 +73,7 @@ public RedisPubSubServer(IRedisClientsManager clientsManager, params string[] ch { this.ClientsManager = clientsManager; this.Channels = channels; + startedAt = Stopwatch.StartNew(); var failoverHost = clientsManager as IRedisFailover; failoverHost?.OnFailover.Add(HandleFailover); @@ -95,10 +96,27 @@ public IRedisPubSubServer Start() //Only 1 thread allowed past if (Interlocked.CompareExchange(ref status, Status.Starting, Status.Stopped) == Status.Stopped) //Should only be 1 thread past this point { - try + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} Stopped] Start()> Stopped -> Starting"); + + var initErrors = 0; + bool hasInit = false; + while (!hasInit) { - Init(); + try + { + Init(); + hasInit = true; + } + catch (Exception ex) + { + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] Start().Init()> Exception: {ex.Message}"); + OnError?.Invoke(ex); + SleepBackOffMultiplier(initErrors++); + } + } + try + { SleepBackOffMultiplier(Interlocked.CompareExchange(ref noOfContinuousErrors, 0, 0)); OnStart?.Invoke(); @@ -135,19 +153,12 @@ public IRedisPubSubServer Start() private void Init() { - try - { - using (var redis = ClientsManager.GetReadOnlyClient()) - { - startedAt = Stopwatch.StartNew(); - serverTimeAtStart = IsSentinelSubscription - ? DateTime.UtcNow - : redis.GetServerTime(); - } - } - catch (Exception ex) + using (var redis = ClientsManager.GetReadOnlyClient()) { - OnError?.Invoke(ex); + startedAt = Stopwatch.StartNew(); + serverTimeAtStart = IsSentinelSubscription + ? DateTime.UtcNow + : redis.GetServerTime(); } DisposeHeartbeatTimer(); @@ -155,7 +166,7 @@ private void Init() if (HeartbeatInterval != null) { heartbeatTimer = new Timer(SendHeartbeat, null, - TimeSpan.FromMilliseconds(0), HeartbeatInterval.Value); + TimeSpan.FromMilliseconds(0), HeartbeatInterval.GetValueOrDefault()); } Interlocked.CompareExchange(ref lastHeartbeatTicks, DateTime.UtcNow.Ticks, lastHeartbeatTicks); @@ -169,7 +180,7 @@ void SendHeartbeat(object state) if (currentStatus != Status.Started) return; - if (DateTime.UtcNow - new DateTime(lastHeartbeatTicks) < HeartbeatInterval.Value) + if (DateTime.UtcNow - new DateTime(lastHeartbeatTicks) < HeartbeatInterval.GetValueOrDefault()) return; OnHeartbeatSent?.Invoke(); @@ -179,6 +190,8 @@ void SendHeartbeat(object state) if (DateTime.UtcNow - new DateTime(lastHeartbeatTicks) > HeartbeatTimeout) { currentStatus = Interlocked.CompareExchange(ref status, 0, 0); + + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {Status.GetStatus(currentStatus)}] SendHeartbeat()> Exceeded HeartbeatTimeout"); if (currentStatus == Status.Started) { Restart(); @@ -200,6 +213,9 @@ private void DisposeHeartbeatTimer() try { + if (Log.IsDebugEnabled) + Log.Debug("RedisPubServer.DisposeHeartbeatTimer()"); + heartbeatTimer.Dispose(); } catch (Exception ex) @@ -215,85 +231,111 @@ private void RunLoop() if (Interlocked.CompareExchange(ref status, Status.Started, Status.Starting) != Status.Starting) return; Interlocked.Increment(ref timesStarted); + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} Started] RunLoop().Stop> Starting -> Started, timesStarted: {timesStarted}"); + try { //RESET while (Interlocked.CompareExchange(ref status, 0, 0) == Status.Started) { - using (var redis = ClientsManager.GetReadOnlyClient()) + using var redis = ClientsManager.GetReadOnlyClient(); + masterClient = redis; + + //Record that we had a good run... + Interlocked.CompareExchange(ref noOfContinuousErrors, 0, noOfContinuousErrors); + + using var subscription = redis.CreateSubscription(); + subscription.OnUnSubscribe = HandleUnSubscribe; + + if (OnMessageBytes != null) { - masterClient = redis; + bool IsCtrlMessage(byte[] msg) + { + if (msg.Length < 4) + return false; + return msg[0] == 'C' && msg[1] == 'T' && msg[0] == 'R' && msg[0] == 'L'; + } + + ((RedisSubscription)subscription).OnMessageBytes = (channel, msg) => { + if (IsCtrlMessage(msg)) + return; - //Record that we had a good run... - Interlocked.CompareExchange(ref noOfContinuousErrors, 0, noOfContinuousErrors); + OnMessageBytes(channel, msg); + }; + } + + subscription.OnMessage = (channel, msg) => + { + if (string.IsNullOrEmpty(msg)) + return; - using (var subscription = redis.CreateSubscription()) + var ctrlMsg = msg.LeftPart(':'); + if (ctrlMsg == ControlCommand.Control) { - subscription.OnUnSubscribe = HandleUnSubscribe; + var op = Interlocked.CompareExchange(ref doOperation, Operation.NoOp, doOperation); + + var msgType = msg.IndexOf(':') >= 0 + ? msg.RightPart(':') + : null; + + OnControlCommand?.Invoke(msgType ?? Operation.GetName(op)); - subscription.OnMessage = (channel, msg) => + switch (op) { - if (string.IsNullOrEmpty(msg)) - return; + case Operation.Stop: + if (Log.IsDebugEnabled) + Log.Debug("Stop Command Issued"); - var ctrlMsg = msg.SplitOnFirst(':'); - if (ctrlMsg[0] == ControlCommand.Control) - { - var op = Interlocked.CompareExchange(ref doOperation, Operation.NoOp, doOperation); + var holdStatus = GetStatus(); - var msgType = ctrlMsg.Length > 1 - ? ctrlMsg[1] - : null; + Interlocked.CompareExchange(ref status, Status.Stopping, Status.Started); - OnControlCommand?.Invoke(msgType ?? Operation.GetName(op)); - - switch (op) + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {holdStatus}] RunLoop().Stop> Started -> Stopping"); + try { - case Operation.Stop: - if (Log.IsDebugEnabled) - Log.Debug("Stop Command Issued"); - - Interlocked.CompareExchange(ref status, Status.Stopping, Status.Started); - try - { - if (Log.IsDebugEnabled) - Log.Debug("UnSubscribe From All Channels..."); - - subscription.UnSubscribeFromAllChannels(); //Un block thread. - } - finally - { - Interlocked.CompareExchange(ref status, Status.Stopped, Status.Stopping); - } - return; - - case Operation.Reset: - subscription.UnSubscribeFromAllChannels(); //Un block thread. - return; - } + if (Log.IsDebugEnabled) + Log.Debug("UnSubscribe From All Channels..."); - switch (msgType) + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] RunLoop().Stop> subscription.UnSubscribeFromAllChannels()"); + + // ReSharper disable once AccessToDisposedClosure + subscription.UnSubscribeFromAllChannels(); //Un block thread. + } + finally { - case ControlCommand.Pulse: - Pulse(); - break; + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] RunLoop().Stop> Stopping -> Stopped"); + Interlocked.CompareExchange(ref status, Status.Stopped, Status.Stopping); } - } - else - { - OnMessage(channel, msg); - } - }; - - //blocks thread - if (ChannelsMatching != null && ChannelsMatching.Length > 0) - subscription.SubscribeToChannelsMatching(ChannelsMatching); - else - subscription.SubscribeToChannels(Channels); - - masterClient = null; + return; + + case Operation.Reset: + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] RunLoop().Reset> subscription.UnSubscribeFromAllChannels()"); + + // ReSharper disable once AccessToDisposedClosure + subscription.UnSubscribeFromAllChannels(); //Un block thread. + return; + } + + switch (msgType) + { + case ControlCommand.Pulse: + Pulse(); + break; + } } - } + else + { + OnMessage(channel, msg); + } + }; + + //blocks thread + if (ChannelsMatching != null && ChannelsMatching.Length > 0) + subscription.SubscribeToChannelsMatching(ChannelsMatching); + else + subscription.SubscribeToChannels(Channels); + + masterClient = null; } OnStop?.Invoke(); @@ -303,10 +345,14 @@ private void RunLoop() lastExMsg = ex.Message; Interlocked.Increment(ref noOfErrors); Interlocked.Increment(ref noOfContinuousErrors); + + var holdStatus = GetStatus(); if (Interlocked.CompareExchange(ref status, Status.Stopped, Status.Started) != Status.Started) Interlocked.CompareExchange(ref status, Status.Stopped, Status.Stopping); + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {holdStatus}] RunLoop().Stop> Started|Stopping -> Stopped"); + OnStop?.Invoke(); OnError?.Invoke(ex); @@ -316,6 +362,8 @@ private void RunLoop() { if (WaitBeforeNextRestart != null) TaskUtils.Sleep(WaitBeforeNextRestart.Value); + + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] RunLoop().AutoRestart> Start()"); Start(); } } @@ -334,10 +382,12 @@ private void Stop(bool shouldRestart) if (Interlocked.CompareExchange(ref status, Status.Stopping, Status.Started) == Status.Started) { + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] Stop()> Started -> Stopping"); + if (Log.IsDebugEnabled) Log.Debug("Stopping RedisPubSubServer..."); - //Unblock current bgthread by issuing StopCommand + //Unblock current bg thread by issuing StopCommand SendControlCommand(Operation.Stop); } } @@ -356,18 +406,16 @@ private void NotifyAllSubscribers(string commandType=null) try { - using (var redis = ClientsManager.GetClient()) + using var redis = ClientsManager.GetClient(); + foreach (var channel in Channels) { - foreach (var channel in Channels) - { - redis.PublishMessage(channel, msg); - } + redis.PublishMessage(channel, msg); } } catch (Exception ex) { OnError?.Invoke(ex); - Log.Warn("Could not send '{0}' message to bg thread: {1}".Fmt(msg, ex.Message)); + Log.WarnFormat("Could not send '{0}' message to bg thread: {1}", msg, ex.Message); } } @@ -380,13 +428,11 @@ private void HandleFailover(IRedisClientsManager clientsManager) if (masterClient != null) { //New thread-safe client with same connection info as connected master - using (var currentlySubscribedClient = ((RedisClient)masterClient).CloneClient()) + using var currentlySubscribedClient = ((RedisClient)masterClient).CloneClient(); + Interlocked.CompareExchange(ref doOperation, Operation.Reset, doOperation); + foreach (var channel in Channels) { - Interlocked.CompareExchange(ref doOperation, Operation.Reset, doOperation); - foreach (var channel in Channels) - { - currentlySubscribedClient.PublishMessage(channel, ControlCommand.Control); - } + currentlySubscribedClient.PublishMessage(channel, ControlCommand.Control); } } else @@ -422,12 +468,14 @@ private void KillBgThreadIfExists() //give it a small chance to die gracefully if (!bgThread.Join(500)) { -#if !NETSTANDARD2_0 +#if !NETCORE //Ideally we shouldn't get here, but lets try our hardest to clean it up + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] KillBgThreadIfExists()> bgThread.Interrupt()"); Log.Warn("Interrupting previous Background Thread: " + bgThread.Name); bgThread.Interrupt(); if (!bgThread.Join(TimeSpan.FromSeconds(3))) { + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] KillBgThreadIfExists()> bgThread.Abort()"); Log.Warn(bgThread.Name + " just wont die, so we're now aborting it..."); bgThread.Abort(); } @@ -440,15 +488,15 @@ private void KillBgThreadIfExists() private void SleepBackOffMultiplier(int continuousErrorsCount) { if (continuousErrorsCount == 0) return; - const int MaxSleepMs = 60 * 1000; + const int maxSleepMs = 60 * 1000; //exponential/random retry back-off. var nextTry = Math.Min( rand.Next((int)Math.Pow(continuousErrorsCount, 3), (int)Math.Pow(continuousErrorsCount + 1, 3) + 1), - MaxSleepMs); + maxSleepMs); if (Log.IsDebugEnabled) - Log.Debug("Sleeping for {0}ms after {1} continuous errors".Fmt(nextTry, continuousErrorsCount)); + Log.DebugFormat("Sleeping for {0}ms after {1} continuous errors", nextTry, continuousErrorsCount); TaskUtils.Sleep(nextTry); } @@ -491,26 +539,22 @@ class Status //dep-free copy of WorkerStatus public const int Stopping = 1; public const int Starting = 2; public const int Started = 3; - } - public string GetStatus() - { - switch (Interlocked.CompareExchange(ref status, 0, 0)) + public static string GetStatus(int status) { - case Status.Disposed: - return "Disposed"; - case Status.Stopped: - return "Stopped"; - case Status.Stopping: - return "Stopping"; - case Status.Starting: - return "Starting"; - case Status.Started: - return "Started"; + return status switch { + Disposed => nameof(Disposed), + Stopped => nameof(Stopped), + Stopping => nameof(Stopping), + Starting => nameof(Starting), + Started => nameof(Started), + _ => throw new NotSupportedException("Unknown status: " + status) + }; } - return null; } + public string GetStatus() => Status.GetStatus(Interlocked.CompareExchange(ref status, 0, 0)); + public string GetStatsDescription() { var sb = StringBuilderCache.Allocate(); @@ -528,12 +572,21 @@ public virtual void Dispose() { if (Interlocked.CompareExchange(ref status, 0, 0) == Status.Disposed) return; + + if (Log.IsDebugEnabled) + Log.Debug("RedisPubServer.Dispose()..."); + + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {GetStatus()}] Dispose()>"); Stop(); + var holdStatus = GetStatus(); + if (Interlocked.CompareExchange(ref status, Status.Disposed, Status.Stopped) != Status.Stopped) Interlocked.CompareExchange(ref status, Status.Disposed, Status.Stopping); + OnEvent?.Invoke($"[{DateTime.UtcNow.TimeOfDay:g} {holdStatus}] Dispose()> -> Disposed"); + try { OnDispose?.Invoke(); @@ -556,4 +609,4 @@ public virtual void Dispose() DisposeHeartbeatTimer(); } } -} \ No newline at end of file +} diff --git a/src/ServiceStack.Redis/RedisResolver.cs b/src/ServiceStack.Redis/RedisResolver.cs index bbd149e7..a49e0f01 100644 --- a/src/ServiceStack.Redis/RedisResolver.cs +++ b/src/ServiceStack.Redis/RedisResolver.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading; using ServiceStack.Logging; @@ -19,28 +20,22 @@ public class RedisResolver : IRedisResolver, IRedisResolverExtended HashSet allHosts = new HashSet(); private RedisEndpoint[] masters; - private RedisEndpoint[] slaves; + private RedisEndpoint[] replicas; - public RedisEndpoint[] Masters - { - get { return masters; } - } + public RedisEndpoint[] Masters => masters; - public RedisEndpoint[] Slaves - { - get { return slaves; } - } + public RedisEndpoint[] Slaves => replicas; public RedisResolver() : this(TypeConstants.EmptyArray, TypeConstants.EmptyArray) {} - public RedisResolver(IEnumerable masters, IEnumerable slaves) - : this(masters.ToRedisEndPoints(), slaves.ToRedisEndPoints()){} + public RedisResolver(IEnumerable masters, IEnumerable replicas) + : this(masters.ToRedisEndPoints(), replicas.ToRedisEndPoints()){} - public RedisResolver(IEnumerable masters, IEnumerable slaves) + public RedisResolver(IEnumerable masters, IEnumerable replicas) { ResetMasters(masters.ToList()); - ResetSlaves(slaves.ToList()); + ResetSlaves(replicas.ToList()); ClientFactory = RedisConfig.ClientFactory; } @@ -67,14 +62,14 @@ public virtual void ResetSlaves(IEnumerable hosts) ResetSlaves(hosts.ToRedisEndPoints()); } - public virtual void ResetSlaves(List newSlaves) + public virtual void ResetSlaves(List newReplicas) { - slaves = (newSlaves ?? new List()).ToArray(); - ReadOnlyHostsCount = slaves.Length; - newSlaves.Each(x => allHosts.Add(x)); + replicas = (newReplicas ?? new List()).ToArray(); + ReadOnlyHostsCount = replicas.Length; + newReplicas.Each(x => allHosts.Add(x)); if (log.IsDebugEnabled) - log.Debug("New Redis Slaves: " + string.Join(", ", slaves.Map(x => x.GetHostString()))); + log.Debug("New Redis Replicas: " + string.Join(", ", replicas.Map(x => x.GetHostString()))); } public virtual RedisClient CreateRedisClient(RedisEndpoint config, bool master) @@ -83,51 +78,79 @@ public virtual RedisClient CreateRedisClient(RedisEndpoint config, bool master) if (master && RedisConfig.VerifyMasterConnections) { - var role = client.GetServerRole(); - if (role != RedisServerRole.Master) + var firstAttempt = DateTime.UtcNow; + Exception firstEx = null; + var retryTimeSpan = TimeSpan.FromMilliseconds(config.RetryTimeout); + var i = 0; + while (DateTime.UtcNow - firstAttempt < retryTimeSpan) { - Interlocked.Increment(ref RedisState.TotalInvalidMasters); - log.Error("Redis Master Host '{0}' is {1}. Resetting allHosts...".Fmt(config.GetHostString(), role)); - var newMasters = new List(); - var newSlaves = new List(); - RedisClient masterClient = null; - foreach (var hostConfig in allHosts) + try { - try - { - var testClient = ClientFactory(hostConfig); - testClient.ConnectTimeout = RedisConfig.HostLookupTimeoutMs; - var testRole = testClient.GetServerRole(); - switch (testRole) - { - case RedisServerRole.Master: - newMasters.Add(hostConfig); - if (masterClient == null) - masterClient = testClient; - break; - case RedisServerRole.Slave: - newSlaves.Add(hostConfig); - break; - } - - } - catch { /* skip */ } + client = GetValidMaster(client, config); + return client; + } + catch (Exception ex) + { + if (!RedisConfig.RetryReconnectOnFailedMasters) + throw; + + firstEx ??= ex; + ExecUtils.SleepBackOffMultiplier(++i); + client?.Dispose(); + client = ClientFactory(config); } + } + throw new TimeoutException($"Could not resolve master instance within {config.RetryTimeout}ms RetryTimeout", firstEx); + } + + return client; + } - if (masterClient == null) + protected RedisClient GetValidMaster(RedisClient client, RedisEndpoint config) + { + var role = client.GetServerRole(); + if (role != RedisServerRole.Master) + { + Interlocked.Increment(ref RedisState.TotalInvalidMasters); + log.Error("Redis Master Host '{0}' is {1}. Resetting allHosts...".Fmt(config.GetHostString(), role)); + var newMasters = new List(); + var newReplicas = new List(); + RedisClient masterClient = null; + foreach (var hostConfig in allHosts) + { + try { - Interlocked.Increment(ref RedisState.TotalNoMastersFound); - var errorMsg = "No master found in: " + string.Join(", ", allHosts.Map(x => x.GetHostString())); - log.Error(errorMsg); - throw new Exception(errorMsg); + var testClient = ClientFactory(hostConfig); + testClient.ConnectTimeout = RedisConfig.HostLookupTimeoutMs; + var testRole = testClient.GetServerRole(); + switch (testRole) + { + case RedisServerRole.Master: + newMasters.Add(hostConfig); + if (masterClient == null) + masterClient = testClient; + break; + case RedisServerRole.Slave: + newReplicas.Add(hostConfig); + break; + } + } + catch { /* skip */ } + } - ResetMasters(newMasters); - ResetSlaves(newSlaves); - return masterClient; + if (masterClient == null) + { + Interlocked.Increment(ref RedisState.TotalNoMastersFound); + var errorMsg = "No master found in: " + string.Join(", ", allHosts.Map(x => x.GetHostString())); + log.Error(errorMsg); + throw new InvalidDataException(errorMsg); } - } + ResetMasters(newMasters); + ResetSlaves(newReplicas); + return masterClient; + } return client; } @@ -139,7 +162,7 @@ public RedisEndpoint GetReadWriteHost(int desiredIndex) public RedisEndpoint GetReadOnlyHost(int desiredIndex) { return ReadOnlyHostsCount > 0 - ? slaves[desiredIndex % slaves.Length] + ? replicas[desiredIndex % replicas.Length] : GetReadWriteHost(desiredIndex); } diff --git a/src/ServiceStack.Redis/RedisResponseException.cs b/src/ServiceStack.Redis/RedisResponseException.cs index 00e5c537..c7dba956 100644 --- a/src/ServiceStack.Redis/RedisResponseException.cs +++ b/src/ServiceStack.Redis/RedisResponseException.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // diff --git a/src/ServiceStack.Redis/TemplateRedisFilters.cs b/src/ServiceStack.Redis/RedisScripts.cs similarity index 74% rename from src/ServiceStack.Redis/TemplateRedisFilters.cs rename to src/ServiceStack.Redis/RedisScripts.cs index 04deca56..7def2bd9 100644 --- a/src/ServiceStack.Redis/TemplateRedisFilters.cs +++ b/src/ServiceStack.Redis/RedisScripts.cs @@ -2,8 +2,7 @@ using System.Collections; using System.Linq; using System.Collections.Generic; -using ServiceStack.Configuration; -using ServiceStack.Templates; +using ServiceStack.Script; namespace ServiceStack.Redis { @@ -21,8 +20,13 @@ public class RedisSearchResult public long Size { get; set; } } - public class TemplateRedisFilters : TemplateFilter + [Obsolete("Use RedisScripts")] + public class TemplateRedisFilters : RedisScripts {} + + public class RedisScripts : ScriptMethods { + private const string RedisConnection = "__redisConnection"; + private IRedisClientsManager redisManager; public IRedisClientsManager RedisManager { @@ -30,10 +34,19 @@ public IRedisClientsManager RedisManager set => redisManager = value; } - T exec(Func fn, TemplateScopeContext scope, object options) + T exec(Func fn, ScriptScopeContext scope, object options) { try { + if ((options is Dictionary obj && obj.TryGetValue("connectionString", out var oRedisConn)) + || scope.PageResult.Args.TryGetValue(RedisConnection, out oRedisConn)) + { + using (var redis = new RedisClient((string)oRedisConn)) + { + return fn(redis); + } + } + using (var redis = RedisManager.GetClient()) { return fn(redis); @@ -45,6 +58,16 @@ T exec(Func fn, TemplateScopeContext scope, object options) } } + public IgnoreResult useRedis(ScriptScopeContext scope, string redisConnection) + { + if (redisConnection == null) + scope.PageResult.Args.Remove(RedisConnection); + else + scope.PageResult.Args[RedisConnection] = redisConnection; + + return IgnoreResult.Value; + } + static readonly Dictionary cmdArgCounts = new Dictionary(StringComparer.OrdinalIgnoreCase) { { "SET", 3 } }; @@ -94,8 +117,8 @@ object toObject(RedisText r) return r.Text; } - public object redisCall(TemplateScopeContext scope, object redisCommand) => redisCall(scope, redisCommand, null); - public object redisCall(TemplateScopeContext scope, object redisCommand, object options) + public object redisCall(ScriptScopeContext scope, object redisCommand) => redisCall(scope, redisCommand, null); + public object redisCall(ScriptScopeContext scope, object redisCommand, object options) { if (redisCommand == null) return null; @@ -126,8 +149,8 @@ public object redisCall(TemplateScopeContext scope, object redisCommand, object return result; } - public List redisSearchKeys(TemplateScopeContext scope, string query) => redisSearchKeys(scope, query, null); - public List redisSearchKeys(TemplateScopeContext scope, string query, object options) + public List redisSearchKeys(ScriptScopeContext scope, string query) => redisSearchKeys(scope, query, null); + public List redisSearchKeys(ScriptScopeContext scope, string query, object options) { var json = redisSearchKeysAsJson(scope, query, options); const string noResult = "{\"cursor\":0,\"results\":{}}"; @@ -138,20 +161,20 @@ public List redisSearchKeys(TemplateScopeContext scope, strin return searchResults.Results; } - public Dictionary redisInfo(TemplateScopeContext scope) => redisInfo(scope, null); - public Dictionary redisInfo(TemplateScopeContext scope, object options) => exec(r => r.Info, scope, options); + public Dictionary redisInfo(ScriptScopeContext scope) => redisInfo(scope, null); + public Dictionary redisInfo(ScriptScopeContext scope, object options) => exec(r => r.Info, scope, options); - public string redisConnectionString(TemplateScopeContext scope) => exec(r => $"{r.Host}:{r.Port}?db={r.Db}", scope, null); + public string redisConnectionString(ScriptScopeContext scope) => exec(r => $"{r.Host}:{r.Port}?db={r.Db}", scope, null); - public Dictionary redisConnection(TemplateScopeContext scope) => exec(r => new Dictionary + public Dictionary redisConnection(ScriptScopeContext scope) => exec(r => new Dictionary { { "host", r.Host }, { "port", r.Port }, { "db", r.Db }, }, scope, null); - public string redisToConnectionString(TemplateScopeContext scope, object connectionInfo) => redisToConnectionString(scope, connectionInfo, null); - public string redisToConnectionString(TemplateScopeContext scope, object connectionInfo, object options) + public string redisToConnectionString(ScriptScopeContext scope, object connectionInfo) => redisToConnectionString(scope, connectionInfo, null); + public string redisToConnectionString(ScriptScopeContext scope, object connectionInfo, object options) { var connectionString = connectionInfo as string; if (connectionString != null) @@ -172,8 +195,8 @@ public string redisToConnectionString(TemplateScopeContext scope, object connect return connectionString; } - public string redisChangeConnection(TemplateScopeContext scope, object newConnection) => redisChangeConnection(scope, newConnection, null); - public string redisChangeConnection(TemplateScopeContext scope, object newConnection, object options) + public string redisChangeConnection(ScriptScopeContext scope, object newConnection) => redisChangeConnection(scope, newConnection, null); + public string redisChangeConnection(ScriptScopeContext scope, object newConnection, object options) { try { @@ -196,7 +219,7 @@ public string redisChangeConnection(TemplateScopeContext scope, object newConnec } } - public string redisSearchKeysAsJson(TemplateScopeContext scope, string query, object options) + public string redisSearchKeysAsJson(ScriptScopeContext scope, string query, object options) { if (string.IsNullOrEmpty(query)) return null; diff --git a/src/ServiceStack.Redis/RedisSentinel.cs b/src/ServiceStack.Redis/RedisSentinel.cs index eb0b677a..bdac92c8 100644 --- a/src/ServiceStack.Redis/RedisSentinel.cs +++ b/src/ServiceStack.Redis/RedisSentinel.cs @@ -2,7 +2,7 @@ // Redis Sentinel will connect to a Redis Sentinel Instance and create an IRedisClientsManager based off of the first sentinel that returns data // // Upon failure of a sentinel, other sentinels will be attempted to be connected to -// Upon a s_down event, the RedisClientsManager will be failed over to the new set of slaves/masters +// Upon a s_down event, the RedisClientsManager will be failed over to the new set of masters/replicas // using System; @@ -42,10 +42,15 @@ public class RedisSentinel : IRedisSentinel public Func RedisManagerFactory { get; set; } /// - /// Configure the Redis Connection String to use for a Redis Client Host + /// Configure the Redis Connection String to use for a Redis Instance Host /// public Func HostFilter { get; set; } + /// + /// Configure the Redis Connection String to use for a Redis Sentinel Host + /// + public Func SentinelHostFilter { get; set; } + /// /// The configured Redis Client Manager this Sentinel managers /// @@ -74,54 +79,56 @@ public class RedisSentinel : IRedisSentinel /// /// Whether to routinely scan for other sentinel hosts (default true) /// - public bool ScanForOtherSentinels { get; set; } + public bool ScanForOtherSentinels { get; set; } = true; /// /// What interval to scan for other sentinel hosts (default 10 mins) /// - public TimeSpan RefreshSentinelHostsAfter { get; set; } + public TimeSpan RefreshSentinelHostsAfter { get; set; } = TimeSpan.FromMinutes(10); private DateTime lastSentinelsRefresh; /// /// How long to wait after failing before connecting to next redis instance (default 250ms) /// - public TimeSpan WaitBetweenFailedHosts { get; set; } + public TimeSpan WaitBetweenFailedHosts { get; set; } = TimeSpan.FromMilliseconds(250); /// /// How long to retry connecting to hosts before throwing (default 60 secs) /// - public TimeSpan MaxWaitBetweenFailedHosts { get; set; } + public TimeSpan MaxWaitBetweenFailedHosts { get; set; } = TimeSpan.FromSeconds(60); /// /// How long to wait after consecutive failed connection attempts to master before forcing /// a Sentinel to failover the current master (default 60 secs) /// - public TimeSpan WaitBeforeForcingMasterFailover { get; set; } + public TimeSpan WaitBeforeForcingMasterFailover { get; set; } = TimeSpan.FromSeconds(60); /// - /// The Max Connection time for Sentinel Worker (default 100ms) + /// The Max Connection time for Sentinel Worker (default 250ms) /// - public int SentinelWorkerConnectTimeoutMs { get; set; } + public int SentinelWorkerConnectTimeoutMs { get; set; } = 250; /// - /// The Max TCP Socket Receive time for Sentinel Worker (default 100ms) + /// The Max TCP Socket Receive time for Sentinel Worker (default 250ms) /// - public int SentinelWorkerReceiveTimeoutMs { get; set; } + public int SentinelWorkerReceiveTimeoutMs { get; set; } = 250; /// - /// The Max TCP Socket Send time for Sentinel Worker (default 100ms) + /// The Max TCP Socket Send time for Sentinel Worker (default 250ms) /// - public int SentinelWorkerSendTimeoutMs { get; set; } + public int SentinelWorkerSendTimeoutMs { get; set; } = 250; /// /// Reset client connections when Sentinel reports redis instance is subjectively down (default true) /// - public bool ResetWhenSubjectivelyDown { get; set; } + public bool ResetWhenSubjectivelyDown { get; set; } = true; /// /// Reset client connections when Sentinel reports redis instance is objectively down (default true) /// - public bool ResetWhenObjectivelyDown { get; set; } + public bool ResetWhenObjectivelyDown { get; set; } = true; + + internal string DebugId => $""; public RedisSentinel(string sentinelHost = null, string masterName = null) : this(new[] { sentinelHost ?? DefaultAddress }, masterName ?? DefaultMasterName) { } @@ -135,17 +142,7 @@ public RedisSentinel(IEnumerable sentinelHosts, string masterName = null this.masterName = masterName ?? DefaultMasterName; IpAddressMap = new Dictionary(); - RedisManagerFactory = (masters, slaves) => new PooledRedisClientManager(masters, slaves); - ScanForOtherSentinels = true; - RefreshSentinelHostsAfter = TimeSpan.FromMinutes(10); - ResetWhenObjectivelyDown = true; - ResetWhenSubjectivelyDown = true; - SentinelWorkerConnectTimeoutMs = 100; - SentinelWorkerReceiveTimeoutMs = 100; - SentinelWorkerSendTimeoutMs = 100; - WaitBetweenFailedHosts = TimeSpan.FromMilliseconds(250); - MaxWaitBetweenFailedHosts = TimeSpan.FromSeconds(60); - WaitBeforeForcingMasterFailover = TimeSpan.FromSeconds(60); + RedisManagerFactory = (masters, replicas) => new PooledRedisClientManager(masters, replicas); } /// @@ -193,15 +190,18 @@ public List GetActiveSentinelHosts(IEnumerable sentinelHosts) var endpoint = sentinelHost.ToRedisEndpoint(defaultPort: RedisConfig.DefaultPortSentinel); using (var sentinelWorker = new RedisSentinelWorker(this, endpoint)) { - var activeHosts = sentinelWorker.GetSentinelHosts(MasterName); - if (!activeSentinelHosts.Contains(sentinelHost)) activeSentinelHosts.Add(sentinelHost); + var activeHosts = sentinelWorker.GetSentinelHosts(MasterName); foreach (var activeHost in activeHosts) { if (!activeSentinelHosts.Contains(activeHost)) - activeSentinelHosts.Add(activeHost); + { + activeSentinelHosts.Add(SentinelHostFilter != null + ? SentinelHostFilter(activeHost) + : activeHost); + } } } @@ -274,11 +274,11 @@ public SentinelInfo ResetClients() private IRedisClientsManager CreateRedisManager(SentinelInfo sentinelInfo) { var masters = ConfigureHosts(sentinelInfo.RedisMasters); - var slaves = ConfigureHosts(sentinelInfo.RedisSlaves); - var redisManager = RedisManagerFactory(masters, slaves); + var replicas = ConfigureHosts(sentinelInfo.RedisSlaves); + var redisManager = RedisManagerFactory(masters, replicas); var hasRedisResolver = (IHasRedisResolver)redisManager; - hasRedisResolver.RedisResolver = new RedisSentinelResolver(this, masters, slaves); + hasRedisResolver.RedisResolver = new RedisSentinelResolver(this, masters, replicas); if (redisManager is IRedisFailover canFailover && this.OnFailover != null) { @@ -287,10 +287,8 @@ private IRedisClientsManager CreateRedisManager(SentinelInfo sentinelInfo) return redisManager; } - public IRedisClientsManager GetRedisManager() - { - return RedisManager ?? (RedisManager = CreateRedisManager(GetSentinelInfo())); - } + public IRedisClientsManager GetRedisManager() => + RedisManager ??= CreateRedisManager(GetSentinelInfo()); private RedisSentinelWorker GetValidSentinelWorker() { @@ -304,17 +302,31 @@ private RedisSentinelWorker GetValidSentinelWorker() while (this.worker == null && ShouldRetry()) { + var step = 0; try { this.worker = GetNextSentinel(); + step = 1; GetRedisManager(); + step = 2; this.worker.BeginListeningForConfigurationChanges(); this.failures = 0; //reset return this.worker; } catch (RedisException ex) { + if (Log.IsDebugEnabled) + { + var name = step switch { + 0 => "GetNextSentinel()", + 1 => "GetRedisManager()", + 2 => "BeginListeningForConfigurationChanges()", + _ => $"Step {step}", + }; + Log.Debug($"Failed to {name}: {ex.Message}"); + } + if (OnWorkerError != null) OnWorkerError(ex); @@ -348,7 +360,7 @@ public RedisEndpoint GetMaster() public List GetSlaves() { var sentinelWorker = GetValidSentinelWorker(); - var hosts = sentinelWorker.GetSlaveHosts(masterName); + var hosts = sentinelWorker.GetReplicaHosts(masterName); return ConfigureHosts(hosts).Map(x => x.ToRedisEndpoint()); } @@ -378,6 +390,9 @@ private RedisSentinelWorker GetNextSentinel() if (++sentinelIndex >= SentinelEndpoints.Length) sentinelIndex = 0; + + if (Log.IsDebugEnabled) + Log.Debug($"Attempt to connect to next sentinel '{SentinelEndpoints[sentinelIndex]}'..."); var sentinelWorker = new RedisSentinelWorker(this, SentinelEndpoints[sentinelIndex]) { @@ -434,15 +449,15 @@ public class SentinelInfo public string[] RedisMasters { get; set; } public string[] RedisSlaves { get; set; } - public SentinelInfo(string masterName, IEnumerable redisMasters, IEnumerable redisSlaves) + public SentinelInfo(string masterName, IEnumerable redisMasters, IEnumerable redisReplicas) { MasterName = masterName; RedisMasters = redisMasters?.ToArray() ?? TypeConstants.EmptyStringArray; - RedisSlaves = redisSlaves?.ToArray() ?? TypeConstants.EmptyStringArray; + RedisSlaves = redisReplicas?.ToArray() ?? TypeConstants.EmptyStringArray; } public override string ToString() { - return $"{MasterName} masters: {string.Join(", ", RedisMasters)}, slaves: {string.Join(", ", RedisSlaves)}"; + return $"{MasterName} primary: {string.Join(", ", RedisMasters)}, replicas: {string.Join(", ", RedisSlaves)}"; } } diff --git a/src/ServiceStack.Redis/RedisSentinelResolver.cs b/src/ServiceStack.Redis/RedisSentinelResolver.cs index 0c6f86f9..e0baf03a 100644 --- a/src/ServiceStack.Redis/RedisSentinelResolver.cs +++ b/src/ServiceStack.Redis/RedisSentinelResolver.cs @@ -23,22 +23,22 @@ public class RedisSentinelResolver : IRedisResolver, IRedisResolverExtended private RedisSentinel sentinel; private RedisEndpoint[] masters; - private RedisEndpoint[] slaves; + private RedisEndpoint[] replicas; public RedisEndpoint[] Masters => masters; - public RedisEndpoint[] Slaves => slaves; + public RedisEndpoint[] Slaves => replicas; public RedisSentinelResolver(RedisSentinel sentinel) : this(sentinel, TypeConstants.EmptyArray, TypeConstants.EmptyArray) { } - public RedisSentinelResolver(RedisSentinel sentinel, IEnumerable masters, IEnumerable slaves) - : this(sentinel, masters.ToRedisEndPoints(), slaves.ToRedisEndPoints()) { } + public RedisSentinelResolver(RedisSentinel sentinel, IEnumerable masters, IEnumerable replicas) + : this(sentinel, masters.ToRedisEndPoints(), replicas.ToRedisEndPoints()) { } - public RedisSentinelResolver(RedisSentinel sentinel, IEnumerable masters, IEnumerable slaves) + public RedisSentinelResolver(RedisSentinel sentinel, IEnumerable masters, IEnumerable replicas) { this.sentinel = sentinel; ResetMasters(masters.ToList()); - ResetSlaves(slaves.ToList()); + ResetSlaves(replicas.ToList()); ClientFactory = RedisConfig.ClientFactory; } @@ -65,14 +65,14 @@ public virtual void ResetSlaves(IEnumerable hosts) ResetSlaves(hosts.ToRedisEndPoints()); } - public virtual void ResetSlaves(List newSlaves) + public virtual void ResetSlaves(List newReplicas) { - slaves = (newSlaves ?? new List()).ToArray(); - ReadOnlyHostsCount = slaves.Length; - newSlaves.Each(x => allHosts.Add(x)); + replicas = (newReplicas ?? new List()).ToArray(); + ReadOnlyHostsCount = replicas.Length; + newReplicas.Each(x => allHosts.Add(x)); if (log.IsDebugEnabled) - log.Debug("New Redis Slaves: " + string.Join(", ", slaves.Map(x => x.GetHostString()))); + log.Debug("New Redis Replicas: " + string.Join(", ", replicas.Map(x => x.GetHostString()))); } public RedisEndpoint GetReadWriteHost(int desiredIndex) @@ -82,12 +82,12 @@ public RedisEndpoint GetReadWriteHost(int desiredIndex) public RedisEndpoint GetReadOnlyHost(int desiredIndex) { - var slavesEndpoints = sentinel.GetSlaves(); - if (slavesEndpoints.Count > 0) - return slavesEndpoints[desiredIndex % slavesEndpoints.Count]; + var replicaEndpoints = sentinel.GetSlaves(); + if (replicaEndpoints.Count > 0) + return replicaEndpoints[desiredIndex % replicaEndpoints.Count]; return ReadOnlyHostsCount > 0 - ? slaves[desiredIndex % slaves.Length] + ? replicas[desiredIndex % replicas.Length] : GetReadWriteHost(desiredIndex); } @@ -194,7 +194,7 @@ public virtual RedisClient CreateRedisClient(RedisEndpoint config, bool master) log.Error("Redis Master Host '{0}' is {1}. Resetting allHosts...".Fmt(config.GetHostString(), role), ex); var newMasters = new List(); - var newSlaves = new List(); + var newReplicas = new List(); RedisClient masterClient = null; foreach (var hostConfig in allHosts) { @@ -211,7 +211,7 @@ public virtual RedisClient CreateRedisClient(RedisEndpoint config, bool master) masterClient = testClient; break; case RedisServerRole.Slave: - newSlaves.Add(hostConfig); + newReplicas.Add(hostConfig); break; } @@ -228,7 +228,7 @@ public virtual RedisClient CreateRedisClient(RedisEndpoint config, bool master) } ResetMasters(newMasters); - ResetSlaves(newSlaves); + ResetSlaves(newReplicas); return masterClient; } } diff --git a/src/ServiceStack.Redis/RedisSentinelWorker.cs b/src/ServiceStack.Redis/RedisSentinelWorker.cs index 22ee9053..978ebf37 100644 --- a/src/ServiceStack.Redis/RedisSentinelWorker.cs +++ b/src/ServiceStack.Redis/RedisSentinelWorker.cs @@ -12,11 +12,12 @@ internal class RedisSentinelWorker : IDisposable static int IdCounter = 0; public int Id { get; } - private readonly object oLock = new object(); + private readonly object oLock = new(); + private readonly RedisEndpoint sentinelEndpoint; private readonly RedisSentinel sentinel; private readonly RedisClient sentinelClient; - private RedisPubSubServer sentinePubSub; + private RedisPubSubServer sentinelPubSub; public Action OnSentinelError; @@ -24,6 +25,7 @@ public RedisSentinelWorker(RedisSentinel sentinel, RedisEndpoint sentinelEndpoin { this.Id = Interlocked.Increment(ref IdCounter); this.sentinel = sentinel; + this.sentinelEndpoint = sentinelEndpoint; this.sentinelClient = new RedisClient(sentinelEndpoint) { Db = 0, //Sentinel Servers doesn't support DB, reset to 0 ConnectTimeout = sentinel.SentinelWorkerConnectTimeoutMs, @@ -32,7 +34,7 @@ public RedisSentinelWorker(RedisSentinel sentinel, RedisEndpoint sentinelEndpoin }; if (Log.IsDebugEnabled) - Log.Debug("Set up Redis Sentinel on {0}".Fmt(sentinelEndpoint)); + Log.Debug($"Set up Redis Sentinel on {sentinelEndpoint}"); } /// @@ -43,7 +45,7 @@ public RedisSentinelWorker(RedisSentinel sentinel, RedisEndpoint sentinelEndpoin private void SentinelMessageReceived(string channel, string message) { if (Log.IsDebugEnabled) - Log.Debug("Received '{0}' on channel '{1}' from Sentinel".Fmt(channel, message)); + Log.Debug($"Received '{channel}' on channel '{message}' from Sentinel"); // {+|-}sdown is the event for server coming up or down var c = channel.ToLower(); @@ -61,7 +63,7 @@ private void SentinelMessageReceived(string channel, string message) || (sentinel.ResetWhenObjectivelyDown && isObjectivelyDown)) { if (Log.IsDebugEnabled) - Log.Debug("Sentinel detected server down/up '{0}' with message: {1}".Fmt(channel, message)); + Log.Debug($"Sentinel detected server down/up '{channel}' with message: {message}"); sentinel.ResetClients(); } @@ -79,7 +81,7 @@ internal SentinelInfo GetSentinelInfo() var sentinelInfo = new SentinelInfo( sentinel.MasterName, new[] { masterHost }, - GetSlaveHosts(sentinel.MasterName)); + GetReplicaHosts(sentinel.MasterName)); return sentinelInfo; } @@ -130,24 +132,24 @@ internal List GetSentinelHosts(string masterName) return SanitizeHostsConfig(sentinelSentinels); } - internal List GetSlaveHosts(string masterName) + internal List GetReplicaHosts(string masterName) { - List> sentinelSlaves; + List> sentinelReplicas; lock (oLock) - sentinelSlaves = sentinelClient.SentinelSlaves(sentinel.MasterName); + sentinelReplicas = sentinelClient.SentinelSlaves(sentinel.MasterName); - return SanitizeHostsConfig(sentinelSlaves); + return SanitizeHostsConfig(sentinelReplicas); } - private List SanitizeHostsConfig(IEnumerable> slaves) + private List SanitizeHostsConfig(IEnumerable> replicas) { var servers = new List(); - foreach (var slave in slaves) + foreach (var replica in replicas) { - slave.TryGetValue("flags", out var flags); - slave.TryGetValue("ip", out var ip); - slave.TryGetValue("port", out var port); + replica.TryGetValue("flags", out var flags); + replica.TryGetValue("ip", out var ip); + replica.TryGetValue("port", out var port); if (sentinel.IpAddressMap.TryGetValue(ip, out var aliasIp)) ip = aliasIp; @@ -166,14 +168,19 @@ public void BeginListeningForConfigurationChanges() { lock (oLock) { - if (this.sentinePubSub == null) + if (this.sentinelPubSub == null) { - var sentinelManager = new BasicRedisClientManager(sentinel.SentinelHosts, sentinel.SentinelHosts) + var currentSentinelHost = new[] {sentinelEndpoint}; + var sentinelManager = new BasicRedisClientManager(currentSentinelHost, currentSentinelHost) { //Use BasicRedisResolver which doesn't validate non-Master Sentinel instances - RedisResolver = new BasicRedisResolver(sentinel.SentinelEndpoints, sentinel.SentinelEndpoints) + RedisResolver = new BasicRedisResolver(currentSentinelHost, currentSentinelHost) }; - this.sentinePubSub = new RedisPubSubServer(sentinelManager) + + if (Log.IsDebugEnabled) + Log.Debug($"Starting subscription to {sentinel.SentinelHosts.ToArray()}, replicas: {sentinel.SentinelHosts.ToArray()}..."); + + this.sentinelPubSub = new RedisPubSubServer(sentinelManager) { HeartbeatInterval = null, IsSentinelSubscription = true, @@ -183,11 +190,11 @@ public void BeginListeningForConfigurationChanges() } } - this.sentinePubSub.Start(); + this.sentinelPubSub.Start(); } catch (Exception ex) { - Log.Error($"Error Subscribing to Redis Channel on {this.sentinelClient.Host}:{this.sentinelClient.Port}", ex); + Log.Error($"Error Subscribing to Redis Channel on {sentinelClient.Host}:{sentinelClient.Port}", ex); if (OnSentinelError != null) OnSentinelError(ex); @@ -202,7 +209,7 @@ public void ForceMasterFailover(string masterName) public void Dispose() { - new IDisposable[] { this.sentinelClient, sentinePubSub }.Dispose(Log); + new IDisposable[] { this.sentinelClient, sentinelPubSub }.Dispose(Log); } } } diff --git a/src/ServiceStack.Redis/RedisSubscription.Async.cs b/src/ServiceStack.Redis/RedisSubscription.Async.cs new file mode 100644 index 00000000..95a861f9 --- /dev/null +++ b/src/ServiceStack.Redis/RedisSubscription.Async.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis +{ + partial class RedisSubscription + : IRedisSubscriptionAsync + { + // private events here for +/- semantics + private event Func OnSubscribeAsync; + private event Func OnMessageAsync; + private event Func OnMessageBytesAsync; + private event Func OnUnSubscribeAsync; + + event Func IRedisSubscriptionAsync.OnSubscribeAsync + { + add => OnSubscribeAsync += value; + remove => OnSubscribeAsync -= value; + } + event Func IRedisSubscriptionAsync.OnMessageAsync + { + add => OnMessageAsync += value; + remove => OnMessageAsync -= value; + } + event Func IRedisSubscriptionAsync.OnMessageBytesAsync + { + add => OnMessageBytesAsync += value; + remove => OnMessageBytesAsync -= value; + } + event Func IRedisSubscriptionAsync.OnUnSubscribeAsync + { + add => OnUnSubscribeAsync += value; + remove => OnUnSubscribeAsync -= value; + } + + private IRedisSubscriptionAsync AsAsync() => this; + private IRedisNativeClientAsync NativeAsync + { + get + { + return redisClient as IRedisNativeClientAsync ?? NotAsync(); + static IRedisNativeClientAsync NotAsync() => throw new InvalidOperationException("The underlying client is not async"); + } + } + + private async ValueTask UnSubscribeFromAllChannelsMatchingAnyPatternsAsync(CancellationToken token = default) + { + if (activeChannels.Count == 0) return; + + var multiBytes = await NativeAsync.PUnSubscribeAsync(Array.Empty(), token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + + this.activeChannels = new List(); + } + + ValueTask IAsyncDisposable.DisposeAsync() => IsPSubscription + ? UnSubscribeFromAllChannelsMatchingAnyPatternsAsync() + : AsAsync().UnSubscribeFromAllChannelsAsync(); + + async ValueTask IRedisSubscriptionAsync.SubscribeToChannelsAsync(string[] channels, CancellationToken token) + { + var multiBytes = await NativeAsync.SubscribeAsync(channels, token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + + while (this.SubscriptionCount > 0) + { + multiBytes = await NativeAsync.ReceiveMessagesAsync(token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + } + } + + async ValueTask IRedisSubscriptionAsync.SubscribeToChannelsMatchingAsync(string[] patterns, CancellationToken token) + { + var multiBytes = await NativeAsync.PSubscribeAsync(patterns, token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + + while (this.SubscriptionCount > 0) + { + multiBytes = await NativeAsync.ReceiveMessagesAsync(token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + } + } + + async ValueTask IRedisSubscriptionAsync.UnSubscribeFromAllChannelsAsync(CancellationToken token) + { + if (activeChannels.Count == 0) return; + + var multiBytes = await NativeAsync.UnSubscribeAsync(Array.Empty(), token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + + this.activeChannels = new List(); + } + + async ValueTask IRedisSubscriptionAsync.UnSubscribeFromChannelsAsync(string[] channels, CancellationToken token) + { + var multiBytes = await NativeAsync.UnSubscribeAsync(channels, token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + } + + async ValueTask IRedisSubscriptionAsync.UnSubscribeFromChannelsMatchingAsync(string[] patterns, CancellationToken token) + { + var multiBytes = await NativeAsync.PUnSubscribeAsync(patterns, token).ConfigureAwait(false); + await ParseSubscriptionResultsAsync(multiBytes).ConfigureAwait(false); + } + + private async ValueTask ParseSubscriptionResultsAsync(byte[][] multiBytes) + { + int componentsPerMsg = IsPSubscription ? 4 : 3; + for (var i = 0; i < multiBytes.Length; i += componentsPerMsg) + { + var messageType = multiBytes[i]; + var channel = multiBytes[i + 1].FromUtf8Bytes(); + if (SubscribeWord.AreEqual(messageType) + || PSubscribeWord.AreEqual(messageType)) + { + IsPSubscription = PSubscribeWord.AreEqual(messageType); + + this.SubscriptionCount = int.Parse(multiBytes[i + MsgIndex].FromUtf8Bytes()); + + activeChannels.Add(channel); + + var tmp = OnSubscribeAsync; + if (tmp is object) await tmp.Invoke(channel).ConfigureAwait(false); + } + else if (UnSubscribeWord.AreEqual(messageType) + || PUnSubscribeWord.AreEqual(messageType)) + { + this.SubscriptionCount = int.Parse(multiBytes[i + 2].FromUtf8Bytes()); + + activeChannels.Remove(channel); + + var tmp = OnUnSubscribeAsync; + if (tmp is object) await tmp.Invoke(channel).ConfigureAwait(false); + } + else if (MessageWord.AreEqual(messageType)) + { + var msgBytes = multiBytes[i + MsgIndex]; + var tmp1 = OnMessageBytesAsync; + if (tmp1 is object) await tmp1.Invoke(channel, msgBytes).ConfigureAwait(false); + + var tmp2 = OnMessageAsync; + if (tmp2 is object) + { + var message = msgBytes.FromUtf8Bytes(); + await tmp2.Invoke(channel, message).ConfigureAwait(false); + } + } + else if (PMessageWord.AreEqual(messageType)) + { + channel = multiBytes[i + 2].FromUtf8Bytes(); + var msgBytes = multiBytes[i + MsgIndex + 1]; + var tmp1 = OnMessageBytesAsync; + if (tmp1 is object) await tmp1.Invoke(channel, msgBytes).ConfigureAwait(false); + + var tmp2 = OnMessageAsync; + if (tmp2 is object) + { + var message = msgBytes.FromUtf8Bytes(); + await tmp2.Invoke(channel, message).ConfigureAwait(false); + } + } + else + { + throw new RedisException( + "Invalid state. Expected [[p]subscribe|[p]unsubscribe|message] got: " + messageType.FromUtf8Bytes()); + } + } + } + + ValueTask IRedisSubscriptionAsync.SubscribeToChannelsAsync(params string[] channels) + => AsAsync().SubscribeToChannelsAsync(channels, token: default); + + ValueTask IRedisSubscriptionAsync.SubscribeToChannelsMatchingAsync(params string[] patterns) + => AsAsync().SubscribeToChannelsMatchingAsync(patterns, token: default); + + ValueTask IRedisSubscriptionAsync.UnSubscribeFromChannelsAsync(params string[] channels) + => AsAsync().UnSubscribeFromChannelsAsync(channels, token: default); + + ValueTask IRedisSubscriptionAsync.UnSubscribeFromChannelsMatchingAsync(params string[] patterns) + => AsAsync().UnSubscribeFromChannelsMatchingAsync(patterns, token: default); + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/RedisSubscription.cs b/src/ServiceStack.Redis/RedisSubscription.cs index a7b03cc7..298b1bf4 100644 --- a/src/ServiceStack.Redis/RedisSubscription.cs +++ b/src/ServiceStack.Redis/RedisSubscription.cs @@ -4,7 +4,7 @@ namespace ServiceStack.Redis { - public class RedisSubscription + public partial class RedisSubscription : IRedisSubscription { private readonly IRedisNativeClient redisClient; @@ -30,6 +30,7 @@ public RedisSubscription(IRedisNativeClient redisClient) public Action OnSubscribe { get; set; } public Action OnMessage { get; set; } + public Action OnMessageBytes { get; set; } public Action OnUnSubscribe { get; set; } public void SubscribeToChannels(params string[] channels) @@ -72,10 +73,7 @@ private void ParseSubscriptionResults(byte[][] multiBytes) activeChannels.Add(channel); - if (this.OnSubscribe != null) - { - this.OnSubscribe(channel); - } + this.OnSubscribe?.Invoke(channel); } else if (UnSubscribeWord.AreEqual(messageType) || PUnSubscribeWord.AreEqual(messageType)) @@ -84,28 +82,24 @@ private void ParseSubscriptionResults(byte[][] multiBytes) activeChannels.Remove(channel); - if (this.OnUnSubscribe != null) - { - this.OnUnSubscribe(channel); - } + this.OnUnSubscribe?.Invoke(channel); } else if (MessageWord.AreEqual(messageType)) { - var message = multiBytes[i + MsgIndex].FromUtf8Bytes(); + var msgBytes = multiBytes[i + MsgIndex]; + this.OnMessageBytes?.Invoke(channel, msgBytes); - if (this.OnMessage != null) - { - this.OnMessage(channel, message); - } + var message = msgBytes.FromUtf8Bytes(); + this.OnMessage?.Invoke(channel, message); } else if (PMessageWord.AreEqual(messageType)) { - var message = multiBytes[i + MsgIndex + 1].FromUtf8Bytes(); channel = multiBytes[i + 2].FromUtf8Bytes(); - if (this.OnMessage != null) - { - this.OnMessage(channel, message); - } + var msgBytes = multiBytes[i + MsgIndex + 1]; + this.OnMessageBytes?.Invoke(channel, msgBytes); + + var message = msgBytes.FromUtf8Bytes(); + this.OnMessage?.Invoke(channel, message); } else { diff --git a/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj b/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj new file mode 100644 index 00000000..a2fe7181 --- /dev/null +++ b/src/ServiceStack.Redis/ServiceStack.Redis.Core.csproj @@ -0,0 +1,35 @@ + + + ServiceStack.Redis.Core + ServiceStack.Redis + ServiceStack.Redis + netstandard2.0;net6.0 + ServiceStack.Redis .NET Standard 2.0 + + .NET Standard 2.0 version of ServiceStack.Redis + + Redis;NoSQL;Client;Distributed;Cache;PubSub;Messaging;Transactions + + + $(DefineConstants);ASYNC_MEMORY + + + $(DefineConstants);ASYNC_MEMORY;NET6_0 + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceStack.Redis/ServiceStack.Redis.Source.csproj b/src/ServiceStack.Redis/ServiceStack.Redis.Source.csproj new file mode 100644 index 00000000..3efa57ed --- /dev/null +++ b/src/ServiceStack.Redis/ServiceStack.Redis.Source.csproj @@ -0,0 +1,44 @@ + + + ServiceStack.Redis + ServiceStack.Redis + netstandard2.0;net6.0 + C# Redis client for the Redis NoSQL DB + + C# Redis Client for the worlds fastest distributed NoSQL datastore. + Byte[], String and POCO Typed clients. + Thread-Safe Basic and Pooled client managers included. + + Redis;NoSQL;Client;Distributed;Cache;PubSub;Messaging;Transactions + false + + + + $(DefineConstants);ASYNC_MEMORY + + + $(DefineConstants);ASYNC_MEMORY;NET6_0 + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ServiceStack.Redis/ServiceStack.Redis.csproj b/src/ServiceStack.Redis/ServiceStack.Redis.csproj index ceb46ff3..4e6274a9 100644 --- a/src/ServiceStack.Redis/ServiceStack.Redis.csproj +++ b/src/ServiceStack.Redis/ServiceStack.Redis.csproj @@ -1,40 +1,36 @@  - net45;netstandard2.0 - ServiceStack.Redis ServiceStack.Redis - false - false - false - false - false - false - false - false - false + ServiceStack.Redis + net472;netstandard2.0;netstandard2.1;net6.0 + C# Redis client for the Redis NoSQL DB + + C# Redis Client for the worlds fastest distributed NoSQL datastore. + Byte[], String and POCO Typed clients. + Thread-Safe Basic and Pooled client managers included. + + Redis;NoSQL;Client;Distributed;Cache;PubSub;Messaging;Transactions + + + + $(DefineConstants);NET472 - - true - true + + $(DefineConstants);NETCORE - - portable + + $(DefineConstants);ASYNC_MEMORY;NETCORE - - $(DefineConstants);NET45 - True - False - ../servicestack.snk + + $(DefineConstants);ASYNC_MEMORY;NETCORE;NET6_0 - + + + + - - - + - - $(DefineConstants);NETSTANDARD1_3 - @@ -42,8 +38,10 @@ - - - + + + + + \ No newline at end of file diff --git a/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientProxy.cs b/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientProxy.cs index 777e3dbc..7ee72602 100644 --- a/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientProxy.cs +++ b/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientProxy.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !(NETSTANDARD2_0 || NETSTANDARD2_1 || NET6_0) using System; using System.Reflection; using System.Runtime.Remoting.Messaging; @@ -7,7 +7,7 @@ namespace ServiceStack.Redis.Support.Diagnostic { /// - /// Dynamically proxies access to the IRedisClient providing events for before & after each method invocation + /// Dynamically proxies access to the IRedisClient providing events for before & after each method invocation /// public class TrackingRedisClientProxy : System.Runtime.Remoting.Proxies.RealProxy { diff --git a/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientsManager.cs b/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientsManager.cs index 7aca6db2..ab8216bd 100644 --- a/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientsManager.cs +++ b/src/ServiceStack.Redis/Support/Diagnostic/TrackingRedisClientsManager.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD2_0 +#if !(NETSTANDARD2_0 || NETSTANDARD2_1 || NET6_0) using System; using System.Collections.Generic; using System.Diagnostics; @@ -15,7 +15,7 @@ namespace ServiceStack.Redis.Support.Diagnostic /// Tracks each IRedisClient instance allocated from the IRedisClientsManager logging when they are allocated and disposed. /// Periodically writes the allocated instances to the log for diagnostic purposes. /// - public class TrackingRedisClientsManager : IRedisClientsManager + public partial class TrackingRedisClientsManager : IRedisClientsManager { private static readonly ILog Logger = LogManager.GetLogger(typeof(TrackingRedisClientsManager)); diff --git a/src/ServiceStack.Redis/Support/Locking/DistributedLock.Async.cs b/src/ServiceStack.Redis/Support/Locking/DistributedLock.Async.cs new file mode 100644 index 00000000..4f79a42b --- /dev/null +++ b/src/ServiceStack.Redis/Support/Locking/DistributedLock.Async.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Support.Locking +{ + partial class DistributedLock : IDistributedLockAsync + { + public IDistributedLockAsync AsAsync() => this; + + async ValueTask IDistributedLockAsync.LockAsync(string key, int acquisitionTimeout, int lockTimeout, IRedisClientAsync client, CancellationToken token) + { + long lockExpire = 0; + + // cannot lock on a null key + if (key == null) + return new LockState(LOCK_NOT_ACQUIRED, lockExpire); + + const int sleepIfLockSet = 200; + acquisitionTimeout *= 1000; //convert to ms + int tryCount = (acquisitionTimeout / sleepIfLockSet) + 1; + + var ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)); + var newLockExpire = CalculateLockExpire(ts, lockTimeout); + + var nativeClient = (IRedisNativeClientAsync)client; + long wasSet = await nativeClient.SetNXAsync(key, BitConverter.GetBytes(newLockExpire), token).ConfigureAwait(false); + int totalTime = 0; + while (wasSet == LOCK_NOT_ACQUIRED && totalTime < acquisitionTimeout) + { + int count = 0; + while (wasSet == 0 && count < tryCount && totalTime < acquisitionTimeout) + { + await Task.Delay(sleepIfLockSet).ConfigureAwait(false); + totalTime += sleepIfLockSet; + ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)); + newLockExpire = CalculateLockExpire(ts, lockTimeout); + wasSet = await nativeClient.SetNXAsync(key, BitConverter.GetBytes(newLockExpire), token).ConfigureAwait(false); + count++; + } + // acquired lock! + if (wasSet != LOCK_NOT_ACQUIRED) break; + + // handle possibliity of crashed client still holding the lock + var pipe = client.CreatePipeline(); + await using (pipe.ConfigureAwait(false)) + { + long lockValue = 0; + pipe.QueueCommand(r => ((IRedisNativeClientAsync)r).WatchAsync(new[] { key }, token)); + pipe.QueueCommand(r => ((IRedisNativeClientAsync)r).GetAsync(key, token), x => lockValue = (x != null) ? BitConverter.ToInt64(x, 0) : 0); + await pipe.FlushAsync(token).ConfigureAwait(false); + + // if lock value is 0 (key is empty), or expired, then we can try to acquire it + ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)); + if (lockValue < ts.TotalSeconds) + { + ts = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)); + newLockExpire = CalculateLockExpire(ts, lockTimeout); + var trans = await client.CreateTransactionAsync(token).ConfigureAwait(false); + await using (trans.ConfigureAwait(false)) + { + var expire = newLockExpire; + trans.QueueCommand(r => ((IRedisNativeClientAsync)r).SetAsync(key, BitConverter.GetBytes(expire), token: token)); + if (await trans.CommitAsync(token).ConfigureAwait(false)) + wasSet = LOCK_RECOVERED; //recovered lock! + } + } + else + { + await nativeClient.UnWatchAsync(token).ConfigureAwait(false); + } + } + if (wasSet != LOCK_NOT_ACQUIRED) break; + await Task.Delay(sleepIfLockSet).ConfigureAwait(false); + totalTime += sleepIfLockSet; + } + if (wasSet != LOCK_NOT_ACQUIRED) + { + lockExpire = newLockExpire; + } + return new LockState(wasSet, lockExpire); + } + + async ValueTask IDistributedLockAsync.UnlockAsync(string key, long lockExpire, IRedisClientAsync client, CancellationToken token) + { + if (lockExpire <= 0) + return false; + long lockVal = 0; + var nativeClient = (IRedisNativeClientAsync)client; + var pipe = client.CreatePipeline(); + await using (pipe.ConfigureAwait(false)) + { + pipe.QueueCommand(r => ((IRedisNativeClientAsync)r).WatchAsync(new[] { key }, token)); + pipe.QueueCommand(r => ((IRedisNativeClientAsync)r).GetAsync(key, token), + x => lockVal = (x != null) ? BitConverter.ToInt64(x, 0) : 0); + await pipe.FlushAsync(token).ConfigureAwait(false); + } + + if (lockVal != lockExpire) + { + if (lockVal != 0) + Debug.WriteLine($"Unlock(): Failed to unlock key {key}; lock has been acquired by another client "); + else + Debug.WriteLine($"Unlock(): Failed to unlock key {key}; lock has been identifed as a zombie and harvested "); + await nativeClient.UnWatchAsync(token).ConfigureAwait(false); + return false; + } + + var trans = await client.CreateTransactionAsync(token).ConfigureAwait(false); + await using (trans.ConfigureAwait(false)) + { + trans.QueueCommand(r => ((IRedisNativeClientAsync)r).DelAsync(key, token)); + var rc = await trans.CommitAsync(token).ConfigureAwait(false); + if (!rc) + Debug.WriteLine($"Unlock(): Failed to delete key {key}; lock has been acquired by another client "); + return rc; + } + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Support/Locking/DistributedLock.cs b/src/ServiceStack.Redis/Support/Locking/DistributedLock.cs index 296ee02a..84d1be68 100644 --- a/src/ServiceStack.Redis/Support/Locking/DistributedLock.cs +++ b/src/ServiceStack.Redis/Support/Locking/DistributedLock.cs @@ -3,7 +3,7 @@ namespace ServiceStack.Redis.Support.Locking { - public class DistributedLock : IDistributedLock + public partial class DistributedLock : IDistributedLock { public const int LOCK_NOT_ACQUIRED = 0; public const int LOCK_ACQUIRED = 1; diff --git a/src/ServiceStack.Redis/Support/Locking/IDistributedLock.Async.cs b/src/ServiceStack.Redis/Support/Locking/IDistributedLock.Async.cs new file mode 100644 index 00000000..7b548b82 --- /dev/null +++ b/src/ServiceStack.Redis/Support/Locking/IDistributedLock.Async.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Support.Locking +{ + /// + /// Distributed lock interface + /// + public interface IDistributedLockAsync + { + // note: can't use "out" with async, so return LockState instead + ValueTask LockAsync(string key, int acquisitionTimeout, int lockTimeout, IRedisClientAsync client, CancellationToken token = default); + ValueTask UnlockAsync(string key, long lockExpire, IRedisClientAsync client, CancellationToken token = default); + } + + public readonly struct LockState + { + public long Result { get; } // kinda feels like this should be an enum; leaving alone for API parity (sync vs async) + public long Expiration { get; } + public LockState(long result, long expiration) + { + Result = result; + Expiration = expiration; + } + public override bool Equals(object obj) => throw new NotSupportedException(); + public override int GetHashCode() => throw new NotSupportedException(); + public override string ToString() => nameof(LockState); + + public void Deconstruct(out long result, out long expiration) + { + result = Result; + expiration = Expiration; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Support/ObjectSerializer.cs b/src/ServiceStack.Redis/Support/ObjectSerializer.cs index 07947a62..5c756903 100644 --- a/src/ServiceStack.Redis/Support/ObjectSerializer.cs +++ b/src/ServiceStack.Redis/Support/ObjectSerializer.cs @@ -1,5 +1,5 @@ using System.IO; -#if !NETSTANDARD2_0 +#if !NETCORE using System.Runtime.Serialization.Formatters.Binary; #endif @@ -11,7 +11,7 @@ namespace ServiceStack.Redis.Support /// public class ObjectSerializer : ISerializer { -#if !NETSTANDARD2_0 +#if !NETCORE protected readonly BinaryFormatter bf = new BinaryFormatter(); #endif @@ -23,7 +23,7 @@ public class ObjectSerializer : ISerializer /// public virtual byte[] Serialize(object value) { -#if NETSTANDARD2_0 +#if NETCORE return null; #else if (value == null) @@ -42,7 +42,7 @@ public virtual byte[] Serialize(object value) /// public virtual object Deserialize(byte[] someBytes) { -#if NETSTANDARD2_0 +#if NETCORE return null; #else if (someBytes == null) diff --git a/src/ServiceStack.Redis/Support/OptimizedObjectSerializer.cs b/src/ServiceStack.Redis/Support/OptimizedObjectSerializer.cs index 63b99490..3d4dbf17 100644 --- a/src/ServiceStack.Redis/Support/OptimizedObjectSerializer.cs +++ b/src/ServiceStack.Redis/Support/OptimizedObjectSerializer.cs @@ -122,7 +122,7 @@ SerializedObjectWrapper SerializeToWrapper(object value) break; default: -#if NETSTANDARD2_0 +#if NETCORE data = new byte[0]; length = 0; #else @@ -231,7 +231,7 @@ object Unwrap(SerializedObjectWrapper item) case TypeCode.Object: using (var ms = new MemoryStream(data, offset, count)) { -#if NETSTANDARD2_0 +#if NETCORE return null; #else return bf.Deserialize(ms); diff --git a/src/ServiceStack.Redis/Support/Queue/Implementation/SerializingRedisClient.cs b/src/ServiceStack.Redis/Support/Queue/Implementation/SerializingRedisClient.cs index 2217e173..e53c28cf 100644 --- a/src/ServiceStack.Redis/Support/Queue/Implementation/SerializingRedisClient.cs +++ b/src/ServiceStack.Redis/Support/Queue/Implementation/SerializingRedisClient.cs @@ -61,10 +61,10 @@ public object Deserialize(byte[] someBytes) return serializer.Deserialize(someBytes); } + /// /// deserialize an array of byte arrays /// /// - /// public IList Deserialize(byte[][] byteArray) { IList rc = new ArrayList(); diff --git a/src/ServiceStack.Redis/Transaction/RedisTransaction.Async.cs b/src/ServiceStack.Redis/Transaction/RedisTransaction.Async.cs new file mode 100644 index 00000000..43ca79c8 --- /dev/null +++ b/src/ServiceStack.Redis/Transaction/RedisTransaction.Async.cs @@ -0,0 +1,129 @@ +// +// https://github.com/ServiceStack/ServiceStack.Redis +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. +// +// Licensed under the same terms of ServiceStack. +// + +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Redis.Pipeline; + +namespace ServiceStack.Redis +{ + /// + /// Adds support for Redis Transactions (i.e. MULTI/EXEC/DISCARD operations). + /// + public partial class RedisTransaction + : IRedisTransactionAsync, IRedisQueueCompletableOperationAsync + { + /// + /// Issue exec command (not queued) + /// + private async ValueTask ExecAsync(CancellationToken token) + { + RedisClient.Exec(); + await RedisClient.FlushSendBufferAsync(token).ConfigureAwait(false); + RedisClient.ResetSendBuffer(); + } + + + /// + /// Put "QUEUED" messages at back of queue + /// + partial void QueueExpectQueuedAsync() + { + QueuedCommands.Insert(0, new QueuedRedisOperation + { + }.WithAsyncReadCommand(RedisClient.ExpectQueuedAsync)); + } + + async ValueTask IRedisTransactionAsync.CommitAsync(CancellationToken token) + { + bool rc = true; + try + { + numCommands = QueuedCommands.Count / 2; + + //insert multi command at beginning + QueuedCommands.Insert(0, new QueuedRedisCommand + { + }.WithAsyncReturnCommand(VoidReturnCommandAsync: r => { Init(); return default; }) + .WithAsyncReadCommand(RedisClient.ExpectOkAsync)); + + //the first half of the responses will be "QUEUED", + // so insert reading of multiline after these responses + QueuedCommands.Insert(numCommands + 1, new QueuedRedisOperation + { + OnSuccessIntCallback = handleMultiDataResultCount + }.WithAsyncReadCommand(RedisClient.ReadMultiDataResultCountAsync)); + + // add Exec command at end (not queued) + QueuedCommands.Add(new RedisCommand + { + }.WithAsyncReturnCommand(r => ExecAsync(token))); + + //execute transaction + await ExecAsync(token).ConfigureAwait(false); + + //receive expected results + foreach (var queuedCommand in QueuedCommands) + { + await queuedCommand.ProcessResultAsync(token).ConfigureAwait(false); + } + } + catch (RedisTransactionFailedException) + { + rc = false; + } + finally + { + RedisClient.Transaction = null; + ClosePipeline(); + await RedisClient.AddTypeIdsRegisteredDuringPipelineAsync(token).ConfigureAwait(false); + } + return rc; + } + + ValueTask IRedisTransactionAsync.RollbackAsync(CancellationToken token) + { + Rollback(); // not currently anything different to do on the async path + return default; + } + // note: this also means that Dispose doesn't need to be complex; if Rollback needed + // splitting, we would need to override DisposeAsync and split the code, too + + + private protected override async ValueTask ReplayAsync(CancellationToken token) + { + bool rc = true; + try + { + await ExecuteAsync().ConfigureAwait(false); + + //receive expected results + foreach (var queuedCommand in QueuedCommands) + { + await queuedCommand.ProcessResultAsync(token).ConfigureAwait(false); + } + } + catch (RedisTransactionFailedException) + { + rc = false; + } + finally + { + RedisClient.Transaction = null; + ClosePipeline(); + await RedisClient.AddTypeIdsRegisteredDuringPipelineAsync(token).ConfigureAwait(false); + } + return rc; + } + } +} \ No newline at end of file diff --git a/src/ServiceStack.Redis/Transaction/RedisTransaction.cs b/src/ServiceStack.Redis/Transaction/RedisTransaction.cs index 346759cb..db2c36b2 100644 --- a/src/ServiceStack.Redis/Transaction/RedisTransaction.cs +++ b/src/ServiceStack.Redis/Transaction/RedisTransaction.cs @@ -5,7 +5,7 @@ // Authors: // Demis Bellot (demis.bellot@gmail.com) // -// Copyright 2013 Service Stack LLC. All Rights Reserved. +// Copyright 2013 ServiceStack, Inc. All Rights Reserved. // // Licensed under the same terms of ServiceStack. // @@ -18,12 +18,20 @@ namespace ServiceStack.Redis /// /// Adds support for Redis Transactions (i.e. MULTI/EXEC/DISCARD operations). /// - public class RedisTransaction + public partial class RedisTransaction : RedisAllPurposePipeline, IRedisTransaction, IRedisQueueCompletableOperation { private int numCommands = 0; public RedisTransaction(RedisClient redisClient) - : base(redisClient) {} + : this(redisClient, false) {} + + internal RedisTransaction(RedisClient redisClient, bool isAsync) + : base(redisClient) + { + // if someone casts between sync/async: the sync-over-async or + // async-over-sync is entirely self-inflicted; I can't fix stupid + _isAsync = isAsync; + } protected override void Init() { @@ -161,10 +169,19 @@ public override void Dispose() Rollback(); } + private readonly bool _isAsync; protected override void AddCurrentQueuedOperation() { base.AddCurrentQueuedOperation(); - QueueExpectQueued(); + if (_isAsync) + { + QueueExpectQueuedAsync(); + } + else + { + QueueExpectQueued(); + } } + partial void QueueExpectQueuedAsync(); } } \ No newline at end of file diff --git a/src/ServiceStack.Redis/ValueTask_Utils.Async.cs b/src/ServiceStack.Redis/ValueTask_Utils.Async.cs new file mode 100644 index 00000000..87f6c917 --- /dev/null +++ b/src/ServiceStack.Redis/ValueTask_Utils.Async.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Internal +{ + internal static class ValueTask_Utils + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask Await(this ValueTask pending) + { + if (pending.IsCompletedSuccessfully) + { + _ = pending.Result; // for IValueTaskSource reasons + return default; + } + else + { + return Awaited(pending); + } + static async ValueTask Awaited(ValueTask pending) + => await pending.ConfigureAwait(false); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask Await(this ValueTask pending, Func projection) + { + return pending.IsCompletedSuccessfully ? projection(pending.Result).AsValueTaskResult() : Awaited(pending, projection); + static async ValueTask Awaited(ValueTask pending, Func projection) + => projection(await pending.ConfigureAwait(false)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask AsInt32(this ValueTask pending) + { + return pending.IsCompletedSuccessfully ? (checked((int)pending.Result)).AsValueTaskResult() : Awaited(pending); + static async ValueTask Awaited(ValueTask pending) + => checked((int)await pending.ConfigureAwait(false)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask Await(this ValueTask pending, Func projection, TState state) + { + return pending.IsCompletedSuccessfully ? projection(pending.Result, state).AsValueTaskResult() : Awaited(pending, projection, state); + static async ValueTask Awaited(ValueTask pending, Func projection, TState state) + => projection(await pending.ConfigureAwait(false), state); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask AwaitAsTrue(this ValueTask pending) + { + if (pending.IsCompletedSuccessfully) + { + pending.GetAwaiter().GetResult(); // for IValueTaskSource reasons + return s_ValueTaskTrue; + } + else + { + return Awaited(pending); + } + static async ValueTask Awaited(ValueTask pending) + { + await pending.ConfigureAwait(false); + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Task AwaitAsTrueTask(this ValueTask pending) + { + if (pending.IsCompletedSuccessfully) + { + pending.GetAwaiter().GetResult(); // for IValueTaskSource reasons + return s_TaskTrue; + } + else + { + return Awaited(pending); + } + static async Task Awaited(ValueTask pending) + { + await pending.ConfigureAwait(false); + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask AwaitAsTrue(this ValueTask pending) + { + if (pending.IsCompletedSuccessfully) + { + _ = pending.Result; // for IValueTaskSource reasons + return s_ValueTaskTrue; + } + else + { + return Awaited(pending); + } + static async ValueTask Awaited(ValueTask pending) + { + await pending.ConfigureAwait(false); + return true; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask IsSuccessAsync(this ValueTask pending) + { + return pending.IsCompletedSuccessfully ? (pending.Result == RedisNativeClient.Success).AsValueTaskResult() : Awaited(pending); + static async ValueTask Awaited(ValueTask pending) + => (await pending.ConfigureAwait(false)) == RedisNativeClient.Success; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static Task IsSuccessTaskAsync(this ValueTask pending) + { + return pending.IsCompletedSuccessfully ? (pending.Result == RedisNativeClient.Success ? s_TaskTrue : s_TaskFalse) : Awaited(pending); + static async Task Awaited(ValueTask pending) + => (await pending.ConfigureAwait(false)) == RedisNativeClient.Success; + } + + static readonly Task s_TaskTrue = Task.FromResult(true), s_TaskFalse = Task.FromResult(false); + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask> ConvertEachToAsync(this ValueTask> pending) + { + return pending.IsCompletedSuccessfully ? pending.Result.ConvertEachTo().AsValueTaskResult() : Awaited(pending); + static async ValueTask> Awaited(ValueTask> pending) + => (await pending.ConfigureAwait(false)).ConvertEachTo(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask> ToStringListAsync(this ValueTask pending) + { + return pending.IsCompletedSuccessfully ? pending.Result.ToStringList().AsValueTaskResult() : Awaited(pending); + static async ValueTask> Awaited(ValueTask pending) + => (await pending.ConfigureAwait(false)).ToStringList(); + } + + private static readonly ValueTask s_ValueTaskTrue = true.AsValueTaskResult(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask Await(this ValueTask pending, T result) + { + return pending.IsCompletedSuccessfully ? result.AsValueTaskResult() : Awaited(pending, result); + static async ValueTask Awaited(ValueTask pending, T result) + { + await pending.ConfigureAwait(false); + return result; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask AsValueTaskResult(this T value) => new ValueTask(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ValueTask FromUtf8BytesAsync(this ValueTask pending) + { + return pending.IsCompletedSuccessfully ? pending.Result.FromUtf8Bytes().AsValueTaskResult() : Awaited(pending); + static async ValueTask Awaited(ValueTask pending) + => (await pending.ConfigureAwait(false)).FromUtf8Bytes(); + } + } +} diff --git a/src/StackExchangeTester/App.config b/src/StackExchangeTester/App.config new file mode 100644 index 00000000..731f6de6 --- /dev/null +++ b/src/StackExchangeTester/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/StackExchangeTester/Program.cs b/src/StackExchangeTester/Program.cs new file mode 100644 index 00000000..def9166c --- /dev/null +++ b/src/StackExchangeTester/Program.cs @@ -0,0 +1,22 @@ +using ServiceStack.Redis; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace StackExchangeTester +{ + class Program + { + static void Main(string[] args) + { + var x = new RedisManagerPool("MQHnkdl402DScXzhZIxHwDaA7s8nziy45okp84ykShA=@tls-11.redis.cache.windows.net:6380?ssl=true&sslprotocols=Tls11"); + var y = x.GetClient(); + y.Ping(); + y.Set("keyServiceStackSllChangesIStillHave512mb", "value"); + y.Dispose(); + x.Dispose(); + } + } +} diff --git a/src/StackExchangeTester/StackExchangeTester.csproj b/src/StackExchangeTester/StackExchangeTester.csproj new file mode 100644 index 00000000..83e8e98a --- /dev/null +++ b/src/StackExchangeTester/StackExchangeTester.csproj @@ -0,0 +1,85 @@ + + + + + Debug + AnyCPU + {0214D0F0-EA41-4593-B558-71F974CF7C62} + Exe + StackExchangeTester + StackExchangeTester + v4.6.1 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + {8368c965-b4f6-4263-9abb-731a175b2e77} + Console.Tests + + + {91c55091-a946-49b5-9517-8794ebcc5784} + ServiceStack.Redis.Tests.Sentinel + + + {951d28ee-5d22-4c62-ac0f-1661a8ceec5a} + ServiceStack.Redis.Tests + + + {af99f19b-4c04-4f58-81ef-b092f1fcc540} + ServiceStack.Redis + + + + + {EDE973DE-4C9A-11DE-A33F-06DC55D89593} + 1 + 0 + 0 + tlbimp + False + True + + + + + + + \ No newline at end of file diff --git a/tests/Console.Tests/App.config b/tests/Console.Tests/App.config deleted file mode 100644 index 2c98e0db..00000000 --- a/tests/Console.Tests/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/Console.Tests/BlockingPop.cs b/tests/Console.Tests/BlockingPop.cs index 7a1ba68f..90aef4c5 100644 --- a/tests/Console.Tests/BlockingPop.cs +++ b/tests/Console.Tests/BlockingPop.cs @@ -25,7 +25,7 @@ public void Execute() RedisConfig.VerifyMasterConnections = true; RedisConfig.HostLookupTimeoutMs = 1000; RedisConfig.DeactivatedClientsExpiry = TimeSpan.FromSeconds(15); - RedisConfig.DisableVerboseLogging = false; + RedisConfig.EnableVerboseLogging = true; var redisManager = new RedisManagerPool("localhost?connectTimeout=1000"); @@ -49,7 +49,7 @@ public void Execute() // add items to list for (int i = 1; i <= items; i++) { - redis.PushItemToList(listId, "item {0}".Fmt(i)); + redis.PushItemToList(listId, $"item {i}"); } do @@ -57,7 +57,7 @@ public void Execute() var item = redis.BlockingPopItemFromList(listId, null); // log the popped item. if BRPOP timeout is null and list empty, I do not expect to print anything - log.InfoFormat("{0}", item.IsNullOrEmpty() ? " list empty " : item); + log.InfoFormat("{0}", string.IsNullOrEmpty(item) ? " list empty " : item); System.Threading.Thread.Sleep(1000); diff --git a/tests/Console.Tests/BlockingRemoveAfterReconnection.cs b/tests/Console.Tests/BlockingRemoveAfterReconnection.cs new file mode 100644 index 00000000..b085d9ed --- /dev/null +++ b/tests/Console.Tests/BlockingRemoveAfterReconnection.cs @@ -0,0 +1,35 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Logging; +using ServiceStack.Redis; +using ServiceStack.Text; + +namespace ConsoleTests +{ + public class BlockingRemoveAfterReconnection + { + protected internal static RedisManagerPool BasicRedisClientManager; + + public void Execute() + { + //RedisConfig.AssumeServerVersion = 4000; + RedisConfig.DefaultConnectTimeout = 20 * 1000; + RedisConfig.DefaultRetryTimeout = 20 * 1000; + BasicRedisClientManager = new RedisManagerPool(); + try + { + using (var client = BasicRedisClientManager.GetClient()) + { + Console.WriteLine("Blocking..."); + var fromList = client.BlockingRemoveStartFromList("AnyQueue", TimeSpan.FromMinutes(20)); + Console.WriteLine($"Received: {fromList.Dump()}"); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} \ No newline at end of file diff --git a/tests/Console.Tests/BrPopAfterReconnection.cs b/tests/Console.Tests/BrPopAfterReconnection.cs new file mode 100644 index 00000000..f5867f06 --- /dev/null +++ b/tests/Console.Tests/BrPopAfterReconnection.cs @@ -0,0 +1,66 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Logging; +using ServiceStack.Redis; +using ServiceStack.Text; + +namespace ConsoleTests +{ + public class BrPopAfterReconnection + { + protected internal static BasicRedisClientManager BasicRedisClientManager; + + public void Execute() + { +// RedisConfig.AssumeServerVersion = 4000; +// RedisConfig.DisableVerboseLogging = false; +// LogManager.LogFactory = new ConsoleLogFactory(); + + var host = "localhost"; + var port = "6379"; + var db = "9"; + + var redisUri = $"{host}:{port}?db={db}"; + + BasicRedisClientManager = new BasicRedisClientManager(redisUri); + var queue = "FormSaved"; + + while (true) + { + Task.Run(() => BlockingReceive(queue)); + Thread.Sleep(1000); + + Console.WriteLine("Restart Redis and press Enter..."); + Console.ReadLine(); + + Console.WriteLine("Enter something:"); + var item = Console.ReadLine(); + + if (!string.IsNullOrWhiteSpace(item)) + { + using (var client = BasicRedisClientManager.GetClient()) + { + client.AddItemToList(queue, item); + } + + Console.WriteLine("Item added"); + } + + Thread.Sleep(1000); + } + } + + public static void BlockingReceive(string queue) + { + using (var client = BasicRedisClientManager.GetReadOnlyClient()) + { + Console.WriteLine($"Listening to {queue}"); + + var fromList = client.BlockingPopItemFromList(queue, TimeSpan.FromSeconds(60)); + + Console.WriteLine($"Received:{fromList.Dump()}"); + } + } + } +} \ No newline at end of file diff --git a/tests/Console.Tests/Console.Tests.csproj b/tests/Console.Tests/Console.Tests.csproj index cc9c6be6..5b260ab0 100644 --- a/tests/Console.Tests/Console.Tests.csproj +++ b/tests/Console.Tests/Console.Tests.csproj @@ -1,96 +1,17 @@ - - - - - Debug - AnyCPU - {8368C965-B4F6-4263-9ABB-731A175B2E77} - Exe - Properties - ConsoleTests - Console.Tests - v4.5 - 512 - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\..\lib\net45\ServiceStack.dll - - - ..\..\lib\net45\ServiceStack.Client.dll - - - False - ..\..\lib\net45\ServiceStack.Common.dll - - - ..\..\lib\net45\ServiceStack.Interfaces.dll - - - ..\..\lib\net45\ServiceStack.Server.dll - - - ..\..\lib\net45\ServiceStack.Text.dll - - - - - - - - - - - - - - - - - - - - - - - - - - {AF99F19B-4C04-4F58-81EF-B092F1FCC540} - ServiceStack.Redis - - - - - - - - \ No newline at end of file + + + + Exe + net6.0 + Console.Tests + + + + + + + + + + + diff --git a/tests/Console.Tests/DbSelectConnectionStringIssue.cs b/tests/Console.Tests/DbSelectConnectionStringIssue.cs new file mode 100644 index 00000000..49d453f5 --- /dev/null +++ b/tests/Console.Tests/DbSelectConnectionStringIssue.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading; +using ServiceStack; +using ServiceStack.Logging; +using ServiceStack.Redis; + +namespace ConsoleTests; + +class DbSelectConnectionStringIssue +{ + public void Execute() + { + LogManager.LogFactory = new ConsoleLogFactory(); + + Licensing.RegisterLicense(""); + + var redisManagerPool = new RedisManagerPool("redis://redisHost?db=7"); + + for (int i = 0; i < 5; i++) + { + try + { + using (IRedisClient client = redisManagerPool.GetClient()) + { + string value = client.GetValue("status"); + + Console.WriteLine($"Successfully retrieved value => '{value}'"); + } + } + catch (Exception ex) + { + Console.WriteLine($"Exception handled \n{ex}"); + } + + Console.WriteLine("Sleeping for 25 seconds to allow client to be garbage collected"); + Thread.Sleep(TimeSpan.FromSeconds(25)); + } + } +} \ No newline at end of file diff --git a/tests/Console.Tests/ForceFailover.cs b/tests/Console.Tests/ForceFailover.cs index efca4e1b..7ae40136 100644 --- a/tests/Console.Tests/ForceFailover.cs +++ b/tests/Console.Tests/ForceFailover.cs @@ -9,7 +9,7 @@ public class ForceFailover { public void Execute() { - RedisConfig.DisableVerboseLogging = true; + RedisConfig.EnableVerboseLogging = false; LogManager.LogFactory = new ConsoleLogFactory(debugEnabled:true); var sentinel = new RedisSentinel(new [] { diff --git a/tests/Console.Tests/MasterFailoverWithPassword.cs b/tests/Console.Tests/MasterFailoverWithPassword.cs index f9e0257b..a77b7093 100644 --- a/tests/Console.Tests/MasterFailoverWithPassword.cs +++ b/tests/Console.Tests/MasterFailoverWithPassword.cs @@ -9,23 +9,25 @@ public class MasterFailoverWithPassword { public void Execute() { + string AddPassword(string host) => $"password@{host}"; + var sentinelHosts = new[] { "127.0.0.1:26380", "127.0.0.1:26381", "127.0.0.1:26382" }; - var sentinel = new RedisSentinel(sentinelHosts, masterName: "mymaster"); - sentinel.HostFilter = host => "password@{0}".Fmt(host); + var sentinel = new RedisSentinel(sentinelHosts.Map(AddPassword), masterName: "mymaster") { + HostFilter = AddPassword, + SentinelHostFilter = AddPassword, + }; var manager = sentinel.Start(); - sentinel.OnWorkerError = exception => Console.WriteLine(exception); + sentinel.OnWorkerError = Console.WriteLine; while (true) { try { const string RedisKey = "my Name"; - using (var client = manager.GetClient()) - { - var result = client.Get(RedisKey); - Console.WriteLine("Redis Key: {0} \t Port: {1}", result, client.Port); - } + using var client = manager.GetClient(); + var result = client.Get(RedisKey); + Console.WriteLine("Redis Key: {0} \t Port: {1}", result, client.Port); } catch (Exception ex) { diff --git a/tests/Console.Tests/MultiBlockingRemoveAfterReconnection.cs b/tests/Console.Tests/MultiBlockingRemoveAfterReconnection.cs new file mode 100644 index 00000000..3abdf40a --- /dev/null +++ b/tests/Console.Tests/MultiBlockingRemoveAfterReconnection.cs @@ -0,0 +1,72 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ServiceStack.Logging; +using ServiceStack.Redis; +using ServiceStack.Text; + +namespace ConsoleTests +{ + public class MultiBlockingRemoveAfterReconnection + { + protected internal static RedisManagerPool RedisManager; + + public void Execute() + { +// LogManager.LogFactory = new ConsoleLogFactory(); +// RedisConfig.EnableVerboseLogging = true; + + RedisConfig.DefaultConnectTimeout = 20 * 1000; + RedisConfig.DefaultRetryTimeout = 20 * 1000; + + RedisManager = new RedisManagerPool($"localhost:6379?db=9"); + + MultipleBlocking(3); + + Console.ReadLine(); + } + + private static void MultipleBlocking(int count) + { + for (int i = 0; i < count; i++) + { + var queue = $"Q{i + 1}"; + RunTask(() => BlockingRemoveStartFromList(queue), $"Receive from {queue}"); + } + } + public static void BlockingRemoveStartFromList(string queue) + { + using (var client = RedisManager.GetClient() as RedisClient) + { + client.Ping(); + Console.WriteLine($"#{client.ClientId} Listening to {queue}"); + + var fromList = client.BlockingRemoveStartFromList(queue, TimeSpan.FromHours(10)); + Console.WriteLine($"#{client.ClientId} Received: '{fromList.Dump()}' from '{queue}'"); + } + } + + private static void RunTask(Action action, string name) + { + Task.Run(() => + { + + while (true) + { + try + { + Console.WriteLine($"Invoking {name}"); + action.Invoke(); + } + catch (Exception exception) + { + Console.WriteLine($"Exception in {name}: {exception}"); + //Thread.Sleep(5000);// Give redis some time to wake up! + } + + Thread.Sleep(100); + } + }); + } + } +} \ No newline at end of file diff --git a/tests/Console.Tests/Program.cs b/tests/Console.Tests/Program.cs index 54068dba..2db1835b 100644 --- a/tests/Console.Tests/Program.cs +++ b/tests/Console.Tests/Program.cs @@ -36,7 +36,13 @@ static void Main(string[] args) //new BlockingPop().Execute(); - new MasterFailoverWithPassword().Execute(); + //new MasterFailoverWithPassword().Execute(); + + //new BlockingRemoveAfterReconnection().Execute(); + + //new MultiBlockingRemoveAfterReconnection().Execute(); + + new DbSelectConnectionStringIssue().Execute(); } } } diff --git a/tests/Console.Tests/Properties/AssemblyInfo.cs b/tests/Console.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index d188967b..00000000 --- a/tests/Console.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Console.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Console.Tests")] -[assembly: AssemblyCopyright("Copyright © 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("33cdad3a-8f89-437f-9a64-261c162888aa")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/tests/Console.Tests/RedisSentinelFailoverTests.cs b/tests/Console.Tests/RedisSentinelFailoverTests.cs index 130b86d2..b15bfa88 100644 --- a/tests/Console.Tests/RedisSentinelFailoverTests.cs +++ b/tests/Console.Tests/RedisSentinelFailoverTests.cs @@ -23,7 +23,7 @@ public abstract class RedisSentinelFailoverTests public void Execute() { - RedisConfig.DisableVerboseLogging = true; + RedisConfig.EnableVerboseLogging = false; LogManager.LogFactory = new ConsoleLogFactory(debugEnabled: true); log = LogManager.GetLogger(GetType()); @@ -35,7 +35,7 @@ public void Execute() { if (UseRedisManagerPool) { - sentinel.RedisManagerFactory = (masters, slaves) => + sentinel.RedisManagerFactory = (masters, replicas) => new RedisManagerPool(masters); } diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 00000000..7eb7717e --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,29 @@ + + + + 6.0.3 + latest + false + + + + DEBUG + + + + $(DefineConstants);NETFX;NET472 + + + + $(DefineConstants);NETCORE;NETSTANDARD2_0 + + + + $(DefineConstants);NET6_0;NET6_0_OR_GREATER + + + + $(DefineConstants);NETCORE;NETCORE_SUPPORT + + + diff --git a/tests/ServiceStack.Redis.Benchmark/IncrBenchmarks.cs b/tests/ServiceStack.Redis.Benchmark/IncrBenchmarks.cs new file mode 100644 index 00000000..b39aa825 --- /dev/null +++ b/tests/ServiceStack.Redis.Benchmark/IncrBenchmarks.cs @@ -0,0 +1,281 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Order; +using Pipelines.Sockets.Unofficial; +using Respite; +using StackExchange.Redis; +using System.Linq; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Benchmark +{ + [SimpleJob(RuntimeMoniker.Net472)] + [SimpleJob(RuntimeMoniker.NetCoreApp31)] + [MemoryDiagnoser] + [GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)] + [Orderer(SummaryOrderPolicy.Method, MethodOrderPolicy.Alphabetical)] + [CategoriesColumn] + public class IncrBenchmarks + { + ConnectionMultiplexer _seredis; + IServer _seredis_server; + IDatabase _seredis_db; + RedisClient _ssredis; + IRedisClientAsync _ssAsync; + RespConnection _respite; + + static IncrBenchmarks() + { + RedisClient.NewFactoryFn = () => new RedisClient("127.0.0.1", 6379); + } + + [GlobalSetup] + public Task Setup() => Setup(false); + internal async Task Setup(bool minimal) + { + _ssredis = RedisClient.New(); + _ssAsync = _ssredis; + + if (!minimal) + { + _seredis = await ConnectionMultiplexer.ConnectAsync("127.0.0.1:6379"); + _seredis_server = _seredis.GetServer(_seredis.GetEndPoints().Single()); + _seredis_db = _seredis.GetDatabase(); + + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + SocketConnection.SetRecommendedClientOptions(socket); + socket.Connect("127.0.0.1", 6379); + + _respite = RespConnection.Create(socket); + } + } + + [GlobalCleanup] + public async Task Teardown() + { + _seredis?.Dispose(); + _ssredis?.Dispose(); + if (_respite != null) await _respite.DisposeAsync(); + + _seredis_server = null; + _seredis_db = null; + _seredis = null; + _ssredis = null; + _respite = null; + _ssAsync = null; + } + + const string Key = "my_key"; +#if DEBUG + const int PER_TEST = 10; +#else + const int PER_TEST = 1000; +#endif + + [BenchmarkCategory("IncrAsync")] + [Benchmark(Description = "SERedis", OperationsPerInvoke = PER_TEST)] + public async Task SERedisIncrAsync() + { + long last = default; + await _seredis_db.KeyDeleteAsync(Key); + for (int i = 0; i < PER_TEST; i++) + { + last = await _seredis_db.StringIncrementAsync(Key); + } + return last; + } + + [BenchmarkCategory("IncrSync")] + [Benchmark(Description = "SERedis", OperationsPerInvoke = PER_TEST)] + public long SERedisIncrSync() + { + long last = default; + _seredis_db.KeyDelete(Key); + for (int i = 0; i < PER_TEST; i++) + { + last = _seredis_db.StringIncrement(Key); + } + return last; + } + + [BenchmarkCategory("PipelineIncrAsync")] + [Benchmark(Description = "SERedis", OperationsPerInvoke = PER_TEST)] + public async Task SERedisPipelineIncrAsync() + { + var last = Task.FromResult(0L); + await _seredis_db.KeyDeleteAsync(Key); + var batch = _seredis_db.CreateBatch(); + for (int i = 0; i < PER_TEST; i++) + { + last = batch.StringIncrementAsync(Key); + } + batch.Execute(); + return await last; + } + + [BenchmarkCategory("TransactionIncrAsync")] + [Benchmark(Description = "SERedis", OperationsPerInvoke = PER_TEST)] + public async Task SERedisTransactionIncrAsync() + { + var last = Task.FromResult(0L); + await _seredis_db.KeyDeleteAsync(Key); + var batch = _seredis_db.CreateTransaction(); + for (int i = 0; i < PER_TEST; i++) + { + last = batch.StringIncrementAsync(Key); + } + await batch.ExecuteAsync(); + return await last; + } + + [BenchmarkCategory("TransactionIncrSync")] + [Benchmark(Description = "SERedis", OperationsPerInvoke = PER_TEST)] + public async Task SERedisTransactionIncrSync() + { + var last = Task.FromResult(0L); + _seredis_db.KeyDelete(Key); + var batch = _seredis_db.CreateTransaction(); + for (int i = 0; i < PER_TEST; i++) + { + last = batch.StringIncrementAsync(Key); + } + batch.Execute(); + return await last; + } + + [BenchmarkCategory("IncrAsync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public async Task SSRedisIncrAsync() + { + long last = default; + _ssredis.Del(Key); // todo: asyncify + for (int i = 0; i < PER_TEST; i++) + { + last = await _ssAsync.IncrementValueAsync(Key); + } + return last; + } + + + [BenchmarkCategory("IncrSync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public long SSRedisIncrSync() + { + long last = default; + _ssredis.Del(Key); + for (int i = 0; i < PER_TEST; i++) + { + last = _ssredis.IncrementValue(Key); + } + return last; + } + + [BenchmarkCategory("PipelineIncrSync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public long SSRedisPipelineIncrSync() + { + long last = default; + _ssredis.Del(Key); + using var trans = _ssredis.CreatePipeline(); + for (int i = 0; i < PER_TEST; i++) + { + trans.QueueCommand(r => r.IncrementValue(Key), l => last = l); + } + trans.Flush(); + return last; + } + + [BenchmarkCategory("TransactionIncrSync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public long SSRedisTransactionIncrSync() + { + long last = default; + _ssredis.Del(Key); + using var trans = _ssredis.CreateTransaction(); + for (int i = 0; i < PER_TEST; i++) + { + trans.QueueCommand(r => r.IncrementValue(Key), l => last = l); + } + trans.Commit(); + return last; + } + + [BenchmarkCategory("PipelineIncrAsync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public async Task SSRedisPipelineIncrAsync() + { + long last = default; + _ssredis.Del(Key); // todo: asyncify + await using var trans = _ssAsync.CreatePipeline(); + for (int i = 0; i < PER_TEST; i++) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key), l => last = l); + } + await trans.FlushAsync(); + return last; + } + + [BenchmarkCategory("TransactionIncrAsync")] + [Benchmark(Description = "SSRedis", OperationsPerInvoke = PER_TEST)] + public async Task SSRedisTransactionIncrAsync() + { + long last = default; + _ssredis.Del(Key); // todo: asyncify + await using var trans = await _ssAsync.CreateTransactionAsync(); + for (int i = 0; i < PER_TEST; i++) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key), l => last = l); + } + await trans.CommitAsync(); + return last; + } + + + //static readonly RespValue s_Time = RespValue.CreateAggregate( + // RespType.Array, RespValue.Create(RespType.BlobString, "time")); + + //static DateTime ParseTime(in RespValue value) + //{ + // var parts = value.SubItems; + // if (parts.TryGetSingleSpan(out var span)) + // return Parse(span[0], span[1]); + // return Slow(parts); + // static DateTime Slow(in ReadOnlyBlock parts) + // { + // var iter = parts.GetEnumerator(); + // if (!iter.MoveNext()) Throw(); + // var seconds = iter.Current; + // if (!iter.MoveNext()) Throw(); + // var microseconds = iter.Current; + // return Parse(seconds, microseconds); + // static void Throw() => throw new InvalidOperationException(); + // } + + // static DateTime Parse(in RespValue seconds, in RespValue microseconds) + // => Epoch.AddSeconds(seconds.ToInt64()).AddMilliseconds(microseconds.ToInt64() / 1000.0); + //} + //static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + //[BenchmarkCategory("IncrSync")] + //[Benchmark(Description = "Respite", OperationsPerInvoke = PER_TEST)] + //public void RespiteTimeSync() + //{ + // for (int i = 0; i < PER_TEST; i++) + // { + // _respite.Call(s_Time, val => ParseTime(val)); + // } + //} + + //[BenchmarkCategory("IncrAsync")] + //[Benchmark(Description = "Respite", OperationsPerInvoke = PER_TEST)] + //public async Task RespiteTimeAsync() + //{ + // for (int i = 0; i < PER_TEST; i++) + // { + // await _respite.CallAsync(s_Time, val => ParseTime(val)); + // } + //} + } +} diff --git a/tests/ServiceStack.Redis.Benchmark/Program.cs b/tests/ServiceStack.Redis.Benchmark/Program.cs new file mode 100644 index 00000000..84c48100 --- /dev/null +++ b/tests/ServiceStack.Redis.Benchmark/Program.cs @@ -0,0 +1,39 @@ +using BenchmarkDotNet.Running; +using System.Threading.Tasks; +using System; +namespace ServiceStack.Redis.Benchmark +{ + class Program + { +#if DEBUG + static async Task Main() + { + var obj = new IncrBenchmarks(); + try + { + await obj.Setup(false); + + Console.WriteLine(obj.SERedisIncrSync()); + Console.WriteLine(await obj.SERedisIncrAsync()); + Console.WriteLine(await obj.SERedisPipelineIncrAsync()); + Console.WriteLine(await obj.SERedisTransactionIncrAsync()); + Console.WriteLine(await obj.SERedisTransactionIncrSync()); + + Console.WriteLine(obj.SSRedisIncrSync()); + Console.WriteLine(obj.SSRedisPipelineIncrSync()); + Console.WriteLine(obj.SSRedisTransactionIncrSync()); + Console.WriteLine(await obj.SSRedisIncrAsync()); + Console.WriteLine(await obj.SSRedisPipelineIncrAsync()); + Console.WriteLine(await obj.SSRedisTransactionIncrAsync()); + } + finally + { + await obj.Teardown(); + } + } +#else + static void Main(string[] args) + => BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); +#endif + } +} diff --git a/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj b/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj new file mode 100644 index 00000000..a5bcef26 --- /dev/null +++ b/tests/ServiceStack.Redis.Benchmark/ServiceStack.Redis.Benchmark.csproj @@ -0,0 +1,18 @@ + + + + Exe + net6.0;net472 + 8 + + + + + + + + + + + + diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/RedisResolverTests.cs b/tests/ServiceStack.Redis.Tests.Sentinel/RedisResolverTests.cs index 473cba7c..ee8dddc3 100644 --- a/tests/ServiceStack.Redis.Tests.Sentinel/RedisResolverTests.cs +++ b/tests/ServiceStack.Redis.Tests.Sentinel/RedisResolverTests.cs @@ -24,10 +24,10 @@ public void OneTimeTearDown() [Test] public void RedisResolver_does_reset_when_detects_invalid_master() { - var invalidMaster = new[] { SlaveHosts[0] }; - var invalidSlaves = new[] { MasterHosts[0], SlaveHosts[1] }; + var invalidMaster = new[] { ReplicaHosts[0] }; + var invalidReplicas = new[] { MasterHosts[0], ReplicaHosts[1] }; - using (var redisManager = new PooledRedisClientManager(invalidMaster, invalidSlaves)) + using (var redisManager = new PooledRedisClientManager(invalidMaster, invalidReplicas)) { var resolver = (RedisResolver)redisManager.RedisResolver; @@ -44,7 +44,7 @@ public void RedisResolver_does_reset_when_detects_invalid_master() "Masters:".Print(); resolver.Masters.PrintDump(); - "Slaves:".Print(); + "Replicas:".Print(); resolver.Slaves.PrintDump(); } } @@ -52,71 +52,61 @@ public void RedisResolver_does_reset_when_detects_invalid_master() [Test] public void PooledRedisClientManager_alternates_hosts() { - using (var redisManager = new PooledRedisClientManager(MasterHosts, SlaveHosts)) + using var redisManager = new PooledRedisClientManager(MasterHosts, ReplicaHosts); + using (var master = redisManager.GetClient()) { - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.SetValue("KEY", "1"); - } - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.Increment("KEY", 1); - } + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.SetValue("KEY", "1"); + } + using (var master = redisManager.GetClient()) + { + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.Increment("KEY", 1); + } - 5.Times(i => - { - using (var readOnly = redisManager.GetReadOnlyClient()) - { - Assert.That(readOnly.GetHostString(), Is.EqualTo(SlaveHosts[i % SlaveHosts.Length])); - Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); - } - }); + 5.Times(i => { + using var readOnly = redisManager.GetReadOnlyClient(); + Assert.That(readOnly.GetHostString(), Is.EqualTo(ReplicaHosts[i % ReplicaHosts.Length])); + Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); + }); - using (var cahce = redisManager.GetCacheClient()) - { - Assert.That(cahce.Get("KEY"), Is.EqualTo("2")); - } + using (var cache = redisManager.GetCacheClient()) + { + Assert.That(cache.Get("KEY"), Is.EqualTo("2")); } } [Test] public void RedisManagerPool_alternates_hosts() { - using (var redisManager = new RedisManagerPool(MasterHosts)) + using var redisManager = new RedisManagerPool(MasterHosts); + using (var master = redisManager.GetClient()) { - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.SetValue("KEY", "1"); - } - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.Increment("KEY", 1); - } + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.SetValue("KEY", "1"); + } + using (var master = redisManager.GetClient()) + { + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.Increment("KEY", 1); + } - 5.Times(i => - { - using (var readOnly = redisManager.GetReadOnlyClient()) - { - Assert.That(readOnly.GetHostString(), Is.EqualTo(MasterHosts[0])); - Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); - } - }); + 5.Times(i => { + using var readOnly = redisManager.GetReadOnlyClient(); + Assert.That(readOnly.GetHostString(), Is.EqualTo(MasterHosts[0])); + Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); + }); - using (var cahce = redisManager.GetCacheClient()) - { - Assert.That(cahce.Get("KEY"), Is.EqualTo("2")); - } + using (var cache = redisManager.GetCacheClient()) + { + Assert.That(cache.Get("KEY"), Is.EqualTo("2")); } } [Test] public void BasicRedisClientManager_alternates_hosts() { - using (var redisManager = new BasicRedisClientManager(MasterHosts, SlaveHosts)) + using (var redisManager = new BasicRedisClientManager(MasterHosts, ReplicaHosts)) { using (var master = redisManager.GetClient()) { @@ -129,18 +119,15 @@ public void BasicRedisClientManager_alternates_hosts() master.Increment("KEY", 1); } - 5.Times(i => - { - using (var readOnly = redisManager.GetReadOnlyClient()) - { - Assert.That(readOnly.GetHostString(), Is.EqualTo(SlaveHosts[i % SlaveHosts.Length])); - Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); - } + 5.Times(i => { + using var readOnly = redisManager.GetReadOnlyClient(); + Assert.That(readOnly.GetHostString(), Is.EqualTo(ReplicaHosts[i % ReplicaHosts.Length])); + Assert.That(readOnly.GetValue("KEY"), Is.EqualTo("2")); }); - using (var cahce = redisManager.GetCacheClient()) + using (var cache = redisManager.GetCacheClient()) { - Assert.That(cahce.Get("KEY"), Is.EqualTo("2")); + Assert.That(cache.Get("KEY"), Is.EqualTo("2")); } } } @@ -148,20 +135,20 @@ public void BasicRedisClientManager_alternates_hosts() public class FixedResolver : IRedisResolver { private readonly RedisEndpoint master; - private readonly RedisEndpoint slave; + private readonly RedisEndpoint replica; public int NewClientsInitialized = 0; - public FixedResolver(RedisEndpoint master, RedisEndpoint slave) + public FixedResolver(RedisEndpoint master, RedisEndpoint replica) { this.master = master; - this.slave = slave; + this.replica = replica; this.ClientFactory = RedisConfig.ClientFactory; } public Func ClientFactory { get; set; } - public int ReadWriteHostsCount { get { return 1; } } - public int ReadOnlyHostsCount { get { return 1; } } + public int ReadWriteHostsCount => 1; + public int ReadOnlyHostsCount => 1; public void ResetMasters(IEnumerable hosts) { } public void ResetSlaves(IEnumerable hosts) { } @@ -179,134 +166,127 @@ public RedisClient CreateMasterClient(int desiredIndex) public RedisClient CreateSlaveClient(int desiredIndex) { - return CreateRedisClient(slave, master: false); + return CreateRedisClient(replica, master: false); } } [Test] public void PooledRedisClientManager_can_execute_CustomResolver() { - var resolver = new FixedResolver(MasterHosts[0].ToRedisEndpoint(), SlaveHosts[0].ToRedisEndpoint()); - using (var redisManager = new PooledRedisClientManager("127.0.0.1:8888") + var resolver = new FixedResolver(MasterHosts[0].ToRedisEndpoint(), ReplicaHosts[0].ToRedisEndpoint()); + using var redisManager = new PooledRedisClientManager("127.0.0.1:8888") { RedisResolver = resolver - }) + }; + using (var master = redisManager.GetClient()) { - using (var master = redisManager.GetClient()) + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.SetValue("KEY", "1"); + } + using (var master = redisManager.GetClient()) + { + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.Increment("KEY", 1); + } + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); + + 5.Times(i => + { + using (var replica = redisManager.GetReadOnlyClient()) { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.SetValue("KEY", "1"); + Assert.That(replica.GetHostString(), Is.EqualTo(ReplicaHosts[0])); + Assert.That(replica.GetValue("KEY"), Is.EqualTo("2")); } + }); + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(2)); + + redisManager.FailoverTo("127.0.0.1:9999", "127.0.0.1:9999"); + + 5.Times(i => + { using (var master = redisManager.GetClient()) { Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.Increment("KEY", 1); + Assert.That(master.GetValue("KEY"), Is.EqualTo("2")); } - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); - - 5.Times(i => + using (var replica = redisManager.GetReadOnlyClient()) { - using (var slave = redisManager.GetReadOnlyClient()) - { - Assert.That(slave.GetHostString(), Is.EqualTo(SlaveHosts[0])); - Assert.That(slave.GetValue("KEY"), Is.EqualTo("2")); - } - }); - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(2)); - - redisManager.FailoverTo("127.0.0.1:9999", "127.0.0.1:9999"); - - 5.Times(i => - { - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - Assert.That(master.GetValue("KEY"), Is.EqualTo("2")); - } - using (var slave = redisManager.GetReadOnlyClient()) - { - Assert.That(slave.GetHostString(), Is.EqualTo(SlaveHosts[0])); - Assert.That(slave.GetValue("KEY"), Is.EqualTo("2")); - } - }); - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(4)); - } + Assert.That(replica.GetHostString(), Is.EqualTo(ReplicaHosts[0])); + Assert.That(replica.GetValue("KEY"), Is.EqualTo("2")); + } + }); + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(4)); } [Test] public void RedisManagerPool_can_execute_CustomResolver() { - var resolver = new FixedResolver(MasterHosts[0].ToRedisEndpoint(), SlaveHosts[0].ToRedisEndpoint()); - using (var redisManager = new RedisManagerPool("127.0.0.1:8888") + var resolver = new FixedResolver(MasterHosts[0].ToRedisEndpoint(), ReplicaHosts[0].ToRedisEndpoint()); + using var redisManager = new RedisManagerPool("127.0.0.1:8888") { RedisResolver = resolver - }) + }; + using (var master = redisManager.GetClient()) + { + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.SetValue("KEY", "1"); + } + using (var master = redisManager.GetClient()) + { + Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); + master.Increment("KEY", 1); + } + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); + + 5.Times(i => { + using var replica = redisManager.GetReadOnlyClient(); + Assert.That(replica.GetHostString(), Is.EqualTo(MasterHosts[0])); + Assert.That(replica.GetValue("KEY"), Is.EqualTo("2")); + }); + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); + + redisManager.FailoverTo("127.0.0.1:9999", "127.0.0.1:9999"); + + 5.Times(i => { using (var master = redisManager.GetClient()) { Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.SetValue("KEY", "1"); + Assert.That(master.GetValue("KEY"), Is.EqualTo("2")); } - using (var master = redisManager.GetClient()) + using (var replica = redisManager.GetReadOnlyClient()) { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - master.Increment("KEY", 1); + Assert.That(replica.GetHostString(), Is.EqualTo(MasterHosts[0])); + Assert.That(replica.GetValue("KEY"), Is.EqualTo("2")); } - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); - - 5.Times(i => - { - using (var slave = redisManager.GetReadOnlyClient()) - { - Assert.That(slave.GetHostString(), Is.EqualTo(MasterHosts[0])); - Assert.That(slave.GetValue("KEY"), Is.EqualTo("2")); - } - }); - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(1)); - - redisManager.FailoverTo("127.0.0.1:9999", "127.0.0.1:9999"); - - 5.Times(i => - { - using (var master = redisManager.GetClient()) - { - Assert.That(master.GetHostString(), Is.EqualTo(MasterHosts[0])); - Assert.That(master.GetValue("KEY"), Is.EqualTo("2")); - } - using (var slave = redisManager.GetReadOnlyClient()) - { - Assert.That(slave.GetHostString(), Is.EqualTo(MasterHosts[0])); - Assert.That(slave.GetValue("KEY"), Is.EqualTo("2")); - } - }); - Assert.That(resolver.NewClientsInitialized, Is.EqualTo(2)); - } + }); + Assert.That(resolver.NewClientsInitialized, Is.EqualTo(2)); } - private static void InitializeEmptyRedisManagers(IRedisClientsManager redisManager, string[] masters, string[] slaves) + private static void InitializeEmptyRedisManagers(IRedisClientsManager redisManager, string[] masters, string[] replicas) { var hasResolver = (IHasRedisResolver)redisManager; hasResolver.RedisResolver.ResetMasters(masters); - hasResolver.RedisResolver.ResetSlaves(slaves); + hasResolver.RedisResolver.ResetSlaves(replicas); using (var master = redisManager.GetClient()) { Assert.That(master.GetHostString(), Is.EqualTo(masters[0])); master.SetValue("KEY", "1"); } - using (var slave = redisManager.GetReadOnlyClient()) + using (var replica = redisManager.GetReadOnlyClient()) { - Assert.That(slave.GetHostString(), Is.EqualTo(slaves[0])); - Assert.That(slave.GetValue("KEY"), Is.EqualTo("1")); + Assert.That(replica.GetHostString(), Is.EqualTo(replicas[0])); + Assert.That(replica.GetValue("KEY"), Is.EqualTo("1")); } } [Test] - public void Can_initalize_ClientManagers_with_no_hosts() + public void Can_initialize_ClientManagers_with_no_hosts() { - InitializeEmptyRedisManagers(new PooledRedisClientManager(), MasterHosts, SlaveHosts); + InitializeEmptyRedisManagers(new PooledRedisClientManager(), MasterHosts, ReplicaHosts); InitializeEmptyRedisManagers(new RedisManagerPool(), MasterHosts, MasterHosts); - InitializeEmptyRedisManagers(new BasicRedisClientManager(), MasterHosts, SlaveHosts); + InitializeEmptyRedisManagers(new BasicRedisClientManager(), MasterHosts, ReplicaHosts); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelConnectionTests.cs b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelConnectionTests.cs new file mode 100644 index 00000000..a90f92b4 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelConnectionTests.cs @@ -0,0 +1,49 @@ +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests.Sentinel +{ + public class RedisSentinelConnectionTests + { + [Test] + public void Can_connect_to_AWS_Redis_Sentinel_SentinelMaster() + { + RedisConfig.AssumeServerVersion = 4000; + + var client = new RedisClient("52.7.181.87", 26379); + + var info = client.SentinelMaster("mymaster"); + + info.PrintDump(); + } + + [Test] + public void Can_connect_to_AWS_Redis_Sentinel_Ping() + { + RedisConfig.AssumeServerVersion = 4000; + + var client = new RedisClient("52.7.181.87", 26379); + + Assert.That(client.Ping()); + } + + [Test] + public void Can_connect_to_RedisSentinel() + { + RedisConfig.AssumeServerVersion = 4000; + + var sentinel = new RedisSentinel("52.7.181.87:26379") { + IpAddressMap = { + {"127.0.0.1", "52.7.181.87"} + } + }; + + var redisManager = sentinel.Start(); + + using (var client = redisManager.GetClient()) + { + Assert.That(client.Ping()); + } + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTestBase.cs b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTestBase.cs index 4140a51f..5a176a85 100644 --- a/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTestBase.cs +++ b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTestBase.cs @@ -21,7 +21,7 @@ public abstract class RedisSentinelTestBase "127.0.0.1:6380", }; - public static string[] SlaveHosts = new[] + public static string[] ReplicaHosts = new[] { "127.0.0.1:6381", "127.0.0.1:6382", diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTests.cs b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTests.cs index d148e530..4302719d 100644 --- a/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTests.cs +++ b/tests/ServiceStack.Redis.Tests.Sentinel/RedisSentinelTests.cs @@ -67,12 +67,12 @@ public void Can_Get_Sentinel_Master() } [Test] - public void Can_Get_Sentinel_Slaves() + public void Can_Get_Sentinel_Replicas() { - var slaves = RedisSentinel.SentinelSlaves(MasterName); - slaves.PrintDump(); + var replicas = RedisSentinel.SentinelSlaves(MasterName); + replicas.PrintDump(); - Assert.That(slaves.Count, Is.GreaterThan(0)); + Assert.That(replicas.Count, Is.GreaterThan(0)); } [Test] @@ -100,63 +100,47 @@ public void Can_Get_Master_Addr() [Test] public void Does_scan_for_other_active_sentinels() { - using (var sentinel = new RedisSentinel(SentinelHosts[0]) { + using var sentinel = new RedisSentinel(SentinelHosts[0]) { ScanForOtherSentinels = true - }) - { - var clientsManager = sentinel.Start(); + }; + var clientsManager = sentinel.Start(); - Assert.That(sentinel.SentinelHosts, Is.EquivalentTo(SentinelHosts)); + Assert.That(sentinel.SentinelHosts, Is.EquivalentTo(SentinelHosts)); - using (var client = clientsManager.GetClient()) - { - Assert.That(client.GetHostString(), Is.EqualTo(MasterHosts[0])); - } - } + using var client = clientsManager.GetClient(); + Assert.That(client.GetHostString(), Is.EqualTo(MasterHosts[0])); } [Test] public void Can_Get_Redis_ClientsManager() { - using (var sentinel = CreateSentinel()) - { - var clientsManager = sentinel.Start(); - using (var client = clientsManager.GetClient()) - { - Assert.That(client.GetHostString(), Is.EqualTo(MasterHosts[0])); - } - } + using var sentinel = CreateSentinel(); + var clientsManager = sentinel.Start(); + using var client = clientsManager.GetClient(); + Assert.That(client.GetHostString(), Is.EqualTo(MasterHosts[0])); } [Test] public void Can_specify_Timeout_on_RedisManager() { - using (var sentinel = CreateSentinel()) - { - sentinel.RedisManagerFactory = (masters, slaves) => new PooledRedisClientManager(masters, slaves) { IdleTimeOutSecs = 20 }; + using var sentinel = CreateSentinel(); + sentinel.RedisManagerFactory = (masters, replicas) => new PooledRedisClientManager(masters, replicas) { IdleTimeOutSecs = 20 }; - using (var clientsManager = (PooledRedisClientManager)sentinel.Start()) - using (var client = clientsManager.GetClient()) - { - Assert.That(clientsManager.IdleTimeOutSecs, Is.EqualTo(20)); - Assert.That(((RedisNativeClient)client).IdleTimeOutSecs, Is.EqualTo(20)); - } - } + using var clientsManager = (PooledRedisClientManager)sentinel.Start(); + using var client = clientsManager.GetClient(); + Assert.That(clientsManager.IdleTimeOutSecs, Is.EqualTo(20)); + Assert.That(((RedisNativeClient)client).IdleTimeOutSecs, Is.EqualTo(20)); } [Test] public void Can_specify_db_on_RedisSentinel() { - using (var sentinel = CreateSentinel()) - { - sentinel.HostFilter = host => "{0}?db=1".Fmt(host); - - using (var clientsManager = sentinel.Start()) - using (var client = clientsManager.GetClient()) - { - Assert.That(client.Db, Is.EqualTo(1)); - } - } + using var sentinel = CreateSentinel(); + sentinel.HostFilter = host => "{0}?db=1".Fmt(host); + + using var clientsManager = sentinel.Start(); + using var client = clientsManager.GetClient(); + Assert.That(client.Db, Is.EqualTo(1)); } [Test] diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj b/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj index 6d085b6b..2d59d514 100644 --- a/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj +++ b/tests/ServiceStack.Redis.Tests.Sentinel/ServiceStack.Redis.Tests.Sentinel.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1;net45 + net472;net6.0 portable ServiceStack.Redis.Tests.Sentinel Library @@ -21,16 +21,17 @@ - - - + + + + - + $(DefineConstants);NET45 - + @@ -38,24 +39,13 @@ + - - - - - - - - $(DefineConstants);NETCORE_SUPPORT;NETCORE + + $(DefineConstants);NETCORE;NET6_0;NET6_0_OR_GREATER - - - - - - - + diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/backup/ServiceStack.Redis.Tests.Sentinel.csproj b/tests/ServiceStack.Redis.Tests.Sentinel/backup/ServiceStack.Redis.Tests.Sentinel.csproj deleted file mode 100644 index 7b4a81f2..00000000 --- a/tests/ServiceStack.Redis.Tests.Sentinel/backup/ServiceStack.Redis.Tests.Sentinel.csproj +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Debug - AnyCPU - {91C55091-A946-49B5-9517-8794EBCC5784} - Library - Properties - ServiceStack.Redis.Tests.Sentinel - ServiceStack.Redis.Tests.Sentinel - v4.5 - 512 - ..\..\src\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\lib\ServiceStack.Client.dll - - - ..\..\lib\ServiceStack.Common.dll - - - ..\..\lib\ServiceStack.Interfaces.dll - - - ..\..\lib\ServiceStack.Text.dll - - - - - - - - - - - - - - - - - - - - - - - {af99f19b-4c04-4f58-81ef-b092f1fcc540} - ServiceStack.Redis - - - - - - - This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/backup/global.json b/tests/ServiceStack.Redis.Tests.Sentinel/backup/global.json deleted file mode 100644 index d21f3cb7..00000000 --- a/tests/ServiceStack.Redis.Tests.Sentinel/backup/global.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "projects": [ - "src", - "tests" - ], - "sdk": { - "version": "1.0.0-preview2-1-003177" - } -} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests.Sentinel/backup/project.json b/tests/ServiceStack.Redis.Tests.Sentinel/backup/project.json deleted file mode 100644 index 68f5682a..00000000 --- a/tests/ServiceStack.Redis.Tests.Sentinel/backup/project.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "configurations": { - "Debug": { - "buildOptions": { - "define": ["NETCORE_SUPPORT", "NETCORE", "DEBUG", "TRACE"] - } - }, - "Release": { - "buildOptions": { - "define": ["NETCORE_SUPPORT", "NETCORE", "TRACE"], - "optimize": true - } - } - }, - "buildOptions": { - "debugType": "portable", - "emitEntryPoint": true - }, - "dependencies": { - "Microsoft.NETCore.App": { - "type": "platform", - "version": "1.1.0" - }, - "NUnitLite": "3.5", - "ServiceStack.Redis": "1.0.*", - "ServiceStack.Interfaces": "1.0.*", - "ServiceStack.Text": "1.0.*", - "ServiceStack.Common": "1.0.*" - }, - "frameworks": { - "netcoreapp1.1": { - "imports": "dnxcore50", - "dependencies" : { - "System.Runtime.Serialization.Primitives": "4.*" - } - }, - "net45": { - } - } -} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/AdhocClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/AdhocClientTests.Async.cs new file mode 100644 index 00000000..0bbf678f --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/AdhocClientTests.Async.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class AdhocClientTestsAsync + { + [Test] + public async Task Search_Test() + { + await using var client = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + + const string cacheKey = "urn+metadata:All:SearchProProfiles?SwanShinichi Osawa /0/8,0,0,0"; + const long value = 1L; + await client.SetAsync(cacheKey, value); + var result = await client.GetAsync(cacheKey); + + Assert.That(result, Is.EqualTo(value)); + } + + // remaining tests from parent do not touch redis + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/AsyncImplementationsTests.Async.cs b/tests/ServiceStack.Redis.Tests/AsyncImplementationsTests.Async.cs new file mode 100644 index 00000000..3b835c38 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/AsyncImplementationsTests.Async.cs @@ -0,0 +1,882 @@ +// Copyright (c) Service Stack LLC. All Rights Reserved. +// License: https://raw.github.com/ServiceStack/ServiceStack/master/license.txt + +using NUnit.Framework; +using ServiceStack.Caching; +using ServiceStack.Data; +using ServiceStack.Model; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Pipeline; +using ServiceStack.Redis.Support.Locking; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing.Text; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + // verify that anything that implements IFoo also implements IFooAsync + [Category("Async")] + public class AsyncImplementationTests + { + private static readonly Type[] AllTypes + = typeof(RedisClient).Assembly.GetTypes() + .Concat(typeof(AsyncImplementationTests).Assembly.GetTypes()) + .Where(x => x.IsClass) + .OrderBy(x => x.FullName).ToArray(); + + private string Log(string message) + { + TestContext.Out.WriteLine(message); + return message; + } + + [TestCase(typeof(ICacheClient), typeof(ICacheClientAsync))] + [TestCase(typeof(IEntityStore), typeof(IEntityStoreAsync))] + [TestCase(typeof(IEntityStore<>), typeof(IEntityStoreAsync<>))] + [TestCase(typeof(IRedisClient), typeof(IRedisClientAsync))] + + [TestCase(typeof(IRedisClientsManager), typeof(IRedisClientsManagerAsync))] + [TestCase(typeof(IRedisNativeClient), typeof(IRedisNativeClientAsync))] + [TestCase(typeof(IRedisPipeline), typeof(IRedisPipelineAsync))] + [TestCase(typeof(IRedisPipelineShared), typeof(IRedisPipelineSharedAsync))] + [TestCase(typeof(IRedisQueueableOperation), typeof(IRedisQueueableOperationAsync))] + + [TestCase(typeof(IRedisQueueCompletableOperation), typeof(IRedisQueueCompletableOperationAsync))] + [TestCase(typeof(IRedisTransaction), typeof(IRedisTransactionAsync))] + [TestCase(typeof(IRedisTransactionBase), typeof(IRedisTransactionBaseAsync))] + [TestCase(typeof(IRedisTypedClient<>), typeof(IRedisTypedClientAsync<>))] + [TestCase(typeof(IRemoveByPattern), typeof(IRemoveByPatternAsync))] + + [TestCase(typeof(IDistributedLock), typeof(IDistributedLockAsync))] + [TestCase(typeof(IRedisSubscription), typeof(IRedisSubscriptionAsync))] + [TestCase(typeof(IRedisHash), typeof(IRedisHashAsync))] + [TestCase(typeof(IRedisSortedSet), typeof(IRedisSortedSetAsync))] + [TestCase(typeof(IRedisSet), typeof(IRedisSetAsync))] + + [TestCase(typeof(IRedisList), typeof(IRedisListAsync))] + [TestCase(typeof(IRedisHash<,>), typeof(IRedisHashAsync<,>))] + [TestCase(typeof(IRedisSortedSet<>), typeof(IRedisSortedSetAsync<>))] + [TestCase(typeof(IRedisSet<>), typeof(IRedisSetAsync<>))] + [TestCase(typeof(IRedisList<>), typeof(IRedisListAsync<>))] + + [TestCase(typeof(IRedisTypedPipeline<>), typeof(IRedisTypedPipelineAsync<>))] + [TestCase(typeof(IRedisTypedQueueableOperation<>), typeof(IRedisTypedQueueableOperationAsync<>))] + [TestCase(typeof(IRedisTypedTransaction<>), typeof(IRedisTypedTransactionAsync<>))] + + public void TestSameAPI(Type syncInterface, Type asyncInterface) + { + TestContext.Out.WriteLine($"Comparing '{GetCSharpTypeName(syncInterface)}' and '{GetCSharpTypeName(asyncInterface)}'..."); + + var actual = new List(); + foreach (var method in asyncInterface.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + var tok = new MethodToken(method); + actual.Add(GetSignature(tok)); + } + + var expected = new List(); + ParameterToken cancellationParameter = new ParameterToken("token", typeof(CancellationToken), ParameterAttributes.Optional); + foreach (var method in syncInterface.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + AddExpected(method); + } + if (asyncInterface == typeof(IRedisSortedSetAsync) + || asyncInterface == typeof(IRedisSetAsync) + || asyncInterface == typeof(IRedisListAsync)) + { + AddFrom(typeof(ICollection), nameof(ICollection.Clear)); + AddFrom(typeof(ICollection), nameof(ICollection.Add)); + AddFrom(typeof(ICollection), nameof(ICollection.Remove)); + AddFrom(typeof(ICollection), nameof(ICollection.Contains)); + AddFrom(typeof(ICollection), "get_" + nameof(ICollection.Count), true); + + if (asyncInterface == typeof(IRedisListAsync)) + { + AddFrom(typeof(IList), nameof(IList.IndexOf)); + AddFrom(typeof(IList), nameof(IList.RemoveAt)); + AddFrom(typeof(IList), "set_Item", true); + AddFrom(typeof(IList), "get_Item", true); + } + } + else if (asyncInterface == typeof(IRedisSortedSetAsync<>) + || asyncInterface == typeof(IRedisSetAsync<>) + || asyncInterface == typeof(IRedisListAsync<>)) + { + AddFrom(typeof(ICollection<>), nameof(ICollection.Clear)); + AddFrom(typeof(ICollection<>), nameof(ICollection.Add)); + AddFrom(typeof(ICollection<>), nameof(ICollection.Remove)); + AddFrom(typeof(ICollection<>), nameof(ICollection.Contains)); + AddFrom(typeof(ICollection<>), "get_" + nameof(ICollection.Count), true); + + if (asyncInterface == typeof(IRedisListAsync<>)) + { + AddFrom(typeof(IList<>), nameof(IList.IndexOf)); + AddFrom(typeof(IList<>), nameof(IList.RemoveAt)); + AddFrom(typeof(IList<>), "set_Item", true); + AddFrom(typeof(IList<>), "get_Item", true); + } + } + else if (asyncInterface == typeof(IRedisHashAsync<,>)) + { + AddFrom(typeof(ICollection<>).MakeGenericType(typeof(KeyValuePair<,>).MakeGenericType(asyncInterface.GetGenericArguments())), nameof(IDictionary.Add)); + AddFrom(typeof(IDictionary<,>), nameof(IDictionary.Add)); + AddFrom(typeof(ICollection<>), nameof(IDictionary.Clear)); + AddFrom(typeof(IDictionary<,>), nameof(IDictionary.ContainsKey)); + AddFrom(typeof(IDictionary<,>), nameof(IDictionary.Remove)); + AddFrom(typeof(ICollection<>), "get_" + nameof(IDictionary.Count), true); + } + else if (asyncInterface == typeof(IRedisHashAsync)) + { + AddFrom(typeof(ICollection>), nameof(IDictionary.Add)); + AddFrom(typeof(IDictionary), nameof(IDictionary.Add)); + AddFrom(typeof(ICollection), nameof(IDictionary.Clear)); + AddFrom(typeof(IDictionary), nameof(IDictionary.ContainsKey)); + AddFrom(typeof(IDictionary), nameof(IDictionary.Remove)); + AddFrom(typeof(ICollection), "get_" + nameof(IDictionary.Count), true); + } + else if (asyncInterface == typeof(IRedisNativeClientAsync)) + { + AddFrom(typeof(RedisClient), nameof(RedisClient.SlowlogReset)); + AddFrom(typeof(RedisClient), nameof(RedisClient.BitCount)); + AddFromTyped(typeof(RedisClient), nameof(RedisClient.ZCount), typeof(string), typeof(double), typeof(double)); + // can't expose as SlowlogItem because of interface locations + expected.Add("ValueTask SlowlogGetAsync(int? top = default, CancellationToken token = default)"); + // adding missing "exists" capability + expected.Add("ValueTask SetAsync(string key, byte[] value, bool exists, long expirySeconds = 0, long expiryMilliseconds = 0, CancellationToken token = default)"); + } + else if (asyncInterface == typeof(IRedisClientAsync)) + { + expected.Add("ValueTask GetSlowlogAsync(int? numberOfRecords = default, CancellationToken token = default)"); + expected.Add("ValueTask SlowlogResetAsync(CancellationToken token = default)"); + } + else if (asyncInterface == typeof(ICacheClientAsync)) + { + AddFrom(typeof(ICacheClientExtended), nameof(ICacheClientExtended.GetKeysByPattern)); + AddFrom(typeof(ICacheClientExtended), nameof(ICacheClientExtended.GetTimeToLive)); + AddFrom(typeof(ICacheClientExtended), nameof(ICacheClientExtended.RemoveExpiredEntries)); + } + + void AddFrom(Type syncInterface, string name, bool fromPropertyToMethod = false) + => AddExpected(syncInterface.GetMethod(name), fromPropertyToMethod); + void AddFromTyped(Type syncInterface, string name, params Type[] types) + => AddExpected(syncInterface.GetMethod(name, types), false); + + Type AsyncType(Type result) + { + bool useTask = asyncInterface == typeof(ICacheClientAsync) + || asyncInterface == typeof(IRemoveByPatternAsync) + || asyncInterface == typeof(IEntityStoreAsync) + || asyncInterface == typeof(IEntityStoreAsync<>); + + if (result is null || result == typeof(void)) + return useTask ? typeof(Task) : typeof(ValueTask); + + return (useTask ? typeof(Task<>) : typeof(ValueTask<>)).MakeGenericType(result); + } + void AddExpected(MethodInfo method, bool fromPropertyToMethod = false) + { + if (method is null) return; + var tok = new MethodToken(method); + + ParameterToken[] parameters = tok.GetParameters(); + + // think about the return type + Type returnType; + if (tok.ReturnType == typeof(void)) + { + returnType = AsyncType(tok.ReturnType); + } + else if (tok.ReturnType == typeof(IDisposable)) + { + returnType = typeof(IAsyncDisposable); + } + else if (tok.ReturnType.IsGenericType && tok.ReturnType.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + returnType = typeof(IAsyncEnumerable<>).MakeGenericType(tok.ReturnType.GetGenericArguments()); + } + else + { + returnType = AsyncType(SwapForAsyncIfNeedeed(tok.ReturnType)); + } + string name = tok.Name + "Async"; + bool addCancellation = true; + // sniff to see if this is a delegate hook + if (parameters.Length == 0 && typeof(Delegate).IsAssignableFrom(tok.ReturnType) && name.StartsWith("get_")) + { + // property getter; replace with event add + returnType = typeof(void); + name = "add_" + name.Substring(4); + parameters = new[] { new ParameterToken("value", ActionDelegateToFunc(tok.ReturnType), default) }; + + } + else if (parameters.Length == 1 && tok.ReturnType == typeof(void) && name.StartsWith("set_") + && typeof(Delegate).IsAssignableFrom(parameters[0].ParameterType)) + { + // property setter; replace with event remove + returnType = typeof(void); + name = "remove_" + name.Substring(4); + ref ParameterToken p = ref parameters[0]; + p = p.WithParameterType(ActionDelegateToFunc(p.ParameterType)); + } + + if (name.StartsWith("get_") || name.StartsWith("set_") || name.StartsWith("add_") || name.StartsWith("remove_")) + { + bool preserve = (name.StartsWith("get_") || name.StartsWith("set_")), fullyHandled = false; + if (asyncInterface == typeof(IRedisNativeClientAsync) || asyncInterface == typeof(IRedisClientAsync) + || asyncInterface == typeof(IRedisTypedClientAsync<>)) + { + switch (tok.Name) + { + case "get_" + nameof(IRedisNativeClient.DbSize): + case "get_" + nameof(IRedisNativeClient.LastSave): + case "get_" + nameof(IRedisNativeClient.Info): + fromPropertyToMethod = true; + preserve = false; + break; + case "set_" + nameof(IRedisNativeClient.Db): + name = nameof(IRedisNativeClientAsync.SelectAsync); + parameters[0] = parameters[0].WithName("db"); + fullyHandled = true; + break; + case "set_" + nameof(IRedisClientAsync.Hashes): + case "set_" + nameof(IRedisClientAsync.Lists): + case "set_" + nameof(IRedisClientAsync.Sets): + case "set_" + nameof(IRedisClientAsync.SortedSets): + return; // no "set" included + case "get_Item": + case "set_Item": + return; // no indexer + } + } + + if (fromPropertyToMethod) + { + name = name switch + { + "get_ItemAsync" => "ElementAtAsync", + "set_ItemAsync" => "SetValueAsync", + _ => name.Substring(4), // don't worry about the remove, that isn't in this catchment + }; + } + else if (preserve && !fullyHandled) + { // just keep it the same by default + name = tok.Name; + returnType = SwapForAsyncIfNeedeed(tok.ReturnType); + addCancellation = false; + } + + else if (fullyHandled) { } + else + { + addCancellation = false; + } + } + + static Type ActionDelegateToFunc(Type type) + { + if (type.IsGenericType) + { + var genDef = type.GetGenericTypeDefinition(); + var targs = type.GetGenericArguments(); + Array.Resize(ref targs, targs.Length + 1); + targs[targs.Length - 1] = typeof(ValueTask); + return Expression.GetFuncType(targs); + } + return type; + } + + if (asyncInterface == typeof(IRedisQueueCompletableOperationAsync) && parameters.Length == 1) + { + // very unusual case; Func => Func> + returnType = typeof(void); + ref ParameterToken p = ref parameters[0]; + if (p.ParameterType == typeof(Action)) + { + p = p.WithParameterType(typeof(Func)); + } + else + { + p = p.WithParameterType(typeof(Func<,>).MakeGenericType( + typeof(CancellationToken), typeof(ValueTask<>).MakeGenericType(p.ParameterType.GetGenericArguments()))); + } + tok = new MethodToken(name, returnType, parameters, tok.IsGenericMethod, tok.IsGenericMethodDefinition, tok.GetGenericArguments(), tok.AllAttributes()); + expected.Add(GetSignature(tok)); + } + else if (asyncInterface == typeof(IRedisQueueableOperationAsync) || asyncInterface == typeof(IRedisTypedQueueableOperationAsync<>)) + { + // very unusual case; Func => Func> + if (parameters.Length != 3) return; // move to optionals rather than overloads + ref ParameterToken p = ref parameters[0]; // fixup the delegate type + if (p.ParameterType.IsGenericType) + { + var genDef = p.ParameterType.GetGenericTypeDefinition(); + Type[] funcTypes = p.ParameterType.GetGenericArguments(); + funcTypes[0] = SwapForAsyncIfNeedeed(funcTypes[0]); + + if (genDef == typeof(Action<>)) + { + Array.Resize(ref funcTypes, funcTypes.Length + 1); + funcTypes[funcTypes.Length - 1] = typeof(ValueTask); + } + else + { + funcTypes[funcTypes.Length - 1] = typeof(ValueTask<>) + .MakeGenericType(funcTypes[funcTypes.Length - 1]); + } + + p = p.WithParameterType(typeof(Func<,>).MakeGenericType(funcTypes)); + } + + // make the other parameters optional + p = ref parameters[1]; + p = p.WithAttributes(p.Attributes | ParameterAttributes.Optional); + p = ref parameters[2]; + p = p.WithAttributes(p.Attributes | ParameterAttributes.Optional); + returnType = typeof(void); + name = method.Name; // retain the original name + + tok = new MethodToken(name, returnType, parameters, tok.IsGenericMethod, tok.IsGenericMethodDefinition, tok.GetGenericArguments(), tok.AllAttributes()); + expected.Add(GetSignature(tok)); + } + else + { + for (int i = 0; i < parameters.Length; i++) + { + ref ParameterToken p = ref parameters[i]; + Type type = p.ParameterType, swapped = SwapForAsyncIfNeedeed(type); + if (type != swapped) + { + p = p.WithParameterType(swapped); + } + } + + static bool IsParams(in MethodToken tok) + { + var ps = tok.GetParameters(); + if (ps is null || ps.Length == 0) return false; + return ps.Last().IsDefined(typeof(ParamArrayAttribute)); + } + + if (IsParams(tok)) + { + // include it with params but without CancellationToken + tok = new MethodToken(name, returnType, parameters, tok.IsGenericMethod, tok.IsGenericMethodDefinition, tok.GetGenericArguments(), tok.AllAttributes()); + expected.Add(GetSignature(tok)); + + // and now remove the params so we can get with CancellationToken + ref ParameterToken p = ref parameters[parameters.Length - 1]; + p = p.WithAllAttributes(p.AllAttributes().Where(a => !(a is ParamArrayAttribute)).ToArray()); + } + + if (asyncInterface == typeof(IDistributedLockAsync) && name == nameof(IDistributedLockAsync.LockAsync)) + { + // can't use "out", so uses a new LockState type instead + returnType = typeof(ValueTask); + parameters = RemoveByRef(parameters); + + static ParameterToken[] RemoveByRef(ParameterToken[] parameters) + { + if (parameters.Any(x => x.ParameterType.IsByRef)) + { + parameters = parameters.Where(x => !x.ParameterType.IsByRef).ToArray(); + } + return parameters; + } + } + if (asyncInterface == typeof(IRedisNativeClientAsync)) + { + switch (tok.Name) + { + case nameof(IRedisNativeClient.DecrBy): + case nameof(IRedisNativeClient.IncrBy): + parameters[1] = parameters[1].WithParameterType(typeof(long)); + returnType = typeof(ValueTask); + break; + case nameof(IRedisNativeClient.Shutdown): + Insert(ref parameters, 0, new ParameterToken("noSave", typeof(bool), ParameterAttributes.Optional, false)); + break; + case nameof(IRedisNativeClient.Set): + Insert(ref parameters, 2, new ParameterToken("expirySeconds", typeof(long), ParameterAttributes.Optional, 0)); + Insert(ref parameters, 3, new ParameterToken("expiryMilliseconds", typeof(long), ParameterAttributes.Optional, 0)); + break; + } + + static void Insert(ref ParameterToken[] parameters, int index, ParameterToken value) + { + // don't try to be clever; this is inefficient but correct + var list = parameters.ToList(); + list.Insert(index, value); + parameters = list.ToArray(); + } + } + + if (asyncInterface == typeof(IRedisSubscriptionAsync) && tok.Name == "get_" + nameof(IRedisSubscription.SubscriptionCount)) + { + // this is a purely client value; don't treat as async + name = tok.Name; + returnType = tok.ReturnType; + } + + if (asyncInterface == typeof(IRedisClientAsync) || asyncInterface == typeof(IRedisTypedClientAsync<>)) + { + switch (tok.Name) + { + case nameof(IRedisClient.UrnKey): + case nameof(IRedisClient.As): + addCancellation = false; + name = tok.Name; + returnType = SwapForAsyncIfNeedeed(tok.ReturnType); + break; + case nameof(IRedisClient.Save): // to avoid AsyncAsync and overloaded meaning of Async + name = nameof(IRedisClientAsync.ForegroundSaveAsync); + break; + case nameof(IRedisClient.SaveAsync): // to avoid AsyncAsync and overloaded meaning of Async + name = nameof(IRedisClientAsync.BackgroundSaveAsync); + break; + case nameof(IRedisClient.RewriteAppendOnlyFileAsync): // for consistency + name = nameof(IRedisClientAsync.BackgroundRewriteAppendOnlyFileAsync); + break; + case nameof(IRedisClient.ExecCachedLua): + // Func scriptSha1 => Func> scriptSha1 + parameters[1] = parameters[1].WithParameterType(typeof(Func<,>).MakeGenericType(typeof(string), typeof(ValueTask<>).MakeGenericType(method.GetGenericArguments()))); + break; + case nameof(IRedisClient.AcquireLock) when asyncInterface == typeof(IRedisClientAsync): + if (parameters.Length != 2) return; // 2 overloads combined into 1 + parameters[1] = parameters[1].AsNullable().AsOptional(); + returnType = typeof(ValueTask<>).MakeGenericType(returnType); // add await for acquisition + break; + case nameof(IRedisClient.AcquireLock) when asyncInterface == typeof(IRedisTypedClientAsync<>): + if (parameters.Length != 1) return; // 2 overloads combined into 1 + parameters[0] = parameters[0].AsNullable().AsOptional(); + returnType = typeof(ValueTask<>).MakeGenericType(returnType); // add await for acquisition + break; + case nameof(IRedisClient.SetValueIfExists) when asyncInterface == typeof(IRedisClientAsync): + case nameof(IRedisClient.SetValueIfNotExists) when asyncInterface == typeof(IRedisClientAsync): + if (parameters.Length != 3) return; // 2 overloads combined into 1 + parameters[2] = parameters[2].AsNullable().AsOptional(); + break; + case nameof(IRedisClient.CreatePipeline): + case nameof(IRedisTypedClient.GetHash): + addCancellation = false; + name = tok.Name; + returnType = SwapForAsyncIfNeedeed(tok.ReturnType); + break; + } + } + + for (int i = 0; i < parameters.Length; i++) + { + ref ParameterToken p = ref parameters[i]; + var type = p.ParameterType; + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>)) + { + // prefer IDictionary<,> to Dictionary<,> + p = p.WithParameterType(typeof(IDictionary<,>).MakeGenericType(type.GetGenericArguments())); + } + } + + // append optional CancellationToken + if (addCancellation) + { + Array.Resize(ref parameters, parameters.Length + 1); + parameters[parameters.Length - 1] = cancellationParameter; + } + tok = new MethodToken(name, returnType, parameters, tok.IsGenericMethod, tok.IsGenericMethodDefinition, tok.GetGenericArguments(), tok.AllAttributes()); + expected.Add(GetSignature(tok)); + } + } + + actual.Sort(); + expected.Sort(); + int extra = 0, missing = 0, match = 0; + Log($"actual: {actual.Count}, expected: {expected.Count}"); + foreach (var method in actual.Except(expected)) + { + Log($"+ {method}"); + extra++; + } + foreach (var method in expected.Except(actual)) + { + Log($"- {method}"); + missing++; + } + foreach (var method in expected.Intersect(actual)) + { + Log($"= {method}"); + match++; + } + Assert.True(extra == 0 && missing == 0, $"signature mismatch on {GetCSharpTypeName(asyncInterface)}; missing: {missing}, extra: {extra}, match: {match}"); + + + static Type SwapForAsyncIfNeedeed(Type type) + { + if (type.IsArray) + { + var t = type.GetElementType(); + var swapped = SwapForAsyncIfNeedeed(t); + if (t != swapped) + { + var rank = type.GetArrayRank(); + return swapped.MakeArrayType(rank); + } + return type; + } + if (type == typeof(IRedisClient)) return typeof(IRedisClientAsync); + if (type == typeof(ICacheClient)) return typeof(ICacheClientAsync); + if (type == typeof(IRedisPipeline)) return typeof(IRedisPipelineAsync); + if (type == typeof(IRedisPipelineShared)) return typeof(IRedisPipelineSharedAsync); + if (type == typeof(IDisposable)) return typeof(IAsyncDisposable); + if (type == typeof(IRedisList)) return typeof(IRedisListAsync); + if (type == typeof(IRedisSet)) return typeof(IRedisSetAsync); + if (type == typeof(IRedisSortedSet)) return typeof(IRedisSortedSetAsync); + if (type == typeof(IRedisHash)) return typeof(IRedisHashAsync); + if (type == typeof(IRedisSubscription)) return typeof(IRedisSubscriptionAsync); + if (type == typeof(IRedisTransaction)) return typeof(IRedisTransactionAsync); + + if (type.IsGenericType) + { + var genDef = type.GetGenericTypeDefinition(); + var targs = type.GetGenericArguments(); + for (int i = 0; i < targs.Length; i++) + targs[i] = SwapForAsyncIfNeedeed(targs[i]); + + if (genDef == typeof(IRedisTypedClient<>)) return typeof(IRedisTypedClientAsync<>).MakeGenericType(targs); + if (genDef == typeof(IRedisList<>)) return typeof(IRedisListAsync<>).MakeGenericType(targs); + if (genDef == typeof(IRedisSet<>)) return typeof(IRedisSetAsync<>).MakeGenericType(targs); + if (genDef == typeof(IRedisSortedSet<>)) return typeof(IRedisSortedSetAsync<>).MakeGenericType(targs); + if (genDef == typeof(IRedisTypedTransaction<>)) return typeof(IRedisTypedTransactionAsync<>).MakeGenericType(targs); + if (genDef == typeof(IRedisHash<,>)) return typeof(IRedisHashAsync<,>).MakeGenericType(targs); + if (genDef == typeof(IRedisTypedPipeline<>)) return typeof(IRedisTypedPipelineAsync<>).MakeGenericType(targs); + + return genDef.MakeGenericType(targs); + } + + return type; + } + } + + static string GetCSharpTypeName(Type type) + { + if (!(type.IsGenericType || type.IsArray)) + { + return GetSimpleCSharpTypeName(type); + } + var sb = new StringBuilder(); + AppendCSharpTypeName(type, sb); + return sb.ToString(); + } + static string GetSimpleCSharpTypeName(Type type) + { + if (type == typeof(void)) return "void"; + if (type == typeof(bool)) return "bool"; + if (type == typeof(sbyte)) return "sbyte"; + if (type == typeof(short)) return "short"; + if (type == typeof(int)) return "int"; + if (type == typeof(long)) return "long"; + if (type == typeof(byte)) return "byte"; + if (type == typeof(ushort)) return "ushort"; + if (type == typeof(uint)) return "uint"; + if (type == typeof(ulong)) return "ulong"; + if (type == typeof(string)) return "string"; + if (type == typeof(double)) return "double"; + if (type == typeof(float)) return "float"; + if (type == typeof(object)) return "object"; + + return type.Name; + } + + static void AppendCSharpTypeName(Type type, StringBuilder sb) + { + if (type.IsArray) + { + // we won't worry about the difference between vector and non-vector rank zero arrays + AppendCSharpTypeName(type.GetElementType(), sb); + sb.Append("[").Append(',', type.GetArrayRank() - 1).Append("]"); + } + else if (type.IsGenericParameter) + { + sb.Append(type.Name); + } + else if (type.IsGenericType) + { + var nullable = Nullable.GetUnderlyingType(type); + if (nullable is object) + { + AppendCSharpTypeName(nullable, sb); + sb.Append("?"); + } + else + { + var name = type.Name; + int i = name.IndexOf('`'); + if (i < 0) + { + sb.Append(name); + } + else + { + sb.Append(name, 0, i); + } + sb.Append("<"); + var targs = type.GetGenericArguments(); + for (i = 0; i < targs.Length; i++) + { + if (i != 0) sb.Append(", "); + sb.Append(GetCSharpTypeName(targs[i])); + } + sb.Append(">"); + } + } + else + { + sb.Append(GetSimpleCSharpTypeName(type)); + } + } + static string GetSignature(MethodToken method) + { + var sb = new StringBuilder(); + AppendCSharpTypeName(method.ReturnType, sb); + sb.Append(' ').Append(method.Name); + if (method.IsGenericMethodDefinition) + { + sb.Append('<'); + var args = method.GetGenericArguments(); + for (int i = 0; i < args.Length; i++) + { + if (i != 0) sb.Append(", "); + sb.Append(args[i].Name); + } + sb.Append('>'); + } + sb.Append('('); + var ps = method.GetParameters(); + for (int i = 0; i < ps.Length; i++) + { + var p = ps[i]; + if (i != 0) sb.Append(", "); + if (p.IsDefined(typeof(ParamArrayAttribute))) + { + sb.Append("params "); + } + if (p.ParameterType.IsByRef) + { + const ParameterAttributes InOut = ParameterAttributes.In | ParameterAttributes.Out; + sb.Append((p.Attributes & InOut) switch + { + ParameterAttributes.In => "in", + ParameterAttributes.Out => "out", + _ => "ref" + }).Append(' '); + AppendCSharpTypeName(p.ParameterType.GetElementType(), sb); + } + else + { + AppendCSharpTypeName(p.ParameterType, sb); + } + sb.Append(' ').Append(p.Name); + if ((p.Attributes & ParameterAttributes.Optional) == ParameterAttributes.Optional) + { + sb.Append(" = "); + switch (p.DefaultValue) + { + case null: + case DBNull _: // used for delegates, honest! + sb.Append("default"); + break; + case string s: + sb.Append(@"""").Append(s.Replace(@"""", @"""""")).Append(@""""); + break; + case object o: + sb.Append(Convert.ToString(o, CultureInfo.InvariantCulture)); + break; + } + } + } + return sb.Append(')').ToString(); + } + + readonly struct ParameterToken + { + public bool IsDefined(Type attributeType) + => _allAttributes.Any(a => attributeType.IsAssignableFrom(a.GetType())); + public object DefaultValue { get; } + public ParameterAttributes Attributes { get; } + public string Name { get; } + public Type ParameterType { get; } + private readonly object[] _allAttributes; + public object[] AllAttributes() => MethodToken.Clone(_allAttributes); + + internal ParameterToken WithAllAttributes(params object[] allAttributes) + => new ParameterToken(Name, ParameterType, Attributes, DefaultValue, allAttributes); + + internal ParameterToken WithParameterType(Type parameterType) + => new ParameterToken(Name, parameterType, Attributes, DefaultValue, _allAttributes); + + internal ParameterToken AsNullable() + { + if (!ParameterType.IsValueType) return this; // already nullable (ish) + var existing = Nullable.GetUnderlyingType(ParameterType); + if (existing is object) return this; // already nullable + return WithParameterType(typeof(Nullable<>).MakeGenericType(ParameterType)); + } + + internal ParameterToken AsOptional() + => WithAttributes(Attributes | ParameterAttributes.Optional); + + internal ParameterToken WithAttributes(ParameterAttributes attributes) + => new ParameterToken(Name, ParameterType, attributes, DefaultValue, _allAttributes); + + internal ParameterToken WithName(string name) + => new ParameterToken(name, ParameterType, Attributes, DefaultValue, _allAttributes); + + public ParameterToken(ParameterInfo source) + { + Name = source.Name; + ParameterType = source.ParameterType; + Attributes = source.Attributes; + DefaultValue = source.DefaultValue; + _allAttributes = source.AllAttributes(); + } + + public ParameterToken(string name, Type parameterType, ParameterAttributes attributes, object defaultValue = default, params object[] allAttributes) + { + Name = name; + ParameterType = parameterType; + Attributes = attributes; + DefaultValue = defaultValue; + _allAttributes = allAttributes ?? Array.Empty(); + } + } + + readonly struct MethodToken + { + private readonly ParameterToken[] _parameters; + private readonly Type[] _genericArguments; + private readonly object[] _allAttributes; + public bool IsDefined(Type attributeType) + => _allAttributes.Any(a => attributeType.IsAssignableFrom(a.GetType())); + internal static T[] Clone(T[] source) + { + if (source is null) return null; + var result = new T[source.Length]; + source.CopyTo(result, 0); + return result; + } + public ParameterToken[] GetParameters() => Clone(_parameters); + public Type[] GetGenericArguments() => Clone(_genericArguments); + public string Name { get; } + public bool IsGenericMethodDefinition { get; } + public bool IsGenericMethod { get; } + public Type ReturnType { get; } + public object[] AllAttributes() => Clone(_allAttributes); + public MethodToken(MethodInfo source) + { + Name = source.Name; + IsGenericMethod = source.IsGenericMethod; + IsGenericMethodDefinition = source.IsGenericMethodDefinition; + ReturnType = source.ReturnType; + _genericArguments = (source.IsGenericMethod || source.IsGenericMethodDefinition) + ? source.GetGenericArguments() : null; + var ps = source.GetParameters(); + _parameters = ps is null ? null : Array.ConvertAll(ps, p => new ParameterToken(p)); + _allAttributes = source.AllAttributes(); + } + + public MethodToken(string name, Type returnType, ParameterToken[] parameters, + bool isGenericMethod, bool isGenericMethodDefinition, Type[] genericArguments, + params object[] allAttributes) + { + Name = name; + ReturnType = returnType; + IsGenericMethod = isGenericMethod; + IsGenericMethodDefinition = isGenericMethodDefinition; + _genericArguments = genericArguments; + _parameters = parameters ?? Array.Empty(); + _allAttributes = allAttributes ?? Array.Empty(); + } + } + + [TestCase(typeof(ICacheClient), typeof(ICacheClientAsync))] + [TestCase(typeof(ICacheClientExtended), typeof(ICacheClientAsync), typeof(BasicRedisClientManager))] // duplicate not an error; APIs are coalesced + [TestCase(typeof(IEntityStore), typeof(IEntityStoreAsync))] + [TestCase(typeof(IEntityStore<>), typeof(IEntityStoreAsync<>))] + [TestCase(typeof(IRedisClient), typeof(IRedisClientAsync))] + + [TestCase(typeof(IRedisClientsManager), typeof(IRedisClientsManagerAsync))] + [TestCase(typeof(IRedisNativeClient), typeof(IRedisNativeClientAsync))] + [TestCase(typeof(IRedisPipeline), typeof(IRedisPipelineAsync))] + [TestCase(typeof(IRedisPipelineShared), typeof(IRedisPipelineSharedAsync))] + [TestCase(typeof(IRedisQueueableOperation), typeof(IRedisQueueableOperationAsync))] + + [TestCase(typeof(IRedisQueueCompletableOperation), typeof(IRedisQueueCompletableOperationAsync))] + [TestCase(typeof(IRedisTransaction), typeof(IRedisTransactionAsync))] + [TestCase(typeof(IRedisTransactionBase), typeof(IRedisTransactionBaseAsync))] + [TestCase(typeof(IRedisTypedClient<>), typeof(IRedisTypedClientAsync<>))] + [TestCase(typeof(IRemoveByPattern), typeof(IRemoveByPatternAsync))] + + [TestCase(typeof(IDistributedLock), typeof(IDistributedLockAsync))] + [TestCase(typeof(IRedisSubscription), typeof(IRedisSubscriptionAsync))] + [TestCase(typeof(IRedisHash), typeof(IRedisHashAsync))] + [TestCase(typeof(IRedisSortedSet), typeof(IRedisSortedSetAsync))] + [TestCase(typeof(IRedisSet), typeof(IRedisSetAsync))] + + [TestCase(typeof(IRedisList), typeof(IRedisListAsync))] + [TestCase(typeof(IRedisHash<,>), typeof(IRedisHashAsync<,>))] + [TestCase(typeof(IRedisSortedSet<>), typeof(IRedisSortedSetAsync<>))] + [TestCase(typeof(IRedisSet<>), typeof(IRedisSetAsync<>))] + [TestCase(typeof(IRedisList<>), typeof(IRedisListAsync<>))] + + [TestCase(typeof(IRedisTypedPipeline<>), typeof(IRedisTypedPipelineAsync<>))] + [TestCase(typeof(IRedisTypedQueueableOperation<>), typeof(IRedisTypedQueueableOperationAsync<>))] + [TestCase(typeof(IRedisTypedTransaction<>), typeof(IRedisTypedTransactionAsync<>))] + public void TestFullyImplemented(Type syncInterface, Type asyncInterface, params Type[] ignore) + { + HashSet except = new HashSet(ignore ?? Type.EmptyTypes); +#if NET472 // only exists there! + if (syncInterface == typeof(IRedisClientsManager)) + { + except.Add(typeof(ServiceStack.Redis.Support.Diagnostic.TrackingRedisClientsManager)); + } +#endif + + var syncTypes = AllTypes.Except(except).Where(x => Implements(x, syncInterface)).ToArray(); + DumpTypes(syncInterface, syncTypes); + + var asyncTypes = AllTypes.Except(except).Where(x => Implements(x, asyncInterface)).ToArray(); + DumpTypes(asyncInterface, asyncTypes); + Assert.AreEqual(syncTypes, asyncTypes); + } + + static void DumpTypes(Type @interface, Type[] classes) + { + TestContext.Out.WriteLine($"Classes that implement {@interface.Name}: {classes.Length}:"); + foreach (var @class in classes) + { + TestContext.Out.WriteLine($" {@class.FullName}"); + } + TestContext.Out.WriteLine(); + } + + static bool Implements(Type @class, Type @interface) + { + if (@interface.IsGenericTypeDefinition) + { + var found = (from iType in @class.GetInterfaces() + where iType.IsGenericType + && iType.GetGenericTypeDefinition() == @interface + select iType).SingleOrDefault(); + return found != null && found.IsAssignableFrom(@class); + } + return @interface.IsAssignableFrom(@class); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/BasicRediscClientManagerTests.Async.cs b/tests/ServiceStack.Redis.Tests/BasicRediscClientManagerTests.Async.cs new file mode 100644 index 00000000..70685fd4 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/BasicRediscClientManagerTests.Async.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + public class BasicRediscClientManagerTestsAsync + : RedisClientTestsBaseAsync + { + [Test] + public async Task Can_select_db() + { + var redisManager = new BasicRedisClientManager("127.0.0.1"); + + await using (var client = await redisManager.GetClientAsync()) + { + await client.SelectAsync(2); + await client.SetAsync("db", 2); + } + + await using (var client = await redisManager.GetClientAsync()) + { + await client.SelectAsync(3); + await client.SetAsync("db", 3); + } + + await using (var client = await redisManager.GetClientAsync()) + { + await client.SelectAsync(2); + //((RedisClient)client).ChangeDb(2); + var db = await client.GetAsync("db"); + Assert.That(db, Is.EqualTo(2)); + } + + redisManager = new BasicRedisClientManager("127.0.0.1?db=3"); + await using (var client = await redisManager.GetClientAsync()) + { + var db = await client.GetAsync("db"); + Assert.That(db, Is.EqualTo(3)); + } + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/CacheClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/CacheClientTests.Async.cs new file mode 100644 index 00000000..e63437a0 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/CacheClientTests.Async.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ServiceStack.Redis.Tests +{ + public class CacheClientTestsAsync + { + IRedisClientsManagerAsync redisManager = new RedisManagerPool(TestConfig.SingleHost); + + [Test] + public async Task Can_get_set_CacheClient_Async() + { + await using var cache = await redisManager.GetCacheClientAsync(); + await cache.FlushAllAsync(); + + await cache.SetAsync("key", "A"); + var result = await cache.GetAsync("key"); + Assert.That(result, Is.EqualTo("A")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/CacheClientTests.cs b/tests/ServiceStack.Redis.Tests/CacheClientTests.cs new file mode 100644 index 00000000..4d6f5b3d --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/CacheClientTests.cs @@ -0,0 +1,22 @@ +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ServiceStack.Redis.Tests +{ + public class CacheClientTests + : RedisClientTestsBase + { + IRedisClientsManager redisManager = new RedisManagerPool(TestConfig.SingleHost); + + [Test] + public void Can_get_set_CacheClient() + { + var cache = redisManager.GetCacheClient(); + cache.FlushAll(); + + cache.Set("key", "A"); + var result = cache.Get("key"); + Assert.That(result, Is.EqualTo("A")); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/ConfigTests.cs b/tests/ServiceStack.Redis.Tests/ConfigTests.cs index 5fada075..cc84948a 100644 --- a/tests/ServiceStack.Redis.Tests/ConfigTests.cs +++ b/tests/ServiceStack.Redis.Tests/ConfigTests.cs @@ -27,11 +27,14 @@ public void OneTimeTearDown() [TestCase("host:1?password=pass&client=nunit", "{Host:host,Port:1,Client:nunit,Password:pass}")] [TestCase("host:1?db=2", "{Host:host,Port:1,Db:2}")] [TestCase("host?ssl=true", "{Host:host,Port:6380,Ssl:True}")] + [TestCase("host:6380?ssl=true&password=pass&sslprotocols=Tls12", "{Host:host,Port:6380,Ssl:True,Password:pass,SslProtocols:Tls12}")] [TestCase("host:1?ssl=true", "{Host:host,Port:1,Ssl:True}")] [TestCase("host:1?connectTimeout=1&sendtimeout=2&receiveTimeout=3&idletimeoutsecs=4", "{Host:host,Port:1,ConnectTimeout:1,SendTimeout:2,ReceiveTimeout:3,IdleTimeOutSecs:4}")] [TestCase("redis://nunit:pass@host:1?ssl=true&db=1&connectTimeout=2&sendtimeout=3&receiveTimeout=4&retryTimeout=5&idletimeoutsecs=5&NamespacePrefix=prefix.", "{Host:host,Port:1,Ssl:True,Client:nunit,Password:pass,Db:1,ConnectTimeout:2,SendTimeout:3,ReceiveTimeout:4,RetryTimeout:5,IdleTimeOutSecs:5,NamespacePrefix:prefix.}")] + [TestCase("redis://nunit:pass@host:1?ssl=true&sslprotocols=Tls12&db=1&connectTimeout=2&sendtimeout=3&receiveTimeout=4&retryTimeout=5&idletimeoutsecs=5&NamespacePrefix=prefix.", + "{Host:host,Port:1,Ssl:True,Client:nunit,Password:pass,SslProtocols:Tls12,Db:1,ConnectTimeout:2,SendTimeout:3,ReceiveTimeout:4,RetryTimeout:5,IdleTimeOutSecs:5,NamespacePrefix:prefix.}")] public void Does_handle_different_connection_strings_settings(string connString, string expectedJsv) { var actual = connString.ToRedisEndpoint(); @@ -55,6 +58,7 @@ public void Does_handle_different_connection_strings_settings(string connString, "host:1?ConnectTimeout=1&SendTimeout=2&ReceiveTimeout=3&IdleTimeOutSecs=4")] [TestCase("redis://nunit:pass@host:1?ssl=true&db=1&connectTimeout=2&sendtimeout=3&receiveTimeout=4&idletimeoutsecs=5&NamespacePrefix=prefix.", "host:1?Client=nunit&Password=pass&Db=1&Ssl=true&ConnectTimeout=2&SendTimeout=3&ReceiveTimeout=4&IdleTimeOutSecs=5&NamespacePrefix=prefix.")] + [TestCase("password@host:6380?ssl=true&sslprotocols=Tls12", "host:6380?Password=password&Ssl=true&SslProtocols=Tls12")] public void Does_Serialize_RedisEndpoint(string connString, string expectedString) { var actual = connString.ToRedisEndpoint(); @@ -64,8 +68,8 @@ public void Does_Serialize_RedisEndpoint(string connString, string expectedStrin [Test] public void Does_set_all_properties_on_Client_using_ClientsManagers() { - var connStr = "redis://nunit:pass@host:1?ssl=true&db=0&connectTimeout=2&sendtimeout=3&receiveTimeout=4&idletimeoutsecs=5&NamespacePrefix=prefix."; - var expected = "{Host:host,Port:1,Ssl:True,Client:nunit,Password:pass,Db:0,ConnectTimeout:2,SendTimeout:3,ReceiveTimeout:4,IdleTimeOutSecs:5,NamespacePrefix:prefix.}" + var connStr = "redis://nunit:pass@host:1?ssl=true&sslprotocols=Tls12&db=0&connectTimeout=2&sendtimeout=3&receiveTimeout=4&idletimeoutsecs=5&NamespacePrefix=prefix."; + var expected = "{Host:host,Port:1,Ssl:True,SslProtocols:Tls12,Client:nunit,Password:pass,Db:0,ConnectTimeout:2,SendTimeout:3,ReceiveTimeout:4,IdleTimeOutSecs:5,NamespacePrefix:prefix.}" .FromJsv(); using (var pooledManager = new RedisManagerPool(connStr)) @@ -122,6 +126,7 @@ private static void AssertClient(RedisClient redis, RedisEndpoint expected) Assert.That(redis.Host, Is.EqualTo(expected.Host)); Assert.That(redis.Port, Is.EqualTo(expected.Port)); Assert.That(redis.Ssl, Is.EqualTo(expected.Ssl)); + Assert.That(redis.SslProtocols, Is.EqualTo(expected.SslProtocols)); Assert.That(redis.Client, Is.EqualTo(expected.Client)); Assert.That(redis.Password, Is.EqualTo(expected.Password)); Assert.That(redis.Db, Is.EqualTo(expected.Db)); diff --git a/tests/ServiceStack.Redis.Tests/CultureInfoTests.Async.cs b/tests/ServiceStack.Redis.Tests/CultureInfoTests.Async.cs new file mode 100644 index 00000000..12cc63f6 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/CultureInfoTests.Async.cs @@ -0,0 +1,47 @@ +using NUnit.Framework; +using System.Globalization; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class CultureInfoTestsAsync + : RedisClientTestsBaseAsync + { + private CultureInfo previousCulture = CultureInfo.InvariantCulture; + + [OneTimeSetUp] + public void OneTimeSetUp() + { +#if NETCORE + previousCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); +#else + previousCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); + Thread.CurrentThread.CurrentUICulture = new CultureInfo("fr-FR"); +#endif + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { +#if NETCORE + CultureInfo.CurrentCulture = previousCulture; +#else + Thread.CurrentThread.CurrentCulture = previousCulture; +#endif + } + + [Test] + public async Task Can_AddItemToSortedSet_in_different_Culture() + { + await RedisAsync.AddItemToSortedSetAsync("somekey1", "somevalue", 66121.202); + var score = await RedisAsync.GetItemScoreInSortedSetAsync("somekey1", "somevalue"); + + Assert.That(score, Is.EqualTo(66121.202)); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/CustomCommandTests.Async.cs b/tests/ServiceStack.Redis.Tests/CustomCommandTests.Async.cs new file mode 100644 index 00000000..3033ed5c --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/CustomCommandTests.Async.cs @@ -0,0 +1,140 @@ +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Text; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class CustomCommandTestsAsync + : RedisClientTestsBaseAsync + { + [Test] + public async Task Can_send_custom_commands() + { + await RedisAsync.FlushAllAsync(); + + RedisText ret; + + ret = await RedisAsync.CustomAsync("SET", "foo", 1); + Assert.That(ret.Text, Is.EqualTo("OK")); + _ = await RedisAsync.CustomAsync(Commands.Set, "bar", "b"); + + ret = await RedisAsync.CustomAsync("GET", "foo"); + Assert.That(ret.Text, Is.EqualTo("1")); + ret = await RedisAsync.CustomAsync(Commands.Get, "bar"); + Assert.That(ret.Text, Is.EqualTo("b")); + + ret = await RedisAsync.CustomAsync(Commands.Keys, "*"); + var keys = ret.GetResults(); + Assert.That(keys, Is.EquivalentTo(new[] { "foo", "bar" })); + + ret = await RedisAsync.CustomAsync("MGET", "foo", "bar"); + var values = ret.GetResults(); + Assert.That(values, Is.EquivalentTo(new[] { "1", "b" })); + + foreach (var x in Enum.GetNames(typeof(DayOfWeek))) + { + await RedisAsync.CustomAsync("RPUSH", "DaysOfWeek", x); + } + + ret = await RedisAsync.CustomAsync("LRANGE", "DaysOfWeek", 1, -2); + + var weekDays = ret.GetResults(); + Assert.That(weekDays, Is.EquivalentTo( + new[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" })); + + ret.PrintDump(); + } + + [Test] + public async Task Can_send_custom_commands_longhand() + { + await RedisAsync.FlushAllAsync(); + + RedisText ret; + + ret = await RedisAsync.CustomAsync(new object[] { "SET", "foo", 1 }); + Assert.That(ret.Text, Is.EqualTo("OK")); + _ = await RedisAsync.CustomAsync(new object[] { Commands.Set, "bar", "b" }); + + ret = await RedisAsync.CustomAsync(new object[] { "GET", "foo" }); + Assert.That(ret.Text, Is.EqualTo("1")); + ret = await RedisAsync.CustomAsync(new object[] { Commands.Get, "bar" }); + Assert.That(ret.Text, Is.EqualTo("b")); + + ret = await RedisAsync.CustomAsync(new object[] { Commands.Keys, "*" }); + var keys = ret.GetResults(); + Assert.That(keys, Is.EquivalentTo(new[] { "foo", "bar" })); + + ret = await RedisAsync.CustomAsync(new object[] { "MGET", "foo", "bar" }); + var values = ret.GetResults(); + Assert.That(values, Is.EquivalentTo(new[] { "1", "b" })); + + foreach (var x in Enum.GetNames(typeof(DayOfWeek))) + { + await RedisAsync.CustomAsync(new object[] { "RPUSH", "DaysOfWeek", x }); + } + + ret = await RedisAsync.CustomAsync(new object[] { "LRANGE", "DaysOfWeek", 1, -2 }); + + var weekDays = ret.GetResults(); + Assert.That(weekDays, Is.EquivalentTo( + new[] { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" })); + + ret.PrintDump(); + } + + [Test] + public async Task Can_send_complex_types_in_Custom_Commands() + { + await RedisAsync.FlushAllAsync(); + + RedisText ret; + + ret = await RedisAsync.CustomAsync("SET", "foo", new Poco { Name = "Bar" }); + Assert.That(ret.Text, Is.EqualTo("OK")); + + ret = await RedisAsync.CustomAsync("GET", "foo"); + var dto = ret.GetResult(); + Assert.That(dto.Name, Is.EqualTo("Bar")); + + foreach (var x in Enum.GetNames(typeof(DayOfWeek))) + await RedisAsync.CustomAsync("RPUSH", "DaysOfWeek", new Poco { Name = x }); + + ret = await RedisAsync.CustomAsync("LRANGE", "DaysOfWeek", 1, -2); + var weekDays = ret.GetResults(); + + Assert.That(weekDays.First().Name, Is.EqualTo("Monday")); + + ret.PrintDump(); + } + + [Test] + public async Task Can_send_complex_types_in_Custom_Commands_longhand() + { + await RedisAsync.FlushAllAsync(); + + RedisText ret; + + ret = await RedisAsync.CustomAsync(new object[] { "SET", "foo", new Poco { Name = "Bar" } }); + Assert.That(ret.Text, Is.EqualTo("OK")); + + ret = await RedisAsync.CustomAsync(new object[] { "GET", "foo" }); + var dto = ret.GetResult(); + Assert.That(dto.Name, Is.EqualTo("Bar")); + + foreach (var x in Enum.GetNames(typeof(DayOfWeek))) + await RedisAsync.CustomAsync(new object[] { "RPUSH", "DaysOfWeek", new Poco { Name = x } }); + + ret = await RedisAsync.CustomAsync(new object[] { "LRANGE", "DaysOfWeek", 1, -2 }); + var weekDays = ret.GetResults(); + + Assert.That(weekDays.First().Name, Is.EqualTo("Monday")); + + ret.PrintDump(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsBase.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsBase.Async.cs new file mode 100644 index 00000000..1889d193 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsBase.Async.cs @@ -0,0 +1,250 @@ +using System.Collections.Generic; +using NUnit.Framework; +using ServiceStack.Common; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Async")] + public abstract class RedisClientHashTestsBaseAsync + { + private const string HashId = "testhash"; + + protected abstract IModelFactory Factory { get; } + + private IRedisClientAsync client; + private IRedisTypedClientAsync redis; + private IRedisHashAsync Hash; + + [SetUp] + public async Task SetUp() + { + if (client is object) + { + await client.DisposeAsync(); + client = null; + } + client = new RedisClient(TestConfig.SingleHost); + await client.FlushAllAsync(); + + redis = client.As(); + + Hash = redis.GetHash(HashId); + } + + private Dictionary CreateMap() + { + var listValues = Factory.CreateList(); + var map = new Dictionary(); + listValues.ForEach(x => map[x.ToString()] = x); + return map; + } + + private Dictionary CreateMap2() + { + var listValues = Factory.CreateList2(); + var map = new Dictionary(); + listValues.ForEach(x => map[x.ToString()] = x); + return map; + } + + [Test] + public async Task Can_SetItemInHash_and_GetAllFromHash() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + + [Test] + public async Task Can_RemoveFromHash() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var firstKey = mapValues.First().Key; + + await redis.RemoveEntryFromHashAsync(Hash, firstKey); + + mapValues.Remove(firstKey); + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + + [Test] + public async Task Can_GetItemFromHash() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var firstKey = mapValues.First().Key; + + var hashValue = await redis.GetValueFromHashAsync(Hash, firstKey); + + Assert.That(hashValue, Is.EqualTo(mapValues[firstKey])); + } + + [Test] + public async Task Can_GetHashCount() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var hashCount = await redis.GetHashCountAsync(Hash); + + Assert.That(hashCount, Is.EqualTo(mapValues.Count)); + } + + [Test] + public async Task Does_HashContainsKey() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var existingMember = mapValues.First().Key; + var nonExistingMember = existingMember + "notexists"; + + Assert.That(await redis.HashContainsEntryAsync(Hash, existingMember), Is.True); + Assert.That(await redis.HashContainsEntryAsync(Hash, nonExistingMember), Is.False); + } + + [Test] + public async Task Can_GetHashKeys() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var expectedKeys = mapValues.Map(x => x.Key); + + var hashKeys = await redis.GetHashKeysAsync(Hash); + + Assert.That(hashKeys, Is.EquivalentTo(expectedKeys)); + } + + [Test] + public async Task Can_GetHashValues() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var expectedValues = mapValues.Map(x => x.Value); + + var hashValues = await redis.GetHashValuesAsync(Hash); + + Assert.That(hashValues, Is.EquivalentTo(expectedValues)); + } + + [Test] + public async Task Can_enumerate_small_IDictionary_Hash() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var members = new List(); + await foreach (var item in redis.GetHash(HashId)) + { + Assert.That(mapValues.ContainsKey(item.Key), Is.True); + members.Add(item.Key); + } + Assert.That(members.Count, Is.EqualTo(mapValues.Count)); + } + + [Test] + public async Task Can_Add_to_IDictionary_Hash() + { + var hash = redis.GetHash(HashId); + var mapValues = CreateMap(); + await mapValues.ForEachAsync((k, v) => hash.AddAsync(k, v)); + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + + [Test] + public async Task Can_Clear_IDictionary_Hash() + { + var hash = redis.GetHash(HashId); + var mapValues = CreateMap(); + await mapValues.ForEachAsync((k, v) => hash.AddAsync(k, v)); + + Assert.That(await hash.CountAsync(), Is.EqualTo(mapValues.Count)); + + await hash.ClearAsync(); + + Assert.That(await hash.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_IDictionary_Hash() + { + var hash = redis.GetHash(HashId); + var mapValues = CreateMap(); + await mapValues.ForEachAsync((k, v) => hash.AddAsync(k, v)); + + var existingMember = mapValues.First().Key; + var nonExistingMember = existingMember + "notexists"; + + Assert.That(await hash.ContainsKeyAsync(existingMember), Is.True); + Assert.That(await hash.ContainsKeyAsync(nonExistingMember), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_IDictionary_Hash() + { + var hash = redis.GetHash(HashId); + var mapValues = CreateMap(); + await mapValues.ForEachAsync((k, v) => hash.AddAsync(k, v)); + + var firstKey = mapValues.First().Key; + mapValues.Remove(firstKey); + await hash.RemoveAsync(firstKey); + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + + [Test] + public async Task Can_SetItemInHashIfNotExists() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var existingMember = mapValues.First().Key; + var nonExistingMember = existingMember + "notexists"; + + var lastValue = mapValues.Last().Value; + + await redis.SetEntryInHashIfNotExistsAsync(Hash, existingMember, lastValue); + await redis.SetEntryInHashIfNotExistsAsync(Hash, nonExistingMember, lastValue); + + mapValues[nonExistingMember] = lastValue; + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + + [Test] + public async Task Can_SetRangeInHash() + { + var mapValues = CreateMap(); + await mapValues.ForEachAsync(async (k, v) => await redis.SetEntryInHashAsync(Hash, k, v)); + + var newMapValues = CreateMap2(); + + await redis.SetRangeInHashAsync(Hash, newMapValues); + + newMapValues.Each(x => mapValues[x.Key] = x.Value); + + var members = await redis.GetAllEntriesFromHashAsync(Hash); + Assert.That(members, Is.EquivalentTo(mapValues)); + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsModels.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsModels.Async.cs new file mode 100644 index 00000000..0e7d7216 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientHashTestsModels.Async.cs @@ -0,0 +1,80 @@ +using System; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Tests.Support; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture] + public class RedisClientHashTestsModelWithFieldsOfDifferentTypesAsync + : RedisClientHashTestsBaseAsync + { + private readonly IModelFactory factory = + new ModelWithFieldsOfDifferentTypesFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientHashTestsStringAsync + : RedisClientHashTestsBaseAsync + { + private readonly IModelFactory factory = new BuiltInsFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientHashTestsShipperAsync + : RedisClientHashTestsBaseAsync + { + private readonly IModelFactory factory = new ShipperFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientHashTestsIntAsync + : RedisClientHashTestsBaseAsync + { + private readonly IModelFactory factory = new IntFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientHashTestsCustomTypeAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new CustomTypeFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + //public class RedisClientHashTestsDateTimeAsync + // : RedisClientHashTestsBaseAsync + //{ + // private readonly IModelFactory factory = new DateTimeFactory(); + + // protected override IModelFactory Factory + // { + // get { return factory; } + // } + //} + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestExtra.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestExtra.Async.cs new file mode 100644 index 00000000..42f92e1e --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestExtra.Async.cs @@ -0,0 +1,59 @@ +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using ServiceStack.Redis.Tests.Support; +using System; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Async")] + public class RedisClientListTestExtraAsync + { + const string ListId = "testlist"; + // const string ListId2 = "testlist2"; + private IRedisListAsync List; + // private IRedisListAsync List2; + + + private readonly IModelFactory factory = new CustomTypeFactory(); + + protected IModelFactory Factory { get { return factory; } } + + private IRedisClientAsync client; + private IRedisTypedClientAsync redis; + + [SetUp] + public async Task SetUp() + { + if (client is object) + { + await client.DisposeAsync(); + client = null; + } + client = new RedisClient(TestConfig.SingleHost); + await client.FlushAllAsync(); + + redis = client.As(); + + List = redis.Lists[ListId]; + // List2 = redis.Lists[ListId2]; + } + + [Test] + public async Task Can_Remove_value_from_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + var equalItem = new CustomType() { CustomId = 4 }; + storeMembers.Remove(equalItem); + await List.RemoveAsync(equalItem); + + var members = await List.ToListAsync(); + + Factory.AssertListsAreEqual(members, storeMembers); + } + + } +} diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsBase.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsBase.Async.cs new file mode 100644 index 00000000..58e42b5d --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsBase.Async.cs @@ -0,0 +1,331 @@ +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Async")] + public abstract class RedisClientListTestsBaseAsync + { + const string ListId = "testlist"; + const string ListId2 = "testlist2"; + private IRedisListAsync List; + private IRedisListAsync List2; + + protected abstract IModelFactory Factory { get; } + + private IRedisClientAsync client; + private IRedisTypedClientAsync redis; + + [SetUp] + public async Task SetUp() + { + if (client is object) + { + await client.DisposeAsync(); + client = null; + } + client = new RedisClient(TestConfig.SingleHost); + await client.FlushAllAsync(); + + redis = client.As(); + + List = redis.Lists[ListId]; + List2 = redis.Lists[ListId2]; + } + + [Test] + public async Task Can_AddToList_and_GetAllFromList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + var members = await redis.GetAllItemsFromListAsync(List); + + Factory.AssertListsAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_GetListCount() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + var listCount = await redis.GetListCountAsync(List); + + Assert.That(listCount, Is.EqualTo(storeMembers.Count)); + } + + [Test] + public async Task Can_GetItemFromList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + var storeMember3 = storeMembers[2]; + var item3 = await redis.GetItemFromListAsync(List, 2); + + Factory.AssertIsEqual(item3, storeMember3); + } + + [Test] + public async Task Can_SetItemInList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + storeMembers[2] = Factory.NonExistingValue; + await redis.SetItemInListAsync(List, 2, Factory.NonExistingValue); + + var members = await redis.GetAllItemsFromListAsync(List); + + Factory.AssertListsAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_PopFromList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + var lastValue = await redis.PopItemFromListAsync(List); + + Factory.AssertIsEqual(lastValue, storeMembers[storeMembers.Count - 1]); + } + + [Test] + public async Task Can_BlockingDequeueItemFromList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.EnqueueItemOnListAsync(List, x)); + + var item1 = await redis.BlockingDequeueItemFromListAsync(List, new TimeSpan(0, 0, 1)); + + Factory.AssertIsEqual(item1, (T)storeMembers.First()); + } + + [Test] + public async Task Can_BlockingDequeueItemFromList_Timeout() + { + var item1 = await redis.BlockingDequeueItemFromListAsync(List, new TimeSpan(0, 0, 1)); + Assert.AreEqual(item1, default(T)); + } + + [Test] + public async Task Can_DequeueFromList() + { + + var queue = new Queue(); + var storeMembers = Factory.CreateList(); + storeMembers.ForEach(x => queue.Enqueue(x)); + await storeMembers.ForEachAsync(x => redis.EnqueueItemOnListAsync(List, x)); + + var item1 = await redis.DequeueItemFromListAsync(List); + + Factory.AssertIsEqual(item1, queue.Dequeue()); + } + + [Test] + public async Task PopAndPushSameAsDequeue() + { + var queue = new Queue(); + var storeMembers = Factory.CreateList(); + storeMembers.ForEach(x => queue.Enqueue(x)); + await storeMembers.ForEachAsync(x => redis.EnqueueItemOnListAsync(List, x)); + + var item1 = await redis.PopAndPushItemBetweenListsAsync(List, List2); + Assert.That(item1, Is.EqualTo(queue.Dequeue())); + } + + [Test] + public async Task Can_ClearList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.EnqueueItemOnListAsync(List, x)); + + var count = (await redis.GetAllItemsFromListAsync(List)).Count; + Assert.That(count, Is.EqualTo(storeMembers.Count)); + + await redis.RemoveAllFromListAsync(List); + count = (await redis.GetAllItemsFromListAsync(List)).Count; + Assert.That(count, Is.EqualTo(0)); + + } + + [Test] + public async Task Can_ClearListWithOneItem() + { + var storeMembers = Factory.CreateList(); + await redis.EnqueueItemOnListAsync(List, storeMembers[0]); + + var count = (await redis.GetAllItemsFromListAsync(List)).Count; + Assert.That(count, Is.EqualTo(1)); + + await redis.RemoveAllFromListAsync(List); + count = (await redis.GetAllItemsFromListAsync(List)).Count; + Assert.That(count, Is.EqualTo(0)); + } + + [Test] + public async Task Can_MoveBetweenLists() + { + var list1Members = Factory.CreateList(); + var list2Members = Factory.CreateList2(); + var lastItem = list1Members[list1Members.Count - 1]; + + await list1Members.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + await list2Members.ForEachAsync(x => redis.AddItemToListAsync(List2, x)); + + list1Members.Remove(lastItem); + list2Members.Insert(0, lastItem); + await redis.PopAndPushItemBetweenListsAsync(List, List2); + + var readList1 = await redis.GetAllItemsFromListAsync(List); + var readList2 = await redis.GetAllItemsFromListAsync(List2); + + Factory.AssertListsAreEqual(readList1, list1Members); + Factory.AssertListsAreEqual(readList2, list2Members); + } + + + [Test] + public async Task Can_enumerate_small_list() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + var readMembers = new List(); + await foreach (var item in redis.Lists[ListId]) + { + readMembers.Add(item); + } + Factory.AssertListsAreEqual(readMembers, storeMembers); + } + + [Test] + public async Task Can_enumerate_large_list() + { + if (TestConfig.IgnoreLongTests) return; + + const int listSize = 2500; + + await listSize.TimesAsync(x => redis.AddItemToListAsync(List, Factory.CreateInstance(x))); + + var i = 0; + await foreach (var item in List) + { + Factory.AssertIsEqual(item, Factory.CreateInstance(i++)); + } + } + + [Test] + public async Task Can_Add_to_IList() + { + var storeMembers = Factory.CreateList(); + var list = redis.Lists[ListId]; + await storeMembers.ForEachAsync(x => list.AddAsync(x)); + + var members = await list.ToListAsync(); + Factory.AssertListsAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_Clear_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + Assert.That(await List.CountAsync(), Is.EqualTo(storeMembers.Count)); + + await List.ClearAsync(); + + Assert.That(await List.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + Assert.That(await List.ContainsAsync(Factory.ExistingValue), Is.True); + Assert.That(await List.ContainsAsync(Factory.NonExistingValue), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + storeMembers.Remove(Factory.ExistingValue); + await List.RemoveAsync(Factory.ExistingValue); + + var members = await List.ToListAsync(); + + Factory.AssertListsAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_RemoveAt_value_from_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + storeMembers.RemoveAt(2); + await List.RemoveAtAsync(2); + + var members = await List.ToListAsync(); + + Factory.AssertListsAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_get_default_index_from_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + for (var i = 0; i < storeMembers.Count; i++) + { + Factory.AssertIsEqual(await List.ElementAtAsync(i), storeMembers[i]); + } + } + + [Test] + public async Task Can_test_for_IndexOf_in_IList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => List.AddAsync(x)); + + foreach (var item in storeMembers) + { + Assert.That(await List.IndexOfAsync(item), Is.EqualTo(storeMembers.IndexOf(item))); + } + } + + + [Test] + public async Task Can_GetRangeFromList() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(x => redis.AddItemToListAsync(List, x)); + + //in SetUp(): List = redis.Lists["testlist"]; + //alias for: redis.GetRangeFromList(redis.Lists["testlist"], 1, 3); + var range = await List.GetRangeAsync(1, 3); + var expected = storeMembers.Skip(1).Take(3).ToList(); + + //Uncomment to view list contents + //Debug.WriteLine(range.Dump()); + //Debug.WriteLine(expected.Dump()); + + Factory.AssertListsAreEqual(range, expected); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.Async.cs new file mode 100644 index 00000000..00d6e80f --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.Async.cs @@ -0,0 +1,91 @@ +using System; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Tests.Support; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture] + public class RedisClientListTestsModelWithFieldsOfDifferentTypesAsync + : RedisClientListTestsBaseAsync + { + private readonly IModelFactory factory = + new ModelWithFieldsOfDifferentTypesFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientListTestsStringAsync + : RedisClientListTestsBaseAsync + { + private readonly IModelFactory factory = new BuiltInsFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientListTestsShipperAsync + : RedisClientListTestsBaseAsync + { + private readonly IModelFactory factory = new ShipperFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientListTestsIntAsync + : RedisClientListTestsBaseAsync + { + private readonly IModelFactory factory = new IntFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientListTestsCustomTypeAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new CustomTypeFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientlistTestCustomType_FailingAsync + : RedisClientListTestsBaseAsync + { + private readonly IModelFactory factory = new CustomTypeFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + //public class RedisClientListTestsDateTimeAsync + // : RedisClientListTestsBaseAsync + //{ + // private readonly IModelFactory factory = new DateTimeFactory(); + + // protected override IModelFactory Factory + // { + // get { return factory; } + // } + //} +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.cs index d8c0be73..5d8e0474 100644 --- a/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.cs +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientListTestsModels.cs @@ -5,6 +5,7 @@ namespace ServiceStack.Redis.Tests.Generic { + // TODO: error, missing fixture? public class RedisClientListTestsModelWithFieldsOfDifferentTypes : RedisClientListTestsBase { diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsBase.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsBase.Async.cs new file mode 100644 index 00000000..7f3028fd --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsBase.Async.cs @@ -0,0 +1,344 @@ +using System.Collections.Generic; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using System.Linq; +using System.Threading.Tasks; +using System; + +namespace ServiceStack.Redis.Tests.Generic +{ + [Category("Async")] + public abstract class RedisClientSetTestsBaseAsync + { + private const string SetId = "testset"; + private const string SetId2 = "testset2"; + private const string SetId3 = "testset3"; + protected abstract IModelFactory Factory { get; } + + private IRedisClientAsync client; + private IRedisTypedClientAsync redis; + private IRedisSetAsync Set; + private IRedisSetAsync Set2; + private IRedisSetAsync Set3; + + [SetUp] + public async Task SetUp() + { + if (client is object) + { + await client.DisposeAsync(); + client = null; + } + client = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + await client.FlushAllAsync(); + + redis = client.As(); + + Set = redis.Sets[SetId]; + Set2 = redis.Sets[SetId2]; + Set3 = redis.Sets[SetId3]; + } + + [Test] + public async Task Can_AddToSet_and_GetAllFromSet() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var members = await redis.GetAllItemsFromSetAsync(Set); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_RemoveFromSet() + { + var storeMembers = Factory.CreateList(); + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + await redis.RemoveItemFromSetAsync(Set, Factory.ExistingValue); + + storeMembers.Remove(Factory.ExistingValue); + + var members = await redis.GetAllItemsFromSetAsync(Set); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_PopFromSet() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var member = await redis.PopItemFromSetAsync(Set); + + Assert.That(storeMembers.Contains(member), Is.True); + } + + [Test] + public async Task Can_MoveBetweenSets() + { + var fromSetMembers = Factory.CreateList(); + var toSetMembers = Factory.CreateList2(); + + await fromSetMembers.ForEachAsync(x => redis.AddItemToSetAsync(Set, x)); + await toSetMembers.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + + await redis.MoveBetweenSetsAsync(Set, Set2, Factory.ExistingValue); + + fromSetMembers.Remove(Factory.ExistingValue); + toSetMembers.Add(Factory.ExistingValue); + + var readFromSetId = await redis.GetAllItemsFromSetAsync(Set); + var readToSetId = await redis.GetAllItemsFromSetAsync(Set2); + + Assert.That(readFromSetId, Is.EquivalentTo(fromSetMembers)); + Assert.That(readToSetId, Is.EquivalentTo(toSetMembers)); + } + + [Test] + public async Task Can_GetSetCount() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var setCount = await redis.GetSetCountAsync(Set); + + Assert.That(setCount, Is.EqualTo(storeMembers.Count)); + } + + [Test] + public async Task Does_SetContainsValue() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + Assert.That(await redis.SetContainsItemAsync(Set, Factory.ExistingValue), Is.True); + Assert.That(await redis.SetContainsItemAsync(Set, Factory.NonExistingValue), Is.False); + } + + [Test] + public async Task Can_IntersectBetweenSets() + { + var storeMembers = Factory.CreateList(); + var storeMembers2 = Factory.CreateList2(); + + storeMembers.Add(storeMembers2.First()); + storeMembers2.Add(storeMembers.First()); + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + + var intersectingMembers = await redis.GetIntersectFromSetsAsync(new[] { Set, Set2 }); + + var intersect = (await Set.ToListAsync()).Intersect((await Set2.ToListAsync())).ToList(); + + Assert.That(intersectingMembers, Is.EquivalentTo(intersect)); + } + + [Test] + public async Task Can_Store_IntersectBetweenSets() + { + var storeMembers = Factory.CreateList(); + var storeMembers2 = Factory.CreateList2(); + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + + await redis.StoreIntersectFromSetsAsync(Set3, new[] { Set, Set2 }); + + var intersect = (await Set.ToListAsync()).Intersect(await Set2.ToListAsync()).ToList(); + + Assert.That(await Set3.ToListAsync(), Is.EquivalentTo(intersect)); + } + + [Test] + public async Task Can_UnionBetweenSets() + { + var storeMembers = Factory.CreateList(); + var storeMembers2 = Factory.CreateList2(); + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + + var unionMembers = await redis.GetUnionFromSetsAsync(new[] { Set, Set2 }); + + var union = (await Set.ToListAsync()).Union(await Set2.ToListAsync()).ToList(); + + Assert.That(unionMembers, Is.EquivalentTo(union)); + } + + [Test] + public async Task Can_Store_UnionBetweenSets() + { + var storeMembers = Factory.CreateList(); + var storeMembers2 = Factory.CreateList2(); + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + + await redis.StoreUnionFromSetsAsync(Set3, new[] { Set, Set2 }); + + var union = (await Set.ToListAsync()).Union((await Set2.ToListAsync())).ToList(); + + Assert.That(await Set3.ToListAsync(), Is.EquivalentTo(union)); + } + + [Test] + public async Task Can_DiffBetweenSets() + { + var storeMembers = Factory.CreateList(); + storeMembers.Add(Factory.CreateInstance(1)); + + var storeMembers2 = Factory.CreateList2(); + storeMembers2.Insert(0, Factory.CreateInstance(4)); + + var storeMembers3 = new List { + Factory.CreateInstance(1), + Factory.CreateInstance(5), + Factory.CreateInstance(7), + Factory.CreateInstance(11), + }; + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + await storeMembers3.ForEachAsync(x => redis.AddItemToSetAsync(Set3, x)); + + var diffMembers = await redis.GetDifferencesFromSetAsync(Set, new[] { Set2, Set3 }); + + Assert.That(diffMembers, Is.EquivalentTo( + new List { Factory.CreateInstance(2), Factory.CreateInstance(3) })); + } + + [Test] + public async Task Can_Store_DiffBetweenSets() + { + var storeMembers = Factory.CreateList(); + storeMembers.Add(Factory.CreateInstance(1)); + + var storeMembers2 = Factory.CreateList2(); + storeMembers2.Insert(0, Factory.CreateInstance(4)); + + var storeMembers3 = new List { + Factory.CreateInstance(1), + Factory.CreateInstance(5), + Factory.CreateInstance(7), + Factory.CreateInstance(11), + }; + + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + await storeMembers2.ForEachAsync(x => redis.AddItemToSetAsync(Set2, x)); + await storeMembers3.ForEachAsync(x => redis.AddItemToSetAsync(Set3, x)); + + var storeSet = redis.Sets["testdiffsetstore"]; + + await redis.StoreDifferencesFromSetAsync(storeSet, Set, new[] { Set2, Set3 }); + + Assert.That(await storeSet.ToListAsync(), Is.EquivalentTo( + new List { Factory.CreateInstance(2), Factory.CreateInstance(3) })); + + } + + [Test] + public async Task Can_GetRandomEntryFromSet() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var randomEntry = await redis.GetRandomItemFromSetAsync(Set); + + Assert.That(storeMembers.Contains(randomEntry), Is.True); + } + + + [Test] + public async Task Can_enumerate_small_ICollection_Set() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var members = new List(); + await foreach (var item in Set) + { + members.Add(item); + } + + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_enumerate_large_ICollection_Set() + { + if (TestConfig.IgnoreLongTests) return; + + const int setSize = 2500; + + var storeMembers = new List(); + await setSize.TimesAsync(async x => + { + await redis.AddItemToSetAsync(Set, Factory.CreateInstance(x)); + storeMembers.Add(Factory.CreateInstance(x)); + }); + + var members = new List(); + await foreach (var item in Set) + { + members.Add(item); + } + members.Sort((x, y) => x.GetId().ToString().CompareTo(y.GetId().ToString())); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Add_to_ICollection_Set() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + var members = await Set.ToListAsync(); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Clear_ICollection_Set() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + Assert.That(await Set.CountAsync(), Is.EqualTo(storeMembers.Count)); + + await Set.ClearAsync(); + + Assert.That(await Set.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_ICollection_Set() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + Assert.That(await Set.ContainsAsync(Factory.ExistingValue), Is.True); + Assert.That(await Set.ContainsAsync(Factory.NonExistingValue), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_ICollection_Set() + { + var storeMembers = Factory.CreateList(); + await storeMembers.ForEachAsync(async x => await redis.AddItemToSetAsync(Set, x)); + + storeMembers.Remove(Factory.ExistingValue); + await Set.RemoveAsync(Factory.ExistingValue); + + var members = await Set.ToListAsync(); + + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsModels.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsModels.Async.cs new file mode 100644 index 00000000..9eb33a51 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientSetTestsModels.Async.cs @@ -0,0 +1,81 @@ +using System; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Tests.Support; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture] + public class RedisClientSetTestsModelWithFieldsOfDifferentTypesAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = + new ModelWithFieldsOfDifferentTypesFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientSetTestsStringAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new BuiltInsFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientSetTestsShipperAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new ShipperFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientSetTestsIntAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new IntFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + [TestFixture] + public class RedisClientSetTestsCustomTypeAsync + : RedisClientSetTestsBaseAsync + { + private readonly IModelFactory factory = new CustomTypeFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + + //public class RedisClientSetTestsDateTimeAsync + // : RedisClientSetTestsBaseAsync + //{ + // private readonly IModelFactory factory = new DateTimeFactory(); + + // protected override IModelFactory Factory + // { + // get { return factory; } + // } + //} + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisClientTests.Async.cs new file mode 100644 index 00000000..a553c572 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisClientTests.Async.cs @@ -0,0 +1,129 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Integration"), Category("Async")] + public class RedisClientTestsAsync : RedisClientTestsBaseAsync + { + [OneTimeSetUp] + public void TestFixture() + { + } + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + RedisRaw.NamespacePrefix = "GenericRedisClientTests"; + } + + [Test] + public async Task Can_Set_and_Get_string() + { + const string value = "value"; + await RedisAsync.SetValueAsync("key", value); + var valueString = await RedisAsync.GetValueAsync("key"); + + Assert.That(valueString, Is.EqualTo(value)); + } + + [Test] + public async Task Can_Set_and_Get_key_with_all_byte_values() + { + const string key = "bytesKey"; + + var value = new byte[256]; + for (var i = 0; i < value.Length; i++) + { + value[i] = (byte)i; + } + + await NativeAsync.SetAsync(key, value); + var resultValue = await NativeAsync.GetAsync(key); + + Assert.That(resultValue, Is.EquivalentTo(value)); + } + + public List Sort(IEnumerable list) + { + var sortedList = list.ToList(); + sortedList.Sort((x, y) => + x.GetId().ToString().CompareTo(y.GetId().ToString())); + + return sortedList; + } + + [Test] + public async Task Can_SetBit_And_GetBit_And_BitCount() + { + const string key = "BitKey"; + const int offset = 100; + await NativeAsync.SetBitAsync(key, offset, 1); + Assert.AreEqual(1, await NativeAsync.GetBitAsync(key, offset)); + Assert.AreEqual(1, await NativeAsync.BitCountAsync(key)); + } + + public class Dummy + { + public int Id { get; set; } + public string Name { get; set; } + } + + [Test] + public async Task Can_Delete() + { + var dto = new Dummy { Id = 1, Name = "Name" }; + + await RedisAsync.StoreAsync(dto); + + Assert.That((await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + "ids:Dummy")).ToArray()[0], Is.EqualTo("1")); + Assert.That(await RedisAsync.GetByIdAsync(1), Is.Not.Null); + + await RedisAsync.DeleteAsync(dto); + + Assert.That((await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + "ids:Dummy")).Count, Is.EqualTo(0)); + Assert.That(await RedisAsync.GetByIdAsync(1), Is.Null); + } + + [Test] + public async Task Can_DeleteById() + { + var dto = new Dummy { Id = 1, Name = "Name" }; + await RedisAsync.StoreAsync(dto); + + Assert.That((await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + "ids:Dummy")).ToArray()[0], Is.EqualTo("1")); + Assert.That(await RedisAsync.GetByIdAsync(1), Is.Not.Null); + + await RedisAsync.DeleteByIdAsync(dto.Id); + + Assert.That((await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + "ids:Dummy")).Count, Is.EqualTo(0)); + Assert.That(await RedisAsync.GetByIdAsync(1), Is.Null); + } + + [Test] + public async Task Can_save_via_string() + { + var dtos = 10.Times(i => new Dummy { Id = i, Name = "Name" + i }); + + await RedisAsync.SetValueAsync("dummy:strings", dtos.ToJson()); + + var fromDtos = (await RedisAsync.GetValueAsync("dummy:strings")).FromJson>(); + + Assert.That(fromDtos.Count, Is.EqualTo(10)); + } + + [Test] + public async Task Can_save_via_types() + { + var dtos = 10.Times(i => new Dummy { Id = i, Name = "Name" + i }); + + await RedisAsync.SetAsync("dummy:strings", dtos); + + var fromDtos = await RedisAsync.GetAsync>("dummy:strings"); + + Assert.That(fromDtos.Count, Is.EqualTo(10)); + } + } +} diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBase.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBase.Async.cs new file mode 100644 index 00000000..92880411 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBase.Async.cs @@ -0,0 +1,97 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Async")] + public abstract class RedisPersistenceProviderTestsBaseAsync + { + protected abstract IModelFactory Factory { get; } + + private IRedisClientAsync client; + private IRedisTypedClientAsync redis; + + [SetUp] + public async Task SetUp() + { + if (client is object) + { + await client.DisposeAsync(); + client = null; + } + client = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + await client.FlushAllAsync(); + + redis = client.As(); + } + + [Test] + public async Task Can_Store_and_GetById_ModelWithIdAndName() + { + const int modelId = 1; + var to = Factory.CreateInstance(modelId); + await redis.StoreAsync(to); + + var from = await redis.GetByIdAsync(to.GetId().ToString()); + + Factory.AssertIsEqual(to, from); + } + + [Test] + public async Task Can_StoreAll_and_GetByIds_ModelWithIdAndName() + { + var tos = Factory.CreateList(); + var ids = tos.ConvertAll(x => x.GetId().ToString()); + + await redis.StoreAllAsync(tos); + + var froms = await redis.GetByIdsAsync(ids); + var fromIds = froms.Map(x => x.GetId().ToString()); + + Assert.That(fromIds, Is.EquivalentTo(ids)); + } + + [Test] + public async Task Can_Delete_ModelWithIdAndName() + { + var tos = Factory.CreateList(); + var ids = tos.ConvertAll(x => x.GetId().ToString()); + + await redis.StoreAllAsync(tos); + + var deleteIds = new[] { ids[1], ids[3] }; + + await redis.DeleteByIdsAsync(deleteIds); + + var froms = await redis.GetByIdsAsync(ids); + var fromIds = froms.Map(x => x.GetId().ToString()); + + var expectedIds = ids.Where(x => !deleteIds.Contains(x)) + .ToList().ConvertAll(x => x.ToString()); + + Assert.That(fromIds, Is.EquivalentTo(expectedIds)); + } + + [Test] + public async Task Can_DeleteAll() + { + var tos = Factory.CreateList(); + await redis.StoreAllAsync(tos); + + var all = await redis.GetAllAsync(); + + Assert.That(all.Count, Is.EqualTo(tos.Count)); + + await redis.DeleteAllAsync(); + + all = await redis.GetAllAsync(); + + Assert.That(all.Count, Is.EqualTo(0)); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBaseImpl.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBaseImpl.Async.cs new file mode 100644 index 00000000..3f7c00cf --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisPersistenceProviderTestsBaseImpl.Async.cs @@ -0,0 +1,40 @@ +using System; +using ServiceStack.Common.Tests.Models; + +namespace ServiceStack.Redis.Tests.Generic +{ + public class RedisPersistenceProviderTestsModelWithFieldsOfDifferentTypesAsync + : RedisPersistenceProviderTestsBaseAsync + { + private readonly IModelFactory factory = + new ModelWithFieldsOfDifferentTypesFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + public class RedisPersistenceProviderTestsStringFactoryAsync + : RedisPersistenceProviderTestsBaseAsync + { + private readonly IModelFactory factory = new BuiltInsFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + + public class RedisPersistenceProviderTestsShipperAsync + : RedisPersistenceProviderTestsBaseAsync + { + private readonly IModelFactory factory = new ShipperFactory(); + + protected override IModelFactory Factory + { + get { return factory; } + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientAppTests.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientAppTests.cs index 2111837b..89618a1e 100644 --- a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientAppTests.cs +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientAppTests.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using NUnit.Framework; -using ServiceStack.Common; using ServiceStack.Redis.Generic; namespace ServiceStack.Redis.Tests.Generic @@ -229,5 +228,34 @@ public void Can_GetEarliestFromRecentsList() Assert.That(expectedAnswers.EquivalentTo(earliest3Answers)); } + [Test] + public void Can_save_quoted_strings() + { + var str = "string \"with\" \"quotes\""; + var cacheKey = "quotetest"; + + Redis.As().SetValue(cacheKey, str); + var fromRedis = Redis.As().GetValue(cacheKey); + Assert.That(fromRedis, Is.EqualTo(str)); + + Redis.Set(cacheKey, str); + fromRedis = Redis.Get(cacheKey); + Assert.That(fromRedis, Is.EqualTo(str)); + + Redis.SetValue(cacheKey, str); + fromRedis = Redis.GetValue(cacheKey); + Assert.That(fromRedis, Is.EqualTo(str)); + + Redis.SetValue(cacheKey, str.ToJson()); + fromRedis = Redis.GetValue(cacheKey).FromJson(); + Assert.That(fromRedis, Is.EqualTo(str)); + } + + [Test] + public void Does_return_non_existent_keys_as_defaultValue() + { + Assert.That(Redis.Get("notexists"), Is.Null); + Assert.That(Redis.Get("notexists"), Is.EqualTo(0)); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.Async.cs new file mode 100644 index 00000000..7224d6c5 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.Async.cs @@ -0,0 +1,167 @@ +using NUnit.Framework; +using ServiceStack.Redis.Generic; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture, Category("Integration")] + public class RedisTypedClientTestsAsync + : RedisClientTestsBaseAsync + { + public class CacheRecord + { + public CacheRecord() + { + this.Children = new List(); + } + + public string Id { get; set; } + public List Children { get; set; } + } + + public class CacheRecordChild + { + public string Id { get; set; } + public string Data { get; set; } + } + + protected IRedisTypedClientAsync RedisTyped; + + [SetUp] + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + + RedisRaw?.Dispose(); + RedisRaw = new RedisClient(TestConfig.SingleHost) + { + NamespacePrefix = "RedisTypedClientTests:" + }; + RedisTyped = RedisAsync.As(); + } + + [TearDown] + public virtual async Task TearDown() + { + foreach (var t in await RedisAsync.SearchKeysAsync(RedisRaw.NamespacePrefix + "*")) + { + await NativeAsync.DelAsync(t); + } + } + + [Test] + public async Task Can_Store_with_Prefix() + { + var expected = new CacheRecord() { Id = "123" }; + await RedisTyped.StoreAsync(expected); + var current = await RedisAsync.GetAsync("RedisTypedClientTests:urn:cacherecord:123"); + Assert.AreEqual(expected.Id, current.Id); + } + + [Test] + public async Task Can_Expire() + { + var cachedRecord = new CacheRecord + { + Id = "key", + Children = { + new CacheRecordChild { Id = "childKey", Data = "data" } + } + }; + + await RedisTyped.StoreAsync(cachedRecord); + await RedisTyped.ExpireInAsync("key", TimeSpan.FromSeconds(1)); + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Not.Null); + await Task.Delay(2000); + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Null); + } + + [Ignore("Changes in system clock can break test")] + [Test] + public async Task Can_ExpireAt() + { + var cachedRecord = new CacheRecord + { + Id = "key", + Children = { + new CacheRecordChild { Id = "childKey", Data = "data" } + } + }; + + await RedisTyped.StoreAsync(cachedRecord); + + var in2Secs = DateTime.UtcNow.AddSeconds(2); + + await RedisTyped.ExpireAtAsync("key", in2Secs); + + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Not.Null); + await Task.Delay(3000); + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Null); + } + + [Test] + public async Task Can_Delete_All_Items() + { + var cachedRecord = new CacheRecord + { + Id = "key", + Children = { + new CacheRecordChild { Id = "childKey", Data = "data" } + } + }; + + await RedisTyped.StoreAsync(cachedRecord); + + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Not.Null); + + await RedisTyped.DeleteAllAsync(); + + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Null); + + } + + [Test] + public async Task Can_Delete_All_Items_multiple_batches() + { + // Clear previous usage + await RedisAsync.DeleteAsync(RedisRaw.GetTypeIdsSetKey()); + var cachedRecord = new CacheRecord + { + Id = "key", + Children = { + new CacheRecordChild { Id = "childKey", Data = "data" } + } + }; + + var exists = RedisRaw.Exists(RedisRaw.GetTypeIdsSetKey(typeof(CacheRecord))); + Assert.That(exists, Is.EqualTo(0)); + + await RedisTyped.StoreAsync(cachedRecord); + + exists = RedisRaw.Exists(RedisRaw.GetTypeIdsSetKey(typeof(CacheRecord))); + Assert.That(exists, Is.EqualTo(1)); + + RedisConfig.CommandKeysBatchSize = 5; + + for (int i = 0; i < 50; i++) + { + cachedRecord.Id = "key" + i; + await RedisTyped.StoreAsync(cachedRecord); + } + + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Not.Null); + + await RedisTyped.DeleteAllAsync(); + + Assert.That(await RedisTyped.GetByIdAsync("key"), Is.Null); + + exists = RedisRaw.Exists(RedisRaw.GetTypeIdsSetKey(typeof(CacheRecord))); + Assert.That(exists, Is.EqualTo(0)); + + RedisConfig.Reset(); + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.cs index a2c78b03..18236cf2 100644 --- a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.cs +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedClientTests.cs @@ -117,7 +117,47 @@ public void Can_Delete_All_Items() RedisTyped.DeleteAll(); Assert.That(RedisTyped.GetById("key"), Is.Null); + } + + [Test] + public void Can_Delete_All_Items_multiple_batches() + { + // Clear previous usage + Redis.Delete(Redis.GetTypeIdsSetKey(typeof(CacheRecord))); + var cachedRecord = new CacheRecord + { + Id = "key", + Children = { + new CacheRecordChild { Id = "childKey", Data = "data" } + } + }; + + var exists = Redis.Exists(Redis.GetTypeIdsSetKey(typeof(CacheRecord))); + Assert.That(exists, Is.EqualTo(0)); + + RedisTyped.Store(cachedRecord); + + exists = Redis.Exists(Redis.GetTypeIdsSetKey(typeof(CacheRecord))); + + Assert.That(exists, Is.EqualTo(1)); + + RedisConfig.CommandKeysBatchSize = 5; + + for (int i = 0; i < 50; i++) + { + cachedRecord.Id = "key" + i; + RedisTyped.Store(cachedRecord); + } + + Assert.That(RedisTyped.GetById("key"), Is.Not.Null); + + RedisTyped.DeleteAll(); + + exists = Redis.Exists(Redis.GetTypeIdsSetKey(typeof(CacheRecord))); + Assert.That(exists, Is.EqualTo(0)); + Assert.That(RedisTyped.GetById("key"), Is.Null); + RedisConfig.Reset(); } } diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedPipelineTests.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedPipelineTests.Async.cs new file mode 100644 index 00000000..663fdf97 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedPipelineTests.Async.cs @@ -0,0 +1,230 @@ +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture] + public class RedisTypedPipelineTestsAsync + : RedisClientTestsBaseAsync + { + public RedisTypedPipelineTestsAsync() + { + CleanMask = "gmultitest*"; + } + + private const string Key = "gmultitest"; + private const string ListKey = "gmultitest-list"; + private const string SetKey = "gmultitest-set"; + private const string SortedSetKey = "gmultitest-sortedset"; + + readonly ShipperFactory modelFactory = new ShipperFactory(); + private IRedisTypedClientAsync typedClient; + private Shipper model; + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + + typedClient = RedisAsync.As(); + model = modelFactory.CreateInstance(1); + } + + + [Test] + public async Task Can_call_single_operation_in_pipeline() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.SetValueAsync(Key, model)); + + await pipeline.FlushAsync(); + } + + modelFactory.AssertIsEqual(await typedClient.GetValueAsync(Key), model); + } + + [Test] + public async Task No_commit_of_atomic_pipelines_discards_all_commands() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.SetValueAsync(Key, model)); + } + + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + } + + [Test] + public async Task Exception_in_atomic_pipelines_discards_all_commands() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + try + { + await using var pipeline = typedClient.CreatePipeline(); + pipeline.QueueCommand(r => r.SetValueAsync(Key, model)); + throw new NotSupportedException(); + } + catch (NotSupportedException) + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + } + } + + [Test] + public async Task Can_call_single_operation_3_Times_in_pipeline() + { + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3))); + + await pipeline.FlushAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + } + + [Test] + public async Task Can_call_single_operation_with_callback_3_Times_in_pipeline() + { + var results = new List(); + + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1)), () => results.Add(1)); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2)), () => results.Add(2)); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3)), () => results.Add(3)); + + await pipeline.FlushAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + Assert.That(results, Is.EquivalentTo(new List { 1, 2, 3 })); + } + + [Test] + public async Task Supports_different_operation_types_in_same_pipeline() + { + var incrementResults = new List(); + var collectionCounts = new List(); + var containsItem = false; + + var typedList = typedClient.Lists[ListKey]; + var typedSet = typedClient.Sets[SetKey]; + var typedSortedSet = typedClient.SortedSets[SortedSetKey]; + + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + pipeline.QueueCommand(r => r.AddItemToSetAsync(typedSet, modelFactory.CreateInstance(3))); + pipeline.QueueCommand(r => r.SetContainsItemAsync(typedSet, modelFactory.CreateInstance(3)), b => containsItem = b); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(4))); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(5))); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(6))); + pipeline.QueueCommand(r => r.GetListCountAsync(typedList), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.GetSetCountAsync(typedSet), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.GetSortedSetCountAsync(typedSortedSet), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + + await pipeline.FlushAsync(); + } + + Assert.That(containsItem, Is.True); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("2")); + Assert.That(incrementResults, Is.EquivalentTo(new List { 1, 2 })); + Assert.That(collectionCounts, Is.EquivalentTo(new List { 2, 1, 3 })); + + modelFactory.AssertListsAreEqual(await typedList.GetAllAsync(), new List + { + modelFactory.CreateInstance(1), modelFactory.CreateInstance(2) + }); + + Assert.That(await typedSet.GetAllAsync(), Is.EquivalentTo(new List + { + modelFactory.CreateInstance(3) + })); + + modelFactory.AssertListsAreEqual(await typedSortedSet.GetAllAsync(), new List + { + modelFactory.CreateInstance(4), modelFactory.CreateInstance(5), modelFactory.CreateInstance(6) + }); + } + + [Test] + public async Task Can_call_multi_string_operations_in_pipeline() + { + Shipper item1 = null; + Shipper item4 = null; + + var results = new List(); + + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var pipeline = typedClient.CreatePipeline()) + { + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + pipeline.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3))); + pipeline.QueueCommand(r => r.GetAllItemsFromListAsync(typedList), x => results = x); + pipeline.QueueCommand(r => r.GetItemFromListAsync(typedList, 0), x => item1 = x); + pipeline.QueueCommand(r => r.GetItemFromListAsync(typedList, 4), x => item4 = x); + + await pipeline.FlushAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + + modelFactory.AssertListsAreEqual(results, new List + { + modelFactory.CreateInstance(1), modelFactory.CreateInstance(2), modelFactory.CreateInstance(3) + }); + + modelFactory.AssertIsEqual(item1, modelFactory.CreateInstance(1)); + Assert.That(item4, Is.Null); + } + [Test] + public async Task Pipeline_can_be_replayed() + { + const string keySquared = Key + Key; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(keySquared), Is.Null); + await using var pipeline = typedClient.CreatePipeline(); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + pipeline.QueueCommand(r => r.IncrementValueAsync(keySquared)); + await pipeline.FlushAsync(); + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(keySquared), Is.EqualTo("1")); + await typedClient.RemoveEntryAsync(Key); + await typedClient.RemoveEntryAsync(keySquared); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(keySquared), Is.Null); + + await pipeline.ReplayAsync(); + await pipeline.DisposeAsync(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(keySquared), Is.EqualTo("1")); + + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Generic/RedisTypedTransactionTests.Async.cs b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedTransactionTests.Async.cs new file mode 100644 index 00000000..9d096db7 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/Generic/RedisTypedTransactionTests.Async.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; + +namespace ServiceStack.Redis.Tests.Generic +{ + [TestFixture] + public class RedisTypedTransactionTestsAsync + : RedisClientTestsBaseAsync + { + private const string Key = "multitest"; + private const string ListKey = "multitest-list"; + private const string SetKey = "multitest-set"; + private const string SortedSetKey = "multitest-sortedset"; + + readonly ShipperFactory modelFactory = new ShipperFactory(); + private IRedisTypedClientAsync typedClient; + private Shipper model; + + public RedisTypedTransactionTestsAsync() + { + CleanMask = "multitest*"; + } + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + + typedClient = RedisAsync.As(); + model = modelFactory.CreateInstance(1); + } + + [Test] + public async Task Can_call_single_operation_in_transaction() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.SetValueAsync(Key, model)); + + await trans.CommitAsync(); + } + + modelFactory.AssertIsEqual(await typedClient.GetValueAsync(Key), model); + } + + [Test] + public async Task No_commit_of_atomic_transactions_discards_all_commands() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.SetValueAsync(Key, model)); + } + + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + } + + [Test] + public async Task Exception_in_atomic_transactions_discards_all_commands() + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + try + { + await using var trans = await typedClient.CreateTransactionAsync(); + trans.QueueCommand(r => r.SetValueAsync(Key, model)); + throw new NotSupportedException(); + } + catch (NotSupportedException) + { + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + } + } + + [Test] + public async Task Can_call_single_operation_3_Times_in_transaction() + { + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3))); + + await trans.CommitAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + } + + [Test] + public async Task Can_call_single_operation_with_callback_3_Times_in_transaction() + { + var results = new List(); + + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1)), () => results.Add(1)); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2)), () => results.Add(2)); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3)), () => results.Add(3)); + + await trans.CommitAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + Assert.That(results, Is.EquivalentTo(new List { 1, 2, 3 })); + } + + [Test] + public async Task Supports_different_operation_types_in_same_transaction() + { + var incrementResults = new List(); + var collectionCounts = new List(); + var containsItem = false; + + var typedList = typedClient.Lists[ListKey]; + var typedSet = typedClient.Sets[SetKey]; + var typedSortedSet = typedClient.SortedSets[SortedSetKey]; + + Assert.That(await typedClient.GetValueAsync(Key), Is.Null); + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + trans.QueueCommand(r => r.AddItemToSetAsync(typedSet, modelFactory.CreateInstance(3))); + trans.QueueCommand(r => r.SetContainsItemAsync(typedSet, modelFactory.CreateInstance(3)), b => containsItem = b); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(4))); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(5))); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(typedSortedSet, modelFactory.CreateInstance(6))); + trans.QueueCommand(r => r.GetListCountAsync(typedList), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.GetSetCountAsync(typedSet), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.GetSortedSetCountAsync(typedSortedSet), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + + await trans.CommitAsync(); + } + + Assert.That(containsItem, Is.True); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("2")); + Assert.That(incrementResults, Is.EquivalentTo(new List { 1, 2 })); + Assert.That(collectionCounts, Is.EquivalentTo(new List { 2, 1, 3 })); + + modelFactory.AssertListsAreEqual(await typedList.GetAllAsync(), new List + { + modelFactory.CreateInstance(1), modelFactory.CreateInstance(2) + }); + + Assert.That(await typedSet.GetAllAsync(), Is.EquivalentTo(new List + { + modelFactory.CreateInstance(3) + })); + + modelFactory.AssertListsAreEqual(await typedSortedSet.GetAllAsync(), new List + { + modelFactory.CreateInstance(4), modelFactory.CreateInstance(5), modelFactory.CreateInstance(6) + }); + } + + [Test] + public async Task Can_call_multi_string_operations_in_transaction() + { + Shipper item1 = null; + Shipper item4 = null; + + var results = new List(); + + var typedList = typedClient.Lists[ListKey]; + Assert.That(await typedList.CountAsync(), Is.EqualTo(0)); + + await using (var trans = await typedClient.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(1))); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(2))); + trans.QueueCommand(r => r.AddItemToListAsync(typedList, modelFactory.CreateInstance(3))); + trans.QueueCommand(r => r.GetAllItemsFromListAsync(typedList), x => results = x); + trans.QueueCommand(r => r.GetItemFromListAsync(typedList, 0), x => item1 = x); + trans.QueueCommand(r => r.GetItemFromListAsync(typedList, 4), x => item4 = x); + + await trans.CommitAsync(); + } + + Assert.That(await typedList.CountAsync(), Is.EqualTo(3)); + + modelFactory.AssertListsAreEqual(results, new List + { + modelFactory.CreateInstance(1), modelFactory.CreateInstance(2), modelFactory.CreateInstance(3) + }); + + modelFactory.AssertIsEqual(item1, modelFactory.CreateInstance(1)); + Assert.That(item4, Is.Null); + } + [Test] + // Operations that are not supported in older versions will look at server info to determine what to do. + // If server info is fetched each time, then it will interfer with transaction + public async Task Can_call_operation_not_supported_on_older_servers_in_transaction() + { + var temp = new byte[1]; + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => ((IRedisNativeClientAsync)r).SetExAsync("key", 5, temp)); + await trans.CommitAsync(); + } + + + [Test] + public async Task Transaction_can_be_replayed() + { + string KeySquared = Key + Key; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + trans.QueueCommand(r => r.IncrementValueAsync(KeySquared)); + await trans.CommitAsync(); + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + await NativeAsync.DelAsync(Key); + await NativeAsync.DelAsync(KeySquared); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + + await trans.ReplayAsync(); + await trans.DisposeAsync(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Integration/IntegrationTestBase.cs b/tests/ServiceStack.Redis.Tests/Integration/IntegrationTestBase.cs index 6b8b8b02..9283c309 100644 --- a/tests/ServiceStack.Redis.Tests/Integration/IntegrationTestBase.cs +++ b/tests/ServiceStack.Redis.Tests/Integration/IntegrationTestBase.cs @@ -59,7 +59,7 @@ protected void RunSimultaneously( #else var clientAsyncResults = new List(); #endif - using (var manager = clientManagerFactory(TestConfig.MasterHosts, TestConfig.SlaveHosts)) + using (var manager = clientManagerFactory(TestConfig.MasterHosts, TestConfig.ReplicaHosts)) { for (var i = 0; i < noOfConcurrentClients; i++) { @@ -79,25 +79,25 @@ protected void RunSimultaneously( WaitHandle.WaitAll(clientAsyncResults.ConvertAll(x => x.AsyncWaitHandle).ToArray()); #endif - Debug.WriteLine(String.Format("Time Taken: {0}", (Stopwatch.GetTimestamp() - before) / 1000)); + Debug.WriteLine($"Time Taken: {(Stopwatch.GetTimestamp() - before) / 1000}"); } protected static void CheckHostCountMap(Dictionary hostCountMap) { Debug.WriteLine(TypeSerializer.SerializeToString(hostCountMap)); - if (TestConfig.SlaveHosts.Length <= 1) return; + if (TestConfig.ReplicaHosts.Length <= 1) return; var hostCount = 0; foreach (var entry in hostCountMap) { if (entry.Value < 5) { - Debug.WriteLine("ERROR: Host has unproportianate distrobution: " + entry.Value); + Debug.WriteLine("ERROR: Host has unproportionate distribution: " + entry.Value); } if (entry.Value > 60) { - Debug.WriteLine("ERROR: Host has unproportianate distrobution: " + entry.Value); + Debug.WriteLine("ERROR: Host has unproportionate distribution: " + entry.Value); } hostCount += entry.Value; } diff --git a/tests/ServiceStack.Redis.Tests/Integration/RedisRegressionTestRun.cs b/tests/ServiceStack.Redis.Tests/Integration/RedisRegressionTestRun.cs index 8b653d23..a5aa4707 100644 --- a/tests/ServiceStack.Redis.Tests/Integration/RedisRegressionTestRun.cs +++ b/tests/ServiceStack.Redis.Tests/Integration/RedisRegressionTestRun.cs @@ -31,7 +31,7 @@ public void Can_support_64_threads_using_the_client_simultaneously() const int noOfConcurrentClients = 64; //WaitHandle.WaitAll limit is <= 64 var clientAsyncResults = new List(); - using (var manager = new PooledRedisClientManager(TestConfig.MasterHosts, TestConfig.SlaveHosts)) + using (var manager = new PooledRedisClientManager(TestConfig.MasterHosts, TestConfig.ReplicaHosts)) { manager.GetClient().Run(x => x.FlushAll()); diff --git a/tests/ServiceStack.Redis.Tests/Issues/PipelineIssueTests.cs b/tests/ServiceStack.Redis.Tests/Issues/PipelineIssueTests.cs index 6002465c..f675d178 100644 --- a/tests/ServiceStack.Redis.Tests/Issues/PipelineIssueTests.cs +++ b/tests/ServiceStack.Redis.Tests/Issues/PipelineIssueTests.cs @@ -39,5 +39,43 @@ public void Disposing_Client_Clears_Pipeline() Assert.AreEqual("v3", client.Get("k3")); } } + + [Test] + public void Can_Set_with_DateTime_in_Pipeline() + { + using (var clientsManager = new RedisManagerPool(TestConfig.SingleHost)) + { + bool result; + int value = 111; + string key = $"key:{value}"; + + // Set key with pipeline (batching many requests) + using (var redis = clientsManager.GetClient()) + { + using (var pipeline = redis.CreatePipeline()) + { + //Only atomic operations can be called within a Transaction or Pipeline + Assert.Throws(() => + pipeline.QueueCommand(r => r.Set(key, value, DateTime.Now.AddMinutes(1)), r => result = r)); + } + + using (var pipeline = redis.CreatePipeline()) + { + pipeline.QueueCommand(r => r.Set(key, value), r => result = r); + pipeline.QueueCommand(r => r.ExpireEntryAt(key, DateTime.Now.AddMinutes(1))); + + pipeline.Flush(); + } + } + + // Get key + using (var redis = clientsManager.GetClient()) + { + var res = redis.Get(key); + Assert.That(res, Is.EqualTo(value)); + } + } + + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/Issues/ReportedIssues.cs b/tests/ServiceStack.Redis.Tests/Issues/ReportedIssues.cs index fc4373eb..2b44d1d8 100644 --- a/tests/ServiceStack.Redis.Tests/Issues/ReportedIssues.cs +++ b/tests/ServiceStack.Redis.Tests/Issues/ReportedIssues.cs @@ -1,5 +1,8 @@ using System.Collections.Generic; +using System.Linq; using NUnit.Framework; +using ServiceStack.Redis.Generic; +using ServiceStack.Text; namespace ServiceStack.Redis.Tests.Issues { @@ -49,5 +52,60 @@ public void Success_callback_fails_for_pipeline_using_GetItemScoreInSortedSet() Assert.That(score, Is.EqualTo(1)); } + + public class Test + { + public int Id { get; set; } + public string Name { get; set; } + + protected bool Equals(Test other) => Id == other.Id && Name == other.Name; + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((Test) obj); + } + + public override int GetHashCode() + { + unchecked + { + return (Id * 397) ^ (Name != null ? Name.GetHashCode() : 0); + } + } + } + + [Test] + public void Try_simulate_NRE_when_calling_GetAllEntriesFromHash_using_BasicRedisClientManager() + { + using (var redisManager = new BasicRedisClientManager(TestConfig.SingleHost)) + using (var redis = redisManager.GetClient()) + { + IRedisHash testHash = redis.As() + .GetHash("test-hash"); + + Assert.That(testHash.Count, Is.EqualTo(0)); + + var contents = testHash.GetAll(); + Assert.That(contents.Count, Is.EqualTo(0)); + + var test1 = new Test { Id = 1, Name = "Name1" }; + var test2 = new Test { Id = 2, Name = "Name2" }; + testHash["A"] = test1; + testHash["B"] = test2; + + contents = testHash.GetAll(); + + Assert.That(contents, Is.EqualTo(new Dictionary { + ["A"] = test1, + ["B"] = test2, + })); + + Assert.That(testHash["A"], Is.EqualTo(test1)); + Assert.That(testHash["B"], Is.EqualTo(test2)); + } + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/LexTests.Async.cs b/tests/ServiceStack.Redis.Tests/LexTests.Async.cs new file mode 100644 index 00000000..d00e3f0f --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/LexTests.Async.cs @@ -0,0 +1,115 @@ +using NUnit.Framework; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class LexTestsAsync + : RedisClientTestsBaseAsync + { + readonly string[] values = "a,b,c,d,e,f,g".Split(','); + + [SetUp] + public async Task SetUp() + { + await RedisAsync.FlushAllAsync(); + foreach(var x in values) + { + await NativeAsync.ZAddAsync("zset", 0, x.ToUtf8Bytes()); + } + } + + [Test] + public async Task Can_ZRangeByLex_all_entries() + { + var results = await NativeAsync.ZRangeByLexAsync("zset", "-", "+"); + + Assert.That(results.Map(x => x.FromUtf8Bytes()), Is.EquivalentTo(values)); + + results = await NativeAsync.ZRangeByLexAsync("zset", "-", "+", 1, 3); + Assert.That(results.Map(x => x.FromUtf8Bytes()), Is.EquivalentTo(new[] { "b", "c", "d" })); + } + + [Test] + public async Task Can_ZRangeByLex_Desc() + { + var descInclusive = await NativeAsync.ZRangeByLexAsync("zset", "-", "[c"); + Assert.That(descInclusive.Map(x => x.FromUtf8Bytes()), Is.EquivalentTo(new[] { "a", "b", "c" })); + + var descExclusive = await NativeAsync.ZRangeByLexAsync("zset", "-", "(c"); + Assert.That(descExclusive.Map(x => x.FromUtf8Bytes()), Is.EquivalentTo(new[] { "a", "b" })); + } + + [Test] + public async Task Can_ZRangeByLex_Min_and_Max() + { + var range = await NativeAsync.ZRangeByLexAsync("zset", "[aaa", "(g"); + Assert.That(range.Map(x => x.FromUtf8Bytes()), + Is.EquivalentTo(new[] { "b", "c", "d", "e", "f" })); + } + + [Test] + public async Task Can_ZlexCount() + { + var total = await NativeAsync.ZLexCountAsync("zset", "-", "+"); + Assert.That(total, Is.EqualTo(values.Length)); + + Assert.That(await NativeAsync.ZLexCountAsync("zset", "-", "[c"), Is.EqualTo(3)); + Assert.That(await NativeAsync.ZLexCountAsync("zset", "-", "(c"), Is.EqualTo(2)); + } + + [Test] + public async Task Can_ZRemRangeByLex() + { + var removed = await NativeAsync.ZRemRangeByLexAsync("zset", "[aaa", "(g"); + Assert.That(removed, Is.EqualTo(5)); + + var remainder = await NativeAsync.ZRangeByLexAsync("zset", "-", "+"); + Assert.That(remainder.Map(x => x.FromUtf8Bytes()), Is.EqualTo(new[] { "a", "g" })); + } + + [Test] + public async Task Can_SearchSortedSet() + { + Assert.That(await RedisAsync.SearchSortedSetAsync("zset"), Is.EquivalentTo(values)); + Assert.That(await RedisAsync.SearchSortedSetAsync("zset", start: "-"), Is.EquivalentTo(values)); + Assert.That(await RedisAsync.SearchSortedSetAsync("zset", end: "+"), Is.EquivalentTo(values)); + + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", start: "[aaa")).Count, Is.EqualTo(values.Length - 1)); + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", end: "(g")).Count, Is.EqualTo(values.Length - 1)); + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", "[aaa", "(g")).Count, Is.EqualTo(values.Length - 2)); + + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", "a", "c")).Count, Is.EqualTo(3)); + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", "[a", "[c")).Count, Is.EqualTo(3)); + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", "a", "(c")).Count, Is.EqualTo(2)); + Assert.That((await RedisAsync.SearchSortedSetAsync("zset", "(a", "(c")).Count, Is.EqualTo(1)); + } + + [Test] + public async Task Can_SearchSortedSetCount() + { + Assert.That(await RedisAsync.SearchSortedSetAsync("zset"), Is.EquivalentTo(values)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", start: "-"), Is.EqualTo(values.Length)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", end: "+"), Is.EqualTo(values.Length)); + + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", start: "[aaa"), Is.EqualTo(values.Length - 1)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", end: "(g"), Is.EqualTo(values.Length - 1)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", "[aaa", "(g"), Is.EqualTo(values.Length - 2)); + + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", "a", "c"), Is.EqualTo(3)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", "[a", "[c"), Is.EqualTo(3)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", "a", "(c"), Is.EqualTo(2)); + Assert.That(await RedisAsync.SearchSortedSetCountAsync("zset", "(a", "(c"), Is.EqualTo(1)); + } + + [Test] + public async Task Can_RemoveRangeFromSortedSetBySearch() + { + var removed = await RedisAsync.RemoveRangeFromSortedSetBySearchAsync("zset", "[aaa", "(g"); + Assert.That(removed, Is.EqualTo(5)); + + var remainder = await RedisAsync.SearchSortedSetAsync("zset"); + Assert.That(remainder, Is.EqualTo(new[] { "a", "g" })); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/LuaCachedScripts.Async.cs b/tests/ServiceStack.Redis.Tests/LuaCachedScripts.Async.cs new file mode 100644 index 00000000..4cec7af8 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/LuaCachedScripts.Async.cs @@ -0,0 +1,297 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + [Category("Async")] + public class LuaCachedScriptsAsync + { + private const string LuaScript = @" +local limit = tonumber(ARGV[2]) +local pattern = ARGV[1] +local cursor = 0 +local len = 0 +local results = {} + +repeat + local r = redis.call('scan', cursor, 'MATCH', pattern, 'COUNT', limit) + cursor = tonumber(r[1]) + for k,v in ipairs(r[2]) do + table.insert(results, v) + len = len + 1 + if len == limit then break end + end +until cursor == 0 or len == limit + +return results +"; + + private static async Task AddTestKeysAsync(IRedisClientAsync redis, int count) + { + for (int i = 0; i < count; i++) + await redis.SetValueAsync("key:" + i, "value:" + i); + } + + [Test] + public async Task Can_call_repeated_scans_in_LUA() + { + await using var redis = new RedisClient().ForAsyncOnly(); + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecLuaAsync(LuaScript, "key:*", "10"); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + r = await redis.ExecLuaAsync(LuaScript, "key:*", "40"); + Assert.That(r.Children.Count, Is.EqualTo(20)); + } + + [Test] + public async Task Can_call_Cached_Lua() + { + await using var redis = new RedisClient().ForAsyncOnly(); + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, "key:*", "10")); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, "key:*", "10")); + Assert.That(r.Children.Count, Is.EqualTo(10)); + } + + [Test] + public async Task Can_call_Cached_Lua_even_after_script_is_flushed() + { + await using var redis = new RedisClient().ForAsyncOnly(); + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, "key:*", "10")); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + await ((IRedisNativeClientAsync)redis).ScriptFlushAsync(); + + r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, "key:*", "10")); + Assert.That(r.Children.Count, Is.EqualTo(10)); + } + + [Test] + public async Task Can_call_repeated_scans_in_LUA_longhand() + { + await using var redis = new RedisClient().ForAsyncOnly(); + + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecLuaAsync(LuaScript, null, new[] { "key:*", "10" }); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + r = await redis.ExecLuaAsync(LuaScript, null, new[] { "key:*", "40" }); + Assert.That(r.Children.Count, Is.EqualTo(20)); + } + + [Test] + public async Task Can_call_Cached_Lua_longhand() + { + await using var redis = new RedisClient().ForAsyncOnly(); + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, null, new[] { "key:*", "10" })); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, null, new[] { "key:*", "10" })); + Assert.That(r.Children.Count, Is.EqualTo(10)); + } + + [Test] + public async Task Can_call_Cached_Lua_even_after_script_is_flushed_longhand() + { + await using var redis = new RedisClient().ForAsyncOnly(); + await AddTestKeysAsync(redis, 20); + + var r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, null, new[] { "key:*", "10" })); + Assert.That(r.Children.Count, Is.EqualTo(10)); + + await ((IRedisNativeClientAsync)redis).ScriptFlushAsync(); + + r = await redis.ExecCachedLuaAsync(LuaScript, sha1 => + redis.ExecLuaShaAsync(sha1, null, new[] { "key:*", "10" })); + Assert.That(r.Children.Count, Is.EqualTo(10)); + } + + private const string KeyAttributesScript = @" +local limit = tonumber(ARGV[2]) +local pattern = ARGV[1] +local cursor = 0 +local len = 0 +local keys = {} + +repeat + local r = redis.call('scan', cursor, 'MATCH', pattern, 'COUNT', limit) + cursor = tonumber(r[1]) + for k,v in ipairs(r[2]) do + table.insert(keys, v) + len = len + 1 + if len == limit then break end + end +until cursor == 0 or len == limit + +local keyAttrs = {} +for i,key in ipairs(keys) do + local type = redis.call('type', key)['ok'] + local pttl = redis.call('pttl', key) + local size = 0 + if type == 'string' then + size = redis.call('strlen', key) + elseif type == 'list' then + size = redis.call('llen', key) + elseif type == 'set' then + size = redis.call('scard', key) + elseif type == 'zset' then + size = redis.call('zcard', key) + elseif type == 'hash' then + size = redis.call('hlen', key) + end + + local attrs = {['id'] = key, ['type'] = type, ['ttl'] = pttl, ['size'] = size} + + table.insert(keyAttrs, attrs) +end + +return cjson.encode(keyAttrs)"; + + [Test] + public async Task Can_call_script_with_complex_response() + { + await using var redis = new RedisClient().ForAsyncOnly(); + var r = await redis.ExecCachedLuaAsync(KeyAttributesScript, sha1 => + redis.ExecLuaShaAsStringAsync(sha1, "key:*", "10")); + + r.Print(); + + var results = r.FromJson>(); + + Assert.That(results.Count, Is.EqualTo(10)); + + var result = results[0]; + Assert.That(result.Id.StartsWith("key:")); + Assert.That(result.Type, Is.EqualTo("string")); + Assert.That(result.Size, Is.GreaterThan("value:".Length)); + Assert.That(result.Ttl, Is.EqualTo(-1)); + } + + [Test] + public async Task Can_call_script_with_complex_response_longhand() + { + await using var redis = new RedisClient().ForAsyncOnly(); + var r = await redis.ExecCachedLuaAsync(KeyAttributesScript, sha1 => + redis.ExecLuaShaAsStringAsync(sha1, null, new[] { "key:*", "10" })); + + r.Print(); + + var results = r.FromJson>(); + + Assert.That(results.Count, Is.EqualTo(10)); + + var result = results[0]; + Assert.That(result.Id.StartsWith("key:")); + Assert.That(result.Type, Is.EqualTo("string")); + Assert.That(result.Size, Is.GreaterThan("value:".Length)); + Assert.That(result.Ttl, Is.EqualTo(-1)); + } + + public class SearchResult + { + public string Id { get; set; } + public string Type { get; set; } + public long Ttl { get; set; } + public long Size { get; set; } + } + + [Test] + public async Task Can_merge_multiple_SearchResults() + { + await using var Redis = new RedisClient().ForAsyncOnly(); + var limit = 10; + var query = "key:*"; + + List keys = new List(limit); + await foreach (var key in Redis.ScanAllKeysAsync(pattern: query, pageSize: limit)) + { + keys.Add(key); + if (keys.Count == limit) break; + } + + var keyTypes = new Dictionary(); + var keyTtls = new Dictionary(); + var keySizes = new Dictionary(); + + if (keys.Count > 0) + { + await using (var pipeline = Redis.CreatePipeline()) + { + foreach (var key in keys) + pipeline.QueueCommand(r => r.TypeAsync(key), x => keyTypes[key] = x); + + foreach (var key in keys) + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).PTtlAsync(key), x => keyTtls[key] = x); + + await pipeline.FlushAsync(); + } + + await using (var pipeline = Redis.CreatePipeline()) + { + foreach (var entry in keyTypes) + { + var key = entry.Key; + switch (entry.Value) + { + case "string": + pipeline.QueueCommand(r => r.GetStringCountAsync(key), x => keySizes[key] = x); + break; + case "list": + pipeline.QueueCommand(r => r.GetListCountAsync(key), x => keySizes[key] = x); + break; + case "set": + pipeline.QueueCommand(r => r.GetSetCountAsync(key), x => keySizes[key] = x); + break; + case "zset": + pipeline.QueueCommand(r => r.GetSortedSetCountAsync(key), x => keySizes[key] = x); + break; + case "hash": + pipeline.QueueCommand(r => r.GetHashCountAsync(key), x => keySizes[key] = x); + break; + } + } + + await pipeline.FlushAsync(); + } + } + + var results = keys.Map(x => new SearchResult + { + Id = x, + Type = keyTypes.GetValueOrDefault(x), + Ttl = keyTtls.GetValueOrDefault(x), + Size = keySizes.GetValueOrDefault(x), + }); + + Assert.That(results.Count, Is.EqualTo(limit)); + + var result = results[0]; + Assert.That(result.Id.StartsWith("key:")); + Assert.That(result.Type, Is.EqualTo("string")); + Assert.That(result.Size, Is.GreaterThan("value:".Length)); + Assert.That(result.Ttl, Is.EqualTo(-1)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/LuaCachedScripts.cs b/tests/ServiceStack.Redis.Tests/LuaCachedScripts.cs index fd764dfe..80491fec 100644 --- a/tests/ServiceStack.Redis.Tests/LuaCachedScripts.cs +++ b/tests/ServiceStack.Redis.Tests/LuaCachedScripts.cs @@ -71,6 +71,8 @@ public void Can_call_Cached_Lua_even_after_script_is_flushed() { using (var redis = new RedisClient()) { + AddTestKeys(redis, 20); + var r = redis.ExecCachedLua(LuaScript, sha1 => redis.ExecLuaSha(sha1, "key:*", "10")); Assert.That(r.Children.Count, Is.EqualTo(10)); diff --git a/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.Async.cs b/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.Async.cs new file mode 100644 index 00000000..fdf442f6 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.Async.cs @@ -0,0 +1,440 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration"), Category("Async")] + public class PooledRedisClientManagerTestsAsync + { + [OneTimeSetUp] + public void OneTimeSetUp() + { + RedisConfig.VerifyMasterConnections = false; + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + RedisConfig.VerifyMasterConnections = true; + } + + readonly string[] testReadWriteHosts = new[] { + "readwrite1", "readwrite2:6000", "192.168.0.1", "localhost" + }; + + readonly string[] testReadOnlyHosts = new[] { + "read1", "read2:7000", "127.0.0.1" + }; + + private string firstReadWriteHost; + private string firstReadOnlyHost; + + [SetUp] + public void OnBeforeEachTest() + { + firstReadWriteHost = testReadWriteHosts[0]; + firstReadOnlyHost = testReadOnlyHosts[0]; + } + + public IRedisClientsManagerAsync CreateManager(string[] readWriteHosts, string[] readOnlyHosts, int? defaultDb = null) + { + return new PooledRedisClientManager(readWriteHosts, readOnlyHosts, + new RedisClientManagerConfig + { + MaxWritePoolSize = readWriteHosts.Length, + MaxReadPoolSize = readOnlyHosts.Length, + AutoStart = false, + DefaultDb = defaultDb + }); + } + public IRedisClientsManagerAsync CreateManager(params string[] readWriteHosts) + { + return CreateManager(readWriteHosts, readWriteHosts); + } + + public IRedisClientsManagerAsync CreateManager() + { + return CreateManager(testReadWriteHosts, testReadOnlyHosts); + } + + public IRedisClientsManagerAsync CreateAndStartManager() + { + var manager = CreateManager(); + ((PooledRedisClientManager)manager).Start(); + return manager; + } + + [Test] + public async Task Cant_get_client_without_calling_Start() + { + await using var manager = CreateManager(); + try + { + var client = await manager.GetClientAsync(); + } + catch (InvalidOperationException) + { + return; + } + Assert.Fail("Should throw"); + } + + [Test] + public async Task Can_change_db_for_client_PooledRedisClientManager() + { + await using IRedisClientsManagerAsync db1 = new PooledRedisClientManager(1, new string[] { TestConfig.SingleHost }); + await using IRedisClientsManagerAsync db2 = new PooledRedisClientManager(2, new string[] { TestConfig.SingleHost }); + var val = Environment.TickCount; + var key = "test" + val; + var db1c = await db1.GetClientAsync(); + var db2c = await db2.GetClientAsync(); + try + { + await db1c.SetAsync(key, val); + Assert.That(await db2c.GetAsync(key), Is.EqualTo(0)); + Assert.That(await db1c.GetAsync(key), Is.EqualTo(val)); + } + finally + { + await db1c.RemoveAsync(key); + } + } + + [Test] + public async Task Can_change_db_for_client_RedisManagerPool() + { + await using IRedisClientsManagerAsync db1 = new RedisManagerPool(TestConfig.SingleHost + "?db=1"); + await using IRedisClientsManagerAsync db2 = new RedisManagerPool(TestConfig.SingleHost + "?db=2"); + var val = Environment.TickCount; + var key = "test" + val; + var db1c = await db1.GetClientAsync(); + var db2c = await db2.GetClientAsync(); + try + { + await db1c.SetAsync(key, val); + Assert.That(await db2c.GetAsync(key), Is.EqualTo(0)); + Assert.That(await db1c.GetAsync(key), Is.EqualTo(val)); + } + finally + { + await db1c.RemoveAsync(key); + } + } + + [Test] + public async Task Can_change_db_for_client_BasicRedisClientManager() + { + await using IRedisClientsManagerAsync db1 = new BasicRedisClientManager(1, new string[] { TestConfig.SingleHost }); + await using IRedisClientsManagerAsync db2 = new BasicRedisClientManager(2, new string[] { TestConfig.SingleHost }); + var val = Environment.TickCount; + var key = "test" + val; + var db1c = await db1.GetClientAsync(); + var db2c = await db2.GetClientAsync(); + try + { + await db1c.SetAsync(key, val); + Assert.That(await db2c.GetAsync(key), Is.EqualTo(0)); + Assert.That(await db1c.GetAsync(key), Is.EqualTo(val)); + } + finally + { + await db1c.RemoveAsync(key); + } + } + + [Test] + public async Task Can_get_client_after_calling_Start() + { + await using var manager = CreateAndStartManager(); + var client = await manager.GetClientAsync(); + } + + [Test] + public async Task Can_get_ReadWrite_client() + { + await using var manager = CreateAndStartManager(); + var client = await manager.GetClientAsync(); + + AssertClientHasHost(client, firstReadWriteHost); + } + + private static void AssertClientHasHost(IRedisClientAsync client, string hostWithOptionalPort) + { + var parts = hostWithOptionalPort.Split(':'); + var port = parts.Length > 1 ? int.Parse(parts[1]) : RedisConfig.DefaultPort; + + Assert.That(client.Host, Is.EqualTo(parts[0])); + Assert.That(client.Port, Is.EqualTo(port)); + } + + [Test] + public async Task Can_get_ReadOnly_client() + { + await using var manager = CreateAndStartManager(); + var client = await manager.GetReadOnlyClientAsync(); + + AssertClientHasHost(client, firstReadOnlyHost); + } + + [Test] + public async Task Does_loop_through_ReadWrite_hosts() + { + await using var manager = CreateAndStartManager(); + var client1 = await manager.GetClientAsync(); + await client1.DisposeAsync(); + var client2 = await manager.GetClientAsync(); + var client3 = await manager.GetClientAsync(); + var client4 = await manager.GetClientAsync(); + var client5 = await manager.GetClientAsync(); + + AssertClientHasHost(client1, testReadWriteHosts[0]); + AssertClientHasHost(client2, testReadWriteHosts[1]); + AssertClientHasHost(client3, testReadWriteHosts[2]); + AssertClientHasHost(client4, testReadWriteHosts[3]); + AssertClientHasHost(client5, testReadWriteHosts[0]); + } + + [Test] + public async Task Does_loop_through_ReadOnly_hosts() + { + await using var manager = CreateAndStartManager(); + var client1 = await manager.GetReadOnlyClientAsync(); + await client1.DisposeAsync(); + var client2 = await manager.GetReadOnlyClientAsync(); + await client2.DisposeAsync(); + var client3 = await manager.GetReadOnlyClientAsync(); + var client4 = await manager.GetReadOnlyClientAsync(); + var client5 = await manager.GetReadOnlyClientAsync(); + + AssertClientHasHost(client1, testReadOnlyHosts[0]); + AssertClientHasHost(client2, testReadOnlyHosts[1]); + AssertClientHasHost(client3, testReadOnlyHosts[2]); + AssertClientHasHost(client4, testReadOnlyHosts[0]); + AssertClientHasHost(client5, testReadOnlyHosts[1]); + } + + [Test] + public async Task Can_have_different_pool_size_and_host_configurations() + { + var writeHosts = new[] { "readwrite1" }; + var readHosts = new[] { "read1", "read2" }; + + const int poolSizeMultiplier = 4; + + await using IRedisClientsManagerAsync manager = new PooledRedisClientManager(writeHosts, readHosts, + new RedisClientManagerConfig + { + MaxWritePoolSize = writeHosts.Length * poolSizeMultiplier, + MaxReadPoolSize = readHosts.Length * poolSizeMultiplier, + AutoStart = true, + } + ); + //A poolsize of 4 will not block getting 4 clients + await using (var client1 = await manager.GetClientAsync()) + await using (var client2 = await manager.GetClientAsync()) + await using (var client3 = await manager.GetClientAsync()) + await using (var client4 = await manager.GetClientAsync()) + { + AssertClientHasHost(client1, writeHosts[0]); + AssertClientHasHost(client2, writeHosts[0]); + AssertClientHasHost(client3, writeHosts[0]); + AssertClientHasHost(client4, writeHosts[0]); + } + + //A poolsize of 8 will not block getting 8 clients + await using (var client1 = await manager.GetReadOnlyClientAsync()) + await using (var client2 = await manager.GetReadOnlyClientAsync()) + await using (var client3 = await manager.GetReadOnlyClientAsync()) + await using (var client4 = await manager.GetReadOnlyClientAsync()) + await using (var client5 = await manager.GetReadOnlyClientAsync()) + await using (var client6 = await manager.GetReadOnlyClientAsync()) + await using (var client7 = await manager.GetReadOnlyClientAsync()) + await using (var client8 = await manager.GetReadOnlyClientAsync()) + { + AssertClientHasHost(client1, readHosts[0]); + AssertClientHasHost(client2, readHosts[1]); + AssertClientHasHost(client3, readHosts[0]); + AssertClientHasHost(client4, readHosts[1]); + AssertClientHasHost(client5, readHosts[0]); + AssertClientHasHost(client6, readHosts[1]); + AssertClientHasHost(client7, readHosts[0]); + AssertClientHasHost(client8, readHosts[1]); + } + } + + [Test] + public async Task Does_block_ReadWrite_clients_pool() + { + await using IRedisClientsManagerAsync manager = CreateAndStartManager(); + var delay = TimeSpan.FromSeconds(1); + var client1 = await manager.GetClientAsync(); + var client2 = await manager.GetClientAsync(); + var client3 = await manager.GetClientAsync(); + var client4 = await manager.GetClientAsync(); + +#pragma warning disable IDE0039 // Use local function + Action func = async delegate +#pragma warning restore IDE0039 // Use local function + { + await Task.Delay(delay + TimeSpan.FromSeconds(0.5)); + await client4.DisposeAsync(); + }; + +#if NETCORE + _ = Task.Run(func); +#else + func.BeginInvoke(null, null); +#endif + + var start = DateTime.Now; + + var client5 = await manager.GetClientAsync(); + + Assert.That(DateTime.Now - start, Is.GreaterThanOrEqualTo(delay)); + + AssertClientHasHost(client1, testReadWriteHosts[0]); + AssertClientHasHost(client2, testReadWriteHosts[1]); + AssertClientHasHost(client3, testReadWriteHosts[2]); + AssertClientHasHost(client4, testReadWriteHosts[3]); + AssertClientHasHost(client5, testReadWriteHosts[3]); + } + + [Test] + public async Task Does_block_ReadOnly_clients_pool() + { + var delay = TimeSpan.FromSeconds(1); + + await using var manager = CreateAndStartManager(); + var client1 = await manager.GetReadOnlyClientAsync(); + var client2 = await manager.GetReadOnlyClientAsync(); + var client3 = await manager.GetReadOnlyClientAsync(); + +#pragma warning disable IDE0039 // Use local function + Action func = async delegate +#pragma warning restore IDE0039 // Use local function + { + await Task.Delay(delay + TimeSpan.FromSeconds(0.5)); + await client3.DisposeAsync(); + }; +#if NETCORE + _ =Task.Run(func); +#else + func.BeginInvoke(null, null); +#endif + var start = DateTime.Now; + + var client4 = await manager.GetReadOnlyClientAsync(); + + Assert.That(DateTime.Now - start, Is.GreaterThanOrEqualTo(delay)); + + AssertClientHasHost(client1, testReadOnlyHosts[0]); + AssertClientHasHost(client2, testReadOnlyHosts[1]); + AssertClientHasHost(client3, testReadOnlyHosts[2]); + AssertClientHasHost(client4, testReadOnlyHosts[2]); + } + + [Test] + public async Task Does_throw_TimeoutException_when_PoolTimeout_exceeded() + { + await using IRedisClientsManagerAsync manager = new PooledRedisClientManager(testReadWriteHosts, testReadOnlyHosts, + new RedisClientManagerConfig + { + MaxWritePoolSize = 4, + MaxReadPoolSize = 4, + AutoStart = false, + }); + ((PooledRedisClientManager)manager).PoolTimeout = 100; + + ((PooledRedisClientManager)manager).Start(); + + var masters = 4.Times(i => manager.GetClientAsync()); + + try + { + await manager.GetClientAsync(); + Assert.Fail("Should throw TimeoutException"); + } + catch (TimeoutException ex) + { + Assert.That(ex.Message, Does.StartWith("Redis Timeout expired.")); + } + + for (int i = 0; i < 4; i++) + { + await manager.GetReadOnlyClientAsync(); + } + + try + { + await manager.GetReadOnlyClientAsync(); + Assert.Fail("Should throw TimeoutException"); + } + catch (TimeoutException ex) + { + Assert.That(ex.Message, Does.StartWith("Redis Timeout expired.")); + } + } + + //[Ignore("tempromental integration test")] + //[Test] + //public void Can_support_64_threads_using_the_client_simultaneously() + //{ + // const int noOfConcurrentClients = 64; //WaitHandle.WaitAll limit is <= 64 + // var clientUsageMap = new Dictionary(); + + // var clientAsyncResults = new List(); + // using (var manager = CreateAndStartManager()) + // { + // for (var i = 0; i < noOfConcurrentClients; i++) + // { + // var clientNo = i; + // var action = (Action)(() => UseClient(manager, clientNo, clientUsageMap)); + // clientAsyncResults.Add(action.BeginInvoke(null, null)); + // } + // } + + // WaitHandle.WaitAll(clientAsyncResults.ConvertAll(x => x.AsyncWaitHandle).ToArray()); + + // RedisStats.ToDictionary().PrintDump(); + + // Debug.WriteLine(TypeSerializer.SerializeToString(clientUsageMap)); + + // var hostCount = 0; + // foreach (var entry in clientUsageMap) + // { + // Assert.That(entry.Value, Is.GreaterThanOrEqualTo(2), "Host has unproportionate distribution: " + entry.Value); + // Assert.That(entry.Value, Is.LessThanOrEqualTo(30), "Host has unproportionate distribution: " + entry.Value); + // hostCount += entry.Value; + // } + + // Assert.That(hostCount, Is.EqualTo(noOfConcurrentClients), "Invalid no of clients used"); + //} + + //private static void UseClient(IRedisClientsManager manager, int clientNo, Dictionary hostCountMap) + //{ + // using (var client = manager.GetClient()) + // { + // lock (hostCountMap) + // { + // int hostCount; + // if (!hostCountMap.TryGetValue(client.Host, out hostCount)) + // { + // hostCount = 0; + // } + + // hostCountMap[client.Host] = ++hostCount; + // } + + // Debug.WriteLine(String.Format("Client '{0}' is using '{1}'", clientNo, client.Host)); + // } + //} + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.cs b/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.cs index 31150fab..d1a9f7cb 100644 --- a/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.cs +++ b/tests/ServiceStack.Redis.Tests/PooledRedisClientManagerTests.cs @@ -345,7 +345,7 @@ public void Does_block_ReadOnly_clients_pool() Thread.Sleep(delay + TimeSpan.FromSeconds(0.5)); client3.Dispose(); }; -#if NETCORE +#if NETCORE Task.Run(func); #else func.BeginInvoke(null, null); @@ -390,7 +390,7 @@ public void Does_throw_TimeoutException_when_PoolTimeout_exceeded() Assert.That(ex.Message, Does.StartWith("Redis Timeout expired.")); } - var slaves = 4.Times(i => manager.GetReadOnlyClient()); + var replicas = 4.Times(i => manager.GetReadOnlyClient()); try { @@ -431,8 +431,8 @@ public void Does_throw_TimeoutException_when_PoolTimeout_exceeded() // var hostCount = 0; // foreach (var entry in clientUsageMap) // { - // Assert.That(entry.Value, Is.GreaterThanOrEqualTo(2), "Host has unproportianate distribution: " + entry.Value); - // Assert.That(entry.Value, Is.LessThanOrEqualTo(30), "Host has unproportianate distribution: " + entry.Value); + // Assert.That(entry.Value, Is.GreaterThanOrEqualTo(2), "Host has unproportionate distribution: " + entry.Value); + // Assert.That(entry.Value, Is.LessThanOrEqualTo(30), "Host has unproportionate distribution: " + entry.Value); // hostCount += entry.Value; // } diff --git a/tests/ServiceStack.Redis.Tests/Properties/AssemblyInfo.cs b/tests/ServiceStack.Redis.Tests/Properties/AssemblyInfo.cs index da860cde..157ff9b9 100644 --- a/tests/ServiceStack.Redis.Tests/Properties/AssemblyInfo.cs +++ b/tests/ServiceStack.Redis.Tests/Properties/AssemblyInfo.cs @@ -33,4 +33,4 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.Async.cs new file mode 100644 index 00000000..53bab942 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.Async.cs @@ -0,0 +1,266 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Data; +using ServiceStack.Model; +using ServiceStack.Redis.Generic; +using ServiceStack.Script; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class RedisBasicPersistenceProviderTestsAsync + : RedisClientTestsBaseAsync + { + List testModels; + + public static string TestModelIdsSetKey = "ids:" + typeof(TestModel).Name; + + public class TestModel + : IHasId + { + public Guid Id { get; set; } + public string Name { get; set; } + public int Age { get; set; } + + //Thanking R# for the timesaver + public bool Equals(TestModel other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + return other.Id.Equals(Id) && Equals(other.Name, Name) && other.Age == Age; + } + + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != typeof(TestModel)) return false; + return Equals((TestModel)obj); + } + + [SuppressMessage("Style", "IDE0070:Use 'System.HashCode'", Justification = "not in netfx")] + public override int GetHashCode() + { + unchecked + { + int result = Id.GetHashCode(); + result = (result * 397) ^ (Name != null ? Name.GetHashCode() : 0); + result = (result * 397) ^ Age; + return result; + } + } + } + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + + RedisRaw.NamespacePrefix = "RedisBasicPersistenceProviderTests"; + testModels = new List(); + 5.Times(i => testModels.Add( + new TestModel { Id = Guid.NewGuid(), Name = "Name" + i, Age = 20 + i })); + } + + [Test] + public async Task Can_Store() + { + foreach (var x in testModels) + { + await RedisAsync.StoreAsync(x); + } + + var allModels = (await RedisAsync.As().GetAllAsync()).OrderBy(x => x.Age).ToList(); + + Assert.That(allModels, Is.EquivalentTo(testModels)); + } + + [Test] + public async Task Can_StoreAll() + { + await RedisAsync.StoreAllAsync(testModels); + + var allModels = (await RedisAsync.As().GetAllAsync()).OrderBy(x => x.Age).ToList(); + + Assert.That(allModels, Is.EquivalentTo(testModels)); + } + + [Test] + public async Task Can_WriteAll() + { + await RedisAsync.WriteAllAsync(testModels); + + var testModelIds = testModels.ConvertAll(x => x.Id); + + var allModels = (await RedisAsync.GetByIdsAsync(testModelIds)) + .OrderBy(x => x.Age).ToList(); + + Assert.That(allModels, Is.EquivalentTo(testModels)); + } + + [Test] + public async Task Can_GetById() + { + await RedisAsync.StoreAllAsync(testModels); + + var last = testModels.Last(); + var lastById = await RedisAsync.GetByIdAsync(last.Id); + + Assert.That(lastById, Is.EqualTo(last)); + } + + [Test] + public async Task Can_GetByIds() + { + await RedisAsync.StoreAllAsync(testModels); + + var evenTestModels = testModels.Where(x => x.Age % 2 == 0) + .OrderBy(x => x.Id).ToList(); + var evenTestModelIds = evenTestModels.Select(x => x.Id).ToList(); + + var selectedModels = (await RedisAsync.GetByIdsAsync(evenTestModelIds)) + .OrderBy(x => x.Id).ToList(); + + Assert.That(selectedModels, Is.EqualTo(evenTestModels)); + } + + [Test] + public async Task Can_Delete() + { + await RedisAsync.StoreAllAsync(testModels); + + var last = testModels.Last(); + await RedisAsync.DeleteAsync(last); + + testModels.Remove(last); + + var allModels = (await RedisAsync.As().GetAllAsync()).OrderBy(x => x.Age).ToList(); + + Assert.That(allModels, Is.EquivalentTo(testModels)); + + //Test internal TestModelIdsSetKey state + var idsRemaining = (await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + TestModelIdsSetKey)) + .OrderBy(x => x).Map(x => new Guid(x)); + + var testModelIds = testModels.OrderBy(x => x.Id).Map(x => x.Id); + + Assert.That(idsRemaining, Is.EquivalentTo(testModelIds)); + } + + [Test] + public async Task Can_DeleteAll() + { + await RedisAsync.StoreAllAsync(testModels); + + await RedisAsync.DeleteAllAsync(); + + var allModels = await RedisAsync.As().GetAllAsync(); + + Assert.That(allModels, Is.Empty); + + //Test internal TestModelIdsSetKey state + var idsRemaining = await RedisAsync.GetAllItemsFromSetAsync(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public async Task Can_DeleteAll_with_runtime_type() + { + await RedisAsync.StoreAllAsync(testModels); + + var mi = typeof(IEntityStoreAsync).GetMethod(nameof(IEntityStoreAsync.DeleteAllAsync)); + var genericMi = mi.MakeGenericMethod(typeof(TestModel)); + await (Task)genericMi.Invoke(RedisAsync, new object[] { CancellationToken.None }); + + var allModels = await RedisAsync.As().GetAllAsync(); + Assert.That(allModels, Is.Empty); + var idsRemaining = await RedisAsync.GetAllItemsFromSetAsync(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public async Task Can_As_DeleteAll_with_runtime_type() + { + await RedisAsync.StoreAllAsync(testModels); + + var mi = typeof(IRedisClientAsync).GetMethod(nameof(IRedisClientAsync.As)); + var genericMi = mi.MakeGenericMethod(typeof(TestModel)); + var typedClient = genericMi.Invoke(RedisAsync, TypeConstants.EmptyObjectArray); + var deleteMi = typeof(IEntityStoreAsync).GetMethod(nameof(IEntityStoreAsync.DeleteAllAsync)); + await (Task)deleteMi.Invoke(typedClient, new object[] { CancellationToken.None }); + + var allModels = await RedisAsync.As().GetAllAsync(); + Assert.That(allModels, Is.Empty); + var idsRemaining = await RedisAsync.GetAllItemsFromSetAsync(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public async Task Can_As_DeleteAll_with_script() + { + await RedisAsync.StoreAllAsync(testModels); + + var context = new ScriptContext + { + ScriptLanguages = { ScriptLisp.Language }, + AllowScriptingOfAllTypes = true, + ScriptMethods = { + new ProtectedScripts() + }, + Args = { + ["redis"] = RedisAsync + } + }.Init(); + + var type = typeof(TestModel).FullName; +#if DEBUG + RedisRaw.DebugAllowSync = true; // not reasonable to allow async from Lisp +#endif + context.EvaluateCode($"redis.call('DeleteAll<{type}>') |> return"); + context.EvaluateCode($"redis.call('As<{type}>').call('DeleteAll') |> return"); + context.RenderLisp($"(call redis \"DeleteAll<{type}>\")"); + context.RenderLisp($"(call (call redis \"As<{type}>\") \"DeleteAll\")"); +#if DEBUG + RedisRaw.DebugAllowSync = false; +#endif + + var allModels = await RedisAsync.As().GetAllAsync(); + Assert.That(allModels, Is.Empty); + var idsRemaining = await RedisAsync.GetAllItemsFromSetAsync(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public async Task Can_DeleteByIds() + { + await RedisAsync.StoreAllAsync(testModels); + + var evenTestModels = testModels.Where(x => x.Age % 2 == 0) + .OrderBy(x => x.Id).ToList(); + var evenTestModelIds = evenTestModels.Select(x => x.Id).ToList(); + + await RedisAsync.DeleteByIdsAsync(evenTestModelIds); + + evenTestModels.ForEach(x => testModels.Remove(x)); + + var allModels = (await RedisAsync.As().GetAllAsync()).OrderBy(x => x.Age).ToList(); + + Assert.That(allModels, Is.EqualTo(testModels)); + + + //Test internal TestModelIdsSetKey state + var idsRemaining = (await RedisAsync.GetAllItemsFromSetAsync(RedisRaw.NamespacePrefix + TestModelIdsSetKey)) + .OrderBy(x => x).Map(x => new Guid(x)); + + var testModelIds = testModels.OrderBy(x => x.Id).Map(x => x.Id); + + Assert.That(idsRemaining, Is.EquivalentTo(testModelIds)); + } + + } +} diff --git a/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.cs b/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.cs index 484e82c4..86a10ff2 100644 --- a/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisBasicPersistenceProviderTests.cs @@ -3,6 +3,8 @@ using System.Linq; using NUnit.Framework; using ServiceStack.Model; +using ServiceStack.Redis.Generic; +using ServiceStack.Script; using ServiceStack.Text; namespace ServiceStack.Redis.Tests @@ -158,6 +160,66 @@ public void Can_DeleteAll() Assert.That(idsRemaining, Is.Empty); } + [Test] + public void Can_DeleteAll_with_runtime_type() + { + Redis.StoreAll(testModels); + + var mi = Redis.GetType().GetMethod(nameof(RedisClient.DeleteAll)); + var genericMi = mi.MakeGenericMethod(typeof(TestModel)); + genericMi.Invoke(Redis, TypeConstants.EmptyObjectArray); + + var allModels = Redis.GetAll(); + Assert.That(allModels, Is.Empty); + var idsRemaining = Redis.GetAllItemsFromSet(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public void Can_As_DeleteAll_with_runtime_type() + { + Redis.StoreAll(testModels); + + var mi = Redis.GetType().GetMethod(nameof(RedisClient.As)); + var genericMi = mi.MakeGenericMethod(typeof(TestModel)); + var typedClient = genericMi.Invoke(Redis, TypeConstants.EmptyObjectArray); + var deleteMi = typedClient.GetType().GetMethod(nameof(IRedisTypedClient.DeleteAll)); + deleteMi.Invoke(typedClient, TypeConstants.EmptyObjectArray); + + var allModels = Redis.GetAll(); + Assert.That(allModels, Is.Empty); + var idsRemaining = Redis.GetAllItemsFromSet(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + + [Test] + public void Can_As_DeleteAll_with_script() + { + Redis.StoreAll(testModels); + + var context = new ScriptContext { + ScriptLanguages = { ScriptLisp.Language }, + AllowScriptingOfAllTypes = true, + ScriptMethods = { + new ProtectedScripts() + }, + Args = { + ["redis"] = Redis + } + }.Init(); + + var type = typeof(TestModel).FullName; + context.EvaluateCode($"redis.call('DeleteAll<{type}>') |> return"); + context.EvaluateCode($"redis.call('As<{type}>').call('DeleteAll') |> return"); + context.RenderLisp($"(call redis \"DeleteAll<{type}>\")"); + context.RenderLisp($"(call (call redis \"As<{type}>\") \"DeleteAll\")"); + + var allModels = Redis.GetAll(); + Assert.That(allModels, Is.Empty); + var idsRemaining = Redis.GetAllItemsFromSet(TestModelIdsSetKey); + Assert.That(idsRemaining, Is.Empty); + } + [Test] public void Can_DeleteByIds() { diff --git a/tests/ServiceStack.Redis.Tests/RedisBatchTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisBatchTests.Async.cs new file mode 100644 index 00000000..3a3e1e82 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisBatchTests.Async.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisBatchTestsAsync + : RedisClientTestsBaseAsync + { + public class Message + { + public long Id { get; set; } + public string Key { get; set; } + public string Value { get; set; } + public string Description { get; set; } + } + + [Test] + public async Task Store_batch_items_in_List() + { + var redisMessages = RedisAsync.As(); + const int batchSize = 500; + var nextIds = await redisMessages.GetNextSequenceAsync(batchSize); + + var msgBatch = batchSize.Times(i => + new Message + { + Id = nextIds - (batchSize - i) + 1, + Key = i.ToString(), + Value = Guid.NewGuid().ToString(), + Description = "Description" + }); + + await redisMessages.Lists["listName"].AddRangeAsync(msgBatch); + + var msgs = await redisMessages.Lists["listName"].GetAllAsync(); + Assert.That(msgs.Count, Is.EqualTo(batchSize)); + + Assert.That(msgs.First().Id, Is.EqualTo(1)); + Assert.That(msgs.Last().Id, Is.EqualTo(500)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisCacheClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisCacheClientTests.Async.cs new file mode 100644 index 00000000..4ee85060 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisCacheClientTests.Async.cs @@ -0,0 +1,139 @@ +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Caching; +using ServiceStack.Common.Tests.Models; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + [Category("Async")] + public class RedisCacheClientTestsAsync + { + private ICacheClientAsync cacheClient; + + [SetUp] + public async Task OnBeforeEachTest() + { + if (cacheClient is object) + await cacheClient.DisposeAsync(); + + cacheClient = new RedisClient(TestConfig.SingleHost); + await cacheClient.FlushAllAsync(); + } + + [Test] + public async Task Get_non_existant_value_returns_null() + { + var model = ModelWithIdAndName.Create(1); + var cacheKey = model.CreateUrn(); + var existingModel = await cacheClient.GetAsync(cacheKey); + Assert.That(existingModel, Is.Null); + } + + [Test] + public async Task Get_non_existant_generic_value_returns_null() + { + var model = ModelWithIdAndName.Create(1); + var cacheKey = model.CreateUrn(); + var existingModel = await cacheClient.GetAsync(cacheKey); + Assert.That(existingModel, Is.Null); + } + + [Test] + public async Task Can_store_and_get_model() + { + var model = ModelWithIdAndName.Create(1); + var cacheKey = model.CreateUrn(); + await cacheClient.SetAsync(cacheKey, model); + + var existingModel = await cacheClient.GetAsync(cacheKey); + ModelWithIdAndName.AssertIsEqual(existingModel, model); + } + + [Test] + public async Task Can_store_null_model() + { + await cacheClient.SetAsync("test-key", null); + } + + [Test] + public async Task Can_Set_and_Get_key_with_all_byte_values() + { + const string key = "bytesKey"; + + var value = new byte[256]; + for (var i = 0; i < value.Length; i++) + { + value[i] = (byte)i; + } + + await cacheClient.SetAsync(key, value); + var resultValue = await cacheClient.GetAsync(key); + + Assert.That(resultValue, Is.EquivalentTo(value)); + } + + [Test] + public async Task Can_Replace_By_Pattern() + { + var model = ModelWithIdAndName.Create(1); + string modelKey = "model:" + model.CreateUrn(); + await cacheClient.AddAsync(modelKey, model); + + model = ModelWithIdAndName.Create(2); + string modelKey2 = "xxmodelxx:" + model.CreateUrn(); + await cacheClient.AddAsync(modelKey2, model); + + string s = "this is a string"; + await cacheClient.AddAsync("string1", s); + + var removable = (IRemoveByPatternAsync)cacheClient; + await removable.RemoveByPatternAsync("*model*"); + + ModelWithIdAndName result = await cacheClient.GetAsync(modelKey); + Assert.That(result, Is.Null); + + result = await cacheClient.GetAsync(modelKey2); + Assert.That(result, Is.Null); + + string result2 = await cacheClient.GetAsync("string1"); + Assert.That(result2, Is.EqualTo(s)); + + await removable.RemoveByPatternAsync("string*"); + + result2 = await cacheClient.GetAsync("string1"); + Assert.That(result2, Is.Null); + } + + [Test] + public async Task Can_GetTimeToLive() + { + var model = ModelWithIdAndName.Create(1); + string key = "model:" + model.CreateUrn(); + await cacheClient.AddAsync(key, model); + + var ttl = await cacheClient.GetTimeToLiveAsync(key); + Assert.That(ttl, Is.EqualTo(TimeSpan.MaxValue)); + + await cacheClient.SetAsync(key, model, expiresIn: TimeSpan.FromSeconds(10)); + ttl = await cacheClient.GetTimeToLiveAsync(key); + Assert.That(ttl.Value, Is.GreaterThanOrEqualTo(TimeSpan.FromSeconds(9))); + Assert.That(ttl.Value, Is.LessThanOrEqualTo(TimeSpan.FromSeconds(10))); + + await cacheClient.RemoveAsync(key); + ttl = await cacheClient.GetTimeToLiveAsync(key); + Assert.That(ttl, Is.Null); + } + + [Test] + public async Task Can_increment_and_reset_values() + { + await using var client = await new RedisManagerPool(TestConfig.SingleHost).GetCacheClientAsync(); + + Assert.That(await client.IncrementAsync("incr:counter", 10), Is.EqualTo(10)); + await client.SetAsync("incr:counter", 0); + Assert.That(await client.IncrementAsync("incr:counter", 10), Is.EqualTo(10)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.Async.cs new file mode 100644 index 00000000..8dfe12ec --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.Async.cs @@ -0,0 +1,108 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisClientConfigTestsAsync + : RedisClientTestsBaseAsync + { + [Ignore("Hurts MSOpenTech Redis Server")] + [Test] + public async Task Can_Set_and_Get_Config() + { + var orig = await RedisAsync.GetConfigAsync("maxmemory"); + var newMaxMemory = (long.Parse(orig) + 1).ToString(); + await RedisAsync.SetConfigAsync("maxmemory", newMaxMemory); + var current = await RedisAsync.GetConfigAsync("maxmemory"); + Assert.That(current, Is.EqualTo(newMaxMemory)); + } + + [Test] + public async Task Can_Rewrite_Redis_Config() + { + try + { + await RedisAsync.SaveConfigAsync(); + } + catch (RedisResponseException ex) + { + if (ex.Message.StartsWith("Rewriting config file: Permission denied") + || ex.Message.StartsWith("The server is running without a config file")) + return; + throw; + } + } + + [Test] + public async Task Can_Rewrite_Info_Stats() + { + await RedisAsync.ResetInfoStatsAsync(); + } + + [Test] + public async Task Can_set_and_Get_Client_Name() + { + var clientName = "CLIENT-" + Environment.TickCount; + await RedisAsync.SetClientAsync(clientName); + var client = await RedisAsync.GetClientAsync(); + + Assert.That(client, Is.EqualTo(clientName)); + } + + [Test] + public async Task Can_GetClientsInfo() + { + var clientList = await RedisAsync.GetClientsInfoAsync(); + clientList.PrintDump(); + } + + [Test] + public async Task Can_Kill_Client() + { + var clientList = await RedisAsync.GetClientsInfoAsync(); + var firstAddr = clientList.First()["addr"]; + await RedisAsync.KillClientAsync(firstAddr); + } + + [Test] + public async Task Can_Kill_Clients() + { + await RedisAsync.KillClientsAsync(fromAddress: "192.168.0.1:6379"); + await RedisAsync.KillClientsAsync(withId: "1"); + await RedisAsync.KillClientsAsync(ofType: RedisClientType.Normal); + await RedisAsync.KillClientsAsync(ofType: RedisClientType.PubSub); + await RedisAsync.KillClientsAsync(ofType: RedisClientType.Slave); + await RedisAsync.KillClientsAsync(skipMe: true); + await RedisAsync.KillClientsAsync(fromAddress: "192.168.0.1:6379", withId: "1", ofType: RedisClientType.Normal); + await RedisAsync.KillClientsAsync(skipMe: false); + } + + [Test] + public async Task Can_get_Role_Info() + { + var result = await NativeAsync.RoleAsync(); + result.PrintDump(); + Assert.That(result.Children[0].Text, Is.EqualTo("master")); + Assert.That(await RedisAsync.GetServerRoleAsync(), Is.EqualTo(RedisServerRole.Master)); + + //needs redis-server v3.0 + //var replica = new RedisClient("10.0.0.9:6380"); + //result = replica.Role(); + //result.PrintDump(); + } + + [Test] + public Task Can_PauseAllClients() + { + //needs redis-server v3.0 + //var replica = new RedisClient("10.0.0.9:6380"); + //replica.PauseAllClients(TimeSpan.FromSeconds(2)); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.cs b/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.cs index b79de58d..44b6b4f1 100644 --- a/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisClientConfigTests.cs @@ -29,7 +29,8 @@ public void Can_Rewrite_Redis_Config() } catch (RedisResponseException ex) { - if (ex.Message.StartsWith("Rewriting config file: Permission denied")) + if (ex.Message.StartsWith("Rewriting config file: Permission denied") + || ex.Message.StartsWith("The server is running without a config file")) return; throw; } @@ -88,8 +89,8 @@ public void Can_get_Role_Info() Assert.That(Redis.GetServerRole(), Is.EqualTo(RedisServerRole.Master)); //needs redis-server v3.0 - //var slave = new RedisClient("10.0.0.9:6380"); - //result = slave.Role(); + //var replica = new RedisClient("10.0.0.9:6380"); + //result = replica.Role(); //result.PrintDump(); } @@ -97,8 +98,8 @@ public void Can_get_Role_Info() public void Can_PauseAllClients() { //needs redis-server v3.0 - //var slave = new RedisClient("10.0.0.9:6380"); - //slave.PauseAllClients(TimeSpan.FromSeconds(2)); + //var replica = new RedisClient("10.0.0.9:6380"); + //replica.PauseAllClients(TimeSpan.FromSeconds(2)); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientEvalTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientEvalTests.Async.cs new file mode 100644 index 00000000..8a92f078 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientEvalTests.Async.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Common; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class RedisClientEvalTestsAsync : RedisClientTestsBaseAsync + { + public override void OnBeforeEachTest() + { + //base.OnBeforeEachTest(); + + //Run on local build server + RedisRaw = new RedisClient(TestConfig.SingleHost); + RedisRaw.FlushAll(); + } + + [Test] + public async Task Can_Eval_int() + { + var intVal = await RedisAsync.ExecLuaAsIntAsync("return 3141591", Array.Empty()); + Assert.That(intVal, Is.EqualTo(3141591)); + } + + [Test] + public async Task Can_EvalSha_int() + { + var luaBody = "return 3141591"; + await RedisAsync.ExecLuaAsIntAsync(luaBody, Array.Empty()); + var sha1 = await RedisAsync.CalculateSha1Async(luaBody); + var intVal = await RedisAsync.ExecLuaShaAsIntAsync(sha1, Array.Empty()); + Assert.That(intVal, Is.EqualTo(3141591)); + } + + [Test] + public async Task Can_Eval_int_with_args() + { + var intVal = await RedisAsync.ExecLuaAsIntAsync("return 3141591", new[] { "20", "30", "40" }); + Assert.That(intVal, Is.EqualTo(3141591)); + } + + [Test] + public async Task Can_Eval_int_with_keys_and_args() + { + var intVal = await RedisAsync.ExecLuaAsIntAsync("return KEYS[1] + ARGV[1]", new[] { "20" }, new[] { "30", "40" }); + Assert.That(intVal, Is.EqualTo(50)); + } + + [Test] + public async Task Can_Eval_int2() + { + var intVal = await RedisAsync.ExecLuaAsIntAsync("return ARGV[1] + ARGV[2]", new[] { "10", "20" }); + Assert.That(intVal, Is.EqualTo(30)); + } + + [Test] + public async Task Can_Eval_string() + { + var strVal = await RedisAsync.ExecLuaAsStringAsync(@"return 'abc'", new string[0]); + Assert.That(strVal, Is.EqualTo("abc")); + } + + [Test] + public async Task Can_Eval_HelloWorld_string() + { + var strVal = await RedisAsync.ExecLuaAsStringAsync(@"return 'Hello, ' .. ARGV[1] .. '!'", new[] { "Redis Lua" }); + Assert.That(strVal, Is.EqualTo("Hello, Redis Lua!")); + } + + [Test] + public async Task Can_Eval_string_with_args() + { + var strVal = await RedisAsync.ExecLuaAsStringAsync(@"return 'abc'", new[] { "at", "dot", "com" }); + Assert.That(strVal, Is.EqualTo("abc")); + } + + [Test] + public async Task Can_Eval_string_with_keys_an_args() + { + var strVal = await RedisAsync.ExecLuaAsStringAsync(@"return KEYS[1] .. ARGV[1]", new[] { "at" }, new[] { "dot", "com" }); + Assert.That(strVal, Is.EqualTo("atdot")); + } + + [Test] + public async Task Can_Eval_multidata_with_args() + { + var strVals = await RedisAsync.ExecLuaAsListAsync(@"return {ARGV[1],ARGV[2],ARGV[3]}", new[] { "at", "dot", "com" }); + Assert.That(strVals, Is.EquivalentTo(new List { "at", "dot", "com" })); + } + + [Test] + public async Task Can_Eval_multidata_with_keys_and_args() + { + var strVals = await RedisAsync.ExecLuaAsListAsync(@"return {KEYS[1],ARGV[1],ARGV[2]}", new[] { "at" }, new[] { "dot", "com" }); + Assert.That(strVals, Is.EquivalentTo(new List { "at", "dot", "com" })); + } + + [Test] + public async Task Can_Load_and_Exec_script() + { + var luaBody = "return 'load script and exec'"; + var sha1 = await RedisAsync.LoadLuaScriptAsync(luaBody); + var result = await RedisAsync.ExecLuaShaAsStringAsync(sha1, new string[0]); + Assert.That(result, Is.EqualTo("load script and exec")); + } + + [Test] + public async Task Does_flush_all_scripts() + { + var luaBody = "return 'load script and exec'"; + var sha1 = await RedisAsync.LoadLuaScriptAsync(luaBody); + var result = await RedisAsync.ExecLuaShaAsStringAsync(sha1, new string[0]); + Assert.That(result, Is.EqualTo("load script and exec")); + + await RedisAsync.RemoveAllLuaScriptsAsync(); + + try + { + result = await RedisAsync.ExecLuaShaAsStringAsync(sha1, new string[0]); + Assert.Fail("script shouldn't exist"); + } + catch (RedisResponseException ex) + { + Assert.That(ex.Message, Does.Contain("NOSCRIPT")); + } + } + + [Test] + public async Task Can_detect_which_scripts_exist() + { + var sha1 = await RedisAsync.LoadLuaScriptAsync("return 'script1'"); + var sha2 = await RedisAsync.CalculateSha1Async("return 'script2'"); + var sha3 = await RedisAsync.LoadLuaScriptAsync("return 'script3'"); + + Assert.That(await RedisAsync.HasLuaScriptAsync(sha1)); + + var existsMap = await RedisAsync.WhichLuaScriptsExistsAsync(new[] { sha1, sha2, sha3 }); + Assert.That(existsMap[sha1]); + Assert.That(!existsMap[sha2]); + Assert.That(existsMap[sha3]); + } + + [Test] + public async Task Can_create_ZPop_with_lua() + { + var luaBody = @" + local val = redis.call('zrange', KEYS[1], 0, ARGV[1]-1) + if val then redis.call('zremrangebyrank', KEYS[1], 0, ARGV[1]-1) end + return val"; + + var i = 0; + var alphabet = 26.Times(c => ((char)('A' + c)).ToString()); + foreach (var x in alphabet) + { + await RedisAsync.AddItemToSortedSetAsync("zalphabet", x, i++); + } + + var letters = await RedisAsync.ExecLuaAsListAsync(luaBody, keys: new[] { "zalphabet" }, args: new[] { "3" }); + + letters.PrintDump(); + Assert.That(letters, Is.EquivalentTo(new[] { "A", "B", "C" })); + } + + [Test] + public async Task Can_create_ZRevPop_with_lua() + { + var luaBody = @" + local val = redis.call('zrange', KEYS[1], -ARGV[1], -1) + if val then redis.call('zremrangebyrank', KEYS[1], -ARGV[1], -1) end + return val"; + + var i = 0; + var alphabet = 26.Times(c => ((char)('A' + c)).ToString()); + foreach(var x in alphabet) + { + await RedisAsync.AddItemToSortedSetAsync("zalphabet", x, i++); + } + + var letters = await RedisAsync.ExecLuaAsListAsync(luaBody, keys: new[] { "zalphabet" }, args: new[] { "3" }); + + letters.PrintDump(); + Assert.That(letters, Is.EquivalentTo(new[] { "X", "Y", "Z" })); + } + + [Test] + public async Task Can_return_DaysOfWeek_as_list() + { + foreach(var x in Enum.GetNames(typeof(DayOfWeek)).ToList()) + { + await RedisAsync.AddItemToListAsync("DaysOfWeek", x); + } + (await RedisAsync.ExecLuaAsListAsync("return redis.call('LRANGE', 'DaysOfWeek', 0, -1)", new string[0])).PrintDump(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientHashTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientHashTests.Async.cs new file mode 100644 index 00000000..f95f9376 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientHashTests.Async.cs @@ -0,0 +1,351 @@ +using NUnit.Framework; +using ServiceStack.Text; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisClientHashTestsAsync + : RedisClientTestsBaseAsync + { + private const string HashId = "rchtesthash"; + + Dictionary stringMap; + Dictionary stringIntMap; + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + stringMap = new Dictionary { + {"one","a"}, {"two","b"}, {"three","c"}, {"four","d"} + }; + stringIntMap = new Dictionary { + {"one",1}, {"two",2}, {"three",3}, {"four",4} + }; + } + + public override void OnAfterEachTest() + { + CleanMask = HashId + "*"; + base.OnAfterEachTest(); + } + + [Test] + public async Task Can_SetItemInHash_and_GetAllFromHash() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_RemoveFromHash() + { + const string removeMember = "two"; + + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + await RedisAsync.RemoveEntryFromHashAsync(HashId, removeMember); + + stringMap.Remove(removeMember); + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_GetItemFromHash() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + var hashValue = await RedisAsync.GetValueFromHashAsync(HashId, "two"); + + Assert.That(hashValue, Is.EqualTo(stringMap["two"])); + } + + [Test] + public async Task Can_GetHashCount() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + var hashCount = await RedisAsync.GetHashCountAsync(HashId); + + Assert.That(hashCount, Is.EqualTo(stringMap.Count)); + } + + [Test] + public async Task Does_HashContainsKey() + { + const string existingMember = "two"; + const string nonExistingMember = "five"; + + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + Assert.That(await RedisAsync.HashContainsEntryAsync(HashId, existingMember), Is.True); + Assert.That(await RedisAsync.HashContainsEntryAsync(HashId, nonExistingMember), Is.False); + } + + [Test] + public async Task Can_GetHashKeys() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + var expectedKeys = stringMap.Map(x => x.Key); + + var hashKeys = await RedisAsync.GetHashKeysAsync(HashId); + + Assert.That(hashKeys, Is.EquivalentTo(expectedKeys)); + } + + [Test] + public async Task Can_GetHashValues() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + var expectedValues = stringMap.Map(x => x.Value); + + var hashValues = await RedisAsync.GetHashValuesAsync(HashId); + + Assert.That(hashValues, Is.EquivalentTo(expectedValues)); + } + + [Test] + public async Task Can_enumerate_small_IDictionary_Hash() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + var members = new List(); + await foreach (var item in RedisAsync.Hashes[HashId]) + { + Assert.That(stringMap.ContainsKey(item.Key), Is.True); + members.Add(item.Key); + } + Assert.That(members.Count, Is.EqualTo(stringMap.Count)); + } + + [Test] + public async Task Can_Add_to_IDictionary_Hash() + { + var hash = RedisAsync.Hashes[HashId]; + foreach (var x in stringMap) + { + await hash.AddAsync(x); + } + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_Clear_IDictionary_Hash() + { + var hash = RedisAsync.Hashes[HashId]; + foreach (var x in stringMap) + { + await hash.AddAsync(x); + } + + Assert.That(await hash.CountAsync(), Is.EqualTo(stringMap.Count)); + + await hash.ClearAsync(); + + Assert.That(await hash.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_IDictionary_Hash() + { + var hash = RedisAsync.Hashes[HashId]; + foreach (var x in stringMap) + { + await hash.AddAsync(x); + } + + Assert.That(await hash.ContainsKeyAsync("two"), Is.True); + Assert.That(await hash.ContainsKeyAsync("five"), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_IDictionary_Hash() + { + var hash = RedisAsync.Hashes[HashId]; + foreach (var x in stringMap) + { + await hash.AddAsync(x); + } + + stringMap.Remove("two"); + await hash.RemoveAsync("two"); + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + private static Dictionary ToStringMap(Dictionary stringIntMap) + { + var map = new Dictionary(); + foreach (var kvp in stringIntMap) + { + map[kvp.Key] = kvp.Value.ToString(); + } + return map; + } + + [Test] + public async Task Can_increment_Hash_field() + { + var hash = RedisAsync.Hashes[HashId]; + foreach (var x in stringIntMap) + { + await hash.AddAsync(x.Key, x.Value.ToString()); + } + + stringIntMap["two"] += 10; + await RedisAsync.IncrementValueInHashAsync(HashId, "two", 10); + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(ToStringMap(stringIntMap))); + } + + [Test] + public async Task Can_increment_Hash_field_beyond_32_bits() + { + await RedisAsync.SetEntryInHashAsync(HashId, "int", Int32.MaxValue.ToString()); + await RedisAsync.IncrementValueInHashAsync(HashId, "int", 1); + long actual = Int64.Parse(await RedisAsync.GetValueFromHashAsync(HashId, "int")); + long expected = Int32.MaxValue + 1L; + Assert.That(actual, Is.EqualTo(expected)); + } + + [Test] + public async Task Can_SetItemInHashIfNotExists() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + await RedisAsync.SetEntryInHashIfNotExistsAsync(HashId, "two", "did not change existing item"); + await RedisAsync.SetEntryInHashIfNotExistsAsync(HashId, "five", "changed non existing item"); + stringMap["five"] = "changed non existing item"; + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_SetRangeInHash() + { + var newStringMap = new Dictionary { + {"five","e"}, {"six","f"}, {"seven","g"} + }; + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + await RedisAsync.SetRangeInHashAsync(HashId, newStringMap); + + newStringMap.Each(x => stringMap.Add(x.Key, x.Value)); + + var members = await RedisAsync.GetAllEntriesFromHashAsync(HashId); + Assert.That(members, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_GetItemsFromHash() + { + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashId, x.Key, x.Value); + } + + var expectedValues = new List { stringMap["one"], stringMap["two"], null }; + var hashValues = await RedisAsync.GetValuesFromHashAsync(HashId, new[] { "one", "two", "not-exists" }); + + Assert.That(hashValues.EquivalentTo(expectedValues), Is.True); + } + [Test] + public async Task Can_hash_set() + { + var key = HashId + "key"; + var field = GetBytes("foo"); + var value = GetBytes("value"); + Assert.AreEqual(await NativeAsync.HDelAsync(key, field), 0); + Assert.AreEqual(await NativeAsync.HSetAsync(key, field, value), 1); + Assert.AreEqual(await NativeAsync.HDelAsync(key, field), 1); + } + + [Test] + public async Task Can_hash_multi_set_and_get() + { + const string Key = HashId + "multitest"; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + var fields = new Dictionary { { "field1", "1" }, { "field2", "2" }, { "field3", "3" } }; + + await RedisAsync.SetRangeInHashAsync(Key, fields); + var members = await RedisAsync.GetAllEntriesFromHashAsync(Key); + foreach (var member in members) + { + Assert.IsTrue(fields.ContainsKey(member.Key)); + Assert.AreEqual(fields[member.Key], member.Value); + } + } + + public class HashTest + { + public int Id { get; set; } + public string Name { get; set; } + } + + [Test] + public async Task Can_store_as_Hash() + { + var dto = new HashTest { Id = 1 }; + await RedisAsync.StoreAsHashAsync(dto); + + var storedHash = await RedisAsync.GetHashKeysAsync(dto.ToUrn()); + Assert.That(storedHash, Is.EquivalentTo(new[] { "Id" })); + + var hold = RedisClient.ConvertToHashFn; + RedisClient.ConvertToHashFn = o => + { + var map = new Dictionary(); + o.ToObjectDictionary().Each(x => map[x.Key] = (x.Value ?? "").ToJsv()); + return map; + }; + + await RedisAsync.StoreAsHashAsync(dto); + storedHash = await RedisAsync.GetHashKeysAsync(dto.ToUrn()); + Assert.That(storedHash, Is.EquivalentTo(new[] { "Id", "Name" })); + + RedisClient.ConvertToHashFn = hold; + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientListTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientListTests.Async.cs new file mode 100644 index 00000000..b68445a2 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientListTests.Async.cs @@ -0,0 +1,502 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using System.Linq; +using ServiceStack.Text; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisClientListTestsAsync + : RedisClientTestsBaseAsync + { + const string ListId = "rcl_testlist"; + const string ListId2 = "rcl_testlist2"; + private List storeMembers; + + public RedisClientListTestsAsync() + { + CleanMask = "rcl_testlist*"; + } + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + storeMembers = new List { "one", "two", "three", "four" }; + } + + private static void AssertAreEqual(List actualList, List expectedList) + { + Assert.That(actualList, Has.Count.EqualTo(expectedList.Count)); + var i = 0; + actualList.ForEach(x => Assert.That(x, Is.EqualTo(expectedList[i++]))); + } + + private static void AssertAreEqual(List actualList, Queue expectedList) + { + Assert.That(actualList, Has.Count.EqualTo(expectedList.Count)); + actualList.ForEach(x => Assert.That(x, Is.EqualTo(expectedList.Dequeue()))); + } + + [Test] + public async Task Can_PopAndPushItemBetweenLists() + { + await RedisAsync.AddItemToListAsync(ListId, "1"); + await RedisAsync.PopAndPushItemBetweenListsAsync(ListId, ListId2); + } + + [Test] + public async Task Can_BlockingPopAndPushItemBetweenLists() + { + await RedisAsync.AddItemToListAsync(ListId, "A"); + await RedisAsync.AddItemToListAsync(ListId, "B"); + var r = await RedisAsync.BlockingPopAndPushItemBetweenListsAsync(ListId, ListId2, new TimeSpan(0, 0, 1)); + + Assert.That(r, Is.EqualTo("B")); + } + + [Test] + public async Task Can_Timeout_BlockingPopAndPushItemBetweenLists() + { + var r = await RedisAsync.BlockingPopAndPushItemBetweenListsAsync(ListId, ListId2, new TimeSpan(0, 0, 1)); + Assert.That(r, Is.Null); + } + + [Test] + public async Task Can_AddToList_and_GetAllFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var members = await RedisAsync.GetAllItemsFromListAsync(ListId); + + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_AddRangeToList_and_GetAllFromList() + { + await RedisAsync.AddRangeToListAsync(ListId, storeMembers); + + var members = await RedisAsync.GetAllItemsFromListAsync(ListId); + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_PrependRangeToList_and_GetAllFromList() + { + await RedisAsync.PrependRangeToListAsync(ListId, storeMembers); + + var members = await RedisAsync.GetAllItemsFromListAsync(ListId); + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_GetListCount() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var listCount = await RedisAsync.GetListCountAsync(ListId); + + Assert.That(listCount, Is.EqualTo(storeMembers.Count)); + } + + [Test] + public async Task Can_GetItemFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var storeMember3 = storeMembers[2]; + var item3 = await RedisAsync.GetItemFromListAsync(ListId, 2); + + Assert.That(item3, Is.EqualTo(storeMember3)); + } + + [Test] + public async Task Can_SetItemInList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + storeMembers[2] = "five"; + await RedisAsync.SetItemInListAsync(ListId, 2, "five"); + + var members = await RedisAsync.GetAllItemsFromListAsync(ListId); + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_PopFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var item4 = await RedisAsync.PopItemFromListAsync(ListId); + + Assert.That(item4, Is.EqualTo("four")); + } + + [Test] + public async Task Can_EnqueueOnList() + { + var queue = new Queue(); + storeMembers.ForEach(queue.Enqueue); + foreach (var x in storeMembers) + { + await RedisAsync.EnqueueItemOnListAsync(ListId, x); + } + + while (queue.Count > 0) + { + var actual = await RedisAsync.DequeueItemFromListAsync(ListId); + Assert.That(actual, Is.EqualTo(queue.Dequeue())); + } + } + + [Test] + public async Task Can_DequeueFromList() + { + var queue = new Queue(); + storeMembers.ForEach(queue.Enqueue); + foreach (var x in storeMembers) + { + await RedisAsync.EnqueueItemOnListAsync(ListId, x); + } + + var item1 = await RedisAsync.DequeueItemFromListAsync(ListId); + + Assert.That(item1, Is.EqualTo(queue.Dequeue())); + } + + [Test] + public async Task PopAndPushSameAsDequeue() + { + var queue = new Queue(); + storeMembers.ForEach(queue.Enqueue); + foreach (var x in storeMembers) + { + await RedisAsync.EnqueueItemOnListAsync(ListId, x); + } + + var item1 = await RedisAsync.PopAndPushItemBetweenListsAsync(ListId, ListId2); + Assert.That(item1, Is.EqualTo(queue.Dequeue())); + } + + [Test] + public async Task Can_BlockingDequeueFromList() + { + var queue = new Queue(); + storeMembers.ForEach(queue.Enqueue); + foreach (var x in storeMembers) + { + await RedisAsync.EnqueueItemOnListAsync(ListId, x); + } + + var item1 = await RedisAsync.BlockingDequeueItemFromListAsync(ListId, null); + + Assert.That(item1, Is.EqualTo(queue.Dequeue())); + } + + [Test] + public async Task BlockingDequeueFromList_Can_TimeOut() + { + var item1 = await RedisAsync.BlockingDequeueItemFromListAsync(ListId, TimeSpan.FromSeconds(1)); + Assert.That(item1, Is.Null); + } + + [Test] + public async Task Can_PushToList() + { + var stack = new Stack(); + storeMembers.ForEach(stack.Push); + foreach (var x in storeMembers) + { + await RedisAsync.PushItemToListAsync(ListId, x); + } + + while (stack.Count > 0) + { + var actual = await RedisAsync.PopItemFromListAsync(ListId); + Assert.That(actual, Is.EqualTo(stack.Pop())); + } + } + + [Test] + public async Task Can_BlockingPopFromList() + { + var stack = new Stack(); + storeMembers.ForEach(stack.Push); + foreach (var x in storeMembers) + { + await RedisAsync.PushItemToListAsync(ListId, x); + } + + var item1 = await RedisAsync.BlockingPopItemFromListAsync(ListId, null); + + Assert.That(item1, Is.EqualTo(stack.Pop())); + } + + [Test] + public async Task BlockingPopFromList_Can_TimeOut() + { + var item1 = await RedisAsync.BlockingPopItemFromListAsync(ListId, TimeSpan.FromSeconds(1)); + Assert.That(item1, Is.Null); + } + + [Test] + public async Task Can_RemoveStartFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var item1 = await RedisAsync.RemoveStartFromListAsync(ListId); + + Assert.That(item1, Is.EqualTo(storeMembers.First())); + } + + [Test] + public async Task Can_RemoveEndFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var item1 = await RedisAsync.RemoveEndFromListAsync(ListId); + + Assert.That(item1, Is.EqualTo(storeMembers.Last())); + } + + [Test] + public async Task Can_BlockingRemoveStartFromList() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var item1 = await RedisAsync.BlockingRemoveStartFromListAsync(ListId, null); + + Assert.That(item1, Is.EqualTo(storeMembers.First())); + } + + [Test] + public async Task Can_MoveBetweenLists() + { + var list1Members = new List { "one", "two", "three", "four" }; + var list2Members = new List { "five", "six", "seven" }; + const string item4 = "four"; + + foreach (var x in list1Members) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + foreach (var x in list2Members) + { + await RedisAsync.AddItemToListAsync(ListId2, x); + } + + list1Members.Remove(item4); + list2Members.Insert(0, item4); + await RedisAsync.PopAndPushItemBetweenListsAsync(ListId, ListId2); + + var readList1 = await RedisAsync.GetAllItemsFromListAsync(ListId); + var readList2 = await RedisAsync.GetAllItemsFromListAsync(ListId2); + + AssertAreEqual(readList1, list1Members); + AssertAreEqual(readList2, list2Members); + } + + + [Test] + public async Task Can_enumerate_small_list() + { + foreach (var x in storeMembers) + { + await RedisAsync.AddItemToListAsync(ListId, x); + } + + var readMembers = new List(); + await foreach (var item in RedisAsync.Lists[ListId]) + { + readMembers.Add(item); + } + AssertAreEqual(readMembers, storeMembers); + } + + [Test] + public async Task Can_enumerate_large_list() + { + if (TestConfig.IgnoreLongTests) return; + + const int listSize = 2500; + + storeMembers = new List(); + for (int x = 0; x < listSize; x++) + { + await RedisAsync.AddItemToListAsync(ListId, x.ToString()); + storeMembers.Add(x.ToString()); + } + + var members = new List(); + await foreach (var item in RedisAsync.Lists[ListId]) + { + members.Add(item); + } + members.Sort((x, y) => int.Parse(x).CompareTo(int.Parse(y))); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Add_to_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + var members = await ToListAsync(list); + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_Clear_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + Assert.That(await list.CountAsync(), Is.EqualTo(storeMembers.Count)); + + await list.ClearAsync(); + + Assert.That(await list.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + Assert.That(await list.ContainsAsync("two"), Is.True); + Assert.That(await list.ContainsAsync("five"), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + storeMembers.Remove("two"); + await list.RemoveAsync("two"); + + var members = await ToListAsync(list); + + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_RemoveAt_value_from_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + storeMembers.RemoveAt(2); + await list.RemoveAtAsync(2); + + var members = await ToListAsync(list); + + AssertAreEqual(members, storeMembers); + } + + [Test] + public async Task Can_get_default_index_from_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + for (var i = 0; i < storeMembers.Count; i++) + { + Assert.That(await list.ElementAtAsync(i), Is.EqualTo(storeMembers[i])); + } + } + + [Test] + public async Task Can_test_for_IndexOf_in_IList() + { + var list = RedisAsync.Lists[ListId]; + foreach (var x in storeMembers) + { + await list.AddAsync(x); + } + + foreach (var item in storeMembers) + { + Assert.That(await list.IndexOfAsync(item), Is.EqualTo(storeMembers.IndexOf(item))); + } + } + + [Test] + public async Task Can_AddRangeToList_and_GetSortedItems() + { + await RedisAsync.PrependRangeToListAsync(ListId, storeMembers); + + var members = await RedisAsync.GetSortedItemsFromListAsync(ListId, new SortOptions { SortAlpha = true, SortDesc = true, Skip = 1, Take = 2 }); + AssertAreEqual(members, storeMembers.OrderByDescending(s => s).Skip(1).Take(2).ToList()); + } + + public class Test + { + public string A { get; set; } + } + + [Test] + public async Task RemoveAll_removes_all_items_from_Named_List() + { + var redis = RedisAsync.As(); + + var clientesRepo = redis.Lists["repo:Client:Test"]; + + Assert.IsTrue(await clientesRepo.CountAsync() == 0, "Count 1 = " + await clientesRepo.CountAsync()); + await clientesRepo.AddAsync(new Test() { A = "Test" }); + Assert.IsTrue(await clientesRepo.CountAsync() == 1, "Count 2 = " + await clientesRepo.CountAsync()); + await clientesRepo.RemoveAllAsync(); + Assert.IsTrue(await clientesRepo.CountAsync() == 0, "Count 3 = " + await clientesRepo.CountAsync()); + } + + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientSetTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientSetTests.Async.cs new file mode 100644 index 00000000..ad23cc5a --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientSetTests.Async.cs @@ -0,0 +1,335 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisClientSetTestsAsync + : RedisClientTestsBaseAsync + { + private const string SetIdSuffix = "testset"; + private List storeMembers; + + private string SetId + { + get + { + return this.PrefixedKey(SetIdSuffix); + } + } + + [SetUp] + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + RedisRaw.NamespacePrefix = "RedisClientSetTests"; + storeMembers = new List { "one", "two", "three", "four" }; + } + + [Test] + public async Task Can_AddToSet_and_GetAllFromSet() + { + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + var members = await RedisAsync.GetAllItemsFromSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_AddRangeToSet_and_GetAllFromSet() + { + await RedisAsync.AddRangeToSetAsync(SetId, storeMembers); + + var members = await RedisAsync.GetAllItemsFromSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_RemoveFromSet() + { + const string removeMember = "two"; + + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + await RedisAsync.RemoveItemFromSetAsync(SetId, removeMember); + + storeMembers.Remove(removeMember); + + var members = await RedisAsync.GetAllItemsFromSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_PopFromSet() + { + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + var member = await RedisAsync.PopItemFromSetAsync(SetId); + + Assert.That(storeMembers.Contains(member), Is.True); + } + + [Test] + public async Task Can_MoveBetweenSets() + { + string fromSetId = PrefixedKey("testmovefromset"); + string toSetId = PrefixedKey("testmovetoset"); + const string moveMember = "four"; + var fromSetIdMembers = new List { "one", "two", "three", "four" }; + var toSetIdMembers = new List { "five", "six", "seven" }; + + await fromSetIdMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(fromSetId, x)); + await toSetIdMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(toSetId, x)); + + await RedisAsync.MoveBetweenSetsAsync(fromSetId, toSetId, moveMember); + + fromSetIdMembers.Remove(moveMember); + toSetIdMembers.Add(moveMember); + + var readFromSetId = await RedisAsync.GetAllItemsFromSetAsync(fromSetId); + var readToSetId = await RedisAsync.GetAllItemsFromSetAsync(toSetId); + + Assert.That(readFromSetId, Is.EquivalentTo(fromSetIdMembers)); + Assert.That(readToSetId, Is.EquivalentTo(toSetIdMembers)); + } + + [Test] + public async Task Can_GetSetCount() + { + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + var setCount = await RedisAsync.GetSetCountAsync(SetId); + + Assert.That(setCount, Is.EqualTo(storeMembers.Count)); + } + + [Test] + public async Task Does_SetContainsValue() + { + const string existingMember = "two"; + const string nonExistingMember = "five"; + + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + Assert.That(await RedisAsync.SetContainsItemAsync(SetId, existingMember), Is.True); + Assert.That(await RedisAsync.SetContainsItemAsync(SetId, nonExistingMember), Is.False); + } + + [Test] + public async Task Can_IntersectBetweenSets() + { + string set1Name = PrefixedKey("testintersectset1"); + string set2Name = PrefixedKey("testintersectset2"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + + var intersectingMembers = await RedisAsync.GetIntersectFromSetsAsync(new[] { set1Name, set2Name }); + + Assert.That(intersectingMembers, Is.EquivalentTo(new List { "four", "five" })); + } + + [Test] + public async Task Can_Store_IntersectBetweenSets() + { + string set1Name = PrefixedKey("testintersectset1"); + string set2Name = PrefixedKey("testintersectset2"); + string storeSetName = PrefixedKey("testintersectsetstore"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + + await RedisAsync.StoreIntersectFromSetsAsync(storeSetName, new[] { set1Name, set2Name }); + + var intersectingMembers = await RedisAsync.GetAllItemsFromSetAsync(storeSetName); + + Assert.That(intersectingMembers, Is.EquivalentTo(new List { "four", "five" })); + } + + [Test] + public async Task Can_UnionBetweenSets() + { + string set1Name = PrefixedKey("testunionset1"); + string set2Name = PrefixedKey("testunionset2"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + + var unionMembers = await RedisAsync.GetUnionFromSetsAsync(new[] { set1Name, set2Name }); + + Assert.That(unionMembers, Is.EquivalentTo( + new List { "one", "two", "three", "four", "five", "six", "seven" })); + } + + [Test] + public async Task Can_Store_UnionBetweenSets() + { + string set1Name = PrefixedKey("testunionset1"); + string set2Name = PrefixedKey("testunionset2"); + string storeSetName = PrefixedKey("testunionsetstore"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + + await RedisAsync.StoreUnionFromSetsAsync(storeSetName, new[] { set1Name, set2Name }); + + var unionMembers = await RedisAsync.GetAllItemsFromSetAsync(storeSetName); + + Assert.That(unionMembers, Is.EquivalentTo( + new List { "one", "two", "three", "four", "five", "six", "seven" })); + } + + [Test] + public async Task Can_DiffBetweenSets() + { + string set1Name = PrefixedKey("testdiffset1"); + string set2Name = PrefixedKey("testdiffset2"); + string set3Name = PrefixedKey("testdiffset3"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + var set3Members = new List { "one", "five", "seven", "eleven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + await set3Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set3Name, x)); + + var diffMembers = await RedisAsync.GetDifferencesFromSetAsync(set1Name, new[] { set2Name, set3Name }); + + Assert.That(diffMembers, Is.EquivalentTo( + new List { "two", "three" })); + } + + [Test] + public async Task Can_Store_DiffBetweenSets() + { + string set1Name = PrefixedKey("testdiffset1"); + string set2Name = PrefixedKey("testdiffset2"); + string set3Name = PrefixedKey("testdiffset3"); + string storeSetName = PrefixedKey("testdiffsetstore"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + var set3Members = new List { "one", "five", "seven", "eleven" }; + + await set1Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set1Name, x)); + await set2Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set2Name, x)); + await set3Members.ForEachAsync(x => RedisAsync.AddItemToSetAsync(set3Name, x)); + + await RedisAsync.StoreDifferencesFromSetAsync(storeSetName, set1Name, new[] { set2Name, set3Name }); + + var diffMembers = await RedisAsync.GetAllItemsFromSetAsync(storeSetName); + + Assert.That(diffMembers, Is.EquivalentTo( + new List { "two", "three" })); + } + + [Test] + public async Task Can_GetRandomEntryFromSet() + { + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + var randomEntry = await RedisAsync.GetRandomItemFromSetAsync(SetId); + + Assert.That(storeMembers.Contains(randomEntry), Is.True); + } + + + [Test] + public async Task Can_enumerate_small_ICollection_Set() + { + await storeMembers.ForEachAsync(x => RedisAsync.AddItemToSetAsync(SetId, x)); + + var members = new List(); + await foreach (var item in RedisAsync.Sets[SetId]) + { + members.Add(item); + } + members.Sort(); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_enumerate_large_ICollection_Set() + { + if (TestConfig.IgnoreLongTests) return; + + const int setSize = 2500; + + storeMembers = new List(); + await setSize.TimesAsync(async x => + { + await RedisAsync.AddItemToSetAsync(SetId, x.ToString()); + storeMembers.Add(x.ToString()); + }); + + var members = new List(); + await foreach (var item in RedisAsync.Sets[SetId]) + { + members.Add(item); + } + members.Sort((x, y) => int.Parse(x).CompareTo(int.Parse(y))); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Add_to_ICollection_Set() + { + var list = RedisAsync.Sets[SetId]; + await storeMembers.ForEachAsync(x => list.AddAsync(x)); + + var members = await list.ToListAsync(); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Clear_ICollection_Set() + { + var list = RedisAsync.Sets[SetId]; + await storeMembers.ForEachAsync(x => list.AddAsync(x)); + + Assert.That(await list.CountAsync(), Is.EqualTo(storeMembers.Count)); + + await list.ClearAsync(); + + Assert.That(await list.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_ICollection_Set() + { + var list = RedisAsync.Sets[SetId]; + await storeMembers.ForEachAsync(x => list.AddAsync(x)); + + Assert.That(await list.ContainsAsync("two"), Is.True); + Assert.That(await list.ContainsAsync("five"), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_ICollection_Set() + { + var list = RedisAsync.Sets[SetId]; + await storeMembers.ForEachAsync(x => list.AddAsync(x)); + + storeMembers.Remove("two"); + await list.RemoveAsync("two"); + + var members = await list.ToListAsync(); + + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.Async.cs new file mode 100644 index 00000000..ae31d2d3 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.Async.cs @@ -0,0 +1,454 @@ +using NUnit.Framework; +using ServiceStack.Text; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class RedisClientSortedSetTestsAsync + : RedisClientTestsBaseAsync + { + private const string SetIdSuffix = "testzset"; + private List storeMembers; + + private string SetId + { + get + { + return PrefixedKey(SetIdSuffix); + } + } + + Dictionary stringDoubleMap; + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + RedisRaw.NamespacePrefix = "RedisClientSortedSetTests"; + storeMembers = new List { "one", "two", "three", "four" }; + + stringDoubleMap = new Dictionary { + {"one",1}, {"two",2}, {"three",3}, {"four",4} + }; + } + + [Test] + public async Task Can_AddItemToSortedSet_and_GetAllFromSet() + { + var i = 0; + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x, i++)); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + Assert.That(members.EquivalentTo(storeMembers), Is.True); + } + + [Test] + public async Task Can_AddRangeToSortedSet_and_GetAllFromSet() + { + var success = await RedisAsync.AddRangeToSortedSetAsync(SetId, storeMembers, 1); + Assert.That(success, Is.True); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task AddToSet_without_score_adds_an_implicit_lexical_order_score() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + + storeMembers.Sort((x, y) => x.CompareTo(y)); + Assert.That(members.EquivalentTo(storeMembers), Is.True); + } + + [Test] + public async Task AddToSet_with_same_score_is_still_returned_in_lexical_order_score() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x, 1)); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + + storeMembers.Sort((x, y) => x.CompareTo(y)); + Assert.That(members.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_RemoveFromSet() + { + const string removeMember = "two"; + + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + await RedisAsync.RemoveItemFromSortedSetAsync(SetId, removeMember); + + storeMembers.Remove(removeMember); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_RemoveItemsFromSortedSet() + { + var removeMembers = new[] { "two" , "four", "six" }; + + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + var removeCount = await RedisAsync.RemoveItemsFromSortedSetAsync(SetId, removeMembers.ToList()); + Assert.That(removeCount, Is.EqualTo(2)); + + removeMembers.Each(x => storeMembers.Remove(x)); + + var members = await RedisAsync.GetAllItemsFromSortedSetAsync(SetId); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_PopFromSet() + { + var i = 0; + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x, i++)); + + var member = await RedisAsync.PopItemWithHighestScoreFromSortedSetAsync(SetId); + + Assert.That(member, Is.EqualTo("four")); + } + + [Test] + public async Task Can_GetSetCount() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + var setCount = await RedisAsync.GetSortedSetCountAsync(SetId); + + Assert.That(setCount, Is.EqualTo(storeMembers.Count)); + } + + [Test] + public async Task Can_GetSetCountByScores() + { + var scores = new List(); + + await storeMembers.ForEachAsync(async x => + { + await RedisAsync.AddItemToSortedSetAsync(SetId, x); + scores.Add(RedisClient.GetLexicalScore(x)); + }); + + Assert.That(await RedisAsync.GetSortedSetCountAsync(SetId, scores.Min(), scores.Max()), Is.EqualTo(storeMembers.Count())); + Assert.That(await RedisAsync.GetSortedSetCountAsync(SetId, scores.Min(), scores.Min()), Is.EqualTo(1)); + } + + [Test] + public async Task Does_SortedSetContainsValue() + { + const string existingMember = "two"; + const string nonExistingMember = "five"; + + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + Assert.That(await RedisAsync.SortedSetContainsItemAsync(SetId, existingMember), Is.True); + Assert.That(await RedisAsync.SortedSetContainsItemAsync(SetId, nonExistingMember), Is.False); + } + + [Test] + public async Task Can_GetItemIndexInSortedSet_in_Asc_and_Desc() + { + var i = 10; + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x, i++)); + + Assert.That(await RedisAsync.GetItemIndexInSortedSetAsync(SetId, "one"), Is.EqualTo(0)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetAsync(SetId, "two"), Is.EqualTo(1)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetAsync(SetId, "three"), Is.EqualTo(2)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetAsync(SetId, "four"), Is.EqualTo(3)); + + Assert.That(await RedisAsync.GetItemIndexInSortedSetDescAsync(SetId, "one"), Is.EqualTo(3)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetDescAsync(SetId, "two"), Is.EqualTo(2)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetDescAsync(SetId, "three"), Is.EqualTo(1)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetDescAsync(SetId, "four"), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Store_IntersectBetweenSets() + { + string set1Name = PrefixedKey("testintersectset1"); + string set2Name = PrefixedKey("testintersectset2"); + string storeSetName = PrefixedKey("testintersectsetstore"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(set1Name, x)); + await set2Members.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(set2Name, x)); + + await RedisAsync.StoreIntersectFromSortedSetsAsync(storeSetName, new[] { set1Name, set2Name }); + + var intersectingMembers = await RedisAsync.GetAllItemsFromSortedSetAsync(storeSetName); + + Assert.That(intersectingMembers, Is.EquivalentTo(new List { "four", "five" })); + } + + [Test] + public async Task Can_Store_UnionBetweenSets() + { + string set1Name = PrefixedKey("testunionset1"); + string set2Name = PrefixedKey("testunionset2"); + string storeSetName = PrefixedKey("testunionsetstore"); + var set1Members = new List { "one", "two", "three", "four", "five" }; + var set2Members = new List { "four", "five", "six", "seven" }; + + await set1Members.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(set1Name, x)); + await set2Members.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(set2Name, x)); + + await RedisAsync.StoreUnionFromSortedSetsAsync(storeSetName, new[] { set1Name, set2Name }); + + var unionMembers = await RedisAsync.GetAllItemsFromSortedSetAsync(storeSetName); + + Assert.That(unionMembers, Is.EquivalentTo( + new List { "one", "two", "three", "four", "five", "six", "seven" })); + } + + [Test] + public async Task Can_pop_items_with_lowest_and_highest_scores_from_sorted_set() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + storeMembers.Sort((x, y) => x.CompareTo(y)); + + var lowestScore = await RedisAsync.PopItemWithLowestScoreFromSortedSetAsync(SetId); + Assert.That(lowestScore, Is.EqualTo(storeMembers.First())); + + var highestScore = await RedisAsync.PopItemWithHighestScoreFromSortedSetAsync(SetId); + Assert.That(highestScore, Is.EqualTo(storeMembers[storeMembers.Count - 1])); + } + + [Test, Ignore("seems unstable?")] + public async Task Can_GetRangeFromSortedSetByLowestScore_from_sorted_set() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + storeMembers.Sort((x, y) => x.CompareTo(y)); + var memberRage = storeMembers.Where(x => + x.CompareTo("four") >= 0 && x.CompareTo("three") <= 0).ToList(); + + var range = await RedisAsync.GetRangeFromSortedSetByLowestScoreAsync(SetId, "four", "three"); + Assert.That(range.EquivalentTo(memberRage)); + } + + [Test] + public async Task Can_IncrementItemInSortedSet() + { + await stringDoubleMap.ForEachAsync(async (k,v) => await RedisAsync.AddItemToSortedSetAsync(SetId, k, v)); + + var currentScore = await RedisAsync.IncrementItemInSortedSetAsync(SetId, "one", 3); + stringDoubleMap["one"] = stringDoubleMap["one"] + 3; + Assert.That(currentScore, Is.EqualTo(stringDoubleMap["one"])); + + currentScore = await RedisAsync.IncrementItemInSortedSetAsync(SetId, "four", -3); + stringDoubleMap["four"] = stringDoubleMap["four"] - 3; + Assert.That(currentScore, Is.EqualTo(stringDoubleMap["four"])); + + var map = await RedisAsync.GetAllWithScoresFromSortedSetAsync(SetId); + + Assert.That(stringDoubleMap.UnorderedEquivalentTo(map)); + } + + [Test] + public async Task Can_WorkInSortedSetUnderDifferentCulture() + { +#if NETCORE + var prevCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo("ru-RU"); +#else + var prevCulture = Thread.CurrentThread.CurrentCulture; + Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture("ru-RU"); +#endif + await RedisAsync.AddItemToSortedSetAsync(SetId, "key", 123.22); + + var map = await RedisAsync.GetAllWithScoresFromSortedSetAsync(SetId); + + Assert.AreEqual(123.22, map["key"]); + +#if NETCORE + CultureInfo.CurrentCulture = prevCulture; +#else + Thread.CurrentThread.CurrentCulture = prevCulture; +#endif + } + + + [Ignore("Not implemented yet")] + [Test] + public async Task Can_GetRangeFromSortedSetByHighestScore_from_sorted_set() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + storeMembers.Sort((x, y) => y.CompareTo(x)); + var memberRage = storeMembers.Where(x => + x.CompareTo("four") >= 0 && x.CompareTo("three") <= 0).ToList(); + + var range = await RedisAsync.GetRangeFromSortedSetByHighestScoreAsync(SetId, "four", "three"); + Assert.That(range.EquivalentTo(memberRage)); + } + + [Test] + public async Task Can_get_index_and_score_from_SortedSet() + { + storeMembers = new List { "a", "b", "c", "d" }; + const double initialScore = 10d; + var i = initialScore; + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x, i++)); + + Assert.That(await RedisAsync.GetItemIndexInSortedSetAsync(SetId, "a"), Is.EqualTo(0)); + Assert.That(await RedisAsync.GetItemIndexInSortedSetDescAsync(SetId, "a"), Is.EqualTo(storeMembers.Count - 1)); + + Assert.That(await RedisAsync.GetItemScoreInSortedSetAsync(SetId, "a"), Is.EqualTo(initialScore)); + Assert.That(await RedisAsync.GetItemScoreInSortedSetAsync(SetId, "d"), Is.EqualTo(initialScore + storeMembers.Count - 1)); + } + + [Test] + public async Task Can_enumerate_small_ICollection_Set() + { + await storeMembers.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(SetId, x)); + + var members = new List(); + await foreach (var item in RedisAsync.SortedSets[SetId]) + { + members.Add(item); + } + members.Sort(); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_enumerate_large_ICollection_Set() + { + if (TestConfig.IgnoreLongTests) return; + + const int setSize = 2500; + + storeMembers = new List(); + await setSize.TimesAsync(async x => + { + await RedisAsync.AddItemToSortedSetAsync(SetId, x.ToString()); + storeMembers.Add(x.ToString()); + }); + + var members = new List(); + await foreach (var item in RedisAsync.SortedSets[SetId]) + { + members.Add(item); + } + members.Sort((x, y) => int.Parse(x).CompareTo(int.Parse(y))); + Assert.That(members.Count, Is.EqualTo(storeMembers.Count)); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Add_to_ICollection_Set() + { + var list = RedisAsync.SortedSets[SetId]; + await storeMembers.ForEachAsync(async x => await list.AddAsync(x)); + + var members = await list.ToListAsync(); + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Can_Clear_ICollection_Set() + { + var list = RedisAsync.SortedSets[SetId]; + await storeMembers.ForEachAsync(async x => await list.AddAsync(x)); + + Assert.That(await list.CountAsync(), Is.EqualTo(storeMembers.Count)); + + await list.ClearAsync(); + + Assert.That(await list.CountAsync(), Is.EqualTo(0)); + } + + [Test] + public async Task Can_Test_Contains_in_ICollection_Set() + { + var list = RedisAsync.SortedSets[SetId]; + await storeMembers.ForEachAsync(async x => await list.AddAsync(x)); + + Assert.That(await list.ContainsAsync("two"), Is.True); + Assert.That(await list.ContainsAsync("five"), Is.False); + } + + [Test] + public async Task Can_Remove_value_from_ICollection_Set() + { + var list = RedisAsync.SortedSets[SetId]; + await storeMembers.ForEachAsync(async x => await list.AddAsync(x)); + + storeMembers.Remove("two"); + await list.RemoveAsync("two"); + + var members = await list.ToListAsync(); + + Assert.That(members, Is.EquivalentTo(storeMembers)); + } + + [Test] + public async Task Score_from_non_existent_item_returns_NaN() + { + var score = await RedisAsync.GetItemScoreInSortedSetAsync("nonexistentset", "value"); + + Assert.That(score, Is.EqualTo(Double.NaN)); + } + + [Test] + public async Task Can_add_large_score_to_sortedset() + { + await RedisAsync.AddItemToSortedSetAsync(SetId, "value", 12345678901234567890d); + var score = await RedisAsync.GetItemScoreInSortedSetAsync(SetId, "value"); + + Assert.That(score, Is.EqualTo(12345678901234567890d)); + } + + public class Article + { + public int Id { get; set; } + public string Title { get; set; } + public DateTime ModifiedDate { get; set; } + } + + [Test] + public async Task Can_use_SortedIndex_to_store_articles_by_Date() + { + var redisArticles = RedisAsync.As
    (); + + var articles = new[] + { + new Article { Id = 1, Title = "Article 1", ModifiedDate = new DateTime(2015, 01, 02) }, + new Article { Id = 2, Title = "Article 2", ModifiedDate = new DateTime(2015, 01, 01) }, + new Article { Id = 3, Title = "Article 3", ModifiedDate = new DateTime(2015, 01, 03) }, + }; + + await redisArticles.StoreAllAsync(articles); + + const string LatestArticlesSet = "urn:Article:modified"; + + foreach (var article in articles) + { + await RedisAsync.AddItemToSortedSetAsync(LatestArticlesSet, article.Id.ToString(), article.ModifiedDate.Ticks); + } + + var articleIds = await RedisAsync.GetAllItemsFromSortedSetDescAsync(LatestArticlesSet); + articleIds.PrintDump(); + + var latestArticles = await redisArticles.GetByIdsAsync(articleIds); + latestArticles.PrintDump(); + } + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.cs b/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.cs index 5f48d5bf..bfcb695d 100644 --- a/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisClientSortedSetTests.cs @@ -229,7 +229,7 @@ public void Can_pop_items_with_lowest_and_highest_scores_from_sorted_set() Assert.That(highestScore, Is.EqualTo(storeMembers[storeMembers.Count - 1])); } - [Test] + [Test, Ignore("seems unstable?")] public void Can_GetRangeFromSortedSetByLowestScore_from_sorted_set() { storeMembers.ForEach(x => Redis.AddItemToSortedSet(SetId, x)); diff --git a/tests/ServiceStack.Redis.Tests/RedisClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientTests.Async.cs new file mode 100644 index 00000000..b5b7aad3 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientTests.Async.cs @@ -0,0 +1,673 @@ +using NUnit.Framework; +using ServiceStack.Redis.Support.Locking; +using ServiceStack.Text; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class RedisClientTestsAsync + : RedisClientTestsBaseAsync + { + const string Value = "Value"; + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + RedisRaw.NamespacePrefix = nameof(RedisClientTestsAsync); + } + + [Test] + public async Task Can_Set_and_Get_string() + { + await RedisAsync.SetValueAsync("key", Value); + var valueBytes = await NativeAsync.GetAsync("key"); + var valueString = GetString(valueBytes); + await RedisAsync.RemoveAsync("key"); + + Assert.That(valueString, Is.EqualTo(Value)); + } + + [Test] + public async Task Can_Set_and_Get_key_with_space() + { + await RedisAsync.SetValueAsync("key with space", Value); + var valueBytes = await NativeAsync.GetAsync("key with space"); + var valueString = GetString(valueBytes); + await RedisAsync.RemoveAsync("key with space"); + + Assert.That(valueString, Is.EqualTo(Value)); + } + + [Test] + public async Task Can_Set_and_Get_key_with_spaces() + { + const string key = "key with spaces"; + + await RedisAsync.SetValueAsync(key, Value); + var valueBytes = await NativeAsync.GetAsync(key); + var valueString = GetString(valueBytes); + + Assert.That(valueString, Is.EqualTo(Value)); + } + + [Test] + public async Task Can_Set_and_Get_key_with_all_byte_values() + { + const string key = "bytesKey"; + + var value = new byte[256]; + for (var i = 0; i < value.Length; i++) + { + value[i] = (byte)i; + } + + await RedisAsync.SetAsync(key, value); + var resultValue = await NativeAsync.GetAsync(key); + + Assert.That(resultValue, Is.EquivalentTo(value)); + } + + [Test] + public async Task GetKeys_returns_matching_collection() + { + await RedisAsync.SetAsync("ss-tests:a1", "One"); + await RedisAsync.SetAsync("ss-tests:a2", "One"); + await RedisAsync.SetAsync("ss-tests:b3", "One"); + + var matchingKeys = await RedisAsync.SearchKeysAsync("ss-tests:a*"); + + Assert.That(matchingKeys.Count, Is.EqualTo(2)); + } + + [Test] + public async Task GetKeys_on_non_existent_keys_returns_empty_collection() + { + var matchingKeys = await RedisAsync.SearchKeysAsync("ss-tests:NOTEXISTS"); + + Assert.That(matchingKeys.Count, Is.EqualTo(0)); + } + + [Test] + public async Task Can_get_Types() + { + await RedisAsync.SetValueAsync("string", "string"); + await RedisAsync.AddItemToListAsync("list", "list"); + await RedisAsync.AddItemToSetAsync("set", "set"); + await RedisAsync.AddItemToSortedSetAsync("sortedset", "sortedset"); + await RedisAsync.SetEntryInHashAsync("hash", "key", "val"); + + Assert.That(await RedisAsync.GetEntryTypeAsync("nokey"), Is.EqualTo(RedisKeyType.None)); + Assert.That(await RedisAsync.GetEntryTypeAsync("string"), Is.EqualTo(RedisKeyType.String)); + Assert.That(await RedisAsync.GetEntryTypeAsync("list"), Is.EqualTo(RedisKeyType.List)); + Assert.That(await RedisAsync.GetEntryTypeAsync("set"), Is.EqualTo(RedisKeyType.Set)); + Assert.That(await RedisAsync.GetEntryTypeAsync("sortedset"), Is.EqualTo(RedisKeyType.SortedSet)); + Assert.That(await RedisAsync.GetEntryTypeAsync("hash"), Is.EqualTo(RedisKeyType.Hash)); + } + + [Test] + public async Task Can_delete_keys() + { + await RedisAsync.SetValueAsync("key", "val"); + + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + + await RedisAsync.RemoveAsync("key"); + + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + + var keysMap = new Dictionary(); + + 10.Times(i => keysMap.Add("key" + i, "val" + i)); + + await RedisAsync.SetAllAsync(keysMap); + + for (int i = 0; i < 10; i++) + Assert.That(await RedisAsync.ContainsKeyAsync("key" + i), Is.True); + + await RedisAsync.RemoveEntryAsync(keysMap.Keys.ToArray()); + + for (int i = 0; i < 10; i++) + Assert.That(await RedisAsync.ContainsKeyAsync("key" + i), Is.False); + } + + [Test] + public async Task Can_get_RandomKey() + { + await RedisAsync.SelectAsync(15); + var keysMap = new Dictionary(); + + 10.Times(i => keysMap.Add(RedisRaw.NamespacePrefix + "key" + i, "val" + i)); + + await RedisAsync.SetAllAsync(keysMap); + + var randKey = await RedisAsync.GetRandomKeyAsync(); + + Assert.That(keysMap.ContainsKey(randKey), Is.True); + } + + [Test] + public async Task Can_RenameKey() + { + await RedisAsync.SetValueAsync("oldkey", "val"); + await NativeAsync.RenameAsync("oldkey", "newkey"); + + Assert.That(await RedisAsync.ContainsKeyAsync("oldkey"), Is.False); + Assert.That(await RedisAsync.ContainsKeyAsync("newkey"), Is.True); + } + + [Test] + public async Task Can_Expire() + { + await RedisAsync.SetValueAsync("key", "val"); + await RedisAsync.ExpireEntryInAsync("key", TimeSpan.FromSeconds(1)); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + await Task.Delay(2000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Test] + public async Task Can_Expire_Ms() + { + await RedisAsync.SetValueAsync("key", "val"); + await RedisAsync.ExpireEntryInAsync("key", TimeSpan.FromMilliseconds(100)); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + await Task.Delay(500); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Ignore("Changes in system clock can break test")] + [Test] + public async Task Can_ExpireAt() + { + await RedisAsync.SetValueAsync("key", "val"); + + var unixNow = DateTime.Now.ToUnixTime(); + var in2Secs = unixNow + 2; + + await NativeAsync.ExpireAtAsync("key", in2Secs); + + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + await Task.Delay(3000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Test] + public async Task Can_GetTimeToLive() + { + await RedisAsync.SetValueAsync("key", "val"); + await RedisAsync.ExpireEntryInAsync("key", TimeSpan.FromSeconds(10)); + + var ttl = await RedisAsync.GetTimeToLiveAsync("key"); + Assert.That(ttl.Value.TotalSeconds, Is.GreaterThanOrEqualTo(9)); + await Task.Delay(1700); + + ttl = await RedisAsync.GetTimeToLiveAsync("key"); + Assert.That(ttl.Value.TotalSeconds, Is.LessThanOrEqualTo(9)); + } + + [Test] + public async Task Can_GetServerTime() + { + var now = await RedisAsync.GetServerTimeAsync(); + + now.Kind.PrintDump(); + now.ToString("D").Print(); + now.ToString("T").Print(); + + "UtcNow".Print(); + DateTime.UtcNow.ToString("D").Print(); + DateTime.UtcNow.ToString("T").Print(); + + Assert.That(now.Date, Is.EqualTo(DateTime.UtcNow.Date)); + } + + [Test] + public async Task Can_Ping() + { + Assert.That(await RedisAsync.PingAsync(), Is.True); + } + + [Test] + public async Task Can_Echo() + { + Assert.That(await RedisAsync.EchoAsync("Hello"), Is.EqualTo("Hello")); + } + + [Test] + public async Task Can_SlaveOfNoOne() + { + await NativeAsync.SlaveOfNoOneAsync(); + } + + [Test] + public async Task Can_Save() + { + try + { + await NativeAsync.SaveAsync(); + } + catch (RedisResponseException e) + { + // if exception has that message then it still proves that BgSave works as expected. + if (e.Message.StartsWith("Can't BGSAVE while AOF log rewriting is in progress") + || e.Message.StartsWith("An AOF log rewriting in progress: can't BGSAVE right now") + || e.Message.StartsWith("Background save already in progress") + || e.Message.StartsWith("Another child process is active (AOF?): can't BGSAVE right now")) + return; + + throw; + } + } + + [Test] + public async Task Can_BgSave() + { + try + { + await NativeAsync.BgSaveAsync(); + } + catch (RedisResponseException e) + { + // if exception has that message then it still proves that BgSave works as expected. + if (e.Message.StartsWith("Can't BGSAVE while AOF log rewriting is in progress") + || e.Message.StartsWith("An AOF log rewriting in progress: can't BGSAVE right now") + || e.Message.StartsWith("Background save already in progress") + || e.Message.StartsWith("Another child process is active (AOF?): can't BGSAVE right now")) + return; + + throw; + } + } + + [Test] + public async Task Can_Quit() + { + await NativeAsync.QuitAsync(); + RedisRaw.NamespacePrefix = null; + CleanMask = null; + } + + [Test] + public async Task Can_BgRewriteAof() + { + await NativeAsync.BgRewriteAofAsync(); + } + + [Test] + [Ignore("Works too well and shutdown the server")] + public async Task Can_Shutdown() + { + await RedisAsync.ShutdownAsync(); + } + + [Test] + public async Task Can_get_Keys_with_pattern() + { + for (int i = 0; i < 5; i++) + await RedisAsync.SetValueAsync("k1:" + i, "val"); + for (int i = 0; i < 5; i++) + await RedisAsync.SetValueAsync("k2:" + i, "val"); + + var keys = await NativeAsync.KeysAsync("k1:*"); + Assert.That(keys.Length, Is.EqualTo(5)); + + var scanKeys = await RedisAsync.SearchKeysAsync("k1:*"); + Assert.That(scanKeys.Count, Is.EqualTo(5)); + } + + [Test] + public async Task Can_GetAll() + { + var keysMap = new Dictionary(); + + 10.Times(i => keysMap.Add("key" + i, "val" + i)); + + await RedisAsync.SetAllAsync(keysMap); + + var map = await RedisAsync.GetAllAsync(keysMap.Keys); + var mapKeys = await RedisAsync.GetValuesAsync(keysMap.Keys.ToList()); + + foreach (var entry in keysMap) + { + Assert.That(map.ContainsKey(entry.Key), Is.True); + Assert.That(mapKeys.Contains(entry.Value), Is.True); + } + } + + [Test] + public async Task Can_GetValues_JSON_strings() + { + var val = "{\"AuthorId\":0,\"Created\":\"\\/Date(1345961754013)\\/\",\"Name\":\"test\",\"Base64\":\"BQELAAEBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAViA/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP8BWAFYgP8BWAFYAViA/wFYAVgBWID/AVgBWAFYgP8BWAFYAViA/4D/gP+A/4D/AVgBWID/gP8BWID/gP8BWID/gP+A/wFYgP+A/4D/gP8BWID/gP+A/4D/gP+A/wFYAViA/4D/AViA/4D/AVgBWAFYgP8BWAFYAViA/4D/AViA/4D/gP+A/4D/gP8BWAFYgP+A/wFYgP+A/wFYgP+A/4D/gP+A/wFYgP+A/wFYgP+A/4D/gP+A/4D/AVgBWID/gP8BWID/gP8BWAFYAViA/wFYAVgBWID/gP8BWID/gP+A/4D/gP+A/wFYAViA/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP8BWAFYgP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/AVgBWID/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/wFYAViA/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP8BWAFYgP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/AVgBWID/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/4D/gP+A/wFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAFYAVgBWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\"}"; + + await RedisAsync.SetValueAsync("UserLevel/1", val); + + var vals = await RedisAsync.GetValuesAsync(new List(new[] { "UserLevel/1" })); + + Assert.That(vals.Count, Is.EqualTo(1)); + Assert.That(vals[0], Is.EqualTo(val)); + } + + [Test] + public async Task Can_AcquireLock() + { + // guid here is to prevent competition between concurrent runtime tests + var key = PrefixedKey("AcquireLockKeyTimeOut:" + Guid.NewGuid()); + var lockKey = PrefixedKey("Can_AcquireLock_TimeOut:" + Guid.NewGuid()); + await RedisAsync.IncrementValueAsync(key); //1 + + Task[] tasks = new Task[5]; + for (int i = 0; i < tasks.Length; i++) + { + var snapsot = i; + tasks[snapsot] = Task.Run( + () => IncrementKeyInsideLock(key, lockKey, snapsot, new RedisClient(TestConfig.SingleHost) { NamespacePrefix = RedisRaw.NamespacePrefix }.ForAsyncOnly()) + ); + } + await Task.WhenAll(tasks); + + var val = await RedisAsync.GetAsync(key); + + Assert.That(val, Is.EqualTo(1 + 5)); + } + + private async Task IncrementKeyInsideLock(String key, String lockKey, int clientNo, IRedisClientAsync client) + { + await using (await client.AcquireLockAsync(lockKey)) + { + Debug.WriteLine(String.Format("client {0} acquired lock", clientNo)); + var val = await client.GetAsync(key); + + await Task.Delay(200); + + await client.SetAsync(key, val + 1); + Debug.WriteLine(String.Format("client {0} released lock", clientNo)); + } + } + + [Test] + public async Task Can_AcquireLock_TimeOut() + { + // guid here is to prevent competition between concurrent runtime tests + var key = PrefixedKey("AcquireLockKeyTimeOut:" + Guid.NewGuid()); + var lockKey = PrefixedKey("Can_AcquireLock_TimeOut:" + Guid.NewGuid()); + await RedisAsync.IncrementValueAsync(key); //1 + _ = await RedisAsync.AcquireLockAsync(lockKey); + var waitFor = TimeSpan.FromMilliseconds(1000); + var now = DateTime.Now; + + try + { + await using var client = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + await using (await client.AcquireLockAsync(lockKey, waitFor)) + { + await client.IncrementValueAsync(key); //2 + } + } + catch (TimeoutException) + { + var val = await RedisAsync.GetAsync(key); + Assert.That(val, Is.EqualTo(1)); + + var timeTaken = DateTime.Now - now; + Assert.That(timeTaken.TotalMilliseconds > waitFor.TotalMilliseconds, Is.True); + Assert.That(timeTaken.TotalMilliseconds < waitFor.TotalMilliseconds + 1000, Is.True); + return; + } + finally + { + await RedisAsync.RemoveAsync(key); + await RedisAsync.RemoveAsync(lockKey); + } + Assert.Fail("should have Timed out"); + } + + [Test] + public async Task Can_Append() + { + const string expectedString = "Hello, " + "World!"; + await RedisAsync.SetValueAsync("key", "Hello, "); + var currentLength = await RedisAsync.AppendToValueAsync("key", "World!"); + + Assert.That(currentLength, Is.EqualTo(expectedString.Length)); + + var val = await RedisAsync.GetValueAsync("key"); + Assert.That(val, Is.EqualTo(expectedString)); + } + + [Test] + public async Task Can_GetRange() + { + const string helloWorld = "Hello, World!"; + await RedisAsync.SetValueAsync("key", helloWorld); + + var fromIndex = "Hello, ".Length; + var toIndex = "Hello, World".Length - 1; + + var expectedString = helloWorld.Substring(fromIndex, toIndex - fromIndex + 1); + var world = await NativeAsync.GetRangeAsync("key", fromIndex, toIndex); + + Assert.That(world.Length, Is.EqualTo(expectedString.Length)); + } + + [Test] + public async Task Can_create_distributed_lock() + { + var key = "lockkey"; + int lockTimeout = 2; + + var distributedLock = new DistributedLock().AsAsync(); + + var state = await distributedLock.LockAsync(key, lockTimeout, lockTimeout, RedisAsync); + Assert.AreEqual(state.Result, DistributedLock.LOCK_ACQUIRED); + + //can't re-lock + distributedLock = new DistributedLock(); + state = await distributedLock.LockAsync(key, lockTimeout, lockTimeout, RedisAsync); + Assert.AreEqual(state.Result, DistributedLock.LOCK_NOT_ACQUIRED); + + // re-acquire lock after timeout + await Task.Delay(lockTimeout * 1000 + 1000); + distributedLock = new DistributedLock(); + state = await distributedLock.LockAsync(key, lockTimeout, lockTimeout, RedisAsync); + + (var result, var expire) = state; // test decomposition since we are here + Assert.AreEqual(result, DistributedLock.LOCK_RECOVERED); + + Assert.IsTrue(await distributedLock.UnlockAsync(key, expire, RedisAsync)); + + //can now lock + distributedLock = new DistributedLock(); + state = await distributedLock.LockAsync(key, lockTimeout, lockTimeout, RedisAsync); + Assert.AreEqual(state.Result, DistributedLock.LOCK_ACQUIRED); + + //cleanup + Assert.IsTrue(await distributedLock.UnlockAsync(key, state.Expiration, RedisAsync)); + } + + public class MyPoco + { + public int Id { get; set; } + public string Name { get; set; } + } + + [Test] + public async Task Can_StoreObject() + { + object poco = new MyPoco { Id = 1, Name = "Test" }; + + await RedisAsync.StoreObjectAsync(poco); + + Assert.That(await RedisAsync.GetValueAsync(RedisRaw.NamespacePrefix + "urn:mypoco:1"), Is.EqualTo("{\"Id\":1,\"Name\":\"Test\"}")); + + Assert.That(await RedisAsync.PopItemFromSetAsync(RedisRaw.NamespacePrefix + "ids:MyPoco"), Is.EqualTo("1")); + } + + [Test] + public async Task Can_store_multiple_keys() + { + var keys = 5.Times(x => "key" + x); + var vals = 5.Times(x => "val" + x); + + using var redis = RedisClient.New(); + await RedisAsync.SetAllAsync(keys, vals); + + var all = await RedisAsync.GetValuesAsync(keys); + Assert.AreEqual(vals, all); + } + + [Test] + public async Task Can_store_Dictionary() + { + var keys = 5.Times(x => "key" + x); + var vals = 5.Times(x => "val" + x); + var map = new Dictionary(); + keys.ForEach(x => map[x] = "val" + x); + + await using var client = RedisClient.New().ForAsyncOnly(); + await client.SetAllAsync(map); + + var all = await client.GetValuesMapAsync(keys); + Assert.AreEqual(map, all); + } + + [Test] + public async Task Can_store_Dictionary_as_objects() + { + var map = new Dictionary + { + ["key_a"] = "123", + ["key_b"] = null + }; + + await using var client = RedisClient.New().ForAsyncOnly(); + + await client.SetAllAsync(map); + + Assert.That(await client.GetAsync("key_a"), Is.EqualTo("123")); + Assert.That(await client.GetValueAsync("key_b"), Is.EqualTo("")); + } + + + [Test] + public async Task Can_store_Dictionary_as_bytes() + { + var map = new Dictionary + { + ["key_a"] = "123".ToUtf8Bytes(), + ["key_b"] = null + }; + + await using var client = RedisClient.New().ForAsyncOnly(); + + await client.SetAllAsync(map); + + Assert.That(await client.GetAsync("key_a"), Is.EqualTo("123")); + Assert.That(await client.GetValueAsync("key_b"), Is.EqualTo("")); + } + + [Test] + public async Task Should_reset_slowlog() + { + await using var client = RedisClient.New().ForAsyncOnly(); + await client.SlowlogResetAsync(); + } + + [Test] + public async Task Can_get_slowlog() + { + await using var client = RedisClient.New().ForAsyncOnly(); + + var log = await client.GetSlowlogAsync(10); + + foreach (var t in log) + { + Console.WriteLine(t.Id); + Console.WriteLine(t.Duration); + Console.WriteLine(t.Timestamp); + Console.WriteLine(string.Join(":", t.Arguments)); + } + } + + + [Test] + public async Task Can_change_db_at_runtime() + { + await using var redis = new RedisClient(TestConfig.SingleHost, TestConfig.RedisPort, db: 1).ForAsyncOnly(); + + var val = Environment.TickCount; + var key = "test" + val; + try + { + await redis.SetAsync(key, val); + await redis.SelectAsync(2); + Assert.That(await redis.GetAsync(key), Is.EqualTo(0)); + await redis.SelectAsync(1); + Assert.That(await redis.GetAsync(key), Is.EqualTo(val)); + await redis.DisposeAsync(); + } + finally + { + await redis.SelectAsync(1); + await redis.RemoveAsync(key); + } + } + + [Test] + public async Task Can_Set_Expire_Seconds() + { + await RedisAsync.SetValueAsync("key", "val", expireIn: TimeSpan.FromSeconds(1)); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + await Task.Delay(2000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Test] + public async Task Can_Set_Expire_MilliSeconds() + { + await RedisAsync.SetValueAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1000)); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + await Task.Delay(2000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Test] + public async Task Can_Set_Expire_Seconds_if_exists() + { + Assert.That(await RedisAsync.SetValueIfExistsAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1500)), + Is.False); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + + await RedisAsync.SetValueAsync("key", "val"); + Assert.That(await RedisAsync.SetValueIfExistsAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1000)), + Is.True); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + + await Task.Delay(2000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + } + + [Test] + public async Task Can_Set_Expire_Seconds_if_not_exists() + { + Assert.That(await RedisAsync.SetValueIfNotExistsAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1000)), + Is.True); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + + Assert.That(await RedisAsync.SetValueIfNotExistsAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1000)), + Is.False); + + await Task.Delay(2000); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.False); + + await RedisAsync.RemoveAsync("key"); + await RedisAsync.SetValueIfNotExistsAsync("key", "val", expireIn: TimeSpan.FromMilliseconds(1000)); + Assert.That(await RedisAsync.ContainsKeyAsync("key"), Is.True); + } + } + +} diff --git a/tests/ServiceStack.Redis.Tests/RedisClientTests.cs b/tests/ServiceStack.Redis.Tests/RedisClientTests.cs index 9a3878e0..19bfbfe5 100644 --- a/tests/ServiceStack.Redis.Tests/RedisClientTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisClientTests.cs @@ -256,7 +256,8 @@ public void Can_Save() // if exception has that message then it still proves that BgSave works as expected. if (e.Message.StartsWith("Can't BGSAVE while AOF log rewriting is in progress") || e.Message.StartsWith("An AOF log rewriting in progress: can't BGSAVE right now") - || e.Message.StartsWith("Background save already in progress")) + || e.Message.StartsWith("Background save already in progress") + || e.Message.StartsWith("Another child process is active (AOF?): can't BGSAVE right now")) return; throw; @@ -275,7 +276,8 @@ public void Can_BgSave() // if exception has that message then it still proves that BgSave works as expected. if (e.Message.StartsWith("Can't BGSAVE while AOF log rewriting is in progress") || e.Message.StartsWith("An AOF log rewriting in progress: can't BGSAVE right now") - || e.Message.StartsWith("Background save already in progress")) + || e.Message.StartsWith("Background save already in progress") + || e.Message.StartsWith("Another child process is active (AOF?): can't BGSAVE right now")) return; throw; @@ -311,6 +313,9 @@ public void Can_get_Keys_with_pattern() var keys = Redis.Keys("k1:*"); Assert.That(keys.Length, Is.EqualTo(5)); + + var scanKeys = Redis.ScanAllKeys("k1:*").ToArray(); + Assert.That(scanKeys.Length, Is.EqualTo(5)); } [Test] @@ -348,8 +353,9 @@ public void Can_GetValues_JSON_strings() [Test] public void Can_AcquireLock() { - var key = PrefixedKey("AcquireLockKey"); - var lockKey = PrefixedKey("Can_AcquireLock"); + // guid here is to prevent competition between concurrent runtime tests + var key = PrefixedKey("AcquireLockKeyTimeOut:" + Guid.NewGuid()); + var lockKey = PrefixedKey("Can_AcquireLock_TimeOut:" + Guid.NewGuid()); Redis.IncrementValue(key); //1 var asyncResults = 5.TimesAsync(i => @@ -379,8 +385,9 @@ private void IncrementKeyInsideLock(String key, String lockKey, int clientNo, IR [Test] public void Can_AcquireLock_TimeOut() { - var key = PrefixedKey("AcquireLockKeyTimeOut"); - var lockKey = PrefixedKey("Can_AcquireLock_TimeOut"); + // guid here is to prevent competition between concurrent runtime tests + var key = PrefixedKey("AcquireLockKeyTimeOut:" + Guid.NewGuid()); + var lockKey = PrefixedKey("Can_AcquireLock_TimeOut:" + Guid.NewGuid()); Redis.IncrementValue(key); //1 var acquiredLock = Redis.AcquireLock(lockKey); var waitFor = TimeSpan.FromMilliseconds(1000); @@ -406,6 +413,11 @@ public void Can_AcquireLock_TimeOut() Assert.That(timeTaken.TotalMilliseconds < waitFor.TotalMilliseconds + 1000, Is.True); return; } + finally + { + Redis.Remove(key); + Redis.Remove(lockKey); + } Assert.Fail("should have Timed out"); } @@ -561,7 +573,7 @@ public void Should_reset_slowlog() } [Test] - public void Can_get_showlog() + public void Can_get_slowlog() { using (var redis = RedisClient.New()) { diff --git a/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.Async.cs b/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.Async.cs new file mode 100644 index 00000000..40d56a42 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.Async.cs @@ -0,0 +1,126 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + public static class AsyncExtensions + { + public static async ValueTask ForEachAsync(this List source, Func action) + { + foreach (var item in source) + await action(item).ConfigureAwait(false); + } + public static async ValueTask ForEachAsync(this Dictionary source, Func action) + { + foreach (var item in source) + await action(item.Key, item.Value).ConfigureAwait(false); + } + public static async ValueTask TimesAsync(this int times, Func action) + { + for (int i = 0; i < times; i++) + { + await action(i).ConfigureAwait(false); + } + } + public static async ValueTask> ToListAsync(this IAsyncEnumerable source) + { + var list = new List(); + await foreach (var item in source.ConfigureAwait(false)) + list.Add(item); + return list; + } + + public static async ValueTask CountAsync(this IAsyncEnumerable source) + { + int count = 0; + await foreach (var item in source.ConfigureAwait(false)) + count++; + return count; + } + + public static IRedisClientAsync ForAsyncOnly(this RedisClient client) + { +#if DEBUG + if (client is object) client.DebugAllowSync = false; +#endif + return client; + } + + public static async IAsyncEnumerable TakeAsync(this IAsyncEnumerable source, int count) + { + await foreach (var item in source.ConfigureAwait(false)) + { + if (count > 0) + { + count--; + yield return item; + } + } + } + + public static async ValueTask> ToDictionaryAsync(this IAsyncEnumerable source, Func keySelector, Func valueSelector) + { + var result = new Dictionary(); + await foreach (var item in source.ConfigureAwait(false)) + { + result.Add(keySelector(item), valueSelector(item)); + } + return result; + } + } + public class RedisClientTestsBaseAsyncTests // testing the base class features + : RedisClientTestsBaseAsync + { + [Test] + public void DetectUnexpectedSync() + { + #if DEBUG + Assert.False(RedisRaw.DebugAllowSync, nameof(RedisRaw.DebugAllowSync)); + var ex = Assert.Throws(() => RedisRaw.Ping()); + Assert.AreEqual("Unexpected synchronous operation detected from 'SendReceive'", ex.Message); + #endif + } + } + + [Category("Async")] + public abstract class RedisClientTestsBaseAsync : RedisClientTestsBase + { + protected IRedisClientAsync RedisAsync => base.Redis; + protected IRedisNativeClientAsync NativeAsync => base.Redis; + + [Obsolete("This should use RedisAsync or RedisRaw", true)] + protected new RedisClient Redis => base.Redis; + + protected RedisClient RedisRaw + { + get => base.Redis; + set => base.Redis = value; + } + + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + _ = RedisRaw.ForAsyncOnly(); + } + public override void OnAfterEachTest() + { +#if DEBUG + if(RedisRaw is object) RedisRaw.DebugAllowSync = true; +#endif + base.OnAfterEachTest(); + } + + protected static async ValueTask> ToListAsync(IAsyncEnumerable source, CancellationToken token = default) + { + var list = new List(); + await foreach (var value in source.ConfigureAwait(false).WithCancellation(token)) + { + list.Add(value); + } + return list; + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.cs b/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.cs index 40386c28..087c1c4f 100644 --- a/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.cs +++ b/tests/ServiceStack.Redis.Tests/RedisClientTestsBase.cs @@ -1,8 +1,6 @@ -using System; +using NUnit.Framework; using System.Diagnostics; using System.Text; -using NUnit.Framework; -using ServiceStack.Text; namespace ServiceStack.Redis.Tests { diff --git a/tests/ServiceStack.Redis.Tests/RedisGeoNativeClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisGeoNativeClientTests.Async.cs new file mode 100644 index 00000000..c6f267c3 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisGeoNativeClientTests.Async.cs @@ -0,0 +1,286 @@ +using NUnit.Framework; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Async")] + [Ignore("CI requires redis-server v3.2.0")] + public class RedisGeoNativeClientTestsAsync + { + private readonly IRedisNativeClientAsync redis; + + public RedisGeoNativeClientTestsAsync() + { + redis = new RedisNativeClient(TestConfig.GeoHost); + } + + [OneTimeTearDown] + public async Task OneTimeTearDown() + { + await redis.DisposeAsync(); + } + + [Test] + public async Task Can_GeoAdd_and_GeoPos() + { + await redis.FlushDbAsync(); + var count = await redis.GeoAddAsync("Sicily", 13.361389, 38.115556, "Palermo"); + Assert.That(count, Is.EqualTo(1)); + var results = await redis.GeoPosAsync("Sicily", new[] { "Palermo" }); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + } + + [Test] + public async Task GeoPos_on_NonExistingMember_returns_no_results() + { + await redis.FlushDbAsync(); + var count = await redis.GeoAddAsync("Sicily", 13.361389, 38.115556, "Palermo"); + var results = await redis.GeoPosAsync("Sicily", new[] { "NonExistingMember" }); + Assert.That(results.Count, Is.EqualTo(0)); + + results = await redis.GeoPosAsync("Sicily", new[] { "Palermo", "NonExistingMember" }); + Assert.That(results.Count, Is.EqualTo(1)); + } + + [Test] + public async Task Can_GeoAdd_and_GeoPos_multiple() + { + await redis.FlushDbAsync(); + var count = await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + Assert.That(count, Is.EqualTo(2)); + + var results = await redis.GeoPosAsync("Sicily", new[] { "Palermo", "Catania" }); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + + Assert.That(results[1].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[1].Member, Is.EqualTo("Catania")); + } + + [Test] + public async Task Can_GeoDist() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var distance = await redis.GeoDistAsync("Sicily", "Palermo", "Catania"); + Assert.That(distance, Is.EqualTo(166274.15156960039).Within(.1)); + } + + [Test] + public async Task GeoDist_on_NonExistingMember_returns_NaN() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var distance = await redis.GeoDistAsync("Sicily", "Palermo", "NonExistingMember"); + Assert.That(distance, Is.EqualTo(double.NaN)); + } + + [Test] + public async Task Can_GeoHash() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var hashes = await redis.GeoHashAsync("Sicily", new[] { "Palermo", "Catania" }); + Assert.That(hashes[0], Is.EqualTo("sqc8b49rny0")); + Assert.That(hashes[1], Is.EqualTo("sqdtr74hyu0")); + + hashes = await redis.GeoHashAsync("Sicily", new[] { "Palermo", "NonExistingMember", "Catania" }); + Assert.That(hashes[0], Is.EqualTo("sqc8b49rny0")); + Assert.That(hashes[1], Is.Null); + Assert.That(hashes[2], Is.EqualTo("sqdtr74hyu0")); + } + + [Test] + public async Task Can_GeoRadius_default() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.Null); + Assert.That(results[1].Member, Is.EqualTo("Catania")); + Assert.That(results[1].Unit, Is.Null); + } + + [Test] + public async Task Can_GeoRadiusByMember_default() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.583333, 37.316667, "Agrigento"), + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusByMemberAsync("Sicily", "Agrigento", 100, RedisGeoUnit.Kilometers); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Agrigento")); + Assert.That(results[0].Unit, Is.Null); + Assert.That(results[1].Member, Is.EqualTo("Palermo")); + Assert.That(results[1].Unit, Is.Null); + } + + [Test] + public async Task Can_GeoRadius_WithCoord() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, withCoords: true); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + + Assert.That(results[1].Member, Is.EqualTo("Catania")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(37.502669).Within(.1)); + } + + [Test] + public async Task Can_GeoRadius_WithDist() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, withDist: true); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Distance, Is.EqualTo(190.4424).Within(.1)); + + Assert.That(results[1].Member, Is.EqualTo("Catania")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Distance, Is.EqualTo(56.4413).Within(.1)); + } + + [Test] + public async Task Can_GeoRadius_WithCoord_WithDist_WithHash() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, + withCoords: true, withDist: true, withHash: true); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(190.4424).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479099956230698)); + + Assert.That(results[1].Member, Is.EqualTo("Catania")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[1].Distance, Is.EqualTo(56.4413).Within(.1)); + Assert.That(results[1].Hash, Is.EqualTo(3479447370796909)); + } + + [Test] + public async Task Can_GeoRadiusByMember_WithCoord_WithDist_WithHash() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.583333, 37.316667, "Agrigento"), + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusByMemberAsync("Sicily", "Agrigento", 100, RedisGeoUnit.Kilometers, + withCoords: true, withDist: true, withHash: true); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Agrigento")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.583333).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(37.316667).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(0)); + Assert.That(results[0].Hash, Is.EqualTo(3479030013248308)); + + Assert.That(results[1].Member, Is.EqualTo("Palermo")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[1].Distance, Is.EqualTo(90.9778).Within(.1)); + Assert.That(results[1].Hash, Is.EqualTo(3479099956230698)); + } + + [Test] + public async Task Can_GeoRadius_WithCoord_WithDist_WithHash_Count_and_Asc() + { + await redis.FlushDbAsync(); + await redis.GeoAddAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, + withCoords: true, withDist: true, withHash: true, count:1, asc:false); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(190.4424).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479099956230698)); + + results = await redis.GeoRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, + withCoords: true, withDist: true, withHash: true, count: 1, asc: true); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Member, Is.EqualTo("Catania")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(56.4413).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479447370796909)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisGeoTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisGeoTests.Async.cs new file mode 100644 index 00000000..f06280e2 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisGeoTests.Async.cs @@ -0,0 +1,242 @@ +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Async")] + [Ignore("CI requires redis-server v3.2.0")] + public class RedisGeoTestsAsync + { + private readonly IRedisClientAsync redis; + + public RedisGeoTestsAsync() + { + redis = new RedisClient(TestConfig.GeoHost); + } + + [OneTimeTearDown] + public async Task OneTimeTearDown() + { + if (redis is object) + { + await redis.DisposeAsync(); + } + } + + [Test] + public async Task Can_AddGeoMember_and_GetGeoCoordinates() + { + await redis.FlushDbAsync(); + var count = await redis.AddGeoMemberAsync("Sicily", 13.361389, 38.115556, "Palermo"); + Assert.That(count, Is.EqualTo(1)); + var results = await redis.GetGeoCoordinatesAsync("Sicily", new[] { "Palermo" }); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + } + + [Test] + public async Task GetGeoCoordinates_on_NonExistingMember_returns_no_results() + { + await redis.FlushDbAsync(); + var count = await redis.AddGeoMemberAsync("Sicily", 13.361389, 38.115556, "Palermo"); + var results = await redis.GetGeoCoordinatesAsync("Sicily", new[] { "NonExistingMember" }); + Assert.That(results.Count, Is.EqualTo(0)); + + results = await redis.GetGeoCoordinatesAsync("Sicily", new[] { "Palermo", "NonExistingMember" }); + Assert.That(results.Count, Is.EqualTo(1)); + } + + [Test] + public async Task Can_AddGeoMembers_and_GetGeoCoordinates_multiple() + { + await redis.FlushDbAsync(); + var count = await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + Assert.That(count, Is.EqualTo(2)); + + var results = await redis.GetGeoCoordinatesAsync("Sicily", new[] { "Palermo", "Catania" }); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + + Assert.That(results[1].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[1].Member, Is.EqualTo("Catania")); + } + + [Test] + public async Task Can_CalculateDistanceBetweenGeoMembers() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var distance = await redis.CalculateDistanceBetweenGeoMembersAsync("Sicily", "Palermo", "Catania"); + Assert.That(distance, Is.EqualTo(166274.15156960039).Within(.1)); + } + + [Test] + public async Task CalculateDistanceBetweenGeoMembers_on_NonExistingMember_returns_NaN() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var distance = await redis.CalculateDistanceBetweenGeoMembersAsync("Sicily", "Palermo", "NonExistingMember"); + Assert.That(distance, Is.EqualTo(double.NaN)); + } + + [Test] + public async Task Can_GetGeohashes() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var hashes = await redis.GetGeohashesAsync("Sicily", new[] { "Palermo", "Catania" }); + Assert.That(hashes[0], Is.EqualTo("sqc8b49rny0")); + Assert.That(hashes[1], Is.EqualTo("sqdtr74hyu0")); + + hashes = await redis.GetGeohashesAsync("Sicily", new[] { "Palermo", "NonExistingMember", "Catania" }); + Assert.That(hashes[0], Is.EqualTo("sqc8b49rny0")); + Assert.That(hashes[1], Is.Null); + Assert.That(hashes[2], Is.EqualTo("sqdtr74hyu0")); + } + + [Test] + public async Task Can_FindGeoMembersInRadius() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.FindGeoMembersInRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers); + + Assert.That(results.Length, Is.EqualTo(2)); + Assert.That(results[0], Is.EqualTo("Palermo")); + Assert.That(results[1], Is.EqualTo("Catania")); + } + + //[Test] // method does not exist on IRedisClient/IRedisClientAsync + //public async Task Can_GeoRadiusByMember() + //{ + // await redis.FlushDbAsync(); + // await redis.AddGeoMembersAsync("Sicily", new[] { + // new RedisGeo(13.583333, 37.316667, "Agrigento"), + // new RedisGeo(13.361389, 38.115556, "Palermo"), + // new RedisGeo(15.087269, 37.502669, "Catania") + // }); + + // var results = await redis.GeoRadiusByMemberAsync("Sicily", "Agrigento", 100, RedisGeoUnit.Kilometers); + + // Assert.That(results.Count, Is.EqualTo(2)); + // Assert.That(results[0].Member, Is.EqualTo("Agrigento")); + // Assert.That(results[0].Unit, Is.Null); + // Assert.That(results[1].Member, Is.EqualTo("Palermo")); + // Assert.That(results[1].Unit, Is.Null); + //} + + [Test] + public async Task Can_FindGeoResultsInRadius() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.FindGeoResultsInRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(190.4424).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479099956230698)); + + Assert.That(results[1].Member, Is.EqualTo("Catania")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[1].Distance, Is.EqualTo(56.4413).Within(.1)); + Assert.That(results[1].Hash, Is.EqualTo(3479447370796909)); + } + + [Test] + public async Task Can_FindGeoResultsInRadius_by_Member() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.583333, 37.316667, "Agrigento"), + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.FindGeoResultsInRadiusAsync("Sicily", "Agrigento", 100, RedisGeoUnit.Kilometers); + + Assert.That(results.Count, Is.EqualTo(2)); + Assert.That(results[0].Member, Is.EqualTo("Agrigento")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.583333).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(37.316667).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(0)); + Assert.That(results[0].Hash, Is.EqualTo(3479030013248308)); + + Assert.That(results[1].Member, Is.EqualTo("Palermo")); + Assert.That(results[1].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[1].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[1].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[1].Distance, Is.EqualTo(90.9778).Within(.1)); + Assert.That(results[1].Hash, Is.EqualTo(3479099956230698)); + } + + [Test] + public async Task Can_GeoRadius_WithCoord_WithDist_WithHash_Count_and_Asc() + { + await redis.FlushDbAsync(); + await redis.AddGeoMembersAsync("Sicily", new[] { + new RedisGeo(13.361389, 38.115556, "Palermo"), + new RedisGeo(15.087269, 37.502669, "Catania") + }); + + var results = await redis.FindGeoResultsInRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, + count: 1, sortByNearest: false); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Member, Is.EqualTo("Palermo")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(13.361389).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(38.115556).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(190.4424).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479099956230698)); + + results = await redis.FindGeoResultsInRadiusAsync("Sicily", 15, 37, 200, RedisGeoUnit.Kilometers, + count: 1, sortByNearest: true); + + Assert.That(results.Count, Is.EqualTo(1)); + Assert.That(results[0].Member, Is.EqualTo("Catania")); + Assert.That(results[0].Unit, Is.EqualTo(RedisGeoUnit.Kilometers)); + Assert.That(results[0].Longitude, Is.EqualTo(15.087269).Within(.1)); + Assert.That(results[0].Latitude, Is.EqualTo(37.502669).Within(.1)); + Assert.That(results[0].Distance, Is.EqualTo(56.4413).Within(.1)); + Assert.That(results[0].Hash, Is.EqualTo(3479447370796909)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisHyperLogTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisHyperLogTests.Async.cs new file mode 100644 index 00000000..31748b6e --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisHyperLogTests.Async.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; +using System; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Ignore("Integration"), Category("Async")] + public class RedisHyperLogTestsAsync + { + const string Host = "localhost"; // "10.0.0.14" + private IRedisClientAsync Connect() => new RedisClient(Host); + + [Test] + public async Task Can_Add_to_Hyperlog() + { + await using var redis = Connect(); + + await redis.FlushAllAsync(); + + await redis.AddToHyperLogAsync("hyperlog", new[] { "a", "b", "c" }); + await redis.AddToHyperLogAsync("hyperlog", new[] { "c", "d" }); + + var count = await redis.CountHyperLogAsync("hyperlog"); + + Assert.That(count, Is.EqualTo(4)); + + await redis.AddToHyperLogAsync("hyperlog2", new[] { "c", "d", "e", "f" }); + + await redis.MergeHyperLogsAsync("hypermerge", new[] { "hyperlog", "hyperlog2" }); + + var mergeCount = await redis.CountHyperLogAsync("hypermerge"); + + Assert.That(mergeCount, Is.EqualTo(6)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisManagerPoolTests.cs b/tests/ServiceStack.Redis.Tests/RedisManagerPoolTests.cs index 4aca12c2..a32f69aa 100644 --- a/tests/ServiceStack.Redis.Tests/RedisManagerPoolTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisManagerPoolTests.cs @@ -215,8 +215,8 @@ public void Can_support_64_threads_using_the_client_simultaneously() var hostCount = 0; foreach (var entry in clientUsageMap) { - Assert.That(entry.Value, Is.GreaterThanOrEqualTo(5), "Host has unproportianate distrobution: " + entry.Value); - Assert.That(entry.Value, Is.LessThanOrEqualTo(30), "Host has unproportianate distrobution: " + entry.Value); + Assert.That(entry.Value, Is.GreaterThanOrEqualTo(5), "Host has unproportionate distribution: " + entry.Value); + Assert.That(entry.Value, Is.LessThanOrEqualTo(30), "Host has unproportionate distribution: " + entry.Value); hostCount += entry.Value; } diff --git a/tests/ServiceStack.Redis.Tests/RedisPasswordTests.cs b/tests/ServiceStack.Redis.Tests/RedisPasswordTests.cs index 018e3b10..db10e714 100644 --- a/tests/ServiceStack.Redis.Tests/RedisPasswordTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisPasswordTests.cs @@ -1,50 +1,53 @@ -using NUnit.Framework; +using System; +using NUnit.Framework; namespace ServiceStack.Redis.Tests { [TestFixture] public class RedisPasswordTests { - [Ignore("Integration")] [Test] - public void Can_connect_to_Slaves_and_Masters_with_Password() + public void Can_connect_to_Replicas_and_Masters_with_Password() { var factory = new PooledRedisClientManager( - readWriteHosts: new[] { "pass@10.0.0.59:6379" }, - readOnlyHosts: new[] { "pass@10.0.0.59:6380" }); + readWriteHosts: new[] {"pass@10.0.0.59:6379"}, + readOnlyHosts: new[] {"pass@10.0.0.59:6380"}); - using (var readWrite = factory.GetClient()) - using (var readOnly = factory.GetReadOnlyClient()) - { - readWrite.SetValue("Foo", "Bar"); - var value = readOnly.GetValue("Foo"); + using var readWrite = factory.GetClient(); + using var readOnly = factory.GetReadOnlyClient(); + readWrite.SetValue("Foo", "Bar"); + var value = readOnly.GetValue("Foo"); - Assert.That(value, Is.EqualTo("Bar")); - } + Assert.That(value, Is.EqualTo("Bar")); } [Test] public void Passwords_are_not_leaked_in_exception_messages() - { + { const string password = "yesterdayspassword"; - Assert.Throws(() => { - try - { - var factory = new PooledRedisClientManager(password + "@" + TestConfig.SingleHost); // redis will throw when using password and it's not configured - using (var redis = factory.GetClient()) + Assert.Throws(() => { + try { + var connString = password + "@" + TestConfig.SingleHost + "?RetryTimeout=2000"; + // redis will throw when using password and it's not configured + var factory = new PooledRedisClientManager(connString); + using var redis = factory.GetClient(); redis.SetValue("Foo", "Bar"); } - } - catch (RedisResponseException ex) - { - Assert.That(ex.Message, Is.Not.Contains(password)); - throw; - } - }, - "Expected an exception after Redis AUTH command; try using a password that doesn't match."); + catch (RedisResponseException ex) + { + Assert.That(ex.Message, Is.Not.Contains(password)); + throw; + } + catch (TimeoutException tex) + { + Assert.That(tex.InnerException.Message, Is.Not.Contains(password)); + throw tex.InnerException; + } + }, + "Expected an exception after Redis AUTH command; try using a password that doesn't match."); } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisPersistenceProviderTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisPersistenceProviderTests.Async.cs new file mode 100644 index 00000000..1f0e5e42 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisPersistenceProviderTests.Async.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration"), Category("Async")] + public class RedisPersistenceProviderTestsAsync + { + [Test] + public async Task Can_Store_and_GetById_ModelWithIdAndName() + { + await using IRedisClientAsync redis = new RedisClient(TestConfig.SingleHost); + const int modelId = 1; + var to = ModelWithIdAndName.Create(modelId); + await redis.StoreAsync(to); + + var from = await redis.GetByIdAsync(modelId); + + ModelWithIdAndName.AssertIsEqual(to, from); + } + + [Test] + public async Task Can_StoreAll_and_GetByIds_ModelWithIdAndName() + { + await using IRedisClientAsync redis = new RedisClient(TestConfig.SingleHost); + + var ids = new[] { 1, 2, 3, 4, 5 }; + var tos = ids.Map(ModelWithIdAndName.Create); + + await redis.StoreAllAsync(tos); + + var froms = await redis.GetByIdsAsync(ids); + var fromIds = froms.Map(x => x.Id); + + Assert.That(fromIds, Is.EquivalentTo(ids)); + } + + [Test] + public async Task Can_Delete_ModelWithIdAndName() + { + await using IRedisClientAsync redis = new RedisClient(TestConfig.SingleHost); + var ids = new List { 1, 2, 3, 4, 5 }; + var tos = ids.ConvertAll(ModelWithIdAndName.Create); + + await redis.StoreAllAsync(tos); + + var deleteIds = new List { 2, 4 }; + + await redis.DeleteByIdsAsync(deleteIds); + + var froms = await redis.GetByIdsAsync(ids); + var fromIds = froms.Map(x => x.Id); + + var expectedIds = ids.Where(x => !deleteIds.Contains(x)).ToList(); + + Assert.That(fromIds, Is.EquivalentTo(expectedIds)); + } + + } + +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisPipelineCommonTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisPipelineCommonTests.Async.cs new file mode 100644 index 00000000..4c0a707a --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisPipelineCommonTests.Async.cs @@ -0,0 +1,73 @@ +using NUnit.Framework; +using ServiceStack.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisPipelineCommonTestsAsync + : RedisClientTestsBaseAsync + { + [Test] + public async Task Can_Set_and_Expire_key_in_atomic_transaction() + { + var oneSec = TimeSpan.FromSeconds(1); + + Assert.That(await RedisAsync.GetValueAsync("key"), Is.Null); + await using (var trans = RedisAsync.CreatePipeline()) //Calls 'MULTI' + { + trans.QueueCommand(r => r.SetValueAsync("key", "a")); //Queues 'SET key a' + trans.QueueCommand(r => r.ExpireEntryInAsync("key", oneSec)); //Queues 'EXPIRE key 1' + + await trans.FlushAsync(); //Calls 'EXEC' + + } //Calls 'DISCARD' if 'EXEC' wasn't called + + Assert.That(await RedisAsync.GetValueAsync("key"), Is.EqualTo("a")); + await Task.Delay(TimeSpan.FromSeconds(2)); + Assert.That(await RedisAsync.GetValueAsync("key"), Is.Null); + } + + [Test] + public async Task Can_SetAll_and_Publish_in_atomic_transaction() + { + var messages = new Dictionary { { "a", "a" }, { "b", "b" } }; + await using var pipeline = RedisAsync.CreatePipeline(); + pipeline.QueueCommand(c => c.SetAllAsync(messages.ToDictionary(t => t.Key, t => t.Value))); + pipeline.QueueCommand(c => c.PublishMessageAsync("uc", "b")); + + await pipeline.FlushAsync(); + } + + [Test] + public async Task Can_Pop_priority_message_from_SortedSet_and_Add_to_workq_in_atomic_transaction() + { + var messages = new List { "message4", "message3", "message2" }; + + await RedisAsync.AddItemToListAsync("workq", "message1"); + + var priority = 1; + await messages.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync("prioritymsgs", x, priority++)); + + var highestPriorityMessage = await RedisAsync.PopItemWithHighestScoreFromSortedSetAsync("prioritymsgs"); + + await using (var trans = RedisAsync.CreatePipeline()) + { + trans.QueueCommand(r => r.RemoveItemFromSortedSetAsync("prioritymsgs", highestPriorityMessage)); + trans.QueueCommand(r => r.AddItemToListAsync("workq", highestPriorityMessage)); + + await trans.FlushAsync(); + } + + Assert.That(await RedisAsync.GetAllItemsFromListAsync("workq"), + Is.EquivalentTo(new List { "message1", "message2" })); + Assert.That(await RedisAsync.GetAllItemsFromSortedSetAsync("prioritymsgs"), + Is.EquivalentTo(new List { "message3", "message4" })); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisPipelineTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisPipelineTests.Async.cs new file mode 100644 index 00000000..6eea8ed1 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisPipelineTests.Async.cs @@ -0,0 +1,281 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisPipelineTestsAsync + : RedisClientTestsBaseAsync + { + private const string Key = "pipemultitest"; + private const string ListKey = "pipemultitest-list"; + private const string SetKey = "pipemultitest-set"; + private const string SortedSetKey = "pipemultitest-sortedset"; + + public override void OnAfterEachTest() + { + CleanMask = Key + "*"; + base.OnAfterEachTest(); + } + + [Test] + public async Task Can_call_single_operation_in_pipeline() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + var map = new Dictionary(); + pipeline.QueueCommand(r => r.GetAsync(Key).AsValueTask(), y => map[Key] = y); + + await pipeline.FlushAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + } + + [Test] + public async Task No_commit_of_atomic_pipelines_discards_all_commands() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + } + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + } + + [Test] + public async Task Exception_in_atomic_pipelines_discards_all_commands() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + try + { + await using var pipeline = RedisAsync.CreatePipeline(); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + throw new NotSupportedException(); + } + catch (NotSupportedException) + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + } + } + + [Test] + public async Task Can_call_single_operation_3_Times_in_pipeline() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + + await pipeline.FlushAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("3")); + } + [Test] + public async Task Can_call_hash_operations_in_pipeline() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + var fields = new[] { "field1", "field2", "field3" }; + var values = new[] { "1", "2", "3" }; + var fieldBytes = new byte[fields.Length][]; + for (int i = 0; i < fields.Length; ++i) + { + fieldBytes[i] = GetBytes(fields[i]); + + } + var valueBytes = new byte[values.Length][]; + for (int i = 0; i < values.Length; ++i) + { + valueBytes[i] = GetBytes(values[i]); + + } + byte[][] members = null; + await using var pipeline = RedisAsync.CreatePipeline(); + + + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).HMSetAsync(Key, fieldBytes, valueBytes)); + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).HGetAllAsync(Key), x => members = x); + + + await pipeline.FlushAsync(); + + + for (var i = 0; i < members.Length; i += 2) + { + Assert.AreEqual(members[i], fieldBytes[i / 2]); + Assert.AreEqual(members[i + 1], valueBytes[i / 2]); + + } + } + + [Test] + public async Task Can_call_multiple_setexs_in_pipeline() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + var keys = new[] { Key + "key1", Key + "key2", Key + "key3" }; + var values = new[] { "1", "2", "3" }; + await using var pipeline = RedisAsync.CreatePipeline(); + + for (int i = 0; i < 3; ++i) + { + int index0 = i; + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).SetExAsync(keys[index0], 100, GetBytes(values[index0]))); + } + + await pipeline.FlushAsync(); + await pipeline.ReplayAsync(); + + + for (int i = 0; i < 3; ++i) + Assert.AreEqual(await RedisAsync.GetValueAsync(keys[i]), values[i]); + } + + [Test] + public async Task Can_call_single_operation_with_callback_3_Times_in_pipeline() + { + var results = new List(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + + await pipeline.FlushAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("3")); + Assert.That(results, Is.EquivalentTo(new List { 1, 2, 3 })); + } + + [Test] + public async Task Supports_different_operation_types_in_same_pipeline() + { + var incrementResults = new List(); + var collectionCounts = new List(); + var containsItem = false; + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + pipeline.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem1")); + pipeline.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem2")); + pipeline.QueueCommand(r => r.AddItemToSetAsync(SetKey, "setitem")); + pipeline.QueueCommand(r => r.SetContainsItemAsync(SetKey, "setitem"), b => containsItem = b); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem1")); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem2")); + pipeline.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem3")); + pipeline.QueueCommand(r => r.GetListCountAsync(ListKey), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.GetSetCountAsync(SetKey), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.GetSortedSetCountAsync(SortedSetKey), intResult => collectionCounts.Add(intResult)); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + + await pipeline.FlushAsync(); + } + + Assert.That(containsItem, Is.True); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("2")); + Assert.That(incrementResults, Is.EquivalentTo(new List { 1, 2 })); + Assert.That(collectionCounts, Is.EquivalentTo(new List { 2, 1, 3 })); + Assert.That(await RedisAsync.GetAllItemsFromListAsync(ListKey), Is.EquivalentTo(new List { "listitem1", "listitem2" })); + Assert.That(await RedisAsync.GetAllItemsFromSetAsync(SetKey), Is.EquivalentTo(new List { "setitem" })); + Assert.That(await RedisAsync.GetAllItemsFromSortedSetAsync(SortedSetKey), Is.EquivalentTo(new List { "sortedsetitem1", "sortedsetitem2", "sortedsetitem3" })); + } + + [Test] + public async Task Can_call_multi_string_operations_in_pipeline() + { + string item1 = null; + string item4 = null; + + var results = new List(); + Assert.That(await RedisAsync.GetListCountAsync(ListKey), Is.EqualTo(0)); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + pipeline.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem1")); + pipeline.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem2")); + pipeline.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem3")); + pipeline.QueueCommand(r => r.GetAllItemsFromListAsync(ListKey), x => results = x); + pipeline.QueueCommand(r => r.GetItemFromListAsync(ListKey, 0), x => item1 = x); + pipeline.QueueCommand(r => r.GetItemFromListAsync(ListKey, 4), x => item4 = x); + + await pipeline.FlushAsync(); + } + + Assert.That(await RedisAsync.GetListCountAsync(ListKey), Is.EqualTo(3)); + Assert.That(results, Is.EquivalentTo(new List { "listitem1", "listitem2", "listitem3" })); + Assert.That(item1, Is.EqualTo("listitem1")); + Assert.That(item4, Is.Null); + } + [Test] + // Operations that are not supported in older versions will look at server info to determine what to do. + // If server info is fetched each time, then it will interfer with pipeline + public async Task Can_call_operation_not_supported_on_older_servers_in_pipeline() + { + var temp = new byte[1]; + await using var pipeline = RedisAsync.CreatePipeline(); + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).SetExAsync(Key + "key", 5, temp)); + await pipeline.FlushAsync(); + } + [Test] + public async Task Pipeline_can_be_replayed() + { + string KeySquared = Key + Key; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + await using var pipeline = RedisAsync.CreatePipeline(); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + pipeline.QueueCommand(r => r.IncrementValueAsync(KeySquared)); + await pipeline.FlushAsync(); + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + await NativeAsync.DelAsync(Key); + await NativeAsync.DelAsync(KeySquared); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + + await pipeline.ReplayAsync(); + await pipeline.DisposeAsync(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + } + + [Test] + public async Task Pipeline_can_be_contain_watch() + { + string KeySquared = Key + Key; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + await using var pipeline = RedisAsync.CreatePipeline(); + pipeline.QueueCommand(r => r.IncrementValueAsync(Key)); + pipeline.QueueCommand(r => r.IncrementValueAsync(KeySquared)); + pipeline.QueueCommand(r => ((IRedisNativeClientAsync)r).WatchAsync(new[] { Key + "FOO" })); + await pipeline.FlushAsync(); + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + } + + [Test] + public async Task Can_call_AddRangeToSet_in_pipeline() + { + await using var pipeline = RedisAsync.CreatePipeline(); + var key = "pipeline-test"; + + pipeline.QueueCommand(r => r.RemoveAsync(key).AsValueTask()); + pipeline.QueueCommand(r => r.AddRangeToSetAsync(key, new[] { "A", "B", "C" }.ToList())); + + await pipeline.FlushAsync(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisPubSubServerTests.cs b/tests/ServiceStack.Redis.Tests/RedisPubSubServerTests.cs index 2b51c6ea..4984228c 100644 --- a/tests/ServiceStack.Redis.Tests/RedisPubSubServerTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisPubSubServerTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading; using NUnit.Framework; using ServiceStack.Text; @@ -9,16 +10,26 @@ namespace ServiceStack.Redis.Tests [TestFixture] public class RedisPubSubServerTests { - private static RedisPubSubServer CreatePubSubServer( - int intervalSecs = 1, int timeoutSecs = 3) + RedisManagerPool clientsManager = new RedisManagerPool(TestConfig.MasterHosts); + + [OneTimeTearDown] + public void OneTimeTearDown() + { + clientsManager.Dispose(); + } + + private RedisPubSubServer CreatePubSubServer( + int intervalSecs = 1, int timeoutSecs = 3, params string[] channels) { - var clientsManager = new RedisManagerPool(TestConfig.MasterHosts); using (var redis = clientsManager.GetClient()) redis.FlushAll(); + + if (channels.Length == 0) + channels = new[] {"topic:test"}; var pubSub = new RedisPubSubServer( clientsManager, - "topic:test") + channels) { HeartbeatInterval = TimeSpan.FromSeconds(intervalSecs), HeartbeatTimeout = TimeSpan.FromSeconds(timeoutSecs) @@ -97,5 +108,42 @@ public void Does_send_heartbeat_pulses_to_multiple_PubSubServers() pubSubs.Each(x => x.Dispose()); } + + [Test] + public void Can_restart_and_subscribe_to_more_channels() + { + var a = new List(); + var b = new List(); + var pubSub = CreatePubSubServer(intervalSecs: 20, timeoutSecs: 30, "topic:a"); + pubSub.OnMessage = (channel, msg) => { + if (channel == "topic:a") + a.Add(msg); + else if (channel == "topic:b") + b.Add(msg); + }; + pubSub.Start(); + Thread.Sleep(100); + + var client = clientsManager.GetClient(); + var i = 0; + client.PublishMessage("topic:a", $"msg: ${++i}"); + client.PublishMessage("topic:b", $"msg: ${++i}"); + + Thread.Sleep(100); + Assert.That(a.Count, Is.EqualTo(1)); + Assert.That(b.Count, Is.EqualTo(0)); + + pubSub.Channels = new[] {"topic:a", "topic:b"}; + pubSub.Restart(); + Thread.Sleep(100); + + client.PublishMessage("topic:a", $"msg: ${++i}"); + client.PublishMessage("topic:b", $"msg: ${++i}"); + + + Thread.Sleep(100); + Assert.That(a.Count, Is.EqualTo(2)); + Assert.That(b.Count, Is.EqualTo(1)); + } } } \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisPubSubTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisPubSubTests.Async.cs new file mode 100644 index 00000000..b8865dc6 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisPubSubTests.Async.cs @@ -0,0 +1,293 @@ +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration")] + public class RedisPubSubTestsAsync + : RedisClientTestsBaseAsync + { + public override void OnBeforeEachTest() + { + base.OnBeforeEachTest(); + RedisRaw.NamespacePrefix = "RedisPubSubTests"; + } + + [Test] + public async Task Can_Subscribe_and_Publish_single_message() + { + var channelName = PrefixedKey("CHANNEL1"); + const string message = "Hello, World!"; + var key = PrefixedKey("Can_Subscribe_and_Publish_single_message"); + + await RedisAsync.IncrementValueAsync(key); + + await using (var subscription = await RedisAsync.CreateSubscriptionAsync()) + { + subscription.OnSubscribeAsync += channel => + { + Log("Subscribed to '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelName)); + return default; + }; + subscription.OnUnSubscribeAsync += channel => + { + Log("UnSubscribed from '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelName)); + return default; + }; + subscription.OnMessageAsync += async (channel, msg) => + { + Log("Received '{0}' from channel '{1}'", msg, channel); + Assert.That(channel, Is.EqualTo(channelName)); + Assert.That(msg, Is.EqualTo(message)); + await subscription.UnSubscribeFromAllChannelsAsync(); + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + Log("Publishing '{0}' to '{1}'", message, channelName); + await redisClient.PublishMessageAsync(channelName, message); + }); + + Log("Start Listening On " + channelName); + await subscription.SubscribeToChannelsAsync(new[] { channelName }); //blocking + } + + Log("Using as normal client again..."); + await RedisAsync.IncrementValueAsync(key); + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo(2)); + } + + [Test] + public async Task Can_Subscribe_and_Publish_single_message_using_wildcard() + { + var channelWildcard = PrefixedKey("CHANNEL.*"); + var channelName = PrefixedKey("CHANNEL.1"); + const string message = "Hello, World!"; + var key = PrefixedKey("Can_Subscribe_and_Publish_single_message"); + + await RedisAsync.IncrementValueAsync(key); + + await using (var subscription = await RedisAsync.CreateSubscriptionAsync()) + { + subscription.OnSubscribeAsync += channel => + { + Log("Subscribed to '{0}'", channelWildcard); + Assert.That(channel, Is.EqualTo(channelWildcard)); + return default; + }; + subscription.OnUnSubscribeAsync += channel => + { + Log("UnSubscribed from '{0}'", channelWildcard); + Assert.That(channel, Is.EqualTo(channelWildcard)); + return default; + }; + subscription.OnMessageAsync += async (channel, msg) => + { + Log("Received '{0}' from channel '{1}'", msg, channel); + Assert.That(channel, Is.EqualTo(channelName)); + Assert.That(msg, Is.EqualTo(message), "we should get the message, not the channel"); + await subscription.UnSubscribeFromChannelsMatchingAsync(new string[0]); + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + Log("Publishing '{0}' to '{1}'", message, channelName); + await redisClient.PublishMessageAsync(channelName, message); + }); + + Log("Start Listening On " + channelName); + await subscription.SubscribeToChannelsMatchingAsync(new[] { channelWildcard }); //blocking + } + + Log("Using as normal client again..."); + await RedisAsync.IncrementValueAsync(key); + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo(2)); + } + + [Test] + public async Task Can_Subscribe_and_Publish_multiple_message() + { + var channelName = PrefixedKey("CHANNEL2"); + const string messagePrefix = "MESSAGE "; + string key = PrefixedKey("Can_Subscribe_and_Publish_multiple_message"); + const int publishMessageCount = 5; + var messagesReceived = 0; + + await RedisAsync.IncrementValueAsync(key); + + await using (var subscription = await RedisAsync.CreateSubscriptionAsync()) + { + subscription.OnSubscribeAsync += channel => + { + Log("Subscribed to '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelName)); + return default; + }; + subscription.OnUnSubscribeAsync += channel => + { + Log("UnSubscribed from '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelName)); + return default; + }; + subscription.OnMessageAsync += async (channel, msg) => + { + Log("Received '{0}' from channel '{1}'", msg, channel); + Assert.That(channel, Is.EqualTo(channelName)); + Assert.That(msg, Is.EqualTo(messagePrefix + messagesReceived++)); + + if (messagesReceived == publishMessageCount) + { + await subscription.UnSubscribeFromAllChannelsAsync(); + } + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + for (var i = 0; i < publishMessageCount; i++) + { + var message = messagePrefix + i; + Log("Publishing '{0}' to '{1}'", message, channelName); + await redisClient.PublishMessageAsync(channelName, message); + } + }); + + Log("Start Listening On"); + await subscription.SubscribeToChannelsAsync(new[] { channelName }); //blocking + } + + Log("Using as normal client again..."); + await RedisAsync.IncrementValueAsync(key); + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo(2)); + + Assert.That(messagesReceived, Is.EqualTo(publishMessageCount)); + } + + [Test] + public async Task Can_Subscribe_and_Publish_message_to_multiple_channels() + { + var channelPrefix = PrefixedKey("CHANNEL3 "); + const string message = "MESSAGE"; + const int publishChannelCount = 5; + var key = PrefixedKey("Can_Subscribe_and_Publish_message_to_multiple_channels"); + + var channels = new List(); + publishChannelCount.Times(i => channels.Add(channelPrefix + i)); + + var messagesReceived = 0; + var channelsSubscribed = 0; + var channelsUnSubscribed = 0; + + await RedisAsync.IncrementValueAsync(key); + + await using (var subscription = await RedisAsync.CreateSubscriptionAsync()) + { + subscription.OnSubscribeAsync += channel => + { + Log("Subscribed to '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelPrefix + channelsSubscribed++)); + return default; + }; + subscription.OnUnSubscribeAsync += channel => + { + Log("UnSubscribed from '{0}'", channel); + Assert.That(channel, Is.EqualTo(channelPrefix + channelsUnSubscribed++)); + return default; + }; + subscription.OnMessageAsync += async (channel, msg) => + { + Log("Received '{0}' from channel '{1}'", msg, channel); + Assert.That(channel, Is.EqualTo(channelPrefix + messagesReceived++)); + Assert.That(msg, Is.EqualTo(message)); + + await subscription.UnSubscribeFromChannelsAsync(new[] { channel }); + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + foreach (var channel in channels) + { + Log("Publishing '{0}' to '{1}'", message, channel); + await redisClient.PublishMessageAsync(channel, message); + } + }); + + Log("Start Listening On"); + await subscription.SubscribeToChannelsAsync(channels.ToArray()); //blocking + } + + Log("Using as normal client again..."); + await RedisAsync.IncrementValueAsync(key); + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo(2)); + + Assert.That(messagesReceived, Is.EqualTo(publishChannelCount)); + Assert.That(channelsSubscribed, Is.EqualTo(publishChannelCount)); + Assert.That(channelsUnSubscribed, Is.EqualTo(publishChannelCount)); + } + + [Test] + public async Task Can_Subscribe_to_channel_pattern() + { + int msgs = 0; + await using var subscription = await RedisAsync.CreateSubscriptionAsync(); + subscription.OnMessageAsync += async (channel, msg) => + { + Debug.WriteLine(String.Format("{0}: {1}", channel, msg + msgs++)); + await subscription.UnSubscribeFromChannelsMatchingAsync(new[] { PrefixedKey("CHANNEL4:TITLE*") }); + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + Log("Publishing msg..."); + await redisClient.PublishMessageAsync(PrefixedKey("CHANNEL4:TITLE1"), "hello"); // .ToUtf8Bytes() + }); + + Log("Start Listening On"); + await subscription.SubscribeToChannelsMatchingAsync(new[] { PrefixedKey("CHANNEL4:TITLE*") }); + } + + [Test] + public async Task Can_Subscribe_to_multiplechannel_pattern() + { + var channels = new[] { PrefixedKey("CHANNEL5:TITLE*"), PrefixedKey("CHANNEL5:BODY*") }; + int msgs = 0; + await using var subscription = await RedisAsync.CreateSubscriptionAsync(); + subscription.OnMessageAsync += async (channel, msg) => + { + Debug.WriteLine(String.Format("{0}: {1}", channel, msg + msgs++)); + await subscription.UnSubscribeFromChannelsMatchingAsync(channels); + }; + + ThreadPool.QueueUserWorkItem(async x => + { + await Task.Delay(100); // to be sure that we have subscribers + + await using var redisClient = CreateRedisClient().ForAsyncOnly(); + Log("Publishing msg..."); + await redisClient.PublishMessageAsync(PrefixedKey("CHANNEL5:BODY"), "hello"); // .ToUtf8Bytes() + }); + + Log("Start Listening On"); + await subscription.SubscribeToChannelsMatchingAsync(channels); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisScanTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisScanTests.Async.cs new file mode 100644 index 00000000..b5a79762 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisScanTests.Async.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisScanTestsAsync + : RedisClientTestsBaseAsync + { + [Test] + public async Task Can_scan_10_collection() + { + await RedisAsync.FlushAllAsync(); + var keys = 10.Times(x => "KEY" + x); + await RedisAsync.SetAllAsync(keys.ToSafeDictionary(x => x)); + + var ret = await NativeAsync.ScanAsync(0); + + Assert.That(ret.Cursor, Is.GreaterThanOrEqualTo(0)); + Assert.That(ret.AsStrings(), Is.EquivalentTo(keys)); + } + + [Test] + public async Task Can_scan_100_collection_over_cursor() + { + var allKeys = new HashSet(); + await RedisAsync.FlushAllAsync(); + var keys = 100.Times(x => "KEY" + x); + await RedisAsync.SetAllAsync(keys.ToSafeDictionary(x => x)); + + var i = 0; + var ret = new ScanResult(); + while (true) + { + ret = await NativeAsync.ScanAsync(ret.Cursor, 10); + i++; + ret.AsStrings().ForEach(x => allKeys.Add(x)); + if (ret.Cursor == 0) break; + } + + Assert.That(i, Is.GreaterThanOrEqualTo(2)); + Assert.That(allKeys.Count, Is.EqualTo(keys.Count)); + Assert.That(allKeys, Is.EquivalentTo(keys)); + } + + [Test] + public async Task Can_scan_and_search_10_collection() + { + await RedisAsync.FlushAllAsync(); + var keys = 11.Times(x => "KEY" + x); + await RedisAsync.SetAllAsync(keys.ToSafeDictionary(x => x)); + + var ret = await NativeAsync.ScanAsync(0, 11, match: "KEY1*"); + + Assert.That(ret.Cursor, Is.GreaterThanOrEqualTo(0)); + Assert.That(ret.AsStrings(), Is.EquivalentTo(new[] { "KEY1", "KEY10" })); + } + + [Test] + public async Task Can_SScan_10_sets() + { + await RedisAsync.FlushAllAsync(); + var items = 10.Times(x => "item" + x); + await items.ForEachAsync(async x => await RedisAsync.AddItemToSetAsync("scanset", x)); + + var ret = await NativeAsync.SScanAsync("scanset", 0); + + Assert.That(ret.Cursor, Is.GreaterThanOrEqualTo(0)); + Assert.That(ret.AsStrings(), Is.EquivalentTo(items)); + } + + [Test] + public async Task Can_ZScan_10_sortedsets() + { + await RedisAsync.FlushAllAsync(); + var items = 10.Times(x => "item" + x); + var i = 0; + await items.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync("scanzset", x, i++)); + + var ret = await NativeAsync.ZScanAsync("scanzset", 0); + var itemsWithScore = ret.AsItemsWithScores(); + + Assert.That(itemsWithScore.Keys, Is.EqualTo(items)); + Assert.That(itemsWithScore.Values, Is.EqualTo(10.Times(x => (double)x))); + } + + [Test] + public async Task Can_HScan_10_hashes() + { + await RedisAsync.FlushAllAsync(); + var values = 10.Times(x => "VALUE" + x); + await RedisAsync.SetRangeInHashAsync("scanhash", values.ToSafeDictionary(x => x.Replace("VALUE", "KEY"))); + + var ret = await NativeAsync.HScanAsync("scanhash", 0); + + var keyValues = ret.AsKeyValues(); + + Assert.That(ret.Cursor, Is.GreaterThanOrEqualTo(0)); + Assert.That(keyValues.Keys, Is.EquivalentTo(values.ConvertAll(x => x.Replace("VALUE", "KEY")))); + Assert.That(keyValues.Values, Is.EquivalentTo(values)); + } + + [Test] + public async Task Does_lazy_scan_all_keys() + { + await RedisAsync.FlushAllAsync(); + var keys = 100.Times(x => "KEY" + x); + await RedisAsync.SetAllAsync(keys.ToSafeDictionary(x => x)); + + var scanAllKeys = RedisAsync.ScanAllKeysAsync(pageSize: 10); + var tenKeys = await scanAllKeys.TakeAsync(10).ToListAsync(); + + Assert.That(tenKeys.Count, Is.EqualTo(10)); + + Assert.That(await scanAllKeys.CountAsync(), Is.EqualTo(100)); + } + + [Test] + public async Task Does_lazy_scan_all_set_items() + { + await RedisAsync.FlushAllAsync(); + var items = 100.Times(x => "item" + x); + await items.ForEachAsync(async x => await RedisAsync.AddItemToSetAsync("scanset", x)); + + var scanAllItems = RedisAsync.ScanAllSetItemsAsync("scanset", pageSize: 10); + var tenKeys = await scanAllItems.TakeAsync(10).ToListAsync(); + + Assert.That(tenKeys.Count, Is.EqualTo(10)); + + Assert.That(await scanAllItems.CountAsync(), Is.EqualTo(100)); + } + + [Test] + public async Task Does_lazy_scan_all_sortedset_items() + { + await RedisAsync.FlushAllAsync(); + var items = 100.Times(x => "item" + x); + var i = 0; + await items.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync("scanzset", x, i++)); + + var scanAllItems = RedisAsync.ScanAllSortedSetItemsAsync("scanzset", pageSize: 10); + var tenKeys = await scanAllItems.TakeAsync(10).ToListAsync(); + + Assert.That(tenKeys.Count, Is.EqualTo(10)); + + Assert.That(await scanAllItems.CountAsync(), Is.EqualTo(100)); + + var map = await scanAllItems.ToDictionaryAsync(x => x.Key, x => x.Value); + Assert.That(map.Keys, Is.EquivalentTo(items)); + } + + [Test] + public async Task Does_lazy_scan_all_hash_items() + { + await RedisAsync.FlushAllAsync(); + var values = 100.Times(x => "VALUE" + x); + await RedisAsync.SetRangeInHashAsync("scanhash", values.ToSafeDictionary(x => x.Replace("VALUE", "KEY"))); + + var scanAllItems = RedisAsync.ScanAllHashEntriesAsync("scanhash", pageSize: 10); + var tenKeys = await scanAllItems.TakeAsync(10).ToListAsync(); + + Assert.That(tenKeys.Count, Is.EqualTo(10)); + + Assert.That(await scanAllItems.CountAsync(), Is.EqualTo(100)); + + var map = await scanAllItems.ToDictionaryAsync(x => x.Key, x => x.Value); + Assert.That(map.Values, Is.EquivalentTo(values)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisStatsTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisStatsTests.Async.cs new file mode 100644 index 00000000..2fd145ef --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisStatsTests.Async.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisStatsTestsAsync + : RedisClientTestsBaseAsync + { + [OneTimeSetUp] + public void OneTimeSetUp() + { + RedisConfig.AssumeServerVersion = 2821; + } + + [Test] + [Ignore("too long")] + public async Task Batch_and_Pipeline_requests_only_counts_as_1_request() + { + var reqCount = RedisNativeClient.RequestsPerHour; + + var map = new Dictionary(); + 10.Times(i => map["key" + i] = "value" + i); + + await RedisAsync.SetValuesAsync(map); + + Assert.That(RedisNativeClient.RequestsPerHour, Is.EqualTo(reqCount + 1)); + + var keyTypes = new Dictionary(); + await using (var pipeline = RedisAsync.CreatePipeline()) + { + map.Keys.Each(key => + pipeline.QueueCommand(r => r.TypeAsync(key), x => keyTypes[key] = x)); + + await pipeline.FlushAsync(); + } + + Assert.That(RedisNativeClient.RequestsPerHour, Is.EqualTo(reqCount + 2)); + Assert.That(keyTypes.Count, Is.EqualTo(map.Count)); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisTemplateTests.cs b/tests/ServiceStack.Redis.Tests/RedisTemplateTests.cs index be3728bb..03876a1d 100644 --- a/tests/ServiceStack.Redis.Tests/RedisTemplateTests.cs +++ b/tests/ServiceStack.Redis.Tests/RedisTemplateTests.cs @@ -1,4 +1,5 @@ using NUnit.Framework; +using ServiceStack.Script; using ServiceStack.Templates; namespace ServiceStack.Redis.Tests @@ -8,38 +9,38 @@ class RedisTemplateTests [Test] public void Does_build_connection_string() { - var context = new TemplateContext + var context = new ScriptContext { - TemplateFilters = { new TemplateRedisFilters() } + ScriptMethods = { new RedisScripts() } }; context.Container.AddSingleton(() => new RedisManagerPool()); context.Init(); - Assert.That(context.EvaluateTemplate("{{ redisToConnectionString: host:7000?db=1 }}"), + Assert.That(context.EvaluateScript("{{ redisToConnectionString: host:7000?db=1 }}"), Is.EqualTo("host:7000?db=1")); - Assert.That(context.EvaluateTemplate("{{ { host: 'host' } | redisToConnectionString }}"), + Assert.That(context.EvaluateScript("{{ { host: 'host' } | redisToConnectionString }}"), Is.EqualTo("host:6379?db=0")); - Assert.That(context.EvaluateTemplate("{{ { port: 7000 } | redisToConnectionString }}"), + Assert.That(context.EvaluateScript("{{ { port: 7000 } | redisToConnectionString }}"), Is.EqualTo("localhost:7000?db=0")); - Assert.That(context.EvaluateTemplate("{{ { db: 1 } | redisToConnectionString }}"), + Assert.That(context.EvaluateScript("{{ { db: 1 } | redisToConnectionString }}"), Is.EqualTo("localhost:6379?db=1")); - Assert.That(context.EvaluateTemplate("{{ { host: 'host', port: 7000, db: 1 } | redisToConnectionString }}"), + Assert.That(context.EvaluateScript("{{ { host: 'host', port: 7000, db: 1 } | redisToConnectionString }}"), Is.EqualTo("host:7000?db=1")); - Assert.That(context.EvaluateTemplate("{{ { host: 'host', port: 7000, db: 1, password:'secret' } | redisToConnectionString | raw }}"), + Assert.That(context.EvaluateScript("{{ { host: 'host', port: 7000, db: 1, password:'secret' } | redisToConnectionString | raw }}"), Is.EqualTo("host:7000?db=1&password=secret")); - Assert.That(context.EvaluateTemplate("{{ redisConnectionString }}"), + Assert.That(context.EvaluateScript("{{ redisConnectionString }}"), Is.EqualTo("localhost:6379?db=0")); - Assert.That(context.EvaluateTemplate("{{ { db: 1 } | redisChangeConnection }}"), + Assert.That(context.EvaluateScript("{{ { db: 1 } | redisChangeConnection }}"), Is.EqualTo("localhost:6379?db=1")); - Assert.That(context.EvaluateTemplate("{{ redisConnectionString }}"), + Assert.That(context.EvaluateScript("{{ redisConnectionString }}"), Is.EqualTo("localhost:6379?db=1")); } } diff --git a/tests/ServiceStack.Redis.Tests/RedisTransactionCommonTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisTransactionCommonTests.Async.cs new file mode 100644 index 00000000..6ecd123b --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisTransactionCommonTests.Async.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisTransactionCommonTestsAsync + : RedisClientTestsBaseAsync + { + private const string Prefix = "tran"; + + public override void OnAfterEachTest() + { + CleanMask = Prefix + "*"; + base.OnAfterEachTest(); + } + + [Test] + public async Task Can_Set_and_Expire_key_in_atomic_transaction() + { + var oneSec = TimeSpan.FromSeconds(1); + + Assert.That(await RedisAsync.GetValueAsync(Prefix + "key"), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) //Calls 'MULTI' + { + trans.QueueCommand(r => r.SetValueAsync(Prefix + "key", "a")); //Queues 'SET key a' + trans.QueueCommand(r => r.ExpireEntryInAsync(Prefix + "key", oneSec)); //Queues 'EXPIRE key 1' + + await trans.CommitAsync(); //Calls 'EXEC' + + } //Calls 'DISCARD' if 'EXEC' wasn't called + + Assert.That(await RedisAsync.GetValueAsync(Prefix + "key"), Is.EqualTo("a")); + await Task.Delay(TimeSpan.FromSeconds(2)); + Assert.That(await RedisAsync.GetValueAsync(Prefix + "key"), Is.Null); + } + + [Test] + public async Task Can_Pop_priority_message_from_SortedSet_and_Add_to_workq_in_atomic_transaction() + { + var messages = new List { "message4", "message3", "message2" }; + + await RedisAsync.AddItemToListAsync(Prefix + "workq", "message1"); + + var priority = 1; + await messages.ForEachAsync(async x => await RedisAsync.AddItemToSortedSetAsync(Prefix + "prioritymsgs", x, priority++)); + + var highestPriorityMessage = await RedisAsync.PopItemWithHighestScoreFromSortedSetAsync(Prefix + "prioritymsgs"); + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.RemoveItemFromSortedSetAsync(Prefix + "prioritymsgs", highestPriorityMessage)); + trans.QueueCommand(r => r.AddItemToListAsync(Prefix + "workq", highestPriorityMessage)); + + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetAllItemsFromListAsync(Prefix + "workq"), + Is.EquivalentTo(new List { "message1", "message2" })); + Assert.That(await RedisAsync.GetAllItemsFromSortedSetAsync(Prefix + "prioritymsgs"), + Is.EquivalentTo(new List { "message3", "message4" })); + } + + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RedisTransactionTests.Async.cs b/tests/ServiceStack.Redis.Tests/RedisTransactionTests.Async.cs new file mode 100644 index 00000000..3d11b6b4 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RedisTransactionTests.Async.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture] + public class RedisTransactionTestsAsync + : RedisClientTestsBaseAsync + { + private const string Key = "rdtmultitest"; + private const string ListKey = "rdtmultitest-list"; + private const string SetKey = "rdtmultitest-set"; + private const string SortedSetKey = "rdtmultitest-sortedset"; + private const string HashKey = "rdthashtest"; + + public override void OnAfterEachTest() + { + CleanMask = Key + "*"; + base.OnAfterEachTest(); + } + + [Test] + public async Task Can_call_single_operation_in_transaction() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + var map = new Dictionary(); + trans.QueueCommand(r => r.GetAsync(Key).AsValueTask(), y => map[Key] = y); + + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + } + + [Test] + public async Task No_commit_of_atomic_transactions_discards_all_commands() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + } + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + } + + [Test] + public async Task Watch_aborts_transaction() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + const string value1 = "value1"; + try + { + await RedisAsync.WatchAsync(new[] { Key }); + await RedisAsync.SetAsync(Key, value1); + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => r.SetAsync(Key, value1).AsValueTask()); + var success = await trans.CommitAsync(); + Assert.False(success); + Assert.AreEqual(value1, await RedisAsync.GetAsync(Key)); + } + catch (NotSupportedException) + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + } + } + + [Test] + public async Task Exception_in_atomic_transactions_discards_all_commands() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + try + { + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + throw new NotSupportedException(); + } + catch (NotSupportedException) + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + } + } + + [Test] + public async Task Can_call_single_operation_3_Times_in_transaction() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("3")); + } + + [Test] + public async Task Can_call_single_operation_with_callback_3_Times_in_transaction() + { + var results = new List(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + trans.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + trans.QueueCommand(r => r.IncrementValueAsync(Key), results.Add); + + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("3")); + Assert.That(results, Is.EquivalentTo(new List { 1, 2, 3 })); + } + + [Test] + public async Task Supports_different_operation_types_in_same_transaction() + { + var incrementResults = new List(); + var collectionCounts = new List(); + var containsItem = false; + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + trans.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem1")); + trans.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem2")); + trans.QueueCommand(r => r.AddItemToSetAsync(SetKey, "setitem")); + trans.QueueCommand(r => r.SetContainsItemAsync(SetKey, "setitem"), b => containsItem = b); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem1")); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem2")); + trans.QueueCommand(r => r.AddItemToSortedSetAsync(SortedSetKey, "sortedsetitem3")); + trans.QueueCommand(r => r.GetListCountAsync(ListKey), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.GetSetCountAsync(SetKey), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.GetSortedSetCountAsync(SortedSetKey), intResult => collectionCounts.Add(intResult)); + trans.QueueCommand(r => r.IncrementValueAsync(Key), intResult => incrementResults.Add(intResult)); + + await trans.CommitAsync(); + } + + Assert.That(containsItem, Is.True); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("2")); + Assert.That(incrementResults, Is.EquivalentTo(new List { 1, 2 })); + Assert.That(collectionCounts, Is.EquivalentTo(new List { 2, 1, 3 })); + Assert.That(await RedisAsync.GetAllItemsFromListAsync(ListKey), Is.EquivalentTo(new List { "listitem1", "listitem2" })); + Assert.That(await RedisAsync.GetAllItemsFromSetAsync(SetKey), Is.EquivalentTo(new List { "setitem" })); + Assert.That(await RedisAsync.GetAllItemsFromSortedSetAsync(SortedSetKey), Is.EquivalentTo(new List { "sortedsetitem1", "sortedsetitem2", "sortedsetitem3" })); + } + + [Test] + public async Task Can_call_multi_string_operations_in_transaction() + { + string item1 = null; + string item4 = null; + + var results = new List(); + Assert.That(await RedisAsync.GetListCountAsync(ListKey), Is.EqualTo(0)); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem1")); + trans.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem2")); + trans.QueueCommand(r => r.AddItemToListAsync(ListKey, "listitem3")); + trans.QueueCommand(r => r.GetAllItemsFromListAsync(ListKey), x => results = x); + trans.QueueCommand(r => r.GetItemFromListAsync(ListKey, 0), x => item1 = x); + trans.QueueCommand(r => r.GetItemFromListAsync(ListKey, 4), x => item4 = x); + + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetListCountAsync(ListKey), Is.EqualTo(3)); + Assert.That(results, Is.EquivalentTo(new List { "listitem1", "listitem2", "listitem3" })); + Assert.That(item1, Is.EqualTo("listitem1")); + Assert.That(item4, Is.Null); + } + [Test] + public async Task Can_call_multiple_setexs_in_transaction() + { + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + var keys = new[] { "key1", "key2", "key3" }; + var values = new[] { "1", "2", "3" }; + await using var trans = await RedisAsync.CreateTransactionAsync(); + + for (int i = 0; i < 3; ++i) + { + int index0 = i; + trans.QueueCommand(r => ((IRedisNativeClientAsync)r).SetExAsync(keys[index0], 100, GetBytes(values[index0]))); + } + + await trans.CommitAsync(); + await trans.ReplayAsync(); + + + for (int i = 0; i < 3; ++i) + Assert.AreEqual(await RedisAsync.GetValueAsync(keys[i]), values[i]); + } + [Test] + // Operations that are not supported in older versions will look at server info to determine what to do. + // If server info is fetched each time, then it will interfer with transaction + public async Task Can_call_operation_not_supported_on_older_servers_in_transaction() + { + var temp = new byte[1]; + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => ((IRedisNativeClientAsync)r).SetExAsync(Key, 5, temp)); + await trans.CommitAsync(); + } + + + [Test] + public async Task Transaction_can_be_replayed() + { + string KeySquared = Key + Key; + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + await using var trans = await RedisAsync.CreateTransactionAsync(); + trans.QueueCommand(r => r.IncrementValueAsync(Key)); + trans.QueueCommand(r => r.IncrementValueAsync(KeySquared)); + await trans.CommitAsync(); + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + await NativeAsync.DelAsync(Key); + await NativeAsync.DelAsync(KeySquared); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + + await trans.ReplayAsync(); + await trans.DisposeAsync(); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("1")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.EqualTo("1")); + } + + [Test] + public async Task Transaction_can_issue_watch() + { + await NativeAsync.DelAsync(Key); + Assert.That(await RedisAsync.GetValueAsync(Key), Is.Null); + + string KeySquared = Key + Key; + await NativeAsync.DelAsync(KeySquared); + + await RedisAsync.WatchAsync(new[] { Key, KeySquared }); + await RedisAsync.SetAsync(Key, 7); + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.SetAsync(Key, 1).AsValueTask()); + trans.QueueCommand(r => r.SetAsync(KeySquared, 2).AsValueTask()); + await trans.CommitAsync(); + } + + Assert.That(await RedisAsync.GetValueAsync(Key), Is.EqualTo("7")); + Assert.That(await RedisAsync.GetValueAsync(KeySquared), Is.Null); + } + + [Test] + public async Task Can_set_Expiry_on_key_in_transaction() + { + var expiresIn = TimeSpan.FromMinutes(15); + + const string key = "No TTL-Transaction"; + var keyWithTtl = "{0}s TTL-Transaction".Fmt(expiresIn.TotalSeconds); + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddAsync(key, "Foo").AsValueTask()); + trans.QueueCommand(r => r.AddAsync(keyWithTtl, "Bar", expiresIn).AsValueTask()); + + if (!await trans.CommitAsync()) + throw new Exception("Transaction Failed"); + } + + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo("Foo")); + Assert.That(await RedisAsync.GetAsync(keyWithTtl), Is.EqualTo("Bar")); + + Assert.That(await RedisAsync.GetTimeToLiveAsync(key), Is.EqualTo(TimeSpan.MaxValue)); + Assert.That((await RedisAsync.GetTimeToLiveAsync(keyWithTtl)).Value.TotalSeconds, Is.GreaterThan(1)); + } + + [Test] + public async Task Does_not_set_Expiry_on_existing_key_in_transaction() + { + var expiresIn = TimeSpan.FromMinutes(15); + + var key = "Exting TTL-Transaction"; + await RedisAsync.AddAsync(key, "Foo"); + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.AddAsync(key, "Bar", expiresIn).AsValueTask()); + + if (!await trans.CommitAsync()) + throw new Exception("Transaction Failed"); + } + + Assert.That(await RedisAsync.GetAsync(key), Is.EqualTo("Foo")); + Assert.That(await RedisAsync.GetTimeToLiveAsync(key), Is.EqualTo(TimeSpan.MaxValue)); + } + + [Test] + public async Task Can_call_GetAllEntriesFromHash_in_transaction() + { + var stringMap = new Dictionary { + {"one","a"}, {"two","b"}, {"three","c"}, {"four","d"} + }; + foreach (var x in stringMap) + { + await RedisAsync.SetEntryInHashAsync(HashKey, x.Key, x.Value); + } + + Dictionary results = null; + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.GetAllEntriesFromHashAsync(HashKey), x => results = x); + + await trans.CommitAsync(); + } + + Assert.That(results, Is.EquivalentTo(stringMap)); + } + + [Test] + public async Task Can_call_Type_in_transaction() + { + await RedisAsync.SetValueAsync("string", "STRING"); + await RedisAsync.AddItemToListAsync("list", "LIST"); + await RedisAsync.AddItemToSetAsync("set", "SET"); + await RedisAsync.AddItemToSortedSetAsync("zset", "ZSET", 1); + + var keys = new[] { "string", "list", "set", "zset" }; + + var results = new Dictionary(); + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + foreach (var key in keys) + { + trans.QueueCommand(r => r.TypeAsync(key), x => results[key] = x); + } + + await trans.CommitAsync(); + } + + results.PrintDump(); + + Assert.That(results, Is.EquivalentTo(new Dictionary + { + {"string", "string" }, + {"list", "list" }, + {"set", "set" }, + {"zset", "zset" }, + })); + } + + [Test] + public async Task Can_call_HashSet_commands_in_transaction() + { + await RedisAsync.AddItemToSetAsync("set", "ITEM 1"); + await RedisAsync.AddItemToSetAsync("set", "ITEM 2"); + HashSet result = null; + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.GetAllItemsFromSetAsync("set"), values => result = values); + + await trans.CommitAsync(); + } + + Assert.That(result, Is.EquivalentTo(new[] { "ITEM 1", "ITEM 2" })); + } + + [Test] + public async Task Can_call_LUA_Script_in_transaction() + { + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.ExecLuaAsync("return {'myval', 'myotherval'}", new string[0])); + + await trans.CommitAsync(); + } + + RedisText result = null; + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(r => r.ExecLuaAsync("return {'myval', 'myotherval'}", new string[0]), s => result = s); + + await trans.CommitAsync(); + } + + Assert.That(result.Children[0].Text, Is.EqualTo("myval")); + Assert.That(result.Children[1].Text, Is.EqualTo("myotherval")); + } + + [Test] + public async Task Can_call_SetValueIfNotExists_in_transaction() + { + bool f = false; + bool s = false; + + await using (var trans = await RedisAsync.CreateTransactionAsync()) + { + trans.QueueCommand(c => c.SetValueIfNotExistsAsync("foo", "blah"), r => f = r); + trans.QueueCommand(c => c.SetValueIfNotExistsAsync("bar", "blah"), r => s = r); + await trans.CommitAsync(); + } + + Assert.That(f); + Assert.That(s); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RetryCommandTests.Async.cs b/tests/ServiceStack.Redis.Tests/RetryCommandTests.Async.cs new file mode 100644 index 00000000..6e2ed462 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/RetryCommandTests.Async.cs @@ -0,0 +1,143 @@ +using NUnit.Framework; +using System; +using System.Linq; +using System.Net.Sockets; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Async")] + public class RetryCommandTestsAsync + { + [Test, Ignore("3 vs 2 needs investigation; does same in non-async")] + public async Task Does_retry_failed_commands() + { + // warning: this test looks brittle; is often failing "Expected: 3 But was: 2" (on main branch); + + // LogManager.LogFactory = new ConsoleLogFactory(debugEnabled: true); + // RedisConfig.EnableVerboseLogging = true; + RedisStats.Reset(); + + var redisCtrl = new RedisClient(RedisConfig.DefaultHost).ForAsyncOnly(); + await redisCtrl.FlushAllAsync(); + await redisCtrl.SetClientAsync("redisCtrl"); + + var redis = new RedisClient(RedisConfig.DefaultHost).ForAsyncOnly(); + await redis.SetClientAsync("redisRetry"); + + var clientInfo = await redisCtrl.GetClientsInfoAsync(); + var redisId = clientInfo.First(m => m["name"] == "redisRetry")["id"]; + Assert.That(redisId.Length, Is.GreaterThan(0)); + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(1)); + + ((RedisClient)redis).OnBeforeFlush = () => + { + ((IRedisClient)redisCtrl).KillClients(withId: redisId); + }; + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(2)); + Assert.That(await redis.GetAsync("retryCounter"), Is.EqualTo(3)); + + Assert.That(RedisStats.TotalRetryCount, Is.EqualTo(1)); + Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(1)); + Assert.That(RedisStats.TotalRetryTimedout, Is.EqualTo(0)); + } + + [Test] + public async Task Does_retry_failed_commands_with_SocketException() + { + RedisStats.Reset(); + + var redis = new RedisClient(RedisConfig.DefaultHost).ForAsyncOnly(); + await redis.FlushAllAsync(); + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(1)); + + ((RedisClient)redis).OnBeforeFlush = () => + { + ((RedisClient)redis).OnBeforeFlush = null; + throw new SocketException(); + }; + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(2)); + Assert.That(await redis.GetAsync("retryCounter"), Is.EqualTo(3)); + + Assert.That(RedisStats.TotalRetryCount, Is.EqualTo(1)); + Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(1)); + Assert.That(RedisStats.TotalRetryTimedout, Is.EqualTo(0)); + } + + [Test] + public async Task Does_Timeout_with_repeated_SocketException() + { + RedisConfig.Reset(); + RedisConfig.DefaultRetryTimeout = 100; + + var redis = new RedisClient(RedisConfig.DefaultHost).ForAsyncOnly(); + await redis.FlushAllAsync(); + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(1)); + + ((RedisClient)redis).OnBeforeFlush = () => + { + throw new SocketException(); + }; + + try + { + await redis.IncrementValueAsync("retryCounter"); + Assert.Fail("Should throw"); + } + catch (RedisException ex) + { + Assert.That(ex.Message, Does.StartWith("Exceeded timeout")); + + ((RedisClient)redis).OnBeforeFlush = null; + Assert.That(await redis.GetAsync("retryCounter"), Is.EqualTo(1)); + + Assert.That(RedisStats.TotalRetryCount, Is.GreaterThan(1)); + Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(0)); + Assert.That(RedisStats.TotalRetryTimedout, Is.EqualTo(1)); + } + + RedisConfig.Reset(); + } + + [Test] + public async Task Does_not_retry_when_RetryTimeout_is_Zero() + { + RedisConfig.Reset(); + RedisConfig.DefaultRetryTimeout = 0; + + var redis = new RedisClient(RedisConfig.DefaultHost).ForAsyncOnly(); + await redis.FlushAllAsync(); + + Assert.That(await redis.IncrementValueAsync("retryCounter"), Is.EqualTo(1)); + + ((RedisClient)redis).OnBeforeFlush = () => + { + throw new SocketException(); + }; + + try + { + await redis.IncrementValueAsync("retryCounter"); + Assert.Fail("Should throw"); + } + catch (Exception ex) + { + Assert.That(ex.Message, Does.StartWith("Exceeded timeout")); + + ((RedisClient)redis).OnBeforeFlush = null; + Assert.That(await redis.GetAsync("retryCounter"), Is.EqualTo(1)); + + Assert.That(RedisStats.TotalRetryCount, Is.EqualTo(0)); + Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(0)); + Assert.That(RedisStats.TotalRetryTimedout, Is.EqualTo(1)); + } + + RedisConfig.Reset(); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/RetryCommandTests.cs b/tests/ServiceStack.Redis.Tests/RetryCommandTests.cs index b716177d..e81575d4 100644 --- a/tests/ServiceStack.Redis.Tests/RetryCommandTests.cs +++ b/tests/ServiceStack.Redis.Tests/RetryCommandTests.cs @@ -14,8 +14,10 @@ public class RetryCommandTests [Test] public void Does_retry_failed_commands() { - //LogManager.LogFactory = new ConsoleLogFactory(debugEnabled: true); + // warning: this test looks brittle; is often failing "Expected: 3 But was: 2" (on main branch); + // LogManager.LogFactory = new ConsoleLogFactory(debugEnabled: true); + // RedisConfig.EnableVerboseLogging = true; RedisStats.Reset(); var redisCtrl = new RedisClient(RedisConfig.DefaultHost); @@ -37,7 +39,7 @@ public void Does_retry_failed_commands() }; Assert.That(redis.IncrementValue("retryCounter"), Is.EqualTo(2)); - Assert.That(redis.Get("retryCounter"), Is.EqualTo(2)); + Assert.That(redis.Get("retryCounter"), Is.EqualTo(3)); Assert.That(RedisStats.TotalRetryCount, Is.EqualTo(1)); Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(1)); @@ -61,7 +63,7 @@ public void Does_retry_failed_commands_with_SocketException() }; Assert.That(redis.IncrementValue("retryCounter"), Is.EqualTo(2)); - Assert.That(redis.Get("retryCounter"), Is.EqualTo(2)); + Assert.That(redis.Get("retryCounter"), Is.EqualTo(3)); Assert.That(RedisStats.TotalRetryCount, Is.EqualTo(1)); Assert.That(RedisStats.TotalRetrySuccess, Is.EqualTo(1)); diff --git a/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj b/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj index f20a1f5a..374a13f9 100644 --- a/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj +++ b/tests/ServiceStack.Redis.Tests/ServiceStack.Redis.Tests.csproj @@ -1,6 +1,13 @@  - net46;netcoreapp2.1 + + net472;net6.0 portable ServiceStack.Redis.Tests Library @@ -16,15 +23,23 @@ - - - - + + + + + - + $(DefineConstants);NET45 - + + + + + + $(DefineConstants);NETCORE + + @@ -33,30 +48,7 @@ - - - - - - - $(DefineConstants);NETCORE_SUPPORT;NETCORE - - - - Exe - $(DefineConstants);NUNITLITE - - - - - - - - - + \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/ShippersExample.Async.cs b/tests/ServiceStack.Redis.Tests/ShippersExample.Async.cs new file mode 100644 index 00000000..c0154116 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/ShippersExample.Async.cs @@ -0,0 +1,126 @@ +// +// ServiceStack.Redis: ECMA CLI Binding to the Redis key-value storage system +// +// Authors: +// Demis Bellot (demis.bellot@gmail.com) +// +// Copyright 2013 Service Stack LLC. All Rights Reserved. +// +// Licensed under the same terms of reddis and ServiceStack: new BSD license. +// + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using NUnit.Framework; +using ServiceStack.Common.Tests.Models; +using ServiceStack.Redis.Generic; +using ServiceStack.Text; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Async")] + public class ShippersExampleAsync + { + + public class Shipper + { + public long Id { get; set; } + public string CompanyName { get; set; } + public DateTime DateCreated { get; set; } + public ShipperType ShipperType { get; set; } + public Guid UniqueRef { get; set; } + } + + static void Dump(string message, T entity) + { + var text = TypeSerializer.SerializeToString(entity); + + //make it a little easier on the eyes + var prettyLines = text.Split(new[] { "[", "},{", "]" }, + StringSplitOptions.RemoveEmptyEntries) + .ToList().ConvertAll(x => x.Replace("{", "").Replace("}", "")); + + Debug.WriteLine("\n" + message); + foreach (var l in prettyLines) Debug.WriteLine(l); + } + + [Test] + public async Task Shippers_UseCase() + { + await using var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + //Create a 'strongly-typed' API that makes all Redis Value operations to apply against Shippers + IRedisTypedClientAsync redis = redisClient.As(); + + //Redis lists implement IList while Redis sets implement ICollection + var currentShippers = redis.Lists["urn:shippers:current"]; + var prospectiveShippers = redis.Lists["urn:shippers:prospective"]; + + await currentShippers.AddAsync( + new Shipper + { + Id = await redis.GetNextSequenceAsync(), + CompanyName = "Trains R Us", + DateCreated = DateTime.UtcNow, + ShipperType = ShipperType.Trains, + UniqueRef = Guid.NewGuid() + }); + + await currentShippers.AddAsync( + new Shipper + { + Id = await redis.GetNextSequenceAsync(), + CompanyName = "Planes R Us", + DateCreated = DateTime.UtcNow, + ShipperType = ShipperType.Planes, + UniqueRef = Guid.NewGuid() + }); + + var lameShipper = new Shipper + { + Id = await redis.GetNextSequenceAsync(), + CompanyName = "We do everything!", + DateCreated = DateTime.UtcNow, + ShipperType = ShipperType.All, + UniqueRef = Guid.NewGuid() + }; + + await currentShippers.AddAsync(lameShipper); + + Dump("ADDED 3 SHIPPERS:", await currentShippers.ToListAsync()); + + await currentShippers.RemoveAsync(lameShipper); + + Dump("REMOVED 1:", await currentShippers.ToListAsync()); + + await prospectiveShippers.AddAsync( + new Shipper + { + Id = await redis.GetNextSequenceAsync(), + CompanyName = "Trucks R Us", + DateCreated = DateTime.UtcNow, + ShipperType = ShipperType.Automobiles, + UniqueRef = Guid.NewGuid() + }); + + Dump("ADDED A PROSPECTIVE SHIPPER:", await prospectiveShippers.ToListAsync()); + + await redis.PopAndPushItemBetweenListsAsync(prospectiveShippers, currentShippers); + + Dump("CURRENT SHIPPERS AFTER POP n' PUSH:", await currentShippers.ToListAsync()); + Dump("PROSPECTIVE SHIPPERS AFTER POP n' PUSH:", await prospectiveShippers.ToListAsync()); + + var poppedShipper = await redis.PopItemFromListAsync(currentShippers); + Dump("POPPED a SHIPPER:", poppedShipper); + Dump("CURRENT SHIPPERS AFTER POP:", await currentShippers.ToListAsync()); + + //reset sequence and delete all lists + await redis.SetSequenceAsync(0); + await redis.RemoveEntryAsync(new[] { currentShippers, prospectiveShippers }); + Dump("DELETING CURRENT AND PROSPECTIVE SHIPPERS:", await currentShippers.ToListAsync()); + + } + + } +} diff --git a/tests/ServiceStack.Redis.Tests/SslTests.cs b/tests/ServiceStack.Redis.Tests/SslTests.cs index 65a7a51c..ae082a06 100644 --- a/tests/ServiceStack.Redis.Tests/SslTests.cs +++ b/tests/ServiceStack.Redis.Tests/SslTests.cs @@ -67,6 +67,18 @@ public void Can_connect_to_ssl_azure_redis_with_UrlFormat() } } + [Test] + public void Can_connect_to_ssl_azure_redis_with_UrlFormat_Custom_SSL_Protocol () + { + var url = "redis://{0}?ssl=true&sslprotocols=Tls12&password={1}".Fmt(Host, Password.UrlEncode()); + using (var client = new RedisClient(url)) + { + client.Set("foo", "bar"); + var foo = client.GetValue("foo"); + foo.Print(); + } + } + [Test] public void Can_connect_to_ssl_azure_redis_with_PooledClientsManager() { @@ -163,7 +175,7 @@ public void Can_connect_to_Buffered_SslStream() if (!sslStream.IsEncrypted) throw new Exception("Could not establish an encrypted connection to " + Host); - var bstream = new BufferedStream(sslStream, 16 * 1024); + var bstream = new System.IO.BufferedStream(sslStream, 16 * 1024); SendAuth(bstream); } @@ -280,7 +292,7 @@ public void SSL_can_support_64_threads_using_the_client_simultaneously() const int noOfConcurrentClients = 64; //WaitHandle.WaitAll limit is <= 64 var clientAsyncResults = new List(); - using (var manager = new PooledRedisClientManager(TestConfig.MasterHosts, TestConfig.SlaveHosts)) + using (var manager = new PooledRedisClientManager(TestConfig.MasterHosts, TestConfig.ReplicaHosts)) { manager.GetClient().Run(x => x.FlushAll()); diff --git a/tests/ServiceStack.Redis.Tests/TestConfig.cs b/tests/ServiceStack.Redis.Tests/TestConfig.cs index 0712ad75..744091c8 100644 --- a/tests/ServiceStack.Redis.Tests/TestConfig.cs +++ b/tests/ServiceStack.Redis.Tests/TestConfig.cs @@ -18,7 +18,7 @@ static TestConfig() public static string GeoHost => Environment.GetEnvironmentVariable("CI_REDIS") ?? "10.0.0.121"; public static readonly string[] MasterHosts = new[] { "localhost" }; - public static readonly string[] SlaveHosts = new[] { "localhost" }; + public static readonly string[] ReplicaHosts = new[] { "localhost" }; public const int RedisPort = 6379; diff --git a/tests/ServiceStack.Redis.Tests/TrackThreadTests.cs b/tests/ServiceStack.Redis.Tests/TrackThreadTests.cs index 2e08f59a..88ed49aa 100644 --- a/tests/ServiceStack.Redis.Tests/TrackThreadTests.cs +++ b/tests/ServiceStack.Redis.Tests/TrackThreadTests.cs @@ -18,7 +18,7 @@ public void Does_throw_when_using_same_client_on_different_threads() { var threadId = Thread.CurrentThread.ManagedThreadId.ToString(); var key = $"Thread#{threadId}"; - redis.SetValue(key, threadId); + redis.SetValue(key, threadId); ThreadPool.QueueUserWorkItem(_ => { @@ -43,7 +43,7 @@ public void Does_throw_when_using_same_client_on_different_threads() Thread.Sleep(100); - Console.WriteLine("From Test: " + redis.GetValue(key)); + Console.WriteLine("From Test: " + redis.GetValue(key)); if (poolEx == null) throw new Exception("Should throw InvalidAccessException"); diff --git a/tests/ServiceStack.Redis.Tests/UserSessionRedisClientTests.Async.cs b/tests/ServiceStack.Redis.Tests/UserSessionRedisClientTests.Async.cs new file mode 100644 index 00000000..f6736364 --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/UserSessionRedisClientTests.Async.cs @@ -0,0 +1,436 @@ +using NUnit.Framework; +using ServiceStack.Caching; +using ServiceStack.Logging; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration"), Category("Async")] + public class UserSessionTestsAsync + { + static UserSessionTestsAsync() + { + LogManager.LogFactory = new ConsoleLogFactory(); + } + + //MasterUser master; + + static readonly Guid UserClientGlobalId1 = new Guid("71A30DE3-D7AF-4B8E-BCA2-AB646EE1F3E9"); + static readonly Guid UserClientGlobalId2 = new Guid("A8D300CF-0414-4C99-A495-A7F34C93CDE1"); + static readonly string UserClientKey = new Guid("10B7D0F7-4D4E-4676-AAC7-CF0234E9133E").ToString("N"); + static readonly Guid UserId = new Guid("5697B030-A369-43A2-A842-27303A0A62BC"); + private const string UserName = "User1"; + private const string ShardId = "0"; + + readonly UserClientSession session = new UserClientSession( + Guid.NewGuid(), UserId, "192.168.0.1", UserClientKey, UserClientGlobalId1); + + private RedisClient redisCache; + + [SetUp] + public void OnBeforeEachTest() + { + redisCache = new RedisClient(TestConfig.SingleHost); + redisCache.FlushAll(); + //master = UserMasterDataAccessModel.Instance.MasterUsers.NewDataAccessObject(true); + } + + public CachedUserSessionManagerAsync GetCacheManager(ICacheClientAsync cacheClient) + { + return new CachedUserSessionManagerAsync(cacheClient); + } + + private static void AssertClientSessionsAreEqual( + UserClientSession clientSession, UserClientSession resolvedClientSession) + { + Assert.That(resolvedClientSession.Id, Is.EqualTo(clientSession.Id)); + Assert.That(resolvedClientSession.Base64ClientModulus, Is.EqualTo(clientSession.Base64ClientModulus)); + Assert.That(resolvedClientSession.IPAddress, Is.EqualTo(clientSession.IPAddress)); + Assert.That(resolvedClientSession.UserClientGlobalId, Is.EqualTo(clientSession.UserClientGlobalId)); + Assert.That(resolvedClientSession.UserId, Is.EqualTo(clientSession.UserId)); + } + + [Test] + public async Task Can_add_single_UserSession() + { + var cacheManager = GetCacheManager(redisCache); + + var clientSession = await cacheManager.StoreClientSessionAsync( + UserId, + UserName, + ShardId, + session.IPAddress, + UserClientKey, + UserClientGlobalId1); + + var resolvedClientSession = await cacheManager.GetUserClientSessionAsync( + clientSession.UserId, clientSession.Id); + + AssertClientSessionsAreEqual(clientSession, resolvedClientSession); + } + + [Test] + public async Task Can_add_multiple_UserClientSessions() + { + var cacheManager = GetCacheManager(redisCache); + + var clientSession1 = await cacheManager.StoreClientSessionAsync( + UserId, + UserName, + ShardId, + session.IPAddress, + UserClientKey, + UserClientGlobalId1); + + var clientSession2 = await cacheManager.StoreClientSessionAsync( + UserId, + UserName, + ShardId, + session.IPAddress, + UserClientKey, + UserClientGlobalId2); + + var resolvedClientSession1 = await cacheManager.GetUserClientSessionAsync( + clientSession1.UserId, clientSession1.Id); + + var resolvedClientSession2 = await cacheManager.GetUserClientSessionAsync( + clientSession2.UserId, clientSession2.Id); + + AssertClientSessionsAreEqual(clientSession1, resolvedClientSession1); + AssertClientSessionsAreEqual(clientSession2, resolvedClientSession2); + } + + [Test] + public async Task Does_remove_UserClientSession() + { + var cacheManager = GetCacheManager(redisCache); + + var clientSession1 = await cacheManager.StoreClientSessionAsync( + UserId, + UserName, + ShardId, + session.IPAddress, + UserClientKey, + UserClientGlobalId1); + + var userSession = await cacheManager.GetUserSessionAsync(UserId); + var resolvedClientSession1 = userSession.GetClientSession(clientSession1.Id); + AssertClientSessionsAreEqual(resolvedClientSession1, clientSession1); + + resolvedClientSession1.ExpiryDate = DateTime.UtcNow.AddSeconds(-1); + await cacheManager.UpdateUserSessionAsync(userSession); + + userSession = await cacheManager.GetUserSessionAsync(UserId); + Assert.That(userSession, Is.Null); + } + + } + + public class CachedUserSessionManagerAsync + { + private static readonly ILog Log = LogManager.GetLogger(typeof(CachedUserSessionManager)); + + /// + /// Google/Yahoo seems to make you to login every 2 weeks?? + /// + private readonly ICacheClientAsync cacheClient; + + /// + /// Big perf hit if we Log on every session change + /// + /// The FMT. + /// The args. + [Conditional("DEBUG")] + protected void LogIfDebug(string fmt, params object[] args) + { + if (args.Length > 0) + Log.DebugFormat(fmt, args); + else + Log.Debug(fmt); + } + + public CachedUserSessionManagerAsync(ICacheClientAsync cacheClient) + { + this.cacheClient = cacheClient; + } + + /// + /// Removes the client session. + /// + /// The user global id. + /// The client session ids. + public async ValueTask RemoveClientSession(Guid userId, ICollection clientSessionIds) + { + var userSession = await this.GetUserSessionAsync(userId); + if (userSession == null) return; + + foreach (var clientSessionId in clientSessionIds) + { + userSession.RemoveClientSession(clientSessionId); + } + await this.UpdateUserSessionAsync(userSession); + } + + /// + /// Adds a new client session. + /// Should this be changed to GetOrCreateClientSession? + /// + /// The user global id. + /// Title of the user. + /// + /// The ip address. + /// The base64 client modulus. + /// The user client global id. + /// + public async ValueTask StoreClientSessionAsync(Guid userId, string userName, string shardId, string ipAddress, string base64ClientModulus, Guid userClientGlobalId) + { + var userSession = await this.GetOrCreateSessionAsync(userId, userName, shardId); + + var existingClientSession = userSession.GetClientSessionWithClientId(userClientGlobalId); + if (existingClientSession != null) + { + userSession.RemoveClientSession(existingClientSession.Id); + } + + var newClientSession = userSession.CreateNewClientSession( + ipAddress, base64ClientModulus, userClientGlobalId); + + await this.UpdateUserSessionAsync(userSession); + + return newClientSession; + } + + /// + /// Updates the UserSession in the cache, or removes expired ones. + /// + /// The user session. + public async ValueTask UpdateUserSessionAsync(UserSessionAsync userSession) + { + var hasSessionExpired = userSession.HasExpired(); + if (hasSessionExpired) + { + LogIfDebug("Session has expired, removing: " + userSession.ToCacheKey()); + await this.cacheClient.RemoveAsync(userSession.ToCacheKey()); + } + else + { + LogIfDebug("Updating session: " + userSession.ToCacheKey()); + await this.cacheClient.ReplaceAsync(userSession.ToCacheKey(), userSession, userSession.ExpiryDate.Value); + } + } + + /// + /// Gets the user session if it exists or null. + /// + /// The user global id. + /// + public async ValueTask GetUserSessionAsync(Guid userId) + { + var cacheKey = UserSession.ToCacheKey(userId); + var bytes = await this.cacheClient.GetAsync(cacheKey); + if (bytes != null) + { + var modelStr = Encoding.UTF8.GetString(bytes); + LogIfDebug("UserSession => " + modelStr); + } + return await this.cacheClient.GetAsync(cacheKey); + } + + /// + /// Gets or create a user session if one doesn't exist. + /// + /// The user global id. + /// Title of the user. + /// + /// + public async ValueTask GetOrCreateSessionAsync(Guid userId, string userName, string shardId) + { + var userSession = await this.GetUserSessionAsync(userId); + if (userSession == null) + { + userSession = new UserSessionAsync(userId, userName, shardId); + + await this.cacheClient.AddAsync(userSession.ToCacheKey(), userSession, + userSession.ExpiryDate.GetValueOrDefault(DateTime.UtcNow) + TimeSpan.FromHours(1)); + } + return userSession; + } + + /// + /// Gets the user client session identified by the id if exists otherwise null. + /// + /// The user global id. + /// The client session id. + /// + public async ValueTask GetUserClientSessionAsync(Guid userId, Guid clientSessionId) + { + var userSession = await this.GetUserSessionAsync(userId); + return userSession != null ? userSession.GetClientSession(clientSessionId) : null; + } + } + +#if !NETCORE + [Serializable /* was required when storing in memcached, not required in Redis */] +#endif + public class UserSessionAsync + { + //Empty constructor required for TypeSerializer + public UserSessionAsync() + { + this.PublicClientSessions = new Dictionary(); + } + + public Guid UserId { get; private set; } + + public string UserName { get; private set; } + + public string ShardId { get; private set; } + + public Dictionary PublicClientSessions { get; private set; } + + public UserSessionAsync(Guid userId, string userName, string shardId) + : this() + { + this.UserId = userId; + this.UserName = userName; + this.ShardId = shardId; + } + + /// + /// Gets the max expiry date of all the users client sessions. + /// If the user has no more active client sessions we can remove them from the cache. + /// + /// The expiry date. + public DateTime? ExpiryDate + { + get + { + DateTime? maxExpiryDate = null; + + foreach (var session in this.PublicClientSessions.Values) + { + if (maxExpiryDate == null || session.ExpiryDate > maxExpiryDate) + { + maxExpiryDate = session.ExpiryDate; + } + } + return maxExpiryDate; + } + } + + /// + /// Creates a new client session for the user. + /// + /// The ip address. + /// The base64 client modulus. + /// The user client global id. + /// + public UserClientSession CreateNewClientSession(string ipAddress, string base64ClientModulus, Guid userClientGlobalId) + { + return this.CreateClientSession(Guid.NewGuid(), ipAddress, base64ClientModulus, userClientGlobalId); + } + + public UserClientSession CreateClientSession(Guid sessionId, string ipAddress, string base64ClientModulus, Guid userClientGlobalId) + { + var clientSession = new UserClientSession( + sessionId, this.UserId, ipAddress, base64ClientModulus, userClientGlobalId); + + this.PublicClientSessions[clientSession.Id] = clientSession; + + return clientSession; + } + + /// + /// Removes the client session. + /// + /// The client session id. + public void RemoveClientSession(Guid clientSessionId) + { + if (this.PublicClientSessions.ContainsKey(clientSessionId)) + { + this.PublicClientSessions.Remove(clientSessionId); + } + } + + public UserClientSession GetClientSessionWithClientId(Guid userClientId) + { + foreach (var entry in this.PublicClientSessions) + { + if (entry.Value.UserClientGlobalId == userClientId) + { + return entry.Value; + } + } + + return null; + } + + /// + /// Verifies this UserSession, removing any expired sessions. + /// Returns true to keep the UserSession in the cache. + /// + /// + /// true if this session has any active client sessions; otherwise, false. + /// + public bool HasExpired() + { + RemoveExpiredSessions(this.PublicClientSessions); + + //If there are no more active client sessions we can remove the entire UserSessions + var sessionHasExpired = + this.ExpiryDate == null //There are no UserClientSessions + || this.ExpiryDate.Value <= DateTime.UtcNow; //The max UserClientSession ExpiryDate has expired + + return sessionHasExpired; + } + + private static void RemoveExpiredSessions(IDictionary clientSessions) + { + var expiredSessionKeys = new List(); + + foreach (var clientSession in clientSessions) + { + if (clientSession.Value.ExpiryDate < DateTime.UtcNow) + { + expiredSessionKeys.Add(clientSession.Key); + } + } + + foreach (var sessionKey in expiredSessionKeys) + { + clientSessions.Remove(sessionKey); + } + } + + public void RemoveAllSessions() + { + this.PublicClientSessions.Clear(); + } + + public UserClientSession GetClientSession(Guid clientSessionId) + { + UserClientSession session; + + if (this.PublicClientSessions.TryGetValue(clientSessionId, out session)) + { + return session; + } + + return null; + } + + public string ToCacheKey() + { + return ToCacheKey(this.UserId); + } + + public static string ToCacheKey(Guid userId) + { + return UrnId.Create(userId.ToString()); + } + } +} \ No newline at end of file diff --git a/tests/ServiceStack.Redis.Tests/ValueTypeExamples.Async.cs b/tests/ServiceStack.Redis.Tests/ValueTypeExamples.Async.cs new file mode 100644 index 00000000..c4baa07f --- /dev/null +++ b/tests/ServiceStack.Redis.Tests/ValueTypeExamples.Async.cs @@ -0,0 +1,135 @@ +using NUnit.Framework; +using ServiceStack.Redis.Generic; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ServiceStack.Redis.Tests +{ + [TestFixture, Category("Integration"), Category("Async")] + public class ValueTypeExamplesAsync + { + [SetUp] + public async Task SetUp() + { + await using var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + await redisClient.FlushAllAsync(); + } + + [Test] + public async Task Working_with_int_values() + { + const string intKey = "intkey"; + const int intValue = 1; + + //STORING AN INT USING THE BASIC CLIENT + await using (var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly()) + { + await redisClient.SetValueAsync(intKey, intValue.ToString()); + string strGetIntValue = await redisClient.GetValueAsync(intKey); + int toIntValue = int.Parse(strGetIntValue); + + Assert.That(toIntValue, Is.EqualTo(intValue)); + } + + //STORING AN INT USING THE GENERIC CLIENT + await using (var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly()) + { + //Create a generic client that treats all values as ints: + IRedisTypedClientAsync intRedis = redisClient.As(); + + await intRedis.SetValueAsync(intKey, intValue); + var toIntValue = await intRedis.GetValueAsync(intKey); + + Assert.That(toIntValue, Is.EqualTo(intValue)); + } + } + + [Test] + public async Task Working_with_int_list_values() + { + const string intListKey = "intListKey"; + var intValues = new List { 2, 4, 6, 8 }; + + //STORING INTS INTO A LIST USING THE BASIC CLIENT + await using (var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly()) + { + IRedisListAsync strList = redisClient.Lists[intListKey]; + + //storing all int values in the redis list 'intListKey' as strings + await intValues.ForEachAsync(async x => await strList.AddAsync(x.ToString())); + + //retrieve all values again as strings + List strListValues = await strList.ToListAsync(); + + //convert back to list of ints + List toIntValues = strListValues.ConvertAll(x => int.Parse(x)); + + Assert.That(toIntValues, Is.EqualTo(intValues)); + + //delete all items in the list + await strList.ClearAsync(); + } + + //STORING INTS INTO A LIST USING THE GENERIC CLIENT + await using (var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly()) + { + //Create a generic client that treats all values as ints: + IRedisTypedClientAsync intRedis = redisClient.As(); + + IRedisListAsync intList = intRedis.Lists[intListKey]; + + //storing all int values in the redis list 'intListKey' as ints + await intValues.ForEachAsync(async x => await intList.AddAsync(x)); + + List toIntListValues = await intList.ToListAsync(); + + Assert.That(toIntListValues, Is.EqualTo(intValues)); + } + } + + public class IntAndString + { + public int Id { get; set; } + public string Letter { get; set; } + } + + [Test] + public async Task Working_with_Generic_types() + { + await using var redisClient = new RedisClient(TestConfig.SingleHost).ForAsyncOnly(); + //Create a typed Redis client that treats all values as IntAndString: + var typedRedis = redisClient.As(); + + var pocoValue = new IntAndString { Id = 1, Letter = "A" }; + await typedRedis.SetValueAsync("pocoKey", pocoValue); + IntAndString toPocoValue = await typedRedis.GetValueAsync("pocoKey"); + + Assert.That(toPocoValue.Id, Is.EqualTo(pocoValue.Id)); + Assert.That(toPocoValue.Letter, Is.EqualTo(pocoValue.Letter)); + + var pocoListValues = new List { + new IntAndString {Id = 2, Letter = "B"}, + new IntAndString {Id = 3, Letter = "C"}, + new IntAndString {Id = 4, Letter = "D"}, + new IntAndString {Id = 5, Letter = "E"}, + }; + + IRedisListAsync pocoList = typedRedis.Lists["pocoListKey"]; + + //Adding all IntAndString objects into the redis list 'pocoListKey' + await pocoListValues.ForEachAsync(async x => await pocoList.AddAsync(x)); + + List toPocoListValues = await pocoList.ToListAsync(); + + for (var i = 0; i < pocoListValues.Count; i++) + { + pocoValue = pocoListValues[i]; + toPocoValue = toPocoListValues[i]; + Assert.That(toPocoValue.Id, Is.EqualTo(pocoValue.Id)); + Assert.That(toPocoValue.Letter, Is.EqualTo(pocoValue.Letter)); + } + } + + } +} \ No newline at end of file