I’m having an odd problem with asp.net MVC razor view.
I want my model to be a List<Tuple<string, int, int, int, int>> which is perfectly valid in my other c# methods. But when I paste it into the @model declaration it seems to only pick out the string part of the tuple. So I have no ints. Only item1.
This problem is not present if I make it bind to a Tuple instead of the List.
Seems like the generated code is wrong, so perhaps this is a bug in razor view?
The error I get at compilation is:
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
To isolate this problem I did the following thing:
Create an empty asp.net mvc project. Create a new view. Past the following code.
@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>
}
I know I can just create a class or a struct to hold the data instead. Just asking out of curiosity
EDIT:
Solved and reported to the MVC team here http://aspnet.codeplex.com/workitem/8652
EDIT I’ve trimmed out some of the in-progress comments here – just view the history to see.
So you can make this work with 1, 2, 3 or 4 tuple generic parameters but it doesn’t work with 5. As soon as you use 5 parameters it generates code like this:
I wanted to just find out if it’s a character-length limitation, so I generated a class like this:
And changed the model declaration:
In the end (see the history) I got to
Same problem! It’s not a problem with the @model keyword…
It took a while (reading through the MVC3 and Razor source, adding a couple of tests to that solution) – but here’s a test that shows the why we get this error:
So – the four-parameter version passes, but not the 5 parameter version.
And guess what- the actual value is
Tuple<T– i.e. a truncated generic type name truncated exactly the same way that you’ve observed in your code.Both the standard Razor parser and the Mvc Razor parser use the
CodeTypeReferenceCollectiontype when parsing either the@inheritsor@modelkeyword. Here’s the code for@inheritsduring code generation:GeneratedClass.BaseTypesis aCodeTypeReferenceCollection– andspan.BaseClassis a string. Following that through in ILSpy, the offending method must be the private methodCodeTypeReference.Initialize(string typeName, CodeTypeReferenceOptions options). I’ve not enough time now to figure out why it breaks – but then that’s a Microsoft developer’s job I think 🙂 Update below – couldn’t resist. I now know where it’s wrongBottom line
You can’t use generics with more than 4 parameters in either Razor
@inheritsor@modelstatements (at least in C# – don’t know about VB). It appears that the Razor parser is incorrectly using theCodeTypeReferencetype.Final Update – or, I had the bit between my teeth 🙂
One of the things that
CodeTypeReferencedoes is strip off assembly name information from a passed type name with a call to the methodCodeTypeReference.RipOffAssemblyInformationFromTypeName(string typeName).And of course, if you think about it –
Tuple<T,T,T,T,T>is just like an assembly-qualified type name: With the type name =Tuple<T, Assembly =T, Version=T, Culture=T, PublicKeyToken=T(if you write a really BAD C# parser!).Sure enough – if you pass in
Tuple<T,T,T,T,T,T>as the type name – you actually get aTuple<T,T>.Looking deeper into the code, it’s primed to receive a language-neutral typename (handles ‘[‘ but nothing for ‘<‘, for example) so, actually, the MVC team shouldn’t just be handing the C# typename from our source straight through.
The MVC team needs to change how they generate the base type – They could use the
public CodeTypeReference(string typeName, params CodeTypeReference[] typeArguments)constructor for a new reference (instead of just relying on the.Add(span.BaseClass)creating it), and parse the generic parameters themselves since they know that the type name will be C#/VB style – not language-neutral .Net style with brackets etc as part of the actual name.