# Orchardcore Dynamic Cache

> Skill for configuring dynamic caching in Orchard Core. Covers shape-level caching, cache tag helpers, Liquid cache blocks, cache context dependencies, cache variations (query, cookie, user, role), cache invalidation with ISignal, and performance optimization patterns. Use this skill when requests mention Orchard Core Dynamic Cache, Configure Dynamic Caching, Enabling Dynamic Cache, Well-Known Cache Dependencies, Available Cache Contexts (Vary-By), Shape Tag Helper Attributes, or closely related Orchard Core implementation, setup, extension, or troubleshooting work. Strong matches include work with OrchardCore.DynamicCache, OrchardCore.DisplayManagement.Handlers, OrchardCore.DisplayManagement.Views, OrchardCore.Environment.Cache, ISignal, IDynamicCache, IDistributedCache. It also helps with Available Cache Contexts (Vary-By), Shape Tag Helper Attributes, Caching Shapes via Tag Helpers, plus the code patterns, admin flows, recipe steps, and referenced examples captured in this skill.

- Skill: `crestapps/orchardcore-dynamic-cache` (Agent Skill)
- Install (CLI): `npx skillmds@latest add crestapps/orchardcore-dynamic-cache`
- Raw SKILL.md: https://api.skillmd.com/api/skills/crestapps/orchardcore-dynamic-cache/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: CrestApps (https://skillmd.com/u/crestapps)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/crestapps/orchardcore-dynamic-cache

---


# Orchard Core Dynamic Cache - Prompt Templates

## Configure Dynamic Caching

You are an Orchard Core expert. Generate code and configuration for dynamic caching including shape-level caching, tag helpers, Liquid cache blocks, cache contexts, dependencies, variations, invalidation, and performance optimization. For response compression, distributed cache providers, and cache-control/response headers, see `orchardcore-caching`.

### Guidelines

- Enable `OrchardCore.DynamicCache` for shape-level output caching with dependency tracking.
- Dynamic Cache caches rendered HTML markup at the shape level, not at the page level.
- Cached sections can be nested; each section can have its own cache policy.
- Cached values are stored via `IDynamicCache`, backed by `IDistributedCache` (defaults to `IMemoryCache`).
- The Razor `<dynamic-cache>` tag helper defaults to a 30-second sliding window when no expiration is set. Programmatic and Liquid cache entries use the dynamic-cache service default.
- Invalidating a child cache block also invalidates all parent blocks.
- Use `contentitemid:{ContentItemId}` and `alias:{Alias}` dependencies to auto-invalidate when content changes.
- Create custom dependencies with `ITagCache.RemoveTagAsync()`.
- Contexts are hierarchical: `user` is more specialized than `user.roles`, so if both are specified, only `user` is used.
- Use `<dynamic-cache>` tag helper in Razor (not `<cache>`, which is the ASP.NET Core built-in).
- Use `{% cache %}` blocks in Liquid for declarative caching.
- Use cache scope tags (`cache_dependency`, `cache_expires_after`, etc.) to alter cache policies from within nested content.
- All recipe JSON must be wrapped in `{ "steps": [...] }`.
- All C# classes must use the `sealed` modifier.

### Enabling Dynamic Cache

```json
{
  "steps": [
    {
      "name": "Feature",
      "enable": [
        "OrchardCore.DynamicCache"
      ],
      "disable": []
    }
  ]
}
```

### Well-Known Cache Dependencies

| Dependency | Description |
|------------|-------------|
| `contentitemid:{ContentItemId}` | Invalidated when the content item is Published, Unpublished, or Removed |
| `alias:{Alias}` | Invalidated when the content item with the specified alias is Published, Unpublished, or Removed |

Create custom dependencies by calling `ITagCache.RemoveTagAsync()` in response to events.

### Available Cache Contexts (Vary-By)

| Context | Description |
|---------|-------------|
| `features` | The list of enabled features |
| `features:{featureName}` | A specific feature name |
| `query` | All query string values |
| `query:{queryName}` | A specific query string parameter |
| `user` | The current user |
| `user.roles` | The roles of the current user |
| `route` | The current request path |

Contexts are hierarchical. For example, `user` is more specific than `user.roles`. If both are declared, only `user` is used.

### Dynamic Cache Razor Tag Helper Attributes

| Razor Attribute | Description | Required |
|-----------------|-------------|----------|
| `cache-id` | The identifier of the cached section | Yes |
| `vary-by` | Space/comma-separated cache contexts | No |
| `dependencies` | Space/comma-separated invalidation tags | No |
| `expires-on` | Fixed `DateTimeOffset` expiration | No |
| `expires-after` | Relative expiration | No |
| `expires-sliding` | Sliding expiration | No |

### Caching Shapes from Liquid

Liquid `shape` and `contentitem` tags cache a shape with `cache_id`, `cache_context`, `cache_tag`, `cache_fixed_duration`, and `cache_sliding_duration`:

```liquid
{% shape "menu", alias: "alias:main-menu", cache_id: "main-menu", cache_fixed_duration: "00:05:00", cache_tag: "alias:main-menu" %}
```

Cache a content item shape:

```liquid
{% contentitem alias: "alias:main-menu", cache_id: "main-menu", cache_fixed_duration: "00:05:00", cache_tag: "alias:main-menu" %}
```

### Caching a Shape Programmatically

Use `ShapeMetadata.Cache()` to mark a shape as cached in a display driver:

