Welcome to MSDN Blogs Sign in | Join | Help

The MVC T4 template is now up on CodePlex, and it does change your code a bit

Short version: the MVC T4 template (now named T4MVC) is now available on CodePlex, as one of the downloads in the ASP.NET MVC v1.0 Source page.

Go to download page

Poll verdict: it’s ok for T4MVC to make small changes

Yesterday, I posted asking how people felt about having the template modify their code in small ways.  Thanks to all those who commented!  The fact that Scott Hanselman blogged it certainly helped get traffic there :)

The majority of people thought that it was fine as long as

  • It’s just those small changes: make classes partial and action methods virtual. Don’t mess with ‘real’ code!
  • It asks for permission, or at least tells you what it’s doing

I started looking for a way to pop up a Yes/No dialog, but ended up going with a slightly different approach: T4MVC adds a warning line for every item it modifies.  e.g. when you run it, you might see these in the warnings area:

Running transformation: T4MVC.tt changed the class DinnersController to be partial
Running transformation: T4MVC.tt changed the action method DinnersController.Index to be virtual

Some people were worried about version control.  I tried using TFS, and everything worked fine.  i.e. when the template modifies files, VS automatically checks them out.  We’ll need to see how that works for folks using different systems.

What’s new in this version?

The template on CodePlex (version 2.0.01 at the top of the file) supports what I described in my previous post, plus some new goodies.

Refactoring support for action methods

One of the big issues before was the lack of refactoring support.  e.g. when you wrote:

return RedirectToAction(MVC.Dinners.Details(dinner.DinnerID));

This looked like a call to you Details controller action, but it was actually an unrelated method by the same name.  Hence, if you renamed your action method and refactored, this call was not modified.  It would give a compile error, and had to be hand fixed.

Now the template takes a drastically different approach:

  • It extends the controller class
  • It overrides the action method (hence the need for it to be virtual!)
  • The override never calls the base (that would be very wrong), but instead returns a special ActionResult which captures the call (controller name, action name, parameter value).
  • The template emit a new RedirectToAction (or ActionLink, …) overload which understands this special ActionResults, and turns the call data into a ‘regular’ RedirectToAction call.

Pretty tricky stuff, but it works quite well.  Some credit to my manager Mike Montwill for coming up with this crazy idea!

Because the method you call is an override of the real action method, refactoring works perfectly.  Also, if you F12 (Go To Definition) on the call, it’ll go straight to your Action method and not some generated code.

Unfortunately, Visual Studio doesn’t support refactoring in Views, but 3rd party tools like Resharper and CodeRush do, so if you use one of those, you’re fully covered.

The T4 file automatically runs whenever you build

This was the other big painful issue I was up against: every time you made a change to your code that affect the generated code (e.g. new Action, new View, …), you had to manually save the .tt file to cause it to regenerate the new helper code.

This was a really hard issue, and I must warn you that what I ended up with is more of a workaround than a fix.  However, it is pretty effective, so until we find a better solution, it’ll have to do.

Here is how it works.  Warning: reading this has been shown to cause headaches in lab rats:

  • As part of its execution, the T4 file finds itself in the VS project system (it had to do that anyway)
  • It then runs the magic instruction ‘projectItem.Document.Saved = false;’, which causes it to become dirty.
  • It then proceeds to do its code generation, leaving its file in an unsaved state
  • Next time you Build your project, VS first saves all the files
  • This causes the ‘dirty’ T4 template to execute, mark itself as dirty again, and redo its code generation
  • You get the idea!  If you feel like the lab rats, this may help.

One caveat is that you have to initiate the cycle by opening and saving T4MVC.tt once.  After you do that, you don’t need to worry about it.

Support for strongly typed links to static resources

Credit for this idea goes to Jaco Pretorius, who blogged something similar.

The template generates static helpers for your content files and script files.  So instead of writing:

<img src="/Content/nerd.jpg" />

You can now write:

<img src="<%= Links.Content.nerd_jpg %>" />

Likewise, instead of

<script src="/Scripts/Map.js" type="text/javascript"></script>

You can write:

<script src="<%= Links.Scripts.Map_js %>" type="text/javascript"></script>

The obvious benefit is that you’ll get a compile error if you ever move or rename your static resource, so you’ll catch it earlier.

