{"record":{"id":"527e0f13682372d4","repo":"elsa-workflows/elsa-core","slug":"output-converter-registration-id-must-use-the-converter-id","errorCode":null,"errorMessage":"Output converter registration '{id}' must use the Converter ID as its service key.","messagePattern":"Output converter registration '(.+?)' must use the Converter ID as its service key\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Workflows.Core/Services/OutputConverterRegistry.cs","lineNumber":53,"sourceCode":"\n    internal static bool IsAssignableToDestination(Type valueType, Type destinationType)\n    {\n        if (destinationType.IsAssignableFrom(valueType))\n            return true;\n\n        var underlyingDestinationType = Nullable.GetUnderlyingType(destinationType);\n        return underlyingDestinationType?.IsAssignableFrom(valueType) == true;\n    }\n\n    private static void ValidateRegistrations(IReadOnlyCollection<OutputConverterRegistration> registrations)\n    {\n        foreach (var registration in registrations)\n        {\n            ValidateDescriptor(registration.Descriptor);\n            var id = registration.Descriptor.Id;\n\n            if (!string.Equals(id, registration.ServiceKey, StringComparison.Ordinal))\n                throw new InvalidOperationException($\"Output converter registration '{id}' must use the Converter ID as its service key.\");\n        }\n\n        var duplicate = registrations\n            .GroupBy(x => x.Descriptor.Id, StringComparer.OrdinalIgnoreCase)\n            .FirstOrDefault(x => x.Count() > 1);\n\n        if (duplicate != null)\n            throw new InvalidOperationException($\"Output converter ID '{duplicate.Key}' is registered more than once or differs from another registration only by case.\");\n    }\n\n    internal static void ValidateDescriptor(OutputConverterDescriptor descriptor)\n    {\n        if (string.IsNullOrWhiteSpace(descriptor.Id))\n            throw new InvalidOperationException(\"Output converter IDs cannot be empty.\");\n\n        if (descriptor.SourceType.ContainsGenericParameters || descriptor.ResultType.ContainsGenericParameters)\n            throw new InvalidOperationException($\"Output converter '{descriptor.Id}' cannot use open-generic source or result types.\");\n    }","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Workflows.Core/Services/OutputConverterRegistry.cs#L35-L71","documentation":"Thrown by OutputConverterRegistry.ValidateRegistrations at startup when an output converter is registered with a service key that differs from its descriptor's Converter ID. The registry resolves converters by keyed services, so the DI service key must exactly (ordinal) match the descriptor Id.","triggerScenarios":"Registering an OutputConverterDescriptor via DI with a key such as AddKeyedSingleton(key, ...) where key != descriptor.Id; renaming the descriptor Id without updating the registration key; convention-based registration helpers producing mismatched keys.","commonSituations":"Copy/pasted converter registrations where the descriptor Id was changed, plugin/module registrations assembled dynamically, and refactoring after renaming a converter class.","solutions":["Make the DI service key identical to descriptor.Id (use the same constant in both places).","Derive the service key from the descriptor when registering: AddKeyedSingleton(descriptor.Id, implementation).","If using Elsa's registration helpers, pass the descriptor itself so key and Id stay in sync."],"exampleFix":"// before\nservices.AddKeyedSingleton<IOutputConverter, IntToStringConverter>(\"int-to-str\");\ndescriptor.Id = \"int-to-string\";\n\n// after\nconst string ConverterId = \"int-to-string\";\ndescriptor.Id = ConverterId;\nservices.AddKeyedSingleton<IOutputConverter, IntToStringConverter>(ConverterId);","handlingStrategy":"validation","validationCode":"if (!string.Equals(descriptor.Id, serviceKey, StringComparison.Ordinal))\n    throw new InvalidOperationException($\"Service key '{serviceKey}' must equal converter Id '{descriptor.Id}'.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    services.AddOutputConverters(...); // triggers ValidateRegistrations\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"must use the Converter ID as its service key\"))\n{\n    logger.LogCritical(ex, \"Output converter registration key mismatch\");\n    throw; // fail fast: misconfiguration cannot be recovered at runtime\n}","preventionTips":["Use a single shared constant for both descriptor.Id and the DI service key.","Register converters through Elsa's helper APIs that take the descriptor, not hand-rolled AddKeyed calls.","Add a startup smoke test that builds the host to surface registration errors in CI."],"tags":["dependency-injection","output-converter","startup"],"backgroundTag":"invalid-config-value","analyzedSha":"fe9217bdfa0e27f0e09e45006eb6898f616e513d","analyzedAt":"2026-09-13T20:32:34.702Z","contentChangedAt":"2026-09-13T20:32:34.702Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}