```csharp
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.DisplayManagement.Views;

public sealed class RecentPostsDisplayDriver : DisplayDriver<RecentPostsWidget>
{
    public override IDisplayResult Display(RecentPostsWidget model, BuildDisplayContext context)
    {
        return View("RecentPosts", model)
            .Location("Detail", "Content:5")
            .Cache("recentposts", cache => cache
                .AddTag("contenttype:BlogPost")
                .AddContext("user")
                .WithExpiryAfter(TimeSpan.FromMinutes(15))
            );
    }
}
```

#### CacheContext Methods

| Method | Description |
|--------|-------------|
| `WithExpiryOn(DateTimeOffset)` | Cache until a fixed point in time |
| `WithExpiryAfter(TimeSpan)` | Cache for a fixed duration |
| `WithExpirySliding(TimeSpan)` | Cache with a sliding window |
| `AddContext(params string[])` | Vary cached content by the specified context values |
| `RemoveContext(string)` | Remove a context |
| `AddTag(params string[])` | Add invalidation tags |
| `RemoveTag(string)` | Remove a tag |

### Liquid Cache Block

Use `{% cache %}` blocks in Liquid templates for declarative caching. Blocks can be nested:

#### Arguments

| Argument | Description | Required |
|----------|-------------|----------|
| `id` (first positional) | The cache block identifier | Yes |
| `vary_by` | Space/comma-separated context values | No |
| `dependencies` | Space/comma-separated dependency values | No |
| `expires_after` | Fixed duration (e.g., `"00:05:00"`) | No |
| `expires_sliding` | Sliding duration (e.g., `"00:05:00"`) | No |
| `vary_by` | A custom value to vary the cache entry | No |

#### Simple Cache Block

```liquid
{% cache "my-cache-block" %}
    <p>This content is cached.</p>
{% endcache %}
```

#### Cache Block with Vary-By

```liquid
{% cache "blog-post-summary", vary_by: Model.ContentItem.ContentItemId %}
    <h2>{{ Model.ContentItem | display_text }}</h2>
    <p>{{ Model.ContentItem.Content.BodyPart.Body }}</p>
{% endcache %}
```

#### Nested Cache Blocks

```liquid
{% cache "a" %}
    A {{ "now" | date: "%T" }} (No Duration) <br />
    {% cache "a1", dependencies: "a1", vary_by: "user", expires_after: "0:5:0" %}
        A1 {{ "now" | date: "%T" }} (5 Minutes) <br />
    {% endcache %}
    {% cache "a2", dependencies: "a2", expires_after: "0:0:1" %}
        A2 {{ "now" | date: "%T" }} (1 Second) <br />
        {% cache "a2a", dependencies: "a2a", vary_by: "route", expires_sliding: "0:0:5" %}
            A2A {{ "now" | date: "%T" }} (5 Seconds) <br />
        {% endcache %}
    {% endcache %}
{% endcache %}
```

### Altering Cache Scope from Within

Use these Liquid tags to alter the current cache scope from inside a cache block. These tags are safe to use even if you are not inside a cache block:

| Tag | Description | Example |
|-----|-------------|---------|
| `cache_dependency` | Add a dependency to the current scope | `{% cache_dependency "alias:my-alias" %}` |
| `cache_expires_on` | Set a fixed expiration date/time | `{% cache_expires_on someDate %}` |
| `cache_expires_after` | Set a relative expiration duration | `{% cache_expires_after "01:00:00" %}` |
| `cache_expires_sliding` | Set a sliding window expiration | `{% cache_expires_sliding "00:01:00" %}` |

#### Dynamic Dependencies from Query Results

```liquid
{% cache "recent-blog-posts" %}
    {% assign recentBlogPosts = Queries.RecentBlogPosts | query %}
    {% for item in recentBlogPosts %}
        {{ item | display_text }}

        {% assign cacheDependency = "contentitemid:" | append: item.ContentItemId %}
        {% cache_dependency cacheDependency %}
    {% endfor %}
{% endcache %}
```

### Cache Signals and Tag Invalidation

Use `ISignal.SignalTokenAsync` for consumers that registered `ISignal.GetToken` for the same key. To invalidate Dynamic Cache entries tagged with `AddTag`, use `ITagCache.RemoveTagAsync`.

```csharp
using OrchardCore.Environment.Cache;

public sealed class ProductService
{
    private readonly ISignal _signal;
    private readonly ITagCache _tagCache;

    public ProductService(ISignal signal, ITagCache tagCache)
    {
        _signal = signal;
        _tagCache = tagCache;
    }

    public async Task InvalidateProductCacheAsync()
    {
        await _signal.SignalTokenAsync("productcatalog");
        await _tagCache.RemoveTagAsync("productcatalog");
    }
}
```

The signal API remains useful for signal-token consumers. The tag removal invalidates Dynamic Cache entries that called `AddTag("productcatalog")`.

### Custom Cache Context Provider

Implement `ICacheContextProvider` to create custom context values for cache variation:

```csharp
using OrchardCore.Environment.Cache;

public sealed class TenantCacheContextProvider : ICacheContextProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public TenantCacheContextProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Task PopulateContextEntriesAsync(IEnumerable<string> contexts, List<CacheContextEntry> entries)
    {
        if (contexts.Any(ctx => string.Equals(ctx, "tenant", StringComparison.OrdinalIgnoreCase)))
        {
            var tenantName = _httpContextAccessor.HttpContext?.Request.Host.Value ?? "default";
            entries.Add(new CacheContextEntry("tenant", tenantName));
        }

        return Task.CompletedTask;
    }
}
```

### Cache Options

Dynamic Cache has four modes configurable in **General Settings**:

- **From environment**: Enables in `Production` ASP.NET Core environment only.
- **Enabled**: Always enabled.
- **Disabled**: Always disabled.
- **Enabled with cache debug mode**: Enabled with HTML comments logging cache context metadata for troubleshooting.