Another benefit is that you get a more versatile reference.  When you write src="/Content/nerd.jpg", your app will only work when it’s deployed at the root of the site.  But when you use the helper, it executes some server side logic that makes sure your reference is correct wherever your site is rooted.  It does this by calling VirtualPathUtility.ToAbsolute("~/Content/nerd.jpg").

One unfortunate thing is that for some reason, VS doesn’t support intellisense in the view for parameter values.  As a workaround, you can type it outside of the tag to get intellisense and then copy it there.

More consistent short form to refer to a View from a Controller class

Previously, it supported an _ based short form inside the controller:

return View(View_InvalidOwner);

That was a bit ugly.  Now, the short form is:

return View(Views.InvalidOwner);

Here, Views.InvalidOwner is the same as MVC.Dinners.Views.InvalidOwner, but can be shortened because ‘Views’ is a property on the controller.

Many bug fixes

I also fixed a number of bugs that people reported and that I ran into myself, e.g.

  • It supports controllers that are in sub-folders of the Controllers folder and not directly in there
  • It works better with nested solution folder

I’m sure there are still quite a few little bugs, and we’ll work through them as we encounter them

Published Friday, June 26, 2009 10:23 AM by davidebb
Filed under: , , ,

Comments

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Thanks for this!  I did uncover one bug (kind of).  When creating copies of files in the project the template doesn't deal with spaces in file names.  Also, some of Rob Connery's subsonic mvc templates use a 'ui-lightness' directory under the Scripts folder.  Here is my change, it was just a quick hack to get it done :)  I've just posted it here for reference, do with it what you wish...

void ProcessStaticFilesRecursive(ProjectItem projectItem, string path) {

   if (IsFolder(projectItem)) {

       WriteLine("[CompilerGenerated]");

       string _projectCleanName = projectItem.Name.Replace(" ", "");

       _projectCleanName = projectItem.Name.Replace("-", "_dash_");

       WriteLine(String.Format("public static class {0} {{", _projectCleanName));

       PushIndent("    ");

       // Recurse into all the items in the folder

       foreach (ProjectItem item in projectItem.ProjectItems) {

           ProcessStaticFilesRecursive(item, path + "/" + projectItem.Name);

       }

       PopIndent();

       WriteLine("}");

       WriteLine("");

   }

   else {

       WriteLine(String.Format("public static string {0} {{ get {{ return VirtualPathUtility.ToAbsolute(\"{1}\"); }} }}",

           GetConstantNameFromFileName(projectItem.Name),

           path + "/" + projectItem.Name));

   }

}

Friday, June 26, 2009 2:00 PM by Chad Kranz

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Ok, there are a few other issues I didn't see before my last post.  To replicate, add a file with a space in it (such as Copy of site.css) and a folder with a dash in it (such as Content\ui-lightness).  Then re-run the T4 template and you'll see the errors generated.  Thanks!

Friday, June 26, 2009 2:12 PM by Chad Kranz

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Chad: I fixed that character issue and posted the update on CodePlex (now version 2.0.01)

Friday, June 26, 2009 4:15 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Thanks Dave!  Do you have any suggestions for what Scott H talked about in his blog, looking for the Views.Textbox(), Views.Label and Views.Validation stuff?  I am using the latest version of Subsonic 3.0 and love the idea of automatically generating the validation, as well as the label and control options for it but don't want to reinvent the wheel if someone has even a good starting off point.

Thanks again!

Friday, June 26, 2009 4:58 PM by Chad K

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Chad, take a look et Eric Hexter's solution: http://www.lostechies.com/blogs/hex/archive/2009/06/09/opinionated-input-builders-for-asp-net-mvc-using-partials-part-i.aspx. It looks promising, and may be better than a T4 based solution for input builders.

Friday, June 26, 2009 5:26 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Awesome - great work on this so far.  I haven't had a chance to play with this latest version, but one thing to watch out for on the content & script links is that the application may not be deployed at the root.  For example, "/Scripts/Map.js" will break if you deploy your app to http://myserver/somefolder/myapp.  You may already handle this, but I thought I'd mention...

Friday, June 26, 2009 7:08 PM by daniel

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Daniel, the template does correctly handles non-root sites. I added a paragraph in the post to mention this. Thanks!

