{"record":{"id":"1cc91aa6d428df9a","repo":"dotnet/aspnetcore","slug":"the-type-0-does-not-have-an-associated-typecon","errorCode":null,"errorMessage":"The type '{0}' does not have an associated TypeConverter that supports conversion to a string. Apply 'TypeConverterAttribute' to the type to register a converter.","messagePattern":"The type '(.+?)' does not have an associated TypeConverter that supports conversion to a string\\. Apply 'TypeConverterAttribute' to the type to register a converter\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Components/Components/src/BindConverter.cs","lineNumber":1848,"sourceCode":"                {\n                    builder.Append(\", \\\"\");\n                    builder.Append(JsonEncodedText.Encode(elementFormatter(value[i], culture)?.ToString() ?? string.Empty).Value);\n                    builder.Append('\\\"');\n                }\n\n                builder.Append(']');\n\n                return builder.ToString();\n            }\n        }\n\n        [UnconditionalSuppressMessage(\"Trimming\", \"IL2026\", Justification = \"We expect unknown underlying types are configured by application code to be retained.\")]\n        private static BindFormatter<T> MakeTypeConverterFormatter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] T>()\n        {\n            var typeConverter = TypeDescriptor.GetConverter(typeof(T));\n            if (typeConverter == null || !typeConverter.CanConvertTo(typeof(string)))\n            {\n                throw new InvalidOperationException(\n                    $\"The type '{typeof(T).FullName}' does not have an associated {typeof(TypeConverter).Name} that supports \" +\n                    $\"conversion to a string. \" +\n                    $\"Apply '{typeof(TypeConverterAttribute).Name}' to the type to register a converter.\");\n            }\n\n            return FormatWithTypeConverter;\n\n            string? FormatWithTypeConverter(T value, CultureInfo? culture)\n            {\n                // We intentionally close-over the TypeConverter to cache it. The TypeDescriptor infrastructure is slow.\n                return typeConverter.ConvertToString(context: null, culture ?? CultureInfo.CurrentCulture, value);\n            }\n        }\n    }\n\n    internal static class ParserDelegateCache\n    {\n        private static readonly ConcurrentDictionary<Type, Delegate> _cache = new ConcurrentDictionary<Type, Delegate>();","sourceCodeStart":1830,"sourceCodeEnd":1866,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/294cab2f9b2e03af6b953820c7ab497c3c8b7ad9/src/Components/Components/src/BindConverter.cs#L1830-L1866","documentation":"Thrown by BindConverter's FormatterDelegateCache.MakeTypeConverterFormatter when a type used in @bind has no TypeConverter capable of converting it to a string. Blazor needs to render bound values into input elements; for non-primitive types it falls back to TypeDescriptor.GetConverter, and if that converter can't convert to string, this InvalidOperationException is thrown. The fix is to apply [TypeConverter] on the type registering a converter that supports ConvertTo(typeof(string)).","triggerScenarios":"Using @bind on an <input> element bound to a property of a custom type that has no TypeConverter registered, and is not one of the built-in supported types (string, int, bool, DateTime, Guid, enums, arrays, etc.). The formatter lookup happens at BindConverter.cs:1843-1861.","commonSituations":"Binding a custom struct/record/class (e.g., a Money, EmailAddress, or custom Identifier type) to an input without registering a TypeConverter; using a third-party value type that doesn't ship a converter.","solutions":["Create a TypeConverter subclass that overrides CanConvertTo(typeof(string)) and ConvertTo for string.","Apply [TypeConverter(typeof(YourConverter))] to your custom type.","Alternatively, bind a primitive/string property and convert in get/set accessors or in a separate computed property."],"exampleFix":"// before\npublic struct Money\n{\n    public decimal Amount;\n    public string Currency;\n}\n@* @bind to Money throws *@\n\n// after\n[TypeConverter(typeof(MoneyConverter))]\npublic struct Money { public decimal Amount; public string Currency; }\n\npublic class MoneyConverter : TypeConverter\n{\n    public override bool CanConvertTo(ITypeDescriptorContext c, Type t)\n        => t == typeof(string) || base.CanConvertTo(c, t);\n    public override object ConvertTo(ITypeDescriptorContext c, CultureInfo c2, object v, Type t)\n        => t == typeof(string) ? ((Money)v).Amount.ToString(c2) : base.ConvertTo(c, c2, v, t);\n}","handlingStrategy":"type-guard","validationCode":"var converter = TypeDescriptor.GetConverter(typeof(T));\nif (converter is null || !converter.CanConvertTo(typeof(string)))\n    throw new InvalidOperationException($\"{typeof(T)} has no string TypeConverter.\");","typeGuard":"static bool CanFormatAsString<T>()\n{\n    var converter = TypeDescriptor.GetConverter(typeof(T));\n    return converter is not null && converter.CanConvertTo(typeof(string));\n}","tryCatchPattern":null,"preventionTips":["Always apply [TypeConverter] to custom types used in @bind.","Prefer binding to primitive/string properties and converting in code for exotic types."],"tags":["blazor","binding","typeconverter","data-binding"],"analyzedSha":"294cab2f9b2e03af6b953820c7ab497c3c8b7ad9","analyzedAt":"2026-08-06T20:08:02.189Z","schemaVersion":2},"datasetVersion":"2026-08-06T23:17:07.152Z"}