Razor Pages - 将抽象 class 传递给 Html.Partial 期望接口会引发错误

Razor Pages - Passing abstract class to Html.Partial expecting an interface throws an error

我有:

  1. 具有 2 个泛型的接口
  2. 实现接口的抽象class和PageModel
  3. A class 用于实现抽象的剃刀页面 class
  4. 需要接口的部分

当我尝试 @await Html.PartialAsync("_InterfacePartial", Model) 时,它会抛出以下

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);
    }
}

我是做错了什么还是我只是一个试图做不可能的事的疯子?

您的问题与一些概念有关:分配兼容性协方差逆变.

协变保留赋值兼容性,而逆变则反转赋值兼容性。您可以在此处了解更多信息 Covariance & Contravariance

所以在这种情况下,您的页面声明的模型类型是一个带有通用参数的接口,但要支持从某些具有更派生(更具体)类型的参数的实例接受(赋值)值,您需要支持 covariance通过关键字out为该接口类型,如下:

public interface IPage<out T, out T2>
where T : class
where T2 : IRow
{ ... }

幸运的是,它与接口方法中使用的 return 类型匹配。所以你可以看到 TT2 都是 returned 类型。如果其中之一是参数类型,您将被卡住并且应该考虑您的界面设计。下面是一个不能被声明为支持协变的接口的例子:

//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);
}

所以在幕后,您的页面模型将被分配给 IndexPageModel OK 就像这样(仅用于演示目的,不完全是发生了什么):

IPage<object,IRow> model = new IndexPageModel(...);

更新:

关于一个更复杂的场景,在这个场景中,您的泛型类型 T 没有直接用作参数类型或 return 类型,而是在另一个也有变体泛型类型的泛型类型中使用,例如Func。这是一个如此复杂的例子:

public interface IComplicatedVariant<in T, out TResult> {
     Func<T, TResult> GetFunc();
     void SetFunc(Func<TResult, T> f);
}

如您所见,return 类型 Func<T, TResult> 接受具有相同方差(如级联)的通用参数类型 T(逆变)和 TResult(协变)从 IComplicatedVariant 类型向下)。但是,当使用 Func 的参数类型时,我们需要反转传入 Func 的泛型类型的变体。因此,我们只能传递 Func<TResult,T> 而不是 Func<T,TResult>(这是无效的并且会出现设计时错误),或者简而言之,T 不能用于 Func<T, ...>TResult 不能用于 Func<...,TResult>.

第二种方法SetFunc没有具体的例子很难解释。通用参数的方差似乎是 reversed。 假设 T 可以在 Func<T,...> 中用于 SetFunc,我们有:

//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);

上面的例子是一个假设,表明在不反转方差的情况下会发生什么错误(如上所述)。您可以看到 a.SetFunc 需要 Func<object,T2> 但传入的参数是 Func<T1,T2>Func<,>中的第一个参数类型是contravariant(用in T声明),所以它反转赋值兼容性,这意味着第一个泛型参数类型在左侧侧应该 等于或更多派生 比右边的类型 边。所以我们不能像这样分配函数(伪代码):

//cannot be set like this, the reverse is however OK
Func<object,T2> = Func<T1,T2>

无法接受参数(在调用 a.SetFunc 中),这意味着它违反了为接口 IComplicatedVariant 设计的方差。所以我们原来的假设是无效的。正如我所说,我们使用一个具体的例子来展示一个无效的案例(通过假设)。这相当复杂,但幸运的是我们有设计时编译器,它可以帮助在设计时正确报告错误。如果您不确定,您可以尝试一下,看看它是否在设计时正常工作。