asp.net MVC 3 剃刀视图 - >元组问题的强类型列表
本文关键字:问题 元组 强类型 列表 MVC net 剃刀 视图 asp | 更新日期: 2023-09-27 17:57:15
我在MVC剃须刀视图 asp.net 遇到了一个奇怪的问题。
我希望我的模型是一个在其他 c# 方法中完全有效的List<Tuple<string, int, int, int, int>>
。但是当我将其粘贴到@model
声明中时,它似乎只挑选出元组的字符串部分。所以我没有整数。仅项目1。
如果我将其绑定到元组而不是列表,则不存在此问题。
似乎生成的代码是错误的,所以也许这是剃须刀视图中的错误?
我在编译时遇到的错误是:
Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.
Compiler Error Message: CS1003: Syntax error, '>' expected
Source Error:
Line 27:
Line 28:
Line 29: public class _Page_Views_Dashboard_DestinationStatistics_cshtml : System.Web.Mvc.WebViewPage<List<Tuple<string {
Line 30:
Line 31: #line hidden
为了隔离这个问题,我做了以下事情:
创建一个空的 asp.net mvc 项目。创建新视图。跳过以下代码。
@model List<Tuple<string, int, int, int, int>>
@foreach (var stat in Model)
{
<tr>
<td>
@stat.Item1
</td>
<td>
@stat.Item2
</td>
<td>
@stat.Item3
</td>
<td>
@stat.Item4
</td>
<td>
@stat.Item5
</td>
</tr>
}
我知道我可以创建一个类或结构来保存数据。只是出于好奇而问
编辑:已解决并报告给MVC团队 http://aspnet.codeplex.com/workitem/8652
编辑 我在这里修剪了一些正在进行的评论 - 只需查看历史记录即可查看。
因此,您可以使用 1、2、3 或 4 个元组泛型参数来使其工作,但它不适用于 5。 一旦您使用 5 个参数,它就会生成如下代码:
public class _Page_Views_Home_Index_cshtml :
System.Web.Mvc.WebViewPage<List<System.Tuple<string {
我只想找出它是否是字符长度限制,所以我生成了一个这样的类:
namespace ASP{ //same namespace that the backend code for the page is generated
public class T { }
}
并更改了模型声明:
@model List<Tuple<T,T,T,T,T>>.
最后(见历史)我到了
@inherits System.Web.Mvc.WebViewPage<Tuple<T,T,T,T,T>>
同样的问题! @model关键字不是问题...
这花了一段时间(通读 MVC3 和 Razor 源代码,向该解决方案添加了一些测试) - 但这里有一个测试显示了我们收到此错误的原因:
[TestMethod]
public void TestMethod()
{
System.CodeDom.CodeTypeReferenceCollection c =
new CodeDom.CodeTypeReferenceCollection();
c.Add("Tuple<T,T,T,T>");
c.Add("Tuple<T,T,T,T,T>");
//passes
Assert.AreEqual("Tuple<T,T,T,T>", c[0].BaseType);
//fails
Assert.AreEqual("Tuple<T,T,T,T,T>", c[1].BaseType);
}
所以 - 四参数版本通过,但 5 参数版本不通过。
猜猜怎么着 - 实际值是Tuple<T
- 即截断的泛型类型名称截断的方式与您在代码中观察到的方式完全相同。
器和 Mvc Razor 分析器在分析 @inherits
或 @model
关键字时都使用 CodeTypeReferenceCollection
类型。 下面是代码生成期间@inherits
的代码:
protected internal virtual void VisitSpan(InheritsSpan span) {
// Set the appropriate base type
GeneratedClass.BaseTypes.Clear();
GeneratedClass.BaseTypes.Add(span.BaseClass);
if (DesignTimeMode) {
WriteHelperVariable(span.Content, InheritsHelperName);
}
}
GeneratedClass.BaseTypes
是一个CodeTypeReferenceCollection
- span.BaseClass
是一个字符串。 在ILSpy中,违规方法必须是私有方法CodeTypeReference.Initialize(string typeName, CodeTypeReferenceOptions options)
。 我现在没有足够的时间来弄清楚它为什么会中断 - 但我认为这是一个Microsoft开发人员的工作:)下面的更新 - 无法抗拒。 我现在知道哪里出了问题
底线
您不能在 Razor @inherits
或 @model
语句中使用具有 4 个以上参数的泛型(至少在 C# 中 - 不了解 VB)。 似乎 Razor 分析器错误地使用了 CodeTypeReference
类型。
最终更新 - 或者,我的牙齿咬了一口:)
CodeTypeReference
执行的操作之一是通过调用方法 CodeTypeReference.RipOffAssemblyInformationFromTypeName(string typeName)
从传递的类型名称中删除程序集名称信息。
当然,如果你考虑一下 - Tuple<T,T,T,T,T>
就像一个程序集限定的类型名称:类型名称 = Tuple<T
,程序集 = T
,版本= T
,文化 = T
,公钥令牌= T
(如果你写了一个非常糟糕的 C# 解析器!
果然 - 如果您传入Tuple<T,T,T,T,T,T>
作为类型名称 - 您实际上会得到一个Tuple<T,T>
.
深入了解代码,它准备接收一个与语言无关的类型名(例如,处理"["但不处理"<"),因此,实际上,MVC 团队不应该只是直接从我们的源代码中传递 C# 类型名。
MVC 团队需要更改他们生成基类型的方式 - 他们可以将 public CodeTypeReference(string typeName, params CodeTypeReference[] typeArguments)
构造函数用于新引用(而不仅仅是依赖创建它.Add(span.BaseClass)
),并自行解析泛型参数,因为他们知道类型名称将是 C#/VB 样式 - 而不是与语言无关的 .Net 样式,括号等作为实际名称的一部分。
这已经经历了几波内部讨论,恐怕最终产品是我们无法为 Razor 2.0 (MVC 4) 解决这个问题。让我介绍一下推理的背景。
首先,请务必注意,Razor 分析器有意尽可能少地分析 C#。这样做的主要原因是让您自由使用 C#,而不会妨碍我们。因此(您现在可以通过查看代码自己验证这一点!)我们不会解析 @inherits
和 @model
指令中的类型名称,我们只是运行到行尾。我们也是 Razor 编辑器背后的解析引擎,这意味着我们必须支持部分完整的语句,例如 @model Foo<Bar
这在技术上是无效的,但如果您键入@model Foo<Bar>
这将是一个预期的中间步骤,所以我们应该能够处理它。
现在,我们需要考虑在我们决定改变生成代码的方式时会发生什么。正如 CodeTypeReference 文档所述,我们必须使用1[[ ... ]]
语法来定义泛型。但是,我们仍然只会插入用户键入的任何内容,因此如果您键入@model Foo<Bar>
我们会告诉 CodeDOM 基本类型类似于 System.Web.Mvc.WebViewPage`1[[Foo<Bar>]]
。如您所见,我们仍然会在类型名称中以<>
结尾。因此,我们决定使用 CodeDOM 通常不抱怨<>
和(Of ...)
(在 VB 中)语法来解决这个问题。
即使解析您提供的整个类型名称也很困难,因为我们必须处理不完整的语句,例如 @model Foo<Bar, Baz
.事实上,这也会使编辑器非常脆弱,因为编辑器实际上依赖于我们能够准确地告诉他们 Razor 文本的范围映射到 C#/VB 生成的代码的范围,并且如果我们引入额外的转换层(例如,如果我们使用 CodeTypeReference 构造函数的其他重载,CodeDOM 将执行[]
转换),我们无法再向编辑器,你会看到奇怪的行为
因此,这给我们留下了解决方法,即简单地避免使用这么多通用参数。实际上有很多原因可以避免以这种方式使用 Tuple,因为使用自定义模型类将允许您命名所涉及的属性,并且在添加属性时会为您提供更大的灵活性(使用元组,当您想要向元组添加"属性"时,您必须更新控制器和视图)。话虽如此,我们正在密切关注这个问题,并研究如何在 4.0 之后做得更好。现在我们是开源的,我们很高兴听到您的建议,甚至接受您的代码!
如果您有任何疑问,请随时与我联系(电子邮件在我的 SO 个人资料中)或继续在评论中讨论这个问题。我只是想给你一个你应得的背景背景,你已经付出了如此多的出色工作来追踪这个!
-Andrew Nurse(Dev on Razor parser)
我今天遇到了这个问题。我返回了一些有关用户活动的信息,并尝试使用
@model List<Tuple<string,bool,DateTime,DateTime,DateTime>>
@* Name, Online, Created, Login, Active *@
原因是我有点厌倦了为视图模型制作一次性类,所以这就是为什么我这样做是为了简单使用。我收到了和你一样的错误。我试图通过在@model
中使用不同的元组组合来绕过错误,但无济于事。
最终起作用的是简单地使用ViewBag
。需要注意的是,无论如何,model
都保存在ViewBag
中,因此以这种身份使用它没有任何问题。
在我的操作结果方法中,我只是将元组列表分配给一个 viewbag 值
ViewBag.listTuple = listOfTuples;
然后在我投掷我回去的视野中
@{
List<Tuple<string,bool,DateTime,DateTime,DateTime>> tuples = ViewBag.listTuple;
}
就是这样。跑得很好。我并不是说这是完美的解决方案,但它是一种可行的解决方法。
对于可能在此处查找 Razor 视图中 CS1003 错误源的任何其他人来说,CS1003 错误是 MVC 无法解析 Razor 语法的一般症状。 它可能是由许多原因引起的,包括上面的通用元组问题。
我注意到的一件事是,如果您尝试使用 C# 样式的单行注释在 Razor 视图中注释模型声明,则会出现此问题:
BAD: @model Int32 // user ID
GOOD: @* user ID *@
@model Int32
Visual Studio 语法突出显示不会将其标记为问题,但它会在运行时失败。
只是想指出,如果你删除 @model 指令,你就不会有实际上谁关心的智能感知,但该视图与任意数量的元组参数完美配合。
我遇到了这个问题,因为我使用的是Tuples
Tuple
:
@model Tuple<Tuple<IEnumerable<int>, int, int>, Tuple<float, float, float>>
不用说,剃刀不喜欢这样。无论如何,我认为解析如何打破类型总数而不是顶级对象的类型数量很有趣。
我的解决方法
我最终使用了一个视图模型,该模型为内部元组简单创建了占位符:
public class ViewModel
{
public Tuple<IEnumerable<int>, int, int> Models { get; set; }
public Tuple<float, float, float> Selected { get; set; }
}
我很高兴我制作了视图模型。它更具可读性,并且(稍微)更容易在视图中进行操作和推理。