{"record":{"id":"ab72d378f109996f","repo":"elsa-workflows/elsa-core","slug":"output-converter-descriptor-id-cannot-use-open-generic","errorCode":null,"errorMessage":"Output converter '{descriptor.Id}' cannot use open-generic source or result types.","messagePattern":"Output converter '(.+?)' cannot use open-generic source or result types\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/modules/Elsa.Workflows.Core/Services/OutputConverterRegistry.cs","lineNumber":70,"sourceCode":"            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    }\n}\n","sourceCodeStart":52,"sourceCodeEnd":73,"githubUrl":"https://github.com/elsa-workflows/elsa-core/blob/fe9217bdfa0e27f0e09e45006eb6898f616e513d/src/modules/Elsa.Workflows.Core/Services/OutputConverterRegistry.cs#L52-L73","documentation":"Thrown by OutputConverterRegistry.ValidateDescriptor when a converter's SourceType or ResultType contains open generic parameters (e.g., typeof(IEnumerable<>)). The runtime must match concrete runtime value types against the descriptor, so open generics cannot be resolved or dispatched.","triggerScenarios":"Registering a converter with SourceType = typeof(T) / typeof(List<>) or a ResultType that is a generic type definition, typically when writing a generic converter class and copying its generic type parameters into the descriptor.","commonSituations":"Authors wanting 'convert anything to string' converters using generic parameters, converters ported from open-generic classes, and reflection-built descriptors where MakeGenericType was never called.","solutions":["Close the generic types: use typeof(IEnumerable<int>) or the concrete closed type instead of typeof(IEnumerable<>).","Call MakeGenericType(concreteArgs) on the open type when constructing the descriptor at registration time.","Register one descriptor per concrete type combination you need to support."],"exampleFix":"// before\ndescriptor.SourceType = typeof(IEnumerable<>);\n\n// after\ndescriptor.SourceType = typeof(IEnumerable<int>);\n// or, dynamically:\ndescriptor.SourceType = typeof(IEnumerable<>).MakeGenericType(elementType);","handlingStrategy":"validation","validationCode":"if (descriptor.SourceType.ContainsGenericParameters || descriptor.ResultType.ContainsGenericParameters)\n    throw new InvalidOperationException(\"Converter types must be closed (call MakeGenericType before registering).\");","typeGuard":"bool UsesClosedTypes(OutputConverterDescriptor d) => !d.SourceType.ContainsGenericParameters && !d.ResultType.ContainsGenericParameters;","tryCatchPattern":"try\n{\n    services.AddOutputConverter(descriptor);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"open-generic\"))\n{\n    logger.LogCritical(ex, \"Converter {Id} uses open generics; register closed types instead\", descriptor.Id);\n    throw;\n}","preventionTips":["Remember descriptors must describe concrete runtime types; generics only exist at compile time.","Use MakeGenericType in a registration loop over the concrete types you support.","Avoid generic converter classes unless you also generate closed descriptors for them."],"tags":["output-converter","generics","validation"],"backgroundTag":"invalid-argument-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"}