Round #864
11 May 2026, 09:50· csharp· Meta-Llama-3.3-70B-Instruct· r15-firing-v3
- Complexity
- band 0
- ZP / UO / OBS
- 2 / 5 / 1
- Journey weight
- 477
- Lightshift ΔL
- -0.25
Prompt
Implement a multitenant request-scoped ITenantInfo resolver that reads the tenant id from a custom header and falls back to subdomain.
Zero-point index (what the engine surfaced)
ZPI-ZPI-0021 | 2026-05-11T09:50:35Z
Implement a multitenant request-scoped ITenantInfo resolver that reads the tenant id from a custom header and falls back to subdomain.
ZERO POINT — 2 entries (structural invariants)
● hsl(83.28, 100%, 31%) ITenantInfo [contract]
● hsl(281.88, 100%, 31%) IMultiTenantStore<TTenantInfo> [contract]
UNOBSERVED — 5 entries (implied truths)
○ hsl(27.69, 100%, 50%) async [feature]
○ hsl(9.23, 100%, 50%) interface [feature]
○ hsl(77.54, 100%, 42%) Routing middleware [function]
○ hsl(83.08, 100%, 42%) ILogger of T injection [function]
○ hsl(31.38, 100%, 42%) Scoped registration [function]
OBSERVED — 1 entries (explicit in request)
◉ hsl(102.56, 100%, 34%) scoped [feature]
COMPLEXITY │ 8 entries │ spread: 93° │ band: LOW
Baseline response (no ZPI)
Here's an example implementation of a multitenant request-scoped `ITenantInfo` resolver in C# that reads the tenant ID from a custom header and falls back to subdomain:
```csharp
using Microsoft.AspNetCore.Http;
using System;
using System.Threading.Tasks;
public interface ITenantInfo
{
string Id { get; }
}
public class TenantInfo : ITenantInfo
{
public string Id { get; set; }
}
public interface ITenantInfoResolver
{
Task<ITenantInfo> ResolveAsync();
}
public class HeaderTenantInfoResolver : ITenantInfoResolver
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly string _customHeaderName;
public HeaderTenantInfoResolver(IHttpContextAccessor httpContextAccessor, string customHeaderName)
{
_httpContextAccessor = httpContextAccessor;
_customHeaderName = customHeaderName;
}
public async Task<ITenantInfo> ResolveAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
throw new InvalidOperationException("HttpContext is not available");
}
// Try to read the tenant ID from the custom header
var tenantIdFromHeader = httpContext.Request.Headers[_customHeaderName].FirstOrDefault();
if (!string.IsNullOrEmpty(tenantIdFromHeader))
{
return new TenantInfo { Id = tenantIdFromHeader };
}
// Fallback to subdomain
var host = httpContext.Request.Host;
var subdomain = host.Host.Split('.')[0];
if (!string.IsNullOrEmpty(subdomain))
{
return new TenantInfo { Id = subdomain };
}
// If no tenant ID is found, throw an exception
throw new InvalidOperationException("Tenant ID not found");
}
}
```
To use this resolver, you'll need to register it in the DI container and configure the custom header name. Here's an example:
```csharp
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<ITenantInfoResolver, HeaderTenantInfoResolver>(provider =>
{
var httpContextAccessor = provider.GetService<IHttpContextAccessor>();
return new HeaderTenantInfoResolver(httpContextAccessor, "X-Tenant-Id");
});
}
```
In this example, the `HeaderTenantInfoResolver` is registered as a transient service, and the custom header name is set to "X-Tenant-Id". You can adjust this to fit your needs.
To use the resolver in a controller, you can inject it through the constructor:
```csharp
[ApiController]
[Route("api/[controller]")]
public class MyController : ControllerBase
{
private readonly ITenantInfoResolver _tenantInfoResolver;
public MyController(ITenantInfoResolver tenantInfoResolver)
{
_tenantInfoResolver = tenantInfoResolver;
}
[HttpGet]
public async Task<IActionResult> Get()
{
var tenantInfo = await _tenantInfoResolver.ResolveAsync();
// Use the tenant ID
return Ok(tenantInfo.Id);
}
}
```
Note that this implementation assumes that the custom header is present in every request. If the header is missing, the resolver will fallback to the subdomain. If the subdomain is also missing, an exception will be thrown. You may want to adjust this behavior to fit your specific requirements.
Enhanced response (with ZPI)
```csharp
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
public class TenantInfoResolver : ITenantInfo
{
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<TenantInfoResolver> _logger;
public TenantInfoResolver(IHttpContextAccessor httpContextAccessor, ILogger<TenantInfoResolver> logger)
{
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public async Task<TenantInfo> GetTenantInfoAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
_logger.LogError("HttpContext is null");
return null;
}
var tenantId = httpContext.Request.Headers["X-Tenant-Id"].FirstOrDefault();
if (string.IsNullOrEmpty(tenantId))
{
var host = httpContext.Request.Host;
var subdomain = host.Host.Split('.')[0];
if (!string.IsNullOrEmpty(subdomain))
{
tenantId = subdomain;
}
}
if (string.IsNullOrEmpty(tenantId))
{
_logger.LogError("TenantId is null or empty");
return null;
}
return new TenantInfo { Id = tenantId };
}
}
public class TenantInfo
{
public string Id { get; set; }
}
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ITenantInfo, TenantInfoResolver>();
services.AddHttpContextAccessor();
}
}
```
This implementation uses the `IHttpContextAccessor` to access the current HTTP context and read the custom header or subdomain. The `ILogger` is used to log any errors that occur during the resolution process. The `TenantInfoResolver` is registered as a scoped service, which means a new instance will be created for each request. The `TenantInfo` class is a simple POCO that holds the tenant ID.
Note that this implementation assumes that the custom header is named "X-Tenant-Id" and that the subdomain is the first part of the host header. You may need to adjust these values to fit your specific requirements.
Also, error handling is minimal in this example, you may want to add more robust error handling depending on your application's needs.
This implementation requires more text to fully explain the solution and provide context.
Grader chain
Each grader reads the same baseline + enhanced pair and casts an independent verdict. Disagreement is the framework's own signal — not noise to be averaged away.
V1samba/Meta-Llama-3.3-70B-Instruct
BetterV4qwen2.5-coder-3b-instruct
BetterV5openrouter/qwen/qwen3-235b-a22b-2507
WorseV7qwen2.5-coder-7b-instruct
BetterV8openai/gpt-4.1
SameV9anthropic/claude-opus-4-7
WorseV12openai/gpt-4o
BetterComments
Our grader said what it said. What do you say? Comment as a guest below.
No comments yet. Be the first to say what you make of this round.