Take the community feedback survey now.

Aniket
Aug 16, 2025
  592
(1 votes)

Getting around asynchronous limitations in Optimizely (Visitor Groups etc.)

Optmizely has a powerful personalization engine that allows creating custom Audiences/Visitor groups. It comes with one limitation though. It doesn't support ASYNC operations. The below solution can work for any scenario where you cannot run async operations in Optimizely. 

Here's a real world use case and how we got around it.

Personalizing user experiences often starts with knowing where your visitor is coming from. One lightweight approach is to translate the user's IP address into a zip code to personalize the experience for users depending on their location (state run promotion, etc.). In Optimizely CMS 12 (running on ASP.NET Core 8), on of the way to do this is by using middleware.


Why Middleware?

Visitor Groups has a key limitation: visitor group criteria must run synchronously. This means you can’t safely call an external API or await an async lookup when evaluating a visitor group without causing thread block calls to the external service, which rules out many IP-to-zip services. ASP.NET Core middleware, on the other hand, runs for every request (that can be filtered) and fully supports async operations. Using AsyncHelper.RunSync() as outlined here isn't a scalable solution and can cause thread pool starvation and deadlocks in your application. This makes it the ideal place for concerns like geolocation, logging, authentication, and request enrichment. By resolving the zip code once at the start of the pipeline, you avoid duplicate lookups in controllers, block controllers, or views at the same time avoid blocking calls to external services.


Step 1: Create a Request-Scoped Container

We’ll use a scoped class to store the resolved zip code so it’s easily accessible through DI.

public sealed class GeoContext
{
    public string? ZipCode { get; set; }
}
 

Step 2: Define an IP → Zip Resolver

This is your service that turns an IP address into a zip code. You can use a 3rd-party API, MaxMind, or your own data source.

 public interface IZipFromIpResolver
{
    Task<string?> GetZipFromIpAsync(string ip, CancellationToken ct = default);
}
 
 

Step 3: Implement Middleware

The middleware gets the client IP, calls the resolver, and stores the result in GeoContext

 public sealed class GeoZipMiddleware
{
    private readonly RequestDelegate _next;

    public GeoZipMiddleware(RequestDelegate next) => _next = next;

    public async Task InvokeAsync(HttpContext context, GeoContext geo, IZipFromIpResolver resolver)
    {
      // Filter out any paths to avoid calling the service on these requests
       var path = httpContext.Request.Path.Value ?? "";
       if (!path.StartsWith("/assets", StringComparison.OrdinalIgnoreCase) &&
       !path.StartsWith("/static", StringComparison.OrdinalIgnoreCase) &&
       !path.StartsWith("/api/custom", StringComparison.OrdinalIgnoreCase) &&
       !path.Contains("/globalassets", StringComparison.OrdinalIgnoreCase) &&
       !path.Contains("/.well-known", StringComparison.OrdinalIgnoreCase) &&
       !path.Contains("/siteassets", StringComparison.OrdinalIgnoreCase) &&
       !path.Contains("/Errors", StringComparison.OrdinalIgnoreCase) &&
       !path.Contains("/contentassets", StringComparison.OrdinalIgnoreCase)) // Optimizely blobs
       {
        var ip = context.Connection.RemoteIpAddress?.ToString();
        if (!string.IsNullOrWhiteSpace(ip))
        {
            try
            {
                // Call your GeoLocation Service
                geo.ZipCode = await resolver.GetZipFromIpAsync(ip);
                // You can also save it within the httpContext
                 httpContext.Items["ZipCode"] = geo.ZipCode;
            }
            catch
            {
                // log if needed; don’t block the request
            }
        }
       }
       await _next(context);
    }
}
 
 

Step 4: Register Services & Middleware

Update Program.cs

var builder = WebApplication.CreateBuilder(args);

// Add request-scoped container
builder.Services.AddScoped<GeoContext>();

// Add resolver implementation
// builder.Services.AddSingleton<IZipFromIpResolver, MyIpResolver>();

builder.Services.AddHttpContextAccessor();

var app = builder.Build();

// Use middleware early in the pipeline
app.UseMiddleware<GeoZipMiddleware>();

app.UseRouting();
app.UseAuthorization();

app.MapContent();

app.Run();

 


Step 5: Use in Controllers, Blocks, or Views

Since GeoContext is scoped, you can inject it anywhere. You can also use the httpContext.Items to retrieve the value (set in the middleware)

 public override bool IsMatch(IPrincipal principal, HttpContext httpContext)
 {
     var zipCode = httpContext.Items["ZipCode"];

     // do something with it
    return true;

 }
 

Notes & Best Practices

  • Caching: IP-to-zip lookups can be expensive. Use ISynchronizedObjectCache or a distributed cache to reduce API calls.

  • Privacy: Treat IP and location data as personal information (GDPR/CCPA). Avoid persisting unless you have a clear use case.

  • Performance: Consider skipping static asset and other URL requests inside your middleware for speed.


Wrapping Up

By leveraging ASP.NET Core middleware in Optimizely CMS 12, you can easily enrich each request with geolocation or any other data. This is powerful and can significantly improve the performance of the website by removing blocking calls from the application. 

Aug 16, 2025

Comments

Quan Mai
Quan Mai Aug 22, 2025 11:26 AM

Interesting approach and nice write up Aniket. Thanks for sharing!

Please login to comment.
Latest blogs
Opal Core Concepts

Before you dive into the code, it's crucial to understand the foundational ideas that make Opal tick. Core concepts are consistent across all its...

K Khan | Sep 13, 2025

Optimizely Opal : Reimagining A Utility Sector Use Case

  Introduction Customer engagement through timely and personalized push notifications plays a crucial role in todays Digital First landscape. In th...

Ratish | Sep 12, 2025 |

A day in the life of an Optimizely OMVP - AEO & GEO: The Future of Digital Visibility with Optimizely

The way people discover content online is undergoing a seismic shift. Traditional SEO is no longer enough. With AI-powered tools like ChatGPT,...

Graham Carr | Sep 12, 2025

Building Optimizely OCP Apps Faster with AI and Coding Assistants

Developing Optimizely Connect Platform (OCP) apps can be a rewarding but complex process—especially when integrating with external APIs. Over the...

Pawel Zieba | Sep 11, 2025