asp.net mvc 3 razor视图 - >强类型列表的元组问题

Osk*_*lin 36 c# tuples razor asp.net-mvc-3

我对asp.net MVC razor视图有一个奇怪的问题.

我希望我的模型List<Tuple<string, int, int, int, int>>在我的其他c#方法中完全有效.但是当我将它粘贴到@model声明中时,它似乎只挑选出元组的字符串部分.所以我没有整理.只有item1.

如果我将它绑定到元组而不是List,则不存在此问题.

好像生成的代码是错误的,所以也许这是剃刀视图中的错误?

我在编译时得到的错误是:

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
Run Code Online (Sandbox Code Playgroud)

为了解决这个问题,我做了以下事情:

创建一个空的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>
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以创建一个类或一个结构来代替保存数据.只是出于好奇而问

编辑: 解决并向MVC团队报告http://aspnet.codeplex.com/workitem/8652

And*_*tan 34

编辑我已经删除了一些正在进行中的评论 - 只需查看历史记录即可.

因此,您可以使用1,2,3或4个元组通用参数进行此操作,但它不适用于5.只要您使用5个参数,它就会生成如下代码:

 public class _Page_Views_Home_Index_cshtml : 
   System.Web.Mvc.WebViewPage<List<System.Tuple<string {
Run Code Online (Sandbox Code Playgroud)

我想知道它是否是字符长度限制,所以我生成了一个这样的类:

namespace ASP{  //same namespace that the backend code for the page is generated
  public class T { } 
}
Run Code Online (Sandbox Code Playgroud)

并改变了模型声明:

@model List<Tuple<T,T,T,T,T>>.
Run Code Online (Sandbox Code Playgroud)

最后(见历史)我得到了

@inherits System.Web.Mvc.WebViewPage<Tuple<T,T,T,T,T>>
Run Code Online (Sandbox Code Playgroud)

同样的问题!这对@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);    
}
Run Code Online (Sandbox Code Playgroud)

所以 - 四参数版本通过,但不是5参数版本.

并猜测Tuple<T- 实际值是什么- 即截断的泛型类型名称截断方式与您在代码中观察到的方式完全相同.

CodeTypeReferenceCollection在解析@inheritsor @model关键字时,标准Razor解析器和Mvc Razor解析器都使用该类型.这是@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);
  }
}
Run Code Online (Sandbox Code Playgroud)

GeneratedClass.BaseTypesCodeTypeReferenceCollection- 并且span.BaseClass是一个字符串.在ILSpy之后,违规方法必须是私有方法CodeTypeReference.Initialize(string typeName, CodeTypeReferenceOptions options).我现在没有足够的时间来弄清楚它为什么会中断 - 但那时我认为这是微软开发人员的工作:) 下面的更新 - 无法抗拒.我现在知道哪里错了

底线

你不能在Razor @inherits@model语句中使用带有4个以上参数的泛型(至少在C#中 - 不了解VB).看来Razor解析器错误地使用了该CodeTypeReference类型.

最后更新 - 或者,我有点咬牙切齿:)

其中一件事就是CodeTypeReference通过调用方法从传递的类型名称中去除程序集名称信息CodeTypeReference.RipOffAssemblyInformationFromTypeName(string typeName).

当然,如果你考虑它 - Tuple<T,T,T,T,T>就像一个程序集限定的类型名称:类型名称= Tuple<T,Assembly = T,Version = T,Culture = T,PublicKeyToken = T(如果你写一个真正的BAD C#解析器!).

果然 - 如果你Tuple<T,T,T,T,T,T>作为类型名称传入- 你实际上得到了一个Tuple<T,T>.

深入研究代码,它准备接收一个与语言无关的类型名称(例如处理'['但没有'''),所以,实际上,MVC团队不应该直接从我们的源代码处理C#typename通过.

MVC团队需要改变他们生成基类型的方式 - 他们可以使用public CodeTypeReference(string typeName, params CodeTypeReference[] typeArguments)构造函数进行新的引用(而不是仅仅依赖于.Add(span.BaseClass)创建它),并解析泛型参数本身,因为他们知道类型名称将是C#/ VB样式 - 不是语言中立的.Net样式,括号等作为实际名称的一部分.


And*_*rse 8

这已经在内部进行了一些讨论,我担心最终的产品是我们无法为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生成的代码范围,以及我们是否引入了额外的转换层(如如果CodeDOM使用[]甚至CodeTypeReference构造函数的其他重载将会执行转换,我们就不能再向编辑器做出这些保证,你会看到奇怪的行为

这样就让我们得到了解决方法,就是简单地避免使用这么多泛型参数.实际上有很多理由可以避免以这种方式使用Tuple,因为使用自定义模型类可以让您命名所涉及的属性,并且在添加属性时会给您更大的灵活性(使用元组,您必须更新Controller和查看何时要为元组添加"属性".话虽如此,我们仍然关注这个问题,看看我们如何在4.0之后做得更好.既然我们是开源的,我们很乐意听取您的建议,甚至接受您的代码!

如果您有任何疑问,请随时与我联系(电子邮件在我的SO资料中)或继续在评论中讨论.我只是想给你一个你应得的背景背景,因为我已经做了很多优秀的工作来追踪它!

-Andrew Nurse(Dev on Razor解析器)

  • 我知道这已经很长时间了,但是感谢这次更新,即使它不是我们想听到的:-) (2认同)