microsoft/aspire · error · InvalidOperationException

Aspire.Hosting/Dict.toObject only supports string-key…

Error message

Aspire.Hosting/Dict.toObject only supports string-key dictionaries, but found key type '{key?.GetType().FullName ?? "null"}'.

What it means

Dict.toObject in the Aspire.Hosting ATS exports only supports dictionaries whose keys are strings; GetStringKey casts the key via `key as string` and throws InvalidOperationException when a non-string (or null) key is encountered. This guard exists because the scripting layer cannot represent non-string dictionary keys.

Solutions

  1. Convert the dictionary to Dictionary<string, TValue> (e.g., with ToDictionary(k => k.Key.ToString()!, ...)) before calling toObject.
  2. If keys are enums, use the enum name or ToString as the key.
  3. If keys are complex objects, replace them with a string identifier property.

Example fix

// before
Dict.toObject(myIntKeyedDict);
// after
Dict.toObject(myIntKeyedDict.ToDictionary(kv => kv.Key.ToString(), kv => kv.Value));
Defensive patterns

Strategy: type-guard

Validate before calling

if (dict is not IDictionary<string, object> stringKeyed)
{
    throw new InvalidOperationException($"Dict.toObject requires string keys; got {dict?.GetType().FullName ?? "null"}.");
}

Type guard

static bool HasStringKeys<TValue>(Dictionary<TKey, TValue> d) where TKey : notnull => typeof(TKey) == typeof(string);

Try / catch

try
{
    Dict.toObject(dict);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("only supports string-key"))
{
    // fall back to projecting keys with ToString()
}

Prevention

When it happens

Trigger: Calling Dict.toObject on an IDictionary<TKey,TValue> where keys are not strings — e.g., Dictionary<int, ...>, Dictionary<Guid, ...>, Dictionary<MyEnum, ...> — or a dictionary containing a null key.

Common situations: Passing a numerically-keyed dictionary (config lookups keyed by int, cache keyed by Guid or enum) into the ATS Dict export; localization or lookup maps keyed by enum values.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/b255d042fd57e762. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting/Ats/CollectionExports.cs:202

    private static Type? GetDictionaryKeyType(Type type)
    {
        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IDictionary<,>))
        {
            return type.GetGenericArguments()[0];
        }

        if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
        {
            return type.GetGenericArguments()[0];
        }

        return type.GetInterfaces()
            .FirstOrDefault(static iface => iface.IsGenericType && iface.GetGenericTypeDefinition() == typeof(IDictionary<,>))
            ?.GetGenericArguments()[0];
    }

    private static string GetStringKey(object? key)
        => key as string ?? throw new InvalidOperationException($"Aspire.Hosting/Dict.toObject only supports string-key dictionaries, but found key type '{key?.GetType().FullName ?? "null"}'.");

    #endregion

    #region List Operations

    /// <summary>
    /// Gets an item from a list by index.
    /// </summary>
    /// <param name="list">The list handle.</param>
    /// <param name="index">The zero-based index.</param>
    /// <returns>The item at the specified index.</returns>
    [AspireExport("List.get")]
    public static object? ListGet(this IList list, int index)
        => index >= 0 && index < list.Count ? list[index] : null;

    /// <summary>
    /// Sets an item in a list at a specific index.
    /// </summary>

View on GitHub (pinned to 25830f84bd)