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:
public class _Page_Views_Home_Index_cshtml :
System.Web.Mvc.WebViewPage<List<System.Tuple<string {
I wanted to just find out if it's a character-length limitation, so I generated a class like this:
namespace ASP{ //same namespace that the backend code for the page is generated
public class T { }
}
And changed the model declaration:
@model List<Tuple<T,T,T,T,T>>.
In the end (see the history) I got to
@inherits System.Web.Mvc.WebViewPage<Tuple<T,T,T,T,T>>
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:
[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);
}
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 CodeTypeReferenceCollection
type when parsing either the @inherits
or @model
keyword. Here's the code for @inherits
during code generation:
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
is a CodeTypeReferenceCollection
- and span.BaseClass
is a string. Following that through in ILSpy, the offending method must be the private method CodeTypeReference.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 wrong
Bottom line
You can't use generics with more than 4 parameters in either Razor @inherits
or @model
statements (at least in C# - don't know about VB). It appears that the Razor parser is incorrectly using the CodeTypeReference
type.
Final Update - or, I had the bit between my teeth :)
One of the things that CodeTypeReference
does is strip off assembly name information from a passed type name with a call to the method CodeTypeReference.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 a Tuple<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.