Friday, June 26, 2009 8:34 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Dave,

This is great stuff! May I suggest you also add the following method to your T4Extensions class in order to support object htmlAttributes for ActionLink the same way as the standard html helper does:

public static string ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes) {

        return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes));

   }

Saturday, June 27, 2009 7:09 AM by Adrian Grigore

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

One more thing: Strongly typed links usually work great, but in this instance they didn't and I was unable to find out what's going wrong. I tried to replace the following:

<link rel="STYLESHEET" type="text/css" href="~/Content/CustDatabase.css" />

with this:

<link rel="STYLESHEET" type="text/css" href="<%= Links.Content.CustDatabase_css%>" />

There's no error when compiling the view, but the generated link is strangely messed up:

<link href="Views/Shared/%3C%25=%20Links.Content.CustDatabase_css%25%3E" type="text/css" rel="STYLESHEET"/>

The code generated for the stylesheet in T4MVC.cs is

public static string CustDatabase_css { get { return VirtualPathUtility.ToAbsolute("~/Content/CustDatabase.css"); } }

I am completely clueless why comes up as Views/Shared/%3C%25=%20Links.Content.CustDatabase_css%25%3E in the generated html. Perhaps you have an idea?

Saturday, June 27, 2009 8:50 AM by Adrian Grigore

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

One last question: Is it possible to strong typing in Html.BeginForm?

Saturday, June 27, 2009 9:55 AM by Adrian Grigore

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Adrian:

- ActionLink: I added the suggested ActionLink overload (now version 2.0.02 on CodePlex)

- CSS link: I think this happens because code is simply not allowed in this context, so you just can't use a <%= %> here at all. I can't think of a great way around this.

- BeginForm: in most cases, you don't want this to look like a method call to the Action, because the param values come from the form. But note that you can get some strong typing for the action and controller names by using:

 Html.BeginForm(MVC.Dinners.Actions.Delete, MVC.Dinners.Name)

Saturday, June 27, 2009 11:18 AM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

I get the message "The Views folder has a sub-folder named '{0}', but there is no matching controller". My web project contains the mvc t4 template, but my controllers are stored in a different project. It that not supported?

Monday, June 29, 2009 2:12 AM by Marco

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

@Adrian Grigore: I removed the runat="server" from the head and it then ran perfectly.

<head >

   <link href="<%= Links.Shared.CSS.Website_Default_css %>" rel="stylesheet" type="text/css" />

</head>

Monday, June 29, 2009 3:25 AM by William

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

This is an excellent template! One question, though - is there a reason the s_actions, s_views, and the public fields of the _Actions and _Views classes are not read-only?

For the CSS link, you'll need to use something like Dave Reed's CodeExpressionBuilder:

http://weblogs.asp.net/infinitiesloop/archive/2006/08/09/The-CodeExpressionBuilder.aspx

<link rel="stylesheet" type="text/css" href='<%$ Code: Links.Content.CustDatabase_css %>' />

Monday, June 29, 2009 9:14 AM by Richard

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

New build 2.1.00 is now on CodePlex.

- New BeginForm helpers (thanks Michael Hart and Adrian)

- Various strings changed to readonly, as suggested by Richard

- Misc fixes (see history in .tt file for details)

Monday, June 29, 2009 8:22 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Marco: indeed, that's not currently supported. I think it could work by:

- Putting the .tt in your controllers project, not the web project

- Changing the .tt logic to find the views in the web project

If someone gets to try this and has success, please send me your updates.  Thanks! :)

Monday, June 29, 2009 8:27 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Hey Dave, great template!

How do you see this fitting in with MVC Futures? I sense planets will collide very soon... I personally enjoy the approach you have given us, perhaps over some of the "Future" constructs... As Hanselman said, would be nice for this to be put through QA and baked in =)

Monday, June 29, 2009 9:34 PM by Graham

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Graham: I think this and the Futures can live together, though they do intersect in some aspects. One thing the Futures can't do is the View name and static file support, because that's based on physical file existence and not code constructs. On the other hand, the Futures have some View render helpers (e.g. TextBoxFor) which I'm not sure we can easily  match with the T4 approach.

