【问题标题】:Pass subclass of generic model to razor view将通用模型的子类传递给剃刀视图
【发布时间】:2013-03-26 20:31:51
【问题描述】:

大局:

我发现了 Razor 的一个限制,我无法想出一个好的解决方法。

玩家:

假设我有一个这样的模型:

public abstract class BaseFooModel<T>
    where T : BaseBarType
{
    public abstract string Title { get; } // ACCESSED BY VIEW
    public abstract Table<T> BuildTable();

    protected Table<T> _Table;
    public Table<T> Table // ACCESSED BY VIEW
    {
        get
        {
            if (_Table == null)
            {
                _Table = BuildTable();
            }
            return _Table;
        }
    }
}

还有一个像这样的子类:

public class MyFooModel : BaseFooModel<MyBarType>
{
    // ...
}

public class MyBarType : BaseBarType
{
    // ...
}

我希望能够将MyFooModel 传递到这样定义的剃刀视图中:

// FooView.cshtml
@model BaseFooModel<BaseBarType>

但是,这行不通。我收到一个运行时错误,说FooView 期望BaseFooModel&lt;BaseBarType&gt; 但得到MyFooModel。回想一下 MyFooModel 继承自 BaseFooModel&lt;MyBarType&gt;MyBarType 继承自 BaseBarType

我尝试过的:

我在非剃须刀的土地上尝试过这个,看看是否同样如此,确实如此。我必须在视图中使用模板参数才能使其工作。这是非剃刀观点:

public class FooView<T>
    where T : BaseBarType
{
    BaseFooModel<T> Model;
    public FooView(BaseFooModel<T> model)
    {
        Model = model;
    }
}

使用该结构,以下确实有效

new FooView<MyBarType>(new MyFooModel());

我的问题:

我怎样才能用 Razor 做到这一点?我怎样才能像使用 FooView 一样传递一个类型?
我不能,但是有没有办法解决?我能以某种方式实现相同的架构吗?

如果我能提供更多信息,请告诉我。我正在使用 .NET 4 和 MVC 3。


编辑:
现在,我只是为BaseFooModel&lt;BaseBarType&gt; 的每个子类添加一个剃刀视图。我对此并不感到兴奋,因为我不想每次添加新模型时都必须创建新视图。

另一种选择是利用我 能够在没有剃刀的常规 c# 类中使其正常工作这一事实。我可以让我的剃刀视图@inherits c# 视图,然后调用一些渲染方法。我不喜欢这个选项,因为我不喜欢有两种呈现 html 的方式。

还有其他想法吗?我知道当我用FooBar 给出类名时很难理解问题的背景,但我不能提供太多信息,因为它有点敏感。对此我深表歉意。


到目前为止我所拥有的,使用本杰明的回答:

public interface IFooModel<out T> 
    where T : BaseBarModel
{
    string Title { get; }
    Table<T> Table { get; } // this causes an error:
                            // Invalid variance: The type parameter 'T' must be 
                            // invariantly valid on IFooModel<T>.Table. 
                            // 'T' is covariant.
}

public abstract class BaseFooModel<T> : IFooModel<T>
    where T : BaseBarModel
{
    // ...
}

结果如何:

public interface IFooModel<out T> 
    where T : BaseBarModel
{
    string Title { get; }
    BaseModule Table { get; } // Table<T> inherits from BaseModule
                              // And I only need methods from BaseModule
                              // in my view. 
}

public abstract class BaseFooModel<T> : IFooModel<T>
    where T : BaseBarModel
{
    // ...
}

【问题讨论】:

  • 我经常在输入问题时或至少在不久之后找到答案。这次不是……
  • 你的 BaseFooModel 是什么 - 它在页面上做了什么(插入、只是查看、是否更改等)?这是相关的(一些示例代码)
  • 这是一个很好的观点,但我会争辩说,由于此信息正在用于视图中,因此它应该只被读取。任何更新都应该在控制器操作方法中进行。
  • @NSGaga 回答您的问题,请参阅上面的本杰明评论。他是对的。只是子类必须实现的一些 getter 和方法。
  • @Benjamin 您的评论甚至更好:) 我认为(有道理)-答案值得赏金-但我正在寻找我们是否可以改进这一点。即细节与“差异”完全不同。 IEnumerable&lt;Record&gt; 适用于简单的情况(当然没有“标题”等)。如果基本接口必须支持任何东西 - 或者通过 js 或其他东西 - 那么这可能会失败。

