{"id":21650732,"url":"https://github.com/alexboia/servicestack-routed-cacheclient","last_synced_at":"2025-03-20T03:33:36.037Z","repository":{"id":55037940,"uuid":"252247467","full_name":"alexboia/ServiceStack-Routed-CacheClient","owner":"alexboia","description":"Now you can cache your cake and eat it too: This ServiceStack cache client router enables you to use multiple cache clients and select each based on various cache key-based rules","archived":false,"fork":false,"pushed_at":"2021-01-13T19:17:19.000Z","size":139,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-25T05:27:30.414Z","etag":null,"topics":["cache","cache-client","csharp","net-framework","netcore","netcore2","netstandard","netstandard20","servicestack","servicestack-cache"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexboia.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-04-01T17:50:57.000Z","updated_at":"2020-09-07T09:02:41.000Z","dependencies_parsed_at":"2022-08-14T09:50:27.736Z","dependency_job_id":null,"html_url":"https://github.com/alexboia/ServiceStack-Routed-CacheClient","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexboia%2FServiceStack-Routed-CacheClient","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexboia%2FServiceStack-Routed-CacheClient/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexboia%2FServiceStack-Routed-CacheClient/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexboia%2FServiceStack-Routed-CacheClient/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexboia","download_url":"https://codeload.github.com/alexboia/ServiceStack-Routed-CacheClient/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244544125,"owners_count":20469613,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","cache-client","csharp","net-framework","netcore","netcore2","netstandard","netstandard20","servicestack","servicestack-cache"],"created_at":"2024-11-25T07:42:19.634Z","updated_at":"2025-03-20T03:33:35.999Z","avatar_url":"https://github.com/alexboia.png","language":"C#","funding_links":["https://ko-fi.com/Q5Q01KGLM"],"categories":[],"sub_categories":[],"readme":"# ServiceStack Routed Cache Client\r\n\r\n## What\r\n\r\nThe ServiceStack Routed Cache Client is an `ICacheClient` implementation \r\n(`ICacheClientExtended` to be more exact) that acts as a facade \r\nto a collection of multiple other cache clients. \r\nIt has minimum overhead and does not store cache values itself, but only routes \r\nvarious cache operations (read, write, flush, remove etc.) to a specific registered client, \r\nbased on an associated rule  (for instance, whether the key starts with a given string).  \r\n\r\n## Why \r\n\r\nThe main issue for me was (and possibly many others) that I could not register \r\na different cache client for session storage (say, `OrmLiteCacheClient`), \r\nwhilst continuing to make use of the standard `MemoryCacheClient `.  \r\n\r\n## Installation\r\n\r\nAvailable as a NuGet package, [here](https://www.nuget.org/packages/LVD.ServiceStack.RoutedCacheClient/).\r\n\r\n### Via Package Manager\r\n\r\n`Install-Package LVD.ServiceStack.RoutedCacheClient -Version 1.1.0`\r\n\r\n### Via .NET CLI\r\n`dotnet add package LVD.ServiceStack.RoutedCacheClient --version 1.1.0`\r\n\r\n## How to use\r\n\r\n### 1. Add namespace reference\r\n\r\n`using LVD.ServiceStackRoutedCacheClient`.\r\n\r\n### 2. Create your routed cache client instance\r\n\r\nIn this example, the default `MemoryCacheClient` provided by ServiceStack is used as a fallback client, \r\nwhich means it will used to handle keys that are not matched by any of the registered rules.\r\n\r\n```csharp\r\nIRoutedCacheClient routedCacheClient = new DefaultRoutedCacheClient(new MemoryCacheClient());\r\n```\r\n\r\n### 3. Register your other cache clients\r\n\r\nFor example, registering a ServiceStack OrmLite cache client for ServiceStack sessions:\r\n\r\n```csharp\r\nOrmLiteCacheClient\u003cCacheEntry\u003e ormLiteCacheClient =\r\n    new OrmLiteCacheClient\u003cCacheEntry\u003e();\r\n\r\nroutedCacheClient.PushClientWithRule(new KeyStartsWithStringCacheClientRule(ormLiteCacheClient,\r\n    StringComparison.InvariantCultureIgnoreCase,\r\n    \"urn:iauthsession:\",\r\n    \"sess:\"));\r\n```\r\n\r\nOr simpler, using `PushServiceStackSessionCacheClient()`, which automatically registers your provided cache client\r\nwith a `KeyStartsWithStringCacheClientRule` and the above prefixes: `urn:iauthsession:` and `sess:`.\r\n\r\n```csharp\r\nOrmLiteCacheClient\u003cCacheEntry\u003e ormLiteCacheClient =\r\n    new OrmLiteCacheClient\u003cCacheEntry\u003e();\r\n\r\nroutedCacheClient.PushServiceStackSessionCacheClient(ormLiteCacheClient);\r\n```\r\n\r\n### 4. Built-in routing behaviour\r\n\r\n### 4.1. Built-in conditions\r\n\r\nThe library provides the following built-in conditions:\r\n\r\n- `KeyStartsWithStringCacheClientRuleCondition` - matches keys that start with a given sub-string;\r\n- `KeyEndsWithStringCacheClientRuleCondition` - matches keys that end with a given sub-string;\r\n- `KeyMatchesRegexpCacheClientRuleCondition` - matches keys based on a regular expression;\r\n- `ServiceStackSessionKeyCacheClientRuleCondition` - matches keys used to store ServiceStack session data;\r\n- `AlwaysTrueCacheClientRuleCondition` - matches any key;\r\n- `AlwaysFalseCacheClientRuleCondition` - doesn't match anything;\r\n- `MatchAnyRuleCondition` - allows composing conditions using `OR` boolean operator (i.e. returns true if at least one condition evaluates to true);\r\n- `MatchAllRuleCondition` - allows composing conditions using `AND` boolean operator (i.e. returns true if all conditions evaluate to true).\r\n\r\n### 4.2. Built-in rules\r\n\r\nThe library provides the following built-in rules:\r\n\r\n- `GenericConditionBasedCacheClientRule` - matches keys that satisfy a given condition; \r\n- `KeyStartsWithStringCacheClientRule` - matches keys that start with a given sub-string (uses `KeyStartsWithStringCacheClientRuleCondition`);\r\n- `KeyEndsWithStringCacheClientRule` - matches keys that end with a given sub-string (uses `KeyEndsWithStringCacheClientRuleCondition`);\r\n- `KeyMatchesRegexpCacheClientRule` - matches keys based on a regular expression (uses `KeyMatchesRegexpCacheClientRuleCondition`);\r\n- `ServiceStackSessionKeyCacheClientRule` - matches keys used to store ServiceStack session data (uses `ServiceStackSessionKeyCacheClientRuleCondition`);\r\n- `AlwaysTrueCacheClientRule` - matches any key (uses `AlwaysTrueCacheClientRuleCondition`);\r\n- `AlwaysFalseCacheClientRule` - doesn't match anything (uses `AlwaysFalseCacheClientRuleCondition`).\r\n\r\n### 5. Extending the routing behaviour\r\n\r\n#### 5.1. Creating custom conditions\r\n\r\nCreating your own conditions is as easy as implementing `IRoutedCacheClientRuleCondition` \r\nwith the single method `bool Matches ( string key )`.\r\nThis is also the recommended way of exteding the library's routing behaviour.\r\n\r\nAs an example, here is the implementation of the built-in `KeyStartsWithStringCacheClientRuleCondition` condition:\r\n\r\n```csharp\r\npublic class KeyStartsWithStringCacheClientRuleCondition : IRoutedCacheClientRuleCondition\r\n{\r\n\tprivate List\u003cstring\u003e mTokens = new List\u003cstring\u003e();\r\n\r\n\tprivate StringComparison mStringComparisonMode;\r\n\r\n\tpublic KeyStartsWithStringCacheClientRuleCondition ( StringComparison stringComparisonMode,\r\n\t\tparams string[] tokens )\r\n\t{\r\n\t\tif ( tokens == null || tokens.Length == 0 )\r\n\t\t\tthrow new ArgumentNullException( nameof( tokens ) );\r\n\r\n\t\tmTokens.AddRange( tokens );\r\n\t\tmStringComparisonMode = stringComparisonMode;\r\n\t}\r\n\r\n\tpublic bool Matches ( string key )\r\n\t{\r\n\t\tif ( string.IsNullOrWhiteSpace( key ) )\r\n\t\t\tthrow new ArgumentNullException( nameof( key ) );\r\n\r\n\t\tforeach ( string token in mTokens )\r\n\t\t\tif ( key.StartsWith( token, mStringComparisonMode ) )\r\n\t\t\t\treturn true;\r\n\r\n\t\treturn false;\r\n\t}\r\n\r\n\tpublic StringComparison StringComparisonMode \r\n\t\t=\u003e mStringComparisonMode;\r\n}\r\n```\r\n\r\n#### 5.2. Creating custom rules\r\n\r\nCreating your own rules is as easy as \r\n\t- extending the `GenericConditionBasedCacheClientRule` class and providing a condition to this base class (this is a useful shortcut to always instantiating `GenericConditionBasedCacheClientRule` with the same condition). \r\n\t- or extending the `BaseCacheClientRule` class and providing an implementation for the `bool Matches ( string key )` method (should you wish to bypass using a condition altogether).\r\n\r\nExample #1 - the implementation of the built-in `KeyStartsWithStringCacheClientRule` rule:\r\n\r\n```csharp\r\npublic class KeyStartsWithStringCacheClientRule : GenericConditionBasedCacheClientRule\r\n{\r\n\tpublic KeyStartsWithStringCacheClientRule ( ICacheClient cacheClient,\r\n\t\tKeyStartsWithStringCacheClientRuleCondition condition )\r\n\t\t: base( cacheClient, condition )\r\n\t{\r\n\t\treturn;\r\n\t}\r\n\r\n\tpublic KeyStartsWithStringCacheClientRule ( ICacheClient cacheClient,\r\n\t\tStringComparison stringComparisonMode,\r\n\t\tparams string[] tokens )\r\n\t\t: this( cacheClient, new KeyStartsWithStringCacheClientRuleCondition( stringComparisonMode, tokens ) )\r\n\t{\r\n\t\treturn;\r\n\t}\r\n}\r\n```\r\n\r\nExample #2 - the implementation of the built-in `GenericConditionBasedCacheClientRule` rule:\r\n```csharp\r\npublic class GenericConditionBasedCacheClientRule : BaseCacheClientRule\r\n{\r\n\tprivate IRoutedCacheClientRuleCondition mCondition;\r\n\r\n\tpublic GenericConditionBasedCacheClientRule ( ICacheClient cacheClient, IRoutedCacheClientRuleCondition condition )\r\n\t\t: base( cacheClient )\r\n\t{\r\n\t\tmCondition = condition\r\n\t\t\t?? throw new ArgumentNullException( nameof( condition ) );\r\n\t}\r\n\r\n\tpublic override bool Matches ( string key )\r\n\t{\r\n\t\tif ( string.IsNullOrWhiteSpace( key ) )\r\n\t\t\tthrow new ArgumentNullException( nameof( key ) );\r\n\r\n\t\treturn mCondition.Matches( key );\r\n\t}\r\n\r\n\tprivate IRoutedCacheClientRuleCondition Condition \r\n\t\t=\u003e mCondition;\r\n}\r\n```\r\n\r\n### 6. Dispose behaviour\r\n\r\nThe dispose behaviour of the `DefaultRoutedCacheClient`, before version 1.1.0 was:\r\n\r\n- to automatically dispose all the registered cache clients by default (this could not be altered);\r\n- to no throw `ObjectDisposedException` when invoking methods on diposed instances.\r\n\r\nAfter 1.1.0, the behaviour is as follows:\r\n\r\n- to automatically dispose all the registered cache clients by default (this can now be configured);\r\n- to throw throw `ObjectDisposedException` when invoking methods on diposed instances.\r\n\r\nTo change how `DefaultRoutedCacheClient` handles various registered cache clients you may:\r\n\r\n- use `DefaultRoutedCacheClient ( ICacheClient fallbackClient, bool autoDispose )` constructor to set \r\nwhether or not the fallback cache client should be disposed along with the `DefaultRoutedCacheClient` it is registered with;\r\n- use `IRoutedCacheClientRule.AutoDispose` property to set whether the cache client \r\nassociated with a rule should be disposed along with the `DefaultRoutedCacheClient` it is registered with.\r\n\r\n### 7. Sample project\r\n\r\nA sample project has been provided, to see just how this would behave in the real world.  \r\nHead over to `LVD.ServiceStack.RoutedCacheClient.Example` for the full details, but, in short, it exposes three service endpoints:\r\n\r\n- `/set-cache-randomly` - set some cache values using the `Service.Cache` instance;\r\n- `/set-session-randomly` - set some session values using the `Service.SessionBag` instance;\r\n- `/list-cache-providers-data` - list all values for each of the cache providers.\r\n\r\nThe output of `/list-cache-providers-data` looks something like:\r\n\r\n```json\r\n{\r\n    \"cacheProvidersData\": {\r\n        \"fallbackCacheClient\": {\r\n            \"SampleCacheClientService.randomText\": \"ef42894b-fbb6-4971-b983-0aea9e88f10a\",\r\n            \"SampleCacheClientService2020-04-06 10:51.hitCountsPerMinute\": \"12\",\r\n            \"SampleCacheClientService2020-04-06 10:52.hitCountsPerMinute\": \"4\"\r\n        },\r\n        \"sessionCacheClient\": {\r\n            \"sess:vKQ8aph3Xdp1ro9YqBzK:SampleCacheClientService.sessionRandomText\": \"e2f581da-ad0c-4896-8b4f-b3646f79bc7e\",\r\n            \"sess:vKQ8aph3Xdp1ro9YqBzK:SampleCacheClientService.hitCountsPerSession\": \"12\"\r\n        }\r\n    }\r\n}\r\n```\r\n\r\n## Breaking changes\r\n\r\n### In Version 1.1.0\r\n\r\n- `KeyStartsWithStringCacheClientRule` no longer extends `BaseCacheClientRule`, but `GenericConditionBasedCacheClientRule`;\r\n- `ServiceStackSessionKeyCacheClientRule` no longer extends `BaseCacheClientRule`, but `GenericConditionBasedCacheClientRule`;\r\n- `AlwaysTrueCacheClientRule` no longer extends `BaseCacheClientRule`, but `GenericConditionBasedCacheClientRule`;\r\n- `AlwaysTrueCacheClientRule` now throws an exception when checking for an empty key (calling `.Matches(null|string.Empty)`);\r\n- Calling any method on a disposed `DefaultRoutedCacheClient` instance throws `ObjectDisposedException`.\r\n\r\n## Changelog\r\n\r\n### Version 1.1.0\r\n\r\n- Added utility methods to inspect cache client configuration - [Issue #4](https://github.com/alexboia/ServiceStack-Routed-CacheClient/issues/4);\r\n- Refactoring - extracted the explicit concept of condition from that of a cache client rule - [Issue #2](https://github.com/alexboia/ServiceStack-Routed-CacheClient/issues/2);\r\n- Additional conditions provided:\r\n    - cache key ends with substring - [Issue #5](https://github.com/alexboia/ServiceStack-Routed-CacheClient/issues/5);\r\n    - cache key matches a given regexp - [Issue #6](https://github.com/alexboia/ServiceStack-Routed-CacheClient/issues/6);\r\n    - compose conditions using OR operator;\r\n    - compose conditions using AND operator.\r\n- Improved test coverage and code comments.\r\n\r\n### Version 1.0.0\r\n\r\nInitial release.\r\n\r\n## What's next\r\n\r\nAs a rough timeline, I would like to see the following happening:\r\n\r\n- Refactor things a bit and add some comments, at least for critical areas;\r\n- Add some more routing rules. Here are some of the rules I'm thinking about:\r\n\t- ~~match a regex pattern~~;\r\n\t- ~~match rules that ends with a given string~~;\r\n\t- ~~composed rules using basic `AND`/`OR` logical operators~~.\r\n- Enhance the API:\r\n\t- ~~would be useful to have a way of listing all the registered cache clients~~;\r\n\t- ~~allow method chaining when registering cache client rules~~.\r\n- Additional automated tests (in-progress).\r\n\r\n## Donate\r\n\r\nI put some of my free time into developing and maintaining this plugin.\r\nIf helped you in your projects and you are happy with it, you can...\r\n\r\n[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Q5Q01KGLM)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexboia%2Fservicestack-routed-cacheclient","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexboia%2Fservicestack-routed-cacheclient","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexboia%2Fservicestack-routed-cacheclient/lists"}