Monday, June 29, 2009 9:42 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Just thought that I would let you know I have started using this and with great success. Not sure if you mentioned it or not but another place you can reference the generated code is when registering routs, i.e.

this._Routes.MapRoute("Default", "{controller}/{action}", new { controller = MVC.Home.Name, action = MVC.Home.Actions.Index });

Monday, June 29, 2009 11:40 PM by Anthony

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

>>-Putting the .tt in your controllers project, not the web project

No, does not work...

>>- Changing the .tt logic to find the views in the web project

I think the controllers should be loaded from all the projects in the current solution.Anyone tried to change the logic?

Tuesday, June 30, 2009 7:48 AM by Marco

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Hi,

If your Content folder is empty, you get a build error.

ProcessStaticFiles writes out this line without wrapping in a class.

public static readonly string Content = Url("Content");

Tuesday, June 30, 2009 8:16 AM by Richard Kimber

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

The current implementation doesn't work well with Dependency Injection!

You automatically generate a default constructor and one with your dummy parameter.

StructureMap for example now calls the wrong constructor.

Any ideas on how to work around this without explicitly decorating the real constructor with an DI-specific attribute?

despite this issue, i really like this approach.

best regards, christian

Tuesday, June 30, 2009 9:51 AM by Christian

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

I have controllers that inherit from a base controller, and the code generated in t4mvc.cs  produces 'warnings' at build time.  i.e.

"...Controllers.MyController.RedirectToAction() hides inherited member '...Controllers.MyBaseController.RedirectToAction().  Use the new keyword if hiding was intended."

I'm not really sure if this is a problem, other than making me wade through warnings to find any I'm really interested in, but I thought I'd pass it along.  Mostly likely I'm just doing something wrong.

Thanks ... jim

Tuesday, June 30, 2009 2:35 PM by Jim

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

New version 2.2.00 is up on CodePlex

Richard Kimber: fixed the issue with empty Content folder. Good catch!

Christian: made a change which *should* fix your issue with Dependency Injection. If it doesn't please email me and we'll take it offline.

Jim: Fixed issue with Controller base class. For the fix to work, please make sure you make your base Controller abstract. Thanks!

Tuesday, June 30, 2009 3:17 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Marco: I didn't mean that just putting the .tt in teh Controllers project was enough. It's only a piece of a solution which also involves changing the .tt logic. Hopefully, I can look at that at some point, though if someone else gets to it first, all the better! :)

Tuesday, June 30, 2009 3:21 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Anthony: indeed, this is a great use of it in the routes, I hadn't thought of it.  I actually just added some better support for this in 2.2. Now you can write:

   routes.MapRoute(

       "Default",

       "{controller}/{action}

       MVC.Home.Index()

   );

Tuesday, June 30, 2009 3:25 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Am I right in thinking this could be used to generate static reflection helper classes with things like property names and attributes? I'm going to have a play with the template ASAP!

Tuesday, June 30, 2009 8:00 PM by Harry M

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

I've encountered several bugs in 2.2.00:

BUG 1:

If any controller is already declared partial then there will be an entry in the Controllers list for each constituent file. This triggers am exception on template execution on line 478, because the call to 'SingleOrDefault' will return multiple results.

FIX:

Change the type of the 'Controllers' list to 'HashSet<ControllerInfo>' for an implicit 'distinct', slap an IEquatable<ControllerInfo>' interface in there and modify the ProcessControllerTypesInNamespace method slightly:

line 287:

static List<ControllerInfo> Controllers;

=>

static HashSet<ControllerInfo> Controllers;

line 295:

Controllers = new List<ControllerInfo>();

=>

Controllers = new HashSet<ControllerInfo>();

Expanded ControllerInfo definition:

class ControllerInfo : IEquatable<ControllerInfo> {

   ...

   public bool Equals(ControllerInfo obj) {

       return obj != null && FullClassName == obj.FullClassName;

   }

   public override int GetHashCode() {

       return FullClassName.GetHashCode();

   }      

}

Modification to 'ProcessControllerTypesInNamespace' method:

-----

Controllers.Add(controllerInfo);

controllerInfo.HasExplicitConstructor = HasExplicitConstructor(type);

// Process all the action methods in the controller

ProcessControllerActionMethods(controllerInfo, type);

-----

=>

-----

// either process new controllerinfo or integrate results into existing object for partially defined controllers

