Razor Pages - 将抽象 class 传递给 Html.Partial 期望接口会引发错误
Razor Pages - Passing abstract class to Html.Partial expecting an interface throws an error
我有:
- 具有 2 个泛型的接口
- 实现接口的抽象class和
PageModel
- A class 用于实现抽象的剃刀页面 class
- 需要接口的部分
当我尝试 @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 类型匹配。所以你可以看到 T
和 T2
都是 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
设计的方差。所以我们原来的假设是无效的。正如我所说,我们使用一个具体的例子来展示一个无效的案例(通过假设)。这相当复杂,但幸运的是我们有设计时编译器,它可以帮助在设计时正确报告错误。如果您不确定,您可以尝试一下,看看它是否在设计时正常工作。
我有:
- 具有 2 个泛型的接口
- 实现接口的抽象class和
PageModel
- A class 用于实现抽象的剃刀页面 class
- 需要接口的部分
当我尝试 @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 类型匹配。所以你可以看到 T
和 T2
都是 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
设计的方差。所以我们原来的假设是无效的。正如我所说,我们使用一个具体的例子来展示一个无效的案例(通过假设)。这相当复杂,但幸运的是我们有设计时编译器,它可以帮助在设计时正确报告错误。如果您不确定,您可以尝试一下,看看它是否在设计时正常工作。