dotnet/orleans · error · KeyNotFoundException

Channel binding for grain type {binding.GrainType} is missin

Error message

Channel binding for grain type {binding.GrainType} is missing a "{WellKnownGrainTypeProperties.ChannelIdMapperKey}" value

What it means

Thrown during ImplicitChannelSubscriberTable.BuildCache when a grain binding is marked as type 'broadcast-channel' and has a channel-pattern, but its dictionary does not contain the 'channelid-mapper' key (WellKnownGrainTypeProperties.ChannelIdMapperKey). This key names the IChannelIdMapper keyed service used to map channel keys to grain keys. Under normal usage via [ImplicitChannelSubscription], this key is always populated with DefaultChannelIdMapper.Name or a user-specified mapper name. Even a null/whitespace value is tolerated (line 100 falls back to the default mapper), so the key must be entirely absent to trigger this.

Source

Thrown at src/Orleans.BroadcastChannel/SubscriberTable/ImplicitChannelSubscriberTable.cs:96

                           $"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}\" value");
                    }

                    IChannelNamespacePredicate? predicate = null;
                    foreach (var provider in _providers)
                    {
                        if (provider.TryGetPredicate(pattern, out predicate)) break;
                    }

                    if (predicate is null)
                    {
                        throw new KeyNotFoundException(
                            $"Could not find an {nameof(IChannelNamespacePredicate)} for the pattern \"{pattern}\"."
                            + $" Ensure that a corresponding {nameof(IChannelNamespacePredicateProvider)} is registered");
                    }

                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.ChannelIdMapperKey, out var mapperName))
                    {
                        throw new KeyNotFoundException(
                           $"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.ChannelIdMapperKey}\" value");
                    }

                    var channelIdMapper = _serviceProvider.GetKeyedService<IChannelIdMapper>(string.IsNullOrWhiteSpace(mapperName) ? DefaultChannelIdMapper.Name : mapperName);
                    var subscriber = new BroadcastChannelSubscriber(binding, channelIdMapper!);
                    newPredicates.Add(new BroadcastChannelSubscriberPredicate(subscriber, predicate));
                }
            }

            return new Cache(version, newPredicates);
        }

        /// <summary>
        /// Retrieve a map of implicit subscriptionsIds to implicit subscribers, given a channel ID. This method throws an exception if there's no namespace associated with the channel ID.
        /// </summary>
        /// <param name="channelId">A channel ID.</param>
        /// <param name="grainFactory">The grain factory used to get consumer references.</param>
        /// <returns>A set of references to implicitly subscribed grains. They are expected to support the broadcast channel consumer extension.</returns>

View on GitHub (pinned to fca799fa70)

Solutions

  1. Replace manual binding construction with the [ImplicitChannelSubscription] attribute, which always sets ChannelIdMapperKey to DefaultChannelIdMapper.Name.
  2. If constructing bindings programmatically, include WellKnownGrainTypeProperties.ChannelIdMapperKey in the binding dictionary with the mapper name (use DefaultChannelIdMapper.Name, i.e. "default", for the standard mapper).
  3. Audit any custom IGrainBindingsProviderAttribute implementations to ensure they emit all three required keys: type, channel-pattern, and channelid-mapper.

Example fix

// before — manual binding missing channelid-mapper
var binding = new Dictionary<string, string?>
{
    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,
    [WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey] = "namespace:orders",
    // missing channelid-mapper
};

// after — all required keys present
var binding = new Dictionary<string, string?>
{
    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,
    [WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey] = "namespace:orders",
    [WellKnownGrainTypeProperties.ChannelIdMapperKey] = DefaultChannelIdMapper.Name,
};
Defensive patterns

Strategy: validation

Validate before calling

// Validate a manually constructed broadcast-channel binding has the channelid-mapper key.
static void ValidateBindingHasMapper(Dictionary<string, string?> binding)
{
    if (binding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type)
        && type == WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue
        && !binding.ContainsKey(WellKnownGrainTypeProperties.ChannelIdMapperKey))
    {
        throw new InvalidOperationException(
            "Broadcast-channel binding is missing the required 'channelid-mapper' key. " +
            $"Set it to '{DefaultChannelIdMapper.Name}' for the default mapper.");
    }
}

Prevention

When it happens

Trigger: Grain bindings are constructed programmatically or via a custom IGrainBindingsProviderAttribute that sets BindingTypeKey to 'broadcast-channel' and BroadcastChannelBindingPatternKey but omits ChannelIdMapperKey. This fires at silo startup or on first cache refresh when BuildCache iterates all broadcast-channel bindings. It never occurs with the built-in [ImplicitChannelSubscription] attribute, whose GetBindings always emits all three required keys.

Common situations: A custom grain binding provider includes the pattern but forgets the mapper name. Programmatic manifest generation that partially copies binding dictionaries. A migration from streaming (which uses 'streamid-mapper') to broadcast-channel (which uses 'channelid-mapper') where the key name was not translated.

Related errors


AI-assisted analysis of dotnet/orleans@fca799fa70 (2026-08-13). Data as JSON: /api/errors/4a75d23b500cc77e. Report an issue: GitHub.