var target = Controllers.Add(controllerInfo) ? controllerInfo : Controllers.First(c => c.Equals(controllerInfo));

target.HasExplicitConstructor |= HasExplicitConstructor(type);

// Process all the action methods in the controller

ProcessControllerActionMethods(target, type);

-----

BUG 2:

The static file access generation (and possibly other) code yields incorrect paths on (T4 or designer) generated files:

Imagine an 'Output.tt' file in Project/Content that in turn generates some files called 'foo.css' & 'bar.css'.

The solution explorer will show these files as

Project

+- Content

   +- Output.tt

   +- foo.css

+- bar.css

The physical path for 'foo.css' is 'Project/Content/foo.css', but the T4MVC template generates a path of '~/Project/Content/Output.tt/foo.css' which is clearly wrong :(

FIX:

Don't rely on the IsFolder method. In it's current implementation the results have more of a 'HasChildren' meaning.

Instead use ProjectItem.Kind (http://msdn.microsoft.com/en-us/library/z4bcch80%28VS.80%29.aspx) to check if the item with children is actually a physical folder and only then construct an inner class with modified path.

Since IsFolder is used in several places, I didn't touch it but instead refactored the ProcessStaticFilesRecursive method:

----

void ProcessStaticFilesRecursive(ProjectItem projectItem, string path) {

bool isPhysicalFolder = projectItem.Kind == "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}"; // see ProjectItem.Kind documentation on msdn

if (isPhysicalFolder) {

  WriteLine("[CompilerGenerated]");

  WriteLine(String.Format("public static class {0} {{", SanitizeFileName(projectItem.Name)));

  PushIndent("    ");

  WriteLine(String.Format("public static string Url() {{ return VirtualPathUtility.ToAbsolute(\"{0}\"); }}", path + "/" + projectItem.Name));

  WriteLine(String.Format("public static string Url(string fileName) {{ return VirtualPathUtility.ToAbsolute(\"{0}/\" + fileName); }}", path + "/" + projectItem.Name));

// Recurse into all the items in the folder

foreach (ProjectItem item in projectItem.ProjectItems) {

ProcessStaticFilesRecursive(item, path + "/" + projectItem.Name);

}

  PopIndent();

  WriteLine("}");

  WriteLine("");    

} else {

  WriteLine(String.Format("public static readonly string {0} = Url(\"{1}\");",

      SanitizeFileName(projectItem.Name),

      projectItem.Name));

// non folder items may also have children (virtual folders, Class.cs -> Class.Designer.cs, template output) - just register them on the same path as their parent item

foreach (ProjectItem item in projectItem.ProjectItems) {

ProcessStaticFilesRecursive(item, path );

}

}

}

----

I'm pretty sure there is an equivalent bug for views and controllers (which could theoretically also be auto-generated and thus appear as child nodes to non folders), but I'll leave that fix to somebody else ;)

BUG 3:

If a controller is derived from a base class that already implements some action methods, those methods are never discovered:

abstract class FooBaseController : Controller {

 ActionResult SkippedMethod(...);

}

class FooController : FooBaseController {

 ActionResult FoundMethod(...);

}

There is no code generated for 'SkippedMethod'.

FIX:

Haven't looked into this one yet

Wednesday, July 01, 2009 7:10 AM by Alex M

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Nice! I updated and it broke everything.

Why the new requirement to only support ActionResult return types? My actions return strongly types like ViewResult, etc.

Why can't it keep track of what the action method return type is and carry that over into the helpers?

Wednesday, July 01, 2009 10:43 AM by Pat

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

David,

I am trying to use your latest T4MVC template with my mvc project and I get this error. Please help.

Error 1 Running transformation: System.EntryPointNotFoundException: Entry point was not found.

  at VisualSVN.VS.Interop.NativeExtenderCallback.OnQueryEditFiles(UInt32 rgfQueryEdit, String[] rgpszMkDocuments, UInt32* pfEditVerdict, UInt32* prgfMoreInfo, Boolean* skipOriginal)

  at VisualSVN.VS.Interop.NativeExtender.OnQueryEditFiles(NativeExtender* , UInt32 rgfQueryEdit, Int32 cFiles, UInt16** rgpszMkDocuments, UInt32* rgrgf, __MIDL___MIDL_itf_ivsqueryeditquerysave2_0000_0001* rgFileInfo, UInt32* pfEditVerdict, UInt32* prgfMoreInfo, Boolean* skipOriginal)

  at EnvDTE80.CodeClass2.set_ClassKind(vsCMClassKind Kind)

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.ProcessControllerTypesInNamespace(CodeNamespace ns) in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 401

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.ProcessControllersRecursive(ProjectItem projectItem) in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 381

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.ProcessControllersRecursive(ProjectItem projectItem) in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 374

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.ProcessControllersFolder(Project project) in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 366

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.PrepareDataToRender(TextTransformation tt) in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 306

  at Microsoft.VisualStudio.TextTemplating984425EEA278851503849A871981DA21.GeneratedTextTransformation.TransformText() in e:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt:line 96 E:\Development\CSharp\MVC\MVCExperiment\MVCExperiment\Mvc-CodeGen.tt 1 1

Wednesday, July 01, 2009 11:04 AM by Hien Khieu

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

So I was able to improve it to add support for other Action return types. Not easy. Sometimes these "helpers" take on a life of their own. Basically in the get action methods routine I parsed out the return type name and stored it in the ActionMethodInfo class. Then I turned ControllerActionCallInfo into an interface and added new classes like ControllerActionResultCallInfo, ControllerViewResultCallInfo, etc that implemented that interface and fixed up various other parts to reference the interface and return the correct type instead of the hardcoded ActionResult and everything is working again!

Great stuff....

Wednesday, July 01, 2009 11:41 AM by Pat

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: thanks a lot for working through those issues!  New build 2.2.01 on CodePlex has the fixes for issues 1 and 2. I mostly used your code, with minor changes. I actually fixed the issue you bring up for controllers as well.

BTW, they have constants for all the GUIDs, so you can write Constants.vsProjectItemKindPhysicalFolder. I know, not very discoverable, but once you know where they are, they're all there!

Bug #3: definitely a bug, but I haven't had a chance to look into it.  Anyone? :)

Wednesday, July 01, 2009 6:16 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Hien Khieu: strange, I don't know what could cause that. Is this with VS2008 SP1? I'll ask the Visual Studio team if they can make sense of the stack. It's dying when the code tries to change the class to be partial.

Maybe you can work around by making it partial yourself.

Wednesday, July 01, 2009 6:19 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Pat: I went ahead and fixed this in 2.2.01 (now on CodePlex). My fix is similar to what you described. One difference is that I made it generic, by auto-generating derived classes for all the Result types it encounters, instead of hard coding a few (or maybe that's what you did too?).

Anyway, please make sure it works for you.  BTW, feel free to email me your changes next time, to give me a starting point for the fix :)

Wednesday, July 01, 2009 6:22 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Hien Khieu: it would appear that your issue is related to running VisualSVN. Please see this thread where a similar thing was reported:

http://l2st4.codeplex.com/Thread/View.aspx?ThreadId=44898

Wednesday, July 01, 2009 7:52 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

David,

Thank you for looking into my issue. VisualSVN is what I am thinking when I read the stack trace. One think I don't understand that I was able to use your old MVC T4 template (the one that I download sometimes last week) with no problem. Thank you anyway.

Wednesday, July 01, 2009 11:17 PM by Hien Khieu

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Hi,

thanks for this great work. I wana bring

one issue in your attention. I have a controller method

public  FileContentResult GetSmallImage(long photoID)

       {

           Return somefile;

       }

It makes the method as virtual(no prblem).

It doesnt compile and says Error 'System.Web.Mvc.FileContentResult' does not contain a constructor that takes '0' arguments

This is line where the error lies.

public T4MVC_FileContentResult(string controller, string action) {

       this.InitMVCT4Result(controller, action);

   }

It seems it didnt created controller method name properly.

Thursday, July 02, 2009 5:38 AM by parminder

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Well David, I took another look at my Bug #3 and came up with a partial solution - which of course in turned lead to another problem.

Since I don't know the EnvDTE classes well enough to tell if my new problem is simple or easy to workaround, I'll just throw in what I got this far:

Modify 'ProcessControllerActionMethods' to:

void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current) {

// walk up the controller inheritance chain until we arrive at the mvc default controller

for (CodeClass2 type = current; current != null && current.FullName != "System.Web.Mvc.Controller"; current = current.Bases.Item(1) as CodeClass2) {

foreach (CodeFunction2 method in GetMethods(type)) {

...

// keep existing unmodified code here (skipped for brevity)

...

// Make sure the method is virtual

if (!method.CanOverride) {

method.CanOverride = true; // *** THIS NEEDS SOME MORE CHECKS, SEE REMARKS

Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name));

}

...

// more code that needs no change

...

}

}

}

