'Why can't an interface in a tuple be used as the model in an ASP.NET Razor partial view?

I have an ASP.NET Razor Pages project with a partial view that uses a tuple type for its model. One of the tuple elements uses an interface as its type. When the partial view is called using an object whose type is the interface, it works. But when it's called using a class that implements the interface, it throws an exception.

I've create a simplified repo that reproduces this error.

The project defines an interface with a class implementation:

public interface ISampleInterface
{
    int Value { get; }
}

public class ImplementationClass : ISampleInterface
{
    public int Value => 5;
}

In the Page, I can create objects whose type is either the interface or the class implementing the interface:

public class IndexModel : PageModel
{
    // type is the interface
    public ISampleInterface InterfaceInstance { get; } = new ImplementationClass();

    // type is an implementation of the interface
    public ImplementationClass ClassInstance { get; } = new ImplementationClass();

    public void OnGet() { }
}

Then there's a partial view that uses a tuple including the interface as its model:

@model (ISampleInterface Sample, int Number)

<p>Sample value: @Model.Sample.Value </p>
<p>Unrelated number: @Model.Number </p>

But when referencing the partial view, the interface-typed object can be used, while the class-typed object throws an exception:

<!-- This works: -->
<partial name="_SampleTuplePartial" model="(Model.InterfaceInstance, 1)" />

<!-- This throws an exception: -->
<partial name="_SampleTuplePartial" model="(Model.ClassInstance, 1)" />

The first partial tag renders as expected, but the second partial tag throws this exception:

InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'System.ValueTuple2[ImplementationClass,System.Int32]', but this ViewDataDictionary instance requires a model item of type 'System.ValueTuple2[ISampleInterface,System.Int32]'.

Both objects implement the interface, so why does the second one throw an exception?

This only happens when the model is a tuple. A partial view with just the interface as the model can be used with either type of object:

@model ISampleInterface

<p>Sample value: @Model.Value</p>
<partial name="_SampleInterfacePartial" model="Model.InterfaceInstance" />
<partial name="_SampleInterfacePartial" model="Model.ClassInstance" />


Solution 1:[1]

TL;DR: Because ValueTuple is not covariant.

Covariance enables you to use a more derived type than that specified by the generic parameter

out (generic modifier)

The issue is related to generic type parameters, not the partial view itself.

For example, given the following code:

public static void Main(string[] args)
{
    var strings = new List<string>();
    // this is fine because IEnumerable is covariant
    DoSomething(strings);

    // compile error: cannot convert from 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<object>'
    DoAnotherThing(strings);
}

static void DoSomething(IEnumerable<object> objects)
{
}

static void DoAnotherThing(List<object> objects)
{
}

IEnumerable generic type is covariant, it is declared as IEnumerable<out T>. This means you can cast IEnumerable<string> to IEnumerable<object> because string derives from object. But you cannot cast List<string> to List<object> because List<T> is not covariant (it could not be anyway)

ValueTuple is not covariant either, so you cannot cast ValueTuple<ImplementationClass, int>to ValueTuple<ISampleInterface, int> even tough ImplementationClass implements ISampleInterface

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jesús López