标签: asp.net asp.net-mvc-3 oop razor


【解决方案1】:

你需要在你的类层次结构中引入一个带有covariant泛型类型参数的接口:

public interface IFooModel<out T> where T : BaseBarType
{
}

并从上述接口派生您的 BaseFooModel。

public abstract class BaseFooModel<T> : IFooModel<T> where T : BaseBarType
{
}

在您的控制器中:

[HttpGet]
public ActionResult Index()
{
    return View(new MyFooModel());
}

最后,将视图的模型参数更新为:

@model IFooModel<BaseBarType>

【讨论】:

  • 谢谢本!到目前为止,这工作得很好。在接受之前,我会坚持下去,直到我确定它会在我的情况下起作用。 +1
  • 很高兴听到这个消息!我的代码中有类似的问题,所以我很高兴能够使用它来帮助其他人。如果您遇到任何问题,请发布更多信息,如果可以,我会尽力提供帮助。
  • +1 我不是想破坏你——我没那么饿——例如乔恩是:)
  • 我更新了我的问题,详细介绍了 BaseFooModel 的内容。我将string TitleTable&lt;T&gt; Table 移动到界面,因为它们可以被视图访问。我现在得到一个无效的方差错误('T' 必须在IFooModel&lt;T&gt;.Table 上始终有效)。你能解释一下为什么会这样吗?什么会导致它无效?
  • 你的界面是协变的,所以必须是只读的。确保接口只有只读属性(getter 但没有 setter)并且没有接受 T 类型参数的方法。如果使用 T 参数,您的参数也必须是只读的。
【解决方案2】:

在 ASP.NET MVC 2 和 MVC 3 之间故意更改了使用基于接口的模型。

You can see here

MVC 团队:

我们不鼓励使用基于接口的模型(考虑到错误修复带来的限制,我们也无法实际支持)。切换到抽象基类可以解决这个问题。

“斯科特·汉塞尔曼”

【讨论】:

    【解决方案3】:

    您遇到的问题不是 Razor 错误,而是 C# 错误。尝试用类来做到这一点,你会得到同样的错误。这是因为模型不是BaseFooModel&lt;BaseBarType&gt;,而是BaseFooModel&lt;MyFooModel&gt;,两者之间不能发生隐式转换。通常,在程序中您必须进行转换才能做到这一点。

    但是,在 .NET 4 中,引入了 contravariance and covariance,这听起来像是您正在寻找的功能。这只是 .NET 4 的一项功能,老实说,我不知道 .NET 4 中的 Razor 是否使用它。

    【讨论】:

    • 感谢布赖恩的反馈。你是对的;我确实在类中看到了同样的情况,这就是我在上面尝试用FooView&lt;T&gt; 位解释的内容。添加模板参数是我可以让它工作的唯一方法,这显然不会转化为 Razor。你能解释一下你说列表不是IEnumerable&lt;BaseBarType&gt;,而是IEnumerable&lt;MyFooModel&gt;是什么意思吗?什么清单?
    • 什么IEnumerable?该模型应该只是一个MyFooModel,而不是一个集合。
    • 呃,我真的不行了,我的意思是BaseFooModel,所以我通过上面的帖子更新了。
    • 两个视图(razor 和FooView)都设置为接收BaseFooModel&lt;BaseBarType&gt; 作为模型。在这两种情况下,我都传递了MyFooModel,这是一个BaseFooModel&lt;MyBarType&gt;。似乎唯一可行的方法是如果我将MyBarType 作为模板参数传递给视图。但是,由于我无法在剃刀视图上定义模板参数,所以我被卡住了。
    • 对,您的 Razor 视图不能灵活地使用 BaseBarType,您必须在 .NET 4 中使用没有逆变和协方差隐式转换的 MyBarType(即使那样,我也不知道是否那样甚至会工作)。这是一个不幸的限制。
    猜你喜欢
    • 2015-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-07
    • 2012-03-29
    • 1970-01-01
    • 2021-09-10
    • 1970-01-01
    相关资源
    最近更新 更多