Explanation:

The modified code simply walks up the inheritance chain and looks at all the intermediate types' methods until it stops at the stock mvc Controller base class. Choosing Bases.Item(1) shouldn't cause any problem since no .NET language actually supports multiple inheritance.

Now there are some potential problems with the 'method.CanOverride = true' call:

A) The current controller code class 'type' is part of the same project as the template

=> everything should be okay, but might still fail for external reasons (source code control lock on the code file). The current implementation fails on those errors anyway - with a try/catch block you could just skip those elements, emit a warning and 'continue' with the next method.

B) The current controller code class 'type' is defined in an external source, either referenced project or assembly:

1) The code class 'type' is defined in another project which is part of the same solution as the template

  => switching the method to virtual should succeed (same caveats as in #A apply), but on my machine this always failed (the stacktrace contained some code from jetbrain's resharper, so that component might be to blame for it).

2) The code class 'type' is defined in a referenced assembly.

  => making the method virtual will always fail

So as I see it, there needs to be a check (and try/catch block) around the 'make virtual' functionality for cases A & B.1 to work correctly.

For B.1 it might also be necessary to walk the solution and find the actual code class from the base classes' definition to change properties, since code classes with an 'external' storage are generated from metadata and that might have been why the 'CanOverrid=true' failed - There's already very similar code in the template to find the actual ProjectItem for the template file.

