{"record":{"id":"85067c7a6866e91d","repo":"dotnet/orleans","slug":"channel-binding-for-grain-type-binding-graintype","errorCode":null,"errorMessage":"Channel binding for grain type {binding.GrainType} is missing a \"{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}\" value","messagePattern":"Channel binding for grain type (.+?) is missing a \"(.+?)\" value","errorType":"exception","errorClass":"KeyNotFoundException","httpStatus":null,"severity":"error","filePath":"src/Orleans.BroadcastChannel/SubscriberTable/ImplicitChannelSubscriberTable.cs","lineNumber":77,"sourceCode":"        }\n\n        private Cache BuildCache(MajorMinorVersion version, ImmutableDictionary<GrainType, GrainBindings> bindings)\n        {\n            var newPredicates = new List<BroadcastChannelSubscriberPredicate>();\n\n            foreach (var binding in bindings.Values)\n            {\n                foreach (var grainBinding in binding.Bindings)\n                {\n                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type)\n                        || type != WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue)\n                    {\n                        continue;\n                    }\n\n                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey, out var pattern))\n                    {\n                        throw new KeyNotFoundException(\n                           $\"Channel binding for grain type {binding.GrainType} is missing a \\\"{WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey}\\\" value\");\n                    }\n\n                    IChannelNamespacePredicate? predicate = null;\n                    foreach (var provider in _providers)\n                    {\n                        if (provider.TryGetPredicate(pattern, out predicate)) break;\n                    }\n\n                    if (predicate is null)\n                    {\n                        throw new KeyNotFoundException(\n                            $\"Could not find an {nameof(IChannelNamespacePredicate)} for the pattern \\\"{pattern}\\\".\"\n                            + $\" Ensure that a corresponding {nameof(IChannelNamespacePredicateProvider)} is registered\");\n                    }\n\n                    if (!grainBinding.TryGetValue(WellKnownGrainTypeProperties.ChannelIdMapperKey, out var mapperName))\n                    {","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/dotnet/orleans/blob/fca799fa70ecb6ad975224271703ca43221f58de/src/Orleans.BroadcastChannel/SubscriberTable/ImplicitChannelSubscriberTable.cs#L59-L95","documentation":"Thrown during ImplicitChannelSubscriberTable.BuildCache when a grain binding dictionary is marked as type 'broadcast-channel' (via WellKnownGrainTypeProperties.BindingTypeKey) but does not contain the 'channel-pattern' key (WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey). The pattern specifies which namespace predicate to use for matching published messages to subscribers; without it the binding is malformed. Under normal usage via [ImplicitChannelSubscription], this key is always populated from the predicate's PredicatePattern.","triggerScenarios":"Grain bindings are constructed programmatically or via a custom IGrainBindingsProviderAttribute that sets BindingTypeKey to 'broadcast-channel' but omits BroadcastChannelBindingPatternKey. This occurs at silo startup when the ImplicitChannelSubscriberTable constructor calls BuildCache to build its subscriber cache from all grain bindings. It never occurs when using the built-in [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attributes, since their GetBindings method always emits the pattern key.","commonSituations":"A custom grain binding provider or programmatic manifest generator copies only the type field when constructing broadcast-channel bindings. Migrating from Orleans.Streams implicit stream subscriptions to BroadcastChannel and copying binding dictionaries without translating 'pattern' to 'channel-pattern'. Reflection-based or convention-based grain registration that builds binding dictionaries manually and forgets the pattern entry.","solutions":["Replace manual binding construction with the [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attribute on the grain class, which always populates all required binding keys.","If programmatic bindings are necessary, include WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey in the binding dictionary with a valid pattern value (e.g., '*', 'namespace:orders', or 'regex:orders.*').","Audit any custom IGrainBindingsProviderAttribute implementations to ensure they emit all three required keys: type, channel-pattern, and channelid-mapper."],"exampleFix":"// before — manual binding missing channel-pattern\nvar binding = new Dictionary<string, string?>\n{\n    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,\n    // missing channel-pattern\n};\n\n// after — all required keys present\nvar binding = new Dictionary<string, string?>\n{\n    [WellKnownGrainTypeProperties.BindingTypeKey] = WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue,\n    [WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey] = \"namespace:orders\",\n    [WellKnownGrainTypeProperties.ChannelIdMapperKey] = DefaultChannelIdMapper.Name,\n};","handlingStrategy":"validation","validationCode":"// Validate a manually constructed broadcast-channel binding has all required keys.\nstatic void ValidateBroadcastChannelBinding(Dictionary<string, string?> binding)\n{\n    if (binding.TryGetValue(WellKnownGrainTypeProperties.BindingTypeKey, out var type)\n        && type == WellKnownGrainTypeProperties.BroadcastChannelBindingTypeValue)\n    {\n        if (!binding.ContainsKey(WellKnownGrainTypeProperties.BroadcastChannelBindingPatternKey))\n            throw new InvalidOperationException(\n                \"Broadcast-channel binding is missing the required 'channel-pattern' key.\");\n        if (!binding.ContainsKey(WellKnownGrainTypeProperties.ChannelIdMapperKey))\n            throw new InvalidOperationException(\n                \"Broadcast-channel binding is missing the required 'channelid-mapper' key.\");\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer the [ImplicitChannelSubscription] or [RegexImplicitChannelSubscription] attribute over manual binding construction — it always emits all three required keys.","If you must build bindings programmatically, write a validation helper that checks for all required keys before registering the binding.","Review any custom IGrainBindingsProviderAttribute implementations against the GetBindings implementation in ImplicitChannelSubscriptionAttribute to ensure parity."],"tags":["broadcast-channel","grain-binding","configuration","key-not-found"],"backgroundTag":null,"analyzedSha":"fca799fa70ecb6ad975224271703ca43221f58de","analyzedAt":"2026-08-13T19:55:57.938Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}