I have:
PageModel
When I try @await Html.PartialAsync("_InterfacePartial", Model)
it throws the below
InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'IndexPageModel', but this ViewDataDictionary instance requires a model item of type 'IPage`2[System.Object,IRow]'.
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary.EnsureCompatible(object value)
IPage.cs
public interface IPage<T, T2>
where T : class
where T2 : IRow
{
// STRIPPED FOR BREVITY
IQueryable<T> GetQueryable();
IEnumerable<T2> GetResults(Func<T, bool> predicate);
}
AbstractPage.cs
public abstract class AbstractPage<T, T2> : PageModel, IPage<T, T2>
where T : class
where T2 : IRow
{
// STRIPPED FOR BREVITY
public abstract IQueryable<T> GetQueryable();
public IEnumerable<T2> GetResults(Func<T, bool> predicate)
{
var fields = ... // Edit after answer => `var fields = GetTableFields();` returns Func<T, T2>
return GetQueryable().Where(predicate).ToList().Select(fields);
}
}
IndexPageModel.cshtml.cs
public class IndexPageModel : AbstractPage<Thing, ThingRow>
{
public override IQueryable<Order> GetQueryable()
{
return MyDbContext.Things.Include(x => x.AnExtraThing);
}
}
Am I doing something wrong or am I just a mad man trying to do the impossible?
Your problem is related to some concepts: assignment compatibility, covariance and contravariance.
Covariance preserves assignment compatibility whereas contravariance reverses assign compatibility. You can learn more about that here Covariance & Contravariance
So in this case, your page's declared model type is an interface with generic arguments but to support accepting (assignment) value from some instance with arguments of more derived (more concrete) type, you need to support covariance for that interface type by using the keyword out
, as follows:
public interface IPage<out T, out T2>
where T : class
where T2 : IRow
{ ... }
That luckily matches with the return types used in the methods of your interface. So you can see that both T
and T2
are returned types. If one of them is an argument type, you will be stuck and should consider about your design of the interface. Here is an example of the interface that cannot be declared to support covariance:
//has compile-time error
public interface IPage<out T, out T2>
where T : class
where T2 : IRow
{
IQueryable<T> GetQueryable();
IEnumerable<T2> GetResults(Func<T, bool> predicate);
//just an example, this will violate the covariance rule
//because T2 cannot be used as an input (argument) type once declared with out
int ComputeSomeThing(T2 row);
}
So behind the scene, your page model will be assigned to IndexPageModel
OK like this (for demonstrative purpose only, not exactly what happens):
IPage<object,IRow> model = new IndexPageModel(...);
UPDATE:
About a more complicated scenario in which you have the generic type T
not used directly as argument type or return type but instead in another generic type that also has variant generic types, such as Func
. Here is an example of such complicated example:
public interface IComplicatedVariant<in T, out TResult> {
Func<T, TResult> GetFunc();
void SetFunc(Func<TResult, T> f);
}
As you can see, the return type Func<T, TResult>
accepts generic argument types T
(contravariant) and TResult
(covariant) of the same variance (like cascading down from the type IComplicatedVariant
). However when using an argument type of Func
, we need to reverse the variance of generic types passed in Func
. So instead of Func<T,TResult>
(which is invalid and will have design-time error), we can only pass Func<TResult,T>
or in short, T
cannot be used in Func<T, ...>
and TResult
cannot be used in Func<...,TResult>
.
The second method SetFunc
is harder to be explained without concrete example. The generic arguments' variance seems to be reversed.
Suppose T
can be used in Func<T,...>
for SetFunc
, we have:
//T1, T2 here are concrete types, not generic argument types
var a = new ComplicatedVariant<object,T2>();
IComplicatedVariant<T1, T2> o = a;
Func<T1,T2> f = ...;
//OK
o.SetFunc(f);
//BUT this is not OK
a.SetFunc(f);
The above example is an assumption to show that what will become wrong without reversing the variance (as mentioned above). You can see that a.SetFunc
requires a Func<object,T2>
but the passed-in argument is a Func<T1,T2>
. The first argument type in Func<,>
is contravariant (declared with in T
), so it reverses the assignment compatibility, which means the first generic argument type on the left side should be equal or more derived than the type on the right side. So we cannot assign the func like this (pseudo-code):
//cannot be set like this, the reverse is however OK
Func<object,T2> = Func<T1,T2>
The argument cannot be accepted (in the call a.SetFunc
) meaning it violates the variance designed for the interface IComplicatedVariant
. So our original assumption is invalid. As I said, we use a concrete example to show an invalid case (by assumption). It's fairly complicated but luckily that we have the design-time compiler which can help report the error right at the design time. If you're not sure, you can just try it out and see if it works right at the design time.