I cannot see any way to fix B.2, the best way here would probably just be to skip those methods with a warning, or at least process them with reduced output (and functionality) that doesn't need virtual methods (you could still expose the action names).

Thursday, July 02, 2009 3:24 PM by Alex M.

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Just noticed that I made a mistake in that demo code (wrote if from memory, since I removed those template changes). That new outer for loop should be:

for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = type.Bases.Item(1) as CodeClass2) {

Thursday, July 02, 2009 4:45 PM by Alex M.

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

I have the same problem as Hien with VisualSVN. The thread http://l2st4.codeplex.com/Thread/View.aspx?ThreadId=44898 has a reply where there seems to be a workaround. Can you implement that in your t4 template? Thanks

Thursday, July 02, 2009 5:04 PM by Bob Baker

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Follow up with VisualSVN...

Guidance from the VisualSVN team:

"It turns out that problem is caused by ActiveWriter using DTE from temporary AppDomain. To fix this all calls to DTE should be marshaled to default AppDomain. As a simple workaround you can do code generation on separate working thread."

Thursday, July 02, 2009 5:05 PM by Bob Baker

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

parminder: just fixed this issue in build 2.2.02 on CodePlex

Friday, July 03, 2009 4:28 AM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: thanks for looking into this. I haven't had a chance to look into your code yet, but I plan to early next week!

Friday, July 03, 2009 4:29 AM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Bob (and Hien): VisualSVN issue: I'm not very sure how I could do this from a T4 file. The T4 file is executed by VS in a different AppDomain, and I don't think this can be changed. If someone understands the issue better and has a fix, please let me know.

Friday, July 03, 2009 4:33 AM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: I have integrated your fix to deal with base class action methods. I also added exception handling on the code that tries to make methods virtual (and make controllers partial). When that happens, it skips the method and gives a warning suggesting that the user makes that change themselves if possible. Obviously, when dealing with a true binary you don't control, it won't be possible, but I think that's an edge case. Thanks again!

Monday, July 06, 2009 9:42 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: forgot to mention that the new build is 2.2.03 on CodePlex.

Bob (and Hien): VisualSVN issue: 2.2.03 deals with those errors more gracefully. The workaround for you is to make the methods partial yourself (see previous comment).

Monday, July 06, 2009 9:44 PM by davidebb

# It supports controllers that are in sub-folders of the Controllers folder and not directly in there

I am using T4MVC.tt in my project. I am facing the problem that I have sub folders in the controllers folder.

e.g. (Controllers\Member\Account\ActivationController.cs)

There is no compile time error. But when I run the project it creates the object of those Controllers which are on the root of "Controllers folder" e.g "Home" = T4MVC.T4MVC_HomeController

but Activation(ActivationControllers) is null

And also all other controllers are null because they all are in the sub folders.

Tuesday, July 07, 2009 5:34 AM by Ramandeep Singh

# It supports controllers that are in sub-folders

Please tell me the way out as I have more then 200 controllers and I want to keep them in sub folders.

Thanks in advance

Tuesday, July 07, 2009 5:55 AM by Ramandeep Singh

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Ramandeep: T4MVC is supposed to support controllers that are in sub folders of the Controllers folder. Please see the code in ProcessControllersRecursive. Not sure why it wouldn't work for you. Please look through the tt file and the generated file to try to figure out what's going on. Make sure you use the latest version (2.2.03).

Or if you can put together a small repro, you can email it to me and I'll take a look.

Tuesday, July 07, 2009 10:58 AM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Nice changes there david - I've got some more bugs & fixes ;)

1)

The controller inheritance chain analysis works great now. But it still fails the 'CanOverride=true' call on base types from external project.

If you insert the following code in 'ProcessControllerActionMethods' before the 'foreach (CodeFunction2 method ...' loop you'll get a better refactoring experience:

----

// if the type is defined in another project, try getting a direct reference which might give more access for modifications

if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject) {

var dte = type.DTE;

foreach (Project prj in dte.Solution.Projects) {

if (prj != Project && prj.CodeModel != null) {

var prjCodeType = prj.CodeModel.CodeTypeFromFullName(type.FullName);

if (prjCodeType != null && prjCodeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject) {

type = (CodeClass2) prjCodeType;

break;

}

}

}        

}

----

With this modification I could successfully refactor any base class method from *non-generic* controllers. Generic base controllers still don't work, but since the exception is as specific as 'unexpected error (hresult 0x8004005)' I don't have much hope for those.

2)

I completed the '// TODO: Make the base type check more reliable' task inside 'ProcessControllerActionMethods':

----

// We only support action methods that return an ActionResult derived type

if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult"))

continue;

----

3)

Thanks to the finally working base class methods I found a bug in one of our usage scenarios. For the following definition:

public class FooBarController {

[ActionName("Bar")]

public ActionResult Foo() {

...

}

}

the template will output code like

public partial class FooBarController {

public class _Actions {

public readonly string Foo = "Foo";

...

which is wrong, since the action (and the url where it can be invoked) is actually 'FooBar/Bar', not 'FooBar/Foo'.

You'll probably have to check each controller action method for attributes deriving from System.Web.Mvc.ActionNameSelectorAttribute, but I'd argue it should be enough to only check for the derived 'ActionName' attribute.

Though anybody could easily define their own ActionNameSelectorAttribute derived implementation (imagine an attribute that accepts every action which contains the letter 'a' at least three times), there might be no clearly defined reverse lookup from the attribute instance to the action name, and even if there is one, the template could never know it.

'ActionName' does both ship with the MVC Framework and has a clearly defined value-to-action relationship, so this would always work.

If you intend to implement this functionality, be sure to also add the code from #1, since the 'Attributes' collection is always empty for code with an InfoLocation other than vsCMInfoLocationProject, so you need the direct type references to the defining project here.

Tuesday, July 07, 2009 3:30 PM by Alex M.

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: would you mind contacting me be email (david.ebbo [@ microsoft.com]). It'll be easier to continue discussing this.  Thanks!

Tuesday, July 07, 2009 4:43 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Alex M: please check out v2.3 on CodePlex.

Tuesday, July 07, 2009 9:53 PM by davidebb

# re: The MVC T4 template is now up on CodePlex, and it does change your code a bit

Thanks! I tried the latest version in place of my custom fixed version and everything still works! I had hardcoded classes for the return types so your fix is definitely better. Thanks again! Nice to know I'm back in sync with the latest online version.

Tuesday, July 14, 2009 3:04 PM by Pat
New Comments to this post are disabled
 
Page view tracker