Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 44 additions & 11 deletions _includes/head-custom.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,23 @@
{% endunless %}

{%- comment -%}
Stable @id URIs cross-referenced from every page's @graph.
Stable @id URIs cross-referenced from every page's @graph. The page_id suffix
matches the page's primary @type so the graph reads naturally:
post → #article, /about/ → #profilepage, /tags/ + tagpages → #collection,
everything else → #webpage.
{%- endcomment -%}
{%- assign person_id = site.url | append: '/about/#stanislaw' -%}
{%- assign website_id = site.url | append: '/#website' -%}
{%- assign page_id = page.url | absolute_url | append: '#webpage' -%}
{%- assign breadcrumb_id = page.url | absolute_url | append: '#breadcrumb' -%}
{%- if page.layout == "post" -%}
{%- assign page_id = page.url | absolute_url | append: '#article' -%}
{%- elsif page.url == "/about/" -%}
{%- assign page_id = page.url | absolute_url | append: '#profilepage' -%}
{%- elsif page.url == "/tags/" or page.layout == "tagpage" -%}
{%- assign page_id = page.url | absolute_url | append: '#collection' -%}
{%- else -%}
{%- assign page_id = page.url | absolute_url | append: '#webpage' -%}
{%- endif -%}

<script type="application/ld+json">
{
Expand All @@ -43,11 +55,19 @@
"@type": "Person",
"@id": "{{ person_id }}",
"name": "Stanisław Szołkowski",
"alternateName": "Stanislaw Szolkowski",
"givenName": "Stanisław",
"familyName": "Szołkowski",
"knowsLanguage": ["en", "pl"],
"url": "{{ '/about/' | absolute_url }}",
"image": "{{ person_image | absolute_url }}",
"image": {
"@type": "ImageObject",
"@id": "{{ site.url }}/about/#stanislaw-photo",
"url": "{{ person_image | absolute_url }}",
"width": 400,
"height": 400,
"caption": "Stanisław Szołkowski"
},
"jobTitle": "Principal .NET/Optimizely Engineer",
"award": [
"Optimizely MVP 2025",
Expand Down Expand Up @@ -102,17 +122,19 @@
"@type": "ImageObject",
"url": "{{ person_image | absolute_url }}"
},
"breadcrumb": { "@id": "{{ page.url | absolute_url }}#breadcrumb" }
"breadcrumb": { "@id": "{{ breadcrumb_id }}" }
},
{
"@type": "BreadcrumbList",
"@id": "{{ page.url | absolute_url }}#breadcrumb",
"@id": "{{ breadcrumb_id }}",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ '/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": "About"}
]
}
{%- elsif page.layout == "post" -%}
{%- assign primary = page.primary_tag | default: page.tags.first -%}
{%- assign primary_slug = primary | slugify -%}
,
{
"@type": "BlogPosting",
Expand All @@ -126,21 +148,28 @@
"author": { "@id": "{{ person_id }}" },
"publisher": { "@id": "{{ person_id }}" },
"isPartOf": { "@id": "{{ website_id }}" },
"mainEntityOfPage": { "@id": "{{ page_id }}" }
"mainEntityOfPage": { "@id": "{{ page_id }}" },
"breadcrumb": { "@id": "{{ breadcrumb_id }}" },
"keywords": {{ page.tags | join: ", " | jsonify }},
"articleSection": {{ primary | jsonify }},
"wordCount": {{ content | strip_html | number_of_words }}
{%- if page.image -%}
,
"image": {
"@type": "ImageObject",
"url": "{{ page.image.path | absolute_url }}",
"description": {{ page.image.alt | jsonify }}
"width": {{ page.image.width | plus: 0 }},
"height": {{ page.image.height | plus: 0 }},
"caption": {{ page.image.alt | jsonify }}
}
{%- endif -%}
},
{
"@type": "BreadcrumbList",
"@id": "{{ breadcrumb_id }}",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ '/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": "Archive", "item": "{{ '/archive/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": {{ primary | jsonify }}, "item": "{{ '/tags/' | append: primary_slug | absolute_url }}"},
{"@type": "ListItem", "position": 3, "name": {{ page.title | jsonify }}, "item": "{{ page.url | absolute_url }}"}
]
}
Expand All @@ -154,6 +183,7 @@
"description": {{ page.description | jsonify }},
"inLanguage": "en-US",
"isPartOf": { "@id": "{{ website_id }}" },
"breadcrumb": { "@id": "{{ breadcrumb_id }}" },
"mainEntity": {
"@type": "ItemList",
"numberOfItems": {{ site.tags | size }},
Expand All @@ -169,6 +199,7 @@
},
{
"@type": "BreadcrumbList",
"@id": "{{ breadcrumb_id }}",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ '/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": "Tags", "item": "{{ '/tags/' | absolute_url }}"}
Expand All @@ -182,10 +213,12 @@
"url": "{{ page.url | absolute_url }}",
"name": {{ page.title | jsonify }},
"inLanguage": "en-US",
"isPartOf": { "@id": "{{ website_id }}" }
"isPartOf": { "@id": "{{ website_id }}" },
"breadcrumb": { "@id": "{{ breadcrumb_id }}" }
},
{
"@type": "BreadcrumbList",
"@id": "{{ breadcrumb_id }}",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ '/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": "Tags", "item": "{{ '/tags/' | absolute_url }}"},
Expand All @@ -201,11 +234,11 @@
"name": {{ page.title | jsonify }},
"inLanguage": "en-US",
"isPartOf": { "@id": "{{ website_id }}" },
"breadcrumb": { "@id": "{{ page.url | absolute_url }}#breadcrumb" }
"breadcrumb": { "@id": "{{ breadcrumb_id }}" }
},
{
"@type": "BreadcrumbList",
"@id": "{{ page.url | absolute_url }}#breadcrumb",
"@id": "{{ breadcrumb_id }}",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Home", "item": "{{ '/' | absolute_url }}"},
{"@type": "ListItem", "position": 2, "name": "Archive"}
Expand Down
3 changes: 3 additions & 0 deletions _posts/2022-07-28-episerver-on-apple-silicon.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2022-07-28-episerver-on-apple-silicon-episerver-running-on-m1-1.png
alt: "Optimizely Foundation site running in the CMS Edit view on an Apple Silicon (M1) Mac, showing the Mosey demo home page"
width: 2874
height: 1590
primary_tag: apple silicon
tags:
- episerver
- optimizely
Expand Down
1 change: 1 addition & 0 deletions _posts/2022-07-30-add-github-pipeline-to-epi-12.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ author: Stanisław Szołkowski
comments: true
published: true
redirect_from: /2022/08/15/add-sonarcloud-to-epi-12-pipeline.html
primary_tag: devops
tags:
- episerver
- optimizely
Expand Down
3 changes: 3 additions & 0 deletions _posts/2023-08-15-add-sonarcloud-to-epi-12-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2023-08-15-add-sonarcloud-to-epi-12-pipeline-ogimage.png
alt: Add SonarCloud/SonarQube to Episerver/Optimizely 12 project title image
width: 1280
height: 720
primary_tag: sonarcloud
tags:
- episerver
- optimizely
Expand Down
3 changes: 3 additions & 0 deletions _posts/2024-07-31-adding-hangfire-to-epi-12.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ redirect_from: /2024/07/11/adding-hangfire-to-epi-12.html
image:
path: assets/img/2024-07-31-hangfire-cms-overview.png
alt: Integrated Hangfire dashboard with Optimizely CMS back office
width: 2172
height: 1458
primary_tag: hangfire
tags:
- episerver
- optimizely
Expand Down
3 changes: 3 additions & 0 deletions _posts/2025-05-26-episerver-on-apple-silicon-2025.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2025-05-26-cms-home-page.png
alt: "Running full Optimizely development setup on M1 (ARM) based machine"
width: 2153
height: 1192
primary_tag: apple silicon
tags:
- episerver
- optimizely
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2025-06-12-application-insights-data-digestion-size.png
alt: "Tunning Application Insights telemetry filtering in Optimizely"
width: 2423
height: 888
primary_tag: application insights
redirect_from:
- /2025/06/12/episerver-and_application_insights_filters.html
- /2025/06/12/episerver-and_application_insights_filters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2025-06-12-optimizely-scheduled-jobs-dashboard.png
alt: "How to automatically remove orphaned Opti jobs from the DB"
width: 1221
height: 372
primary_tag: scheduled jobs
tags:
- episerver
- optimizely
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ date: 2025-10-08 10:00:00 +0200
author: Stanisław Szołkowski
comments: true
published: true
redirect_from:
- /2025/10/08/Quiet-Performance-Wins-Scheduled-Job-for-SQL-Index-Maintenance-in-Optimizely.html
image:
path: assets/img/2025-10-08-Quiet-Performance-Wins-Scheduled-Job-for-SQL-Index-Maintenance-in-Optimizely.jpeg
path: assets/img/2025-10-08-quiet-performance-wins-scheduled-job-for-sql-index-maintenance-in-optimizely.jpeg
alt: "Quiet Performance Wins: Scheduled Job for SQL Index Maintenance in Optimizely"
width: 1024
height: 628
primary_tag: performance
tags:
- episerver
- optimizely
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ date: 2026-02-18 10:00:00 +0200
author: Stanisław Szołkowski
comments: true
published: true
redirect_from: /2026/01/18/Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service.html
redirect_from:
- /2026/01/18/Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service.html
- /2026/02/18/Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service.html
image:
path: assets/img/2026-02-18-Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service.png
path: assets/img/2026-02-18-memory-efficient-catalog-traversal-in-optimizely-commerce-part-1-building-the-service.png
alt: "Memory-Efficient Catalog Traversal in Optimizely Commerce. Part 1: Building the Service"
width: 1200
height: 630
primary_tag: commerce
tags:
- episerver
- optimizely
Expand Down Expand Up @@ -267,5 +272,5 @@ Thank you for reading, and stay tuned for Part 2 where we put this service to wo
## This Post is Part of a Series

- Part 1: Building the Service - (this post)
- [Part 2: Real-World Scheduled Job Patterns]({% post_url 2026-02-24-Catalog-Traversal-in-Action-Part-2-Real-World-Scheduled-Job-Patterns %})
- [Part 3: Hangfire Integration]({% post_url 2026-03-03-Catalog-Traversal-with-Hangfire-Part-3-Advanced-Job-Management %})
- [Part 2: Real-World Scheduled Job Patterns]({% post_url 2026-02-24-catalog-traversal-in-action-part-2-real-world-scheduled-job-patterns %})
- [Part 3: Hangfire Integration]({% post_url 2026-03-03-catalog-traversal-with-hangfire-part-3-advanced-job-management %})
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ date: 2026-02-24 10:00:00 +0200
author: Stanisław Szołkowski
comments: true
published: true
redirect_from:
- /2026/02/24/Catalog-Traversal-in-Action-Part-2-Real-World-Scheduled-Job-Patterns.html
image:
path: assets/img/2026-02-24-Catalog-Traversal-in-Action-Part-2-Real-World-Scheduled-Job-Patterns.png
path: assets/img/2026-02-24-catalog-traversal-in-action-part-2-real-world-scheduled-job-patterns.png
alt: "Catalog Traversal in Action. Part 2: Real-World Scheduled Job Patterns"
width: 1200
height: 630
primary_tag: commerce
tags:
- episerver
- optimizely
Expand All @@ -19,7 +24,7 @@ tags:
- .NET
---

In my [previous post]({% post_url 2026-02-18-Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service %}), I showed how to build a memory-efficient catalog traversal service for Optimizely Commerce. The service uses streaming to process large catalogs without loading everything into memory at once.
In my [previous post]({% post_url 2026-02-18-memory-efficient-catalog-traversal-in-optimizely-commerce-part-1-building-the-service %}), I showed how to build a memory-efficient catalog traversal service for Optimizely Commerce. The service uses streaming to process large catalogs without loading everything into memory at once.

But having a well-designed service is only half the battle. The real value comes from knowing how to use it effectively in production scenarios. In this post, I'll walk through practical patterns for scheduled jobs that process catalog data—complete with error handling, progress reporting, and resilience strategies.

Expand Down Expand Up @@ -250,6 +255,6 @@ Thank you for reading, and I hope these patterns help you build robust catalog p

## This Post is Part of a Series

- [Part 1: Building the Service]({% post_url 2026-02-18-Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service %})
- [Part 1: Building the Service]({% post_url 2026-02-18-memory-efficient-catalog-traversal-in-optimizely-commerce-part-1-building-the-service %})
- Part 2: Real-World Scheduled Job Patterns - (this post)
- [Part 3: Hangfire Integration]({% post_url 2026-03-03-Catalog-Traversal-with-Hangfire-Part-3-Advanced-Job-Management %})
- [Part 3: Hangfire Integration]({% post_url 2026-03-03-catalog-traversal-with-hangfire-part-3-advanced-job-management %})
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ date: 2026-03-03 10:00:00 +0200
author: Stanisław Szołkowski
comments: true
published: true
redirect_from:
- /2026/03/03/Catalog-Traversal-with-Hangfire-Part-3-Advanced-Job-Management.html
image:
path: assets/img/2026-03-03-Catalog-Traversal-with-Hangfire-Part-3-Advanced-Job-Management.png
path: assets/img/2026-03-03-catalog-traversal-with-hangfire-part-3-advanced-job-management.png
alt: "Catalog Traversal with Hangfire. Part 3: Advanced Job Management"
width: 1200
height: 630
primary_tag: hangfire
tags:
- episerver
- optimizely
Expand All @@ -21,7 +26,7 @@ tags:
- background jobs
---

In [Part 1]({% post_url 2026-02-18-Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service %}), I showed how to build a memory-efficient catalog traversal service, and in [Part 2]({% post_url 2026-02-24-Catalog-Traversal-in-Action-Part-2-Real-World-Scheduled-Job-Patterns %}), I demonstrated practical patterns using Optimizely's built-in scheduled jobs.
In [Part 1]({% post_url 2026-02-18-memory-efficient-catalog-traversal-in-optimizely-commerce-part-1-building-the-service %}), I showed how to build a memory-efficient catalog traversal service, and in [Part 2]({% post_url 2026-02-24-catalog-traversal-in-action-part-2-real-world-scheduled-job-patterns %}), I demonstrated practical patterns using Optimizely's built-in scheduled jobs.

While Optimizely's scheduled job system works well for basic scenarios, you might find yourself wanting more: better monitoring, automatic retries, distributed execution, or more flexible scheduling. This is where Hangfire shines.

Expand Down Expand Up @@ -432,6 +437,6 @@ Do you have other Hangfire patterns you've found useful for Optimizely Commerce?

Thank you for reading, and I hope this series helps you build robust, scalable catalog processing solutions.

- [Part 1: Building the Service]({% post_url 2026-02-18-Memory-Efficient-Catalog-Traversal-in-Optimizely-Commerce-Part-1-Building-the-Service %})
- [Part 2: Real-World Scheduled Job Patterns]({% post_url 2026-02-24-Catalog-Traversal-in-Action-Part-2-Real-World-Scheduled-Job-Patterns %})
- [Part 1: Building the Service]({% post_url 2026-02-18-memory-efficient-catalog-traversal-in-optimizely-commerce-part-1-building-the-service %})
- [Part 2: Real-World Scheduled Job Patterns]({% post_url 2026-02-24-catalog-traversal-in-action-part-2-real-world-scheduled-job-patterns %})
- Part 3: Hangfire Integration - (this post)
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ date: 2026-03-31 10:00:00 +0200
author: Stanisław Szołkowski
comments: true
published: true
redirect_from:
- /2026/03/31/OptiPowerTools.Hangfire-A-Drop-in-Hangfire-Integration-for-Optimizely-CMS-12.html
image:
path: assets/img/2026-03-31-OptiPowerTools.Hangfire-Dashboard.png
path: assets/img/2026-03-31-optipowertools-hangfire-dashboard.png
alt: "OptiPowerTools.Hangfire: A Drop-in Hangfire Integration for Optimizely CMS 12"
width: 2858
height: 1338
primary_tag: OptiPowerTools.Hangfire
tags:
- episerver
- optimizely
Expand All @@ -29,7 +34,7 @@ What I didn't expect was the interest it would generate. The post kept getting t
Fair point. So I built one. Meet **OptiPowerTools.Hangfire** — a drop-in NuGet package that turns all of that manual setup into two lines of code.

<p style="text-align: center;">
<img src="/assets/img/2026-03-31-OptiPowerTools.Hangfire-icon.png" alt="OptiPowerTools.Hangfire icon" style="max-width: 200px;" />
<img src="/assets/img/2026-03-31-optipowertools-hangfire-icon.png" alt="OptiPowerTools.Hangfire icon" style="max-width: 200px;" />
</p>

## Getting started
Expand Down Expand Up @@ -57,7 +62,7 @@ The dashboard is embedded in the CMS shell, so it feels like a native part of Op
- **Role-based authorization** — by default, only Administrators, CmsAdmins, and WebAdmins can access the dashboard
- **CMS menu integration** with three placement options: under the CMS section (default), as a top-level nav item, or in a custom section group

![Hangfire Dashboard in Optimizely CMS](/assets/img/2026-03-31-OptiPowerTools.Hangfire-Dashboard.png)
![Hangfire Dashboard in Optimizely CMS](/assets/img/2026-03-31-optipowertools-hangfire-dashboard.png)

Jobs get rich console output out of the box — [Hangfire.Console](https://github.com/pieceofsummer/Hangfire.Console) is enabled by default. Inject `PerformContext` into your job method and use `context.WriteLine()` to write directly to the job's console in the dashboard, just like I showed in the original post.

Expand Down Expand Up @@ -114,7 +119,7 @@ services.AddOptiPowerToolHangfire(options =>

## Built-in job filters

This is where things go beyond the original blog post. The package ships with four attribute-based job filters for common patterns I've seen (and written about) in production Optimizely projects. In [Part 3: Hangfire Integration]({% post_url 2026-03-03-Catalog-Traversal-with-Hangfire-Part-3-Advanced-Job-Management %}), I covered job dependencies and concurrency challenges when running multiple Hangfire jobs against the same data. These filters address exactly those scenarios.
This is where things go beyond the original blog post. The package ships with four attribute-based job filters for common patterns I've seen (and written about) in production Optimizely projects. In [Part 3: Hangfire Integration]({% post_url 2026-03-03-catalog-traversal-with-hangfire-part-3-advanced-job-management %}), I covered job dependencies and concurrency challenges when running multiple Hangfire jobs against the same data. These filters address exactly those scenarios.

### MutualExclusion

Expand Down
3 changes: 3 additions & 0 deletions _posts/2026-04-09-optimizely-alloy-on-macos-apple-silicon.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ published: true
image:
path: assets/img/2026-04-09-optimizely-alloy-on-macos-apple-silicon.png
alt: "Running the Optimizely CMS 13 Alloy Site on macOS with Docker"
width: 1376
height: 768
primary_tag: apple silicon
tags:
- optimizely
- episerver
Expand Down
Loading
Loading