microsoft/aspire · error · InvalidOperationException

Handle ' ' contains , expected

Error message

Handle '{handleId}' contains {obj.GetType().FullName}, expected {typeof(T).FullName}

What it means

The typed HandleRegistry.GetObject<T> first resolves the handle, then verifies the stored object is assignable to T. If the stored object's type does not match T, it throws this InvalidOperationException naming both the actual and expected type names.

Solutions

  1. Use the actual registered type as T (check the error message: it names the stored type).
  2. Register the object under the type you intend to retrieve, or register a second handle for the typed view.
  3. Check for type identity issues: the same FullName from different assembly versions is not T.
  4. Retrieve as object via GetObject(handleId) and pattern-match before casting.

Example fix

// before
var project = registry.GetObject<ContainerResource>(handleId); // stored type is ProjectResource

// after
var resource = registry.GetObject<ProjectResource>(handleId);
Defensive patterns

Strategy: type-guard

Validate before calling

var raw = registry.GetObject(handleId);
if (raw is not MyExpectedType)
    throw new InvalidOperationException($"Handle '{handleId}' holds {raw.GetType().FullName}; expected {typeof(MyExpectedType).FullName}.");

Type guard

bool TryGetTyped<T>(Aspire.Hosting.RemoteHost.Ats.HandleRegistry registry, string id, out T? typed) where T : class
{
    typed = null;
    try { typed = registry.GetObject<T>(id); return true; }
    catch (InvalidOperationException) { return false; }
}

Try / catch

try
{
    var resource = registry.GetObject<ContainerResource>(handleId);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("contains") && ex.Message.Contains("expected"))
{
    logger.LogError(ex, "Handle type mismatch: {Message}", ex.Message);
}

Prevention

When it happens

Trigger: Calling GetObject<T>(handleId) where the handle was registered with an object of a different concrete type — e.g. registering a ProjectResource and retrieving as ContainerResource, or two types sharing a name across assemblies.

Common situations: Refactor changed the registered object's type but not retrieval call sites; generic parameter inferred incorrectly; handle IDs collide because IDs were reused for different objects.

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/1bb160aff47e66b1. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.RemoteHost/Ats/HandleRegistry.cs:98

        {
            throw new InvalidOperationException($"Handle '{handleId}' not found in registry");
        }
        return entry.Object;
    }

    /// <summary>
    /// Gets the underlying object for a handle, cast to the specified type.
    /// </summary>
    /// <typeparam name="T">The expected type.</typeparam>
    /// <param name="handleId">The handle ID.</param>
    /// <returns>The underlying object.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the handle is not found or type doesn't match.</exception>
    public T GetObject<T>(string handleId) where T : class
    {
        var obj = GetObject(handleId);
        if (obj is not T typed)
        {
            throw new InvalidOperationException(
                $"Handle '{handleId}' contains {obj.GetType().FullName}, expected {typeof(T).FullName}");
        }
        return typed;
    }

    /// <summary>
    /// Gets the ATS type ID for a handle.
    /// </summary>
    /// <param name="handleId">The handle ID.</param>
    /// <returns>The ATS type ID.</returns>
    /// <exception cref="InvalidOperationException">Thrown if the handle is not found.</exception>
    public string GetTypeId(string handleId)
    {
        if (!_handles.TryGetValue(handleId, out var entry))
        {
            throw new InvalidOperationException($"Handle '{handleId}' not found in registry");
        }
        return entry.TypeId;

View on GitHub (pinned to 25830f84bd)