<# /* T4MVC Version 2.6.64 Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC Discuss on StackOverflow or on the MVC forum (http://forums.asp.net/1146.aspx) T4MVC is part of the MvcContrib project (http://mvccontrib.codeplex.com) Maintained by David Ebbo, with much feedback from the MVC community (thanks all!) david.ebbo@microsoft.com http://twitter.com/davidebbo http://blog.davidebbo.com/ (previously: http://blogs.msdn.com/davidebb) Related blog posts: http://blogs.msdn.com/davidebb/archive/tags/T4MVC/default.aspx Please use in accordance to the MvcContrib license (http://mvccontrib.codeplex.com/license) */ #> <#@ template language="C#v3.5" debug="true" hostspecific="true" #> <#@ assembly name="System.Core" #> <#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="EnvDTE80" #> <#@ assembly name="VSLangProj" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Text.RegularExpressions" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="EnvDTE80" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# // To debug, uncomment the next two lines !! // System.Diagnostics.Debugger.Launch(); // System.Diagnostics.Debugger.Break(); #> <#PrepareDataToRender(this); #> <#var manager = Manager.Create(Host, GenerationEnvironment); #> <#manager.StartHeader(); #>// // This file was generated by a T4 template. // Don't change it directly as your change would get overwritten. Instead, make changes // to the .tt file (i.e. the T4 template) and save it to regenerate this file. // Make sure the compiler doesn't complain about missing Xml comments #pragma warning disable 1591 #region T4MVC using System; using System.Diagnostics; using System.CodeDom.Compiler; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Web; using System.Web.Hosting; using System.Web.Mvc; using System.Web.Mvc.Ajax; using System.Web.Mvc.Html; using System.Web.Routing; using <#=T4MVCNamespace #>; <#foreach (var referencedNamespace in ReferencedNamespaces) { #> using <#=referencedNamespace #>; <#} #> <#manager.EndBlock(); #> [<#= GeneratedCode #>, DebuggerNonUserCode] public static class <#=HelpersPrefix #> { <#if (IncludeAreasToken) { #> public static class Areas { <#} #> <#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #> static readonly <#=area.Name #>Class s_<#=area.Name #> = new <#=area.Name #>Class(); public static <#=area.Name #>Class <#=EscapeID(area.Namespace) #> { get { return s_<#=area.Name #>; } } <#} #> <#if (IncludeAreasToken) { #> } <#} #> <#foreach (var controller in DefaultArea.GetControllers()) { #> public static <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>(); <#} #> } namespace <#=T4MVCNamespace #> { <#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #> [<#= GeneratedCode #>, DebuggerNonUserCode] public class <#=area.Name #>Class { public readonly string Name = "<#=ProcessAreaOrControllerName(area.Name) #>"; <#foreach (var controller in area.GetControllers()) { #> public <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>(); <#} #> } <#} #> } <#if (GenerateMvcT4Extensions) { #> namespace System.Web.Mvc { [<#= GeneratedCode #>, DebuggerNonUserCode] public static class T4Extensions { public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result) { return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary()); } public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes) { return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes)); } public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, object htmlAttributes, string protocol = null, string hostName = null, string fragment = null) { return ActionLink(htmlHelper, linkText, result, new RouteValueDictionary(htmlAttributes), protocol, hostName, fragment); } public static <#=HtmlStringType #> ActionLink(this HtmlHelper htmlHelper, string linkText, ActionResult result, IDictionary htmlAttributes, string protocol = null, string hostName = null, string fragment = null) { return htmlHelper.RouteLink(linkText, null, protocol, hostName, fragment, result.GetRouteValueDictionary(), htmlAttributes); } public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result) { return htmlHelper.BeginForm(result, FormMethod.Post); } public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod) { return htmlHelper.BeginForm(result, formMethod, null); } public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod, object htmlAttributes) { return BeginForm(htmlHelper, result, formMethod, new RouteValueDictionary(htmlAttributes)); } public static MvcForm BeginForm(this HtmlHelper htmlHelper, ActionResult result, FormMethod formMethod, IDictionary htmlAttributes) { var callInfo = result.GetT4MVCResult(); return htmlHelper.BeginForm(callInfo.Action, callInfo.Controller, callInfo.RouteValueDictionary, formMethod, htmlAttributes); } <#if (MvcVersion >= 2) {#> public static void RenderAction(this HtmlHelper htmlHelper, ActionResult result) { var callInfo = result.GetT4MVCResult(); htmlHelper.RenderAction(callInfo.Action, callInfo.Controller, callInfo.RouteValueDictionary); } public static MvcHtmlString Action(this HtmlHelper htmlHelper, ActionResult result) { var callInfo = result.GetT4MVCResult(); return htmlHelper.Action(callInfo.Action, callInfo.Controller, callInfo.RouteValueDictionary); } <#} #> public static string Action(this UrlHelper urlHelper, ActionResult result) { return urlHelper.RouteUrl(null, result.GetRouteValueDictionary()); } public static string Action(this UrlHelper urlHelper, ActionResult result, string protocol = null, string hostName = null) { return urlHelper.RouteUrl(null, result.GetRouteValueDictionary(), protocol, hostName); } public static string ActionAbsolute(this UrlHelper urlHelper, ActionResult result) { return string.Format("{0}{1}",urlHelper.RequestContext.HttpContext.Request.Url.GetLeftPart(UriPartial.Authority), urlHelper.RouteUrl(result.GetRouteValueDictionary())); } public static <#=HtmlStringType #> ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions) { return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions); } public static <#=HtmlStringType #> ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions, object htmlAttributes) { return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions, new RouteValueDictionary(htmlAttributes)); } public static <#=HtmlStringType #> ActionLink(this AjaxHelper ajaxHelper, string linkText, ActionResult result, AjaxOptions ajaxOptions, IDictionary htmlAttributes) { return ajaxHelper.RouteLink(linkText, result.GetRouteValueDictionary(), ajaxOptions, htmlAttributes); } public static MvcForm BeginForm(this AjaxHelper ajaxHelper, ActionResult result, AjaxOptions ajaxOptions) { return ajaxHelper.BeginForm(result, ajaxOptions, null); } public static MvcForm BeginForm(this AjaxHelper ajaxHelper, ActionResult result, AjaxOptions ajaxOptions, object htmlAttributes) { return BeginForm(ajaxHelper, result, ajaxOptions, new RouteValueDictionary(htmlAttributes)); } public static MvcForm BeginForm(this AjaxHelper ajaxHelper, ActionResult result, AjaxOptions ajaxOptions, IDictionary htmlAttributes) { var callInfo = result.GetT4MVCResult(); return ajaxHelper.BeginForm(callInfo.Action, callInfo.Controller, callInfo.RouteValueDictionary, ajaxOptions, htmlAttributes); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result) { return MapRoute(routes, name, url, result, null /*namespaces*/); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults) { return MapRoute(routes, name, url, result, defaults, null /*constraints*/, null /*namespaces*/); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, string[] namespaces) { return MapRoute(routes, name, url, result, null /*defaults*/, namespaces); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults, object constraints) { return MapRoute(routes, name, url, result, defaults, constraints, null /*namespaces*/); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults, string[] namespaces) { return MapRoute(routes, name, url, result, defaults, null /*constraints*/, namespaces); } public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults, object constraints, string[] namespaces) { // Create and add the route var route = CreateRoute(url, result, defaults, constraints, namespaces); routes.Add(name, route); return route; } <#if (MvcVersion >= 2) {#> // Note: can't name the AreaRegistrationContext methods 'MapRoute', as that conflicts with the existing methods public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result) { return MapRouteArea(context, name, url, result, null /*namespaces*/); } public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result, object defaults) { return MapRouteArea(context, name, url, result, defaults, null /*constraints*/, null /*namespaces*/); } public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result, string[] namespaces) { return MapRouteArea(context, name, url, result, null /*defaults*/, namespaces); } public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result, object defaults, object constraints) { return MapRouteArea(context, name, url, result, defaults, constraints, null /*namespaces*/); } public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result, object defaults, string[] namespaces) { return MapRouteArea(context, name, url, result, defaults, null /*constraints*/, namespaces); } public static Route MapRouteArea(this AreaRegistrationContext context, string name, string url, ActionResult result, object defaults, object constraints, string[] namespaces) { // Create and add the route if ((namespaces == null) && (context.Namespaces != null)) { namespaces = context.Namespaces.ToArray(); } var route = CreateRoute(url, result, defaults, constraints, namespaces); context.Routes.Add(name, route); route.DataTokens["area"] = context.AreaName; bool useNamespaceFallback = (namespaces == null) || (namespaces.Length == 0); route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback; return route; } <#} #> private static Route CreateRoute(string url, ActionResult result, object defaults, object constraints, string[] namespaces) { // Start by adding the default values from the anonymous object (if any) var routeValues = new RouteValueDictionary(defaults); // Then add the Controller/Action names and the parameters from the call foreach (var pair in result.GetRouteValueDictionary()) { routeValues.Add(pair.Key, pair.Value); } var routeConstraints = new RouteValueDictionary(constraints); // Create and add the route var route = new Route(url, routeValues, routeConstraints, new MvcRouteHandler()); route.DataTokens = new RouteValueDictionary(); if (namespaces != null && namespaces.Length > 0) { route.DataTokens["Namespaces"] = namespaces; } return route; } public static <#=ActionResultInterfaceName #> GetT4MVCResult(this ActionResult result) { var t4MVCResult = result as <#=ActionResultInterfaceName #>; if (t4MVCResult == null) { throw new InvalidOperationException("T4MVC was called incorrectly. You may need to force it to regenerate by right clicking on T4MVC.tt and choosing Run Custom Tool"); } return t4MVCResult; } public static RouteValueDictionary GetRouteValueDictionary(this ActionResult result) { return result.GetT4MVCResult().RouteValueDictionary; } public static ActionResult AddRouteValues(this ActionResult result, object routeValues) { return result.AddRouteValues(new RouteValueDictionary(routeValues)); } public static ActionResult AddRouteValues(this ActionResult result, RouteValueDictionary routeValues) { RouteValueDictionary currentRouteValues = result.GetRouteValueDictionary(); // Add all the extra values foreach (var pair in routeValues) { currentRouteValues.Add(pair.Key, pair.Value); } return result; } public static ActionResult AddRouteValues(this ActionResult result, System.Collections.Specialized.NameValueCollection nameValueCollection) { // Copy all the values from the NameValueCollection into the route dictionary nameValueCollection.CopyTo(result.GetRouteValueDictionary()); return result; } public static ActionResult AddRouteValue(this ActionResult result, string name, object value) { RouteValueDictionary routeValues = result.GetRouteValueDictionary(); routeValues.Add(name, value); return result; } public static void InitMVCT4Result(this <#=ActionResultInterfaceName #> result, string area, string controller, string action) { result.Controller = controller; result.Action = action; result.RouteValueDictionary = new RouteValueDictionary(); <# if (MvcVersion >= 2) { #>result.RouteValueDictionary.Add("Area", area ?? "");<# } #> result.RouteValueDictionary.Add("Controller", controller); result.RouteValueDictionary.Add("Action", action); } public static bool FileExists(string virtualPath) { if (!HostingEnvironment.IsHosted) return false; string filePath = HostingEnvironment.MapPath(virtualPath); return System.IO.File.Exists(filePath); } static DateTime CenturyBegin=new DateTime(2001,1,1); public static string TimestampString(string virtualPath) { if (!HostingEnvironment.IsHosted) return string.Empty; string filePath = HostingEnvironment.MapPath(virtualPath); return Convert.ToString((System.IO.File.GetLastWriteTimeUtc(filePath).Ticks-CenturyBegin.Ticks)/1000000000,16); } } } namespace <#=T4MVCNamespace #> { [<#= GeneratedCode #>, DebuggerNonUserCode] public class Dummy { private Dummy() { } public static Dummy Instance = new Dummy(); } } <#} #> <#if (GenerateActionResultInterface) { #> [<#= GeneratedCode #>] public interface <#=ActionResultInterfaceName #> { string Action { get; set; } string Controller { get; set; } RouteValueDictionary RouteValueDictionary { get; set; } } <#} #> <#foreach (var resultType in ResultTypes.Values) { #> [<#= GeneratedCode #>, DebuggerNonUserCode] public class T4MVC_<#=resultType.Name #> : <#=resultType.FullName #>, <#=ActionResultInterfaceName #> { public T4MVC_<#=resultType.Name #>(string area, string controller, string action): base(<#resultType.Constructor.WriteNonEmptyParameterValues(true); #>) { this.InitMVCT4Result(area, controller, action); } <#foreach (var method in resultType.AbstractMethods) { #> <#=method.IsPublic ? "public" : "protected" #> override void <#=method.Name #>(<#method.WriteFormalParameters(true); #>) { } <#} #> public string Controller { get; set; } public string Action { get; set; } public RouteValueDictionary RouteValueDictionary { get; set; } } <#} #> namespace <#=LinksNamespace #> { <# foreach (string folder in StaticFilesFolders) { ProcessStaticFiles(Project, folder); } #> } <# RenderAdditionalCode(); #> <#foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #> <#manager.StartNewFile(controller.GeneratedFileName); #> namespace <#=controller.Namespace #> { public partial class <#=controller.ClassName #> { protected <#=controller.ClassName #>() { } } } <#manager.EndBlock(); #> <#} #> <#foreach (var controller in GetControllers()) { #> <# // Don't generate the file at all if the existing one is up to date // NOTE: disable this optimization since it doesn't catch view changes! It can be re-enabled later if smarter change detection is added //if (controller.GeneratedCodeIsUpToDate) { // manager.KeepGeneratedFile(controller.GeneratedFileName); // continue; //} #> <#manager.StartNewFile(controller.GeneratedFileName); #> <#if (!String.IsNullOrEmpty(controller.Namespace)) { #> namespace <#=controller.Namespace #> { <#} #> public <#if (!controller.NotRealController) { #>partial <#} #>class <#=controller.ClassName #> { <#if (!controller.NotRealController) { #> <#if (!controller.HasExplicitConstructor) { #> [<#= GeneratedCode #>, DebuggerNonUserCode] public <#=controller.ClassName #>() { } <#} #> [<#= GeneratedCode #>, DebuggerNonUserCode] protected <#=controller.ClassName #>(Dummy d) { } [<#= GeneratedCode #>, DebuggerNonUserCode] protected RedirectToRouteResult RedirectToAction(ActionResult result) { var callInfo = result.GetT4MVCResult(); return RedirectToRoute(callInfo.RouteValueDictionary); } <#foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #> [NonAction] [<#= GeneratedCode #>, DebuggerNonUserCode] public <#=method.ReturnTypeFullName #> <#=method.Name #>() { return new T4MVC_<#=method.ReturnType #>(Area, Name, ActionNames.<#=method.ActionName #>); } <#} #> [<#= GeneratedCode #>, DebuggerNonUserCode] public <#=controller.ClassName #> Actions { get { return <#=controller.T4MVCControllerFullName #>; } } [<#= GeneratedCode #>] public readonly string Area = "<#=ProcessAreaOrControllerName(controller.AreaName) #>"; [<#= GeneratedCode #>] public readonly string Name = "<#=ProcessAreaOrControllerName(controller.Name) #>"; static readonly ActionNamesClass s_actions = new ActionNamesClass(); [<#= GeneratedCode #>, DebuggerNonUserCode] public ActionNamesClass ActionNames { get { return s_actions; } } [<#= GeneratedCode #>, DebuggerNonUserCode] public class ActionNamesClass { <#foreach (var method in controller.ActionMethodsWithUniqueNames) { #> <# if (UseLowercaseRoutes) { #> public readonly string <#=method.ActionName #> = (<#=method.ActionNameValueExpression #>).ToLowerInvariant(); <# } else { #> public readonly string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>; <# } } #> } <#} #> static readonly ViewNames s_views = new ViewNames(); [<#= GeneratedCode #>, DebuggerNonUserCode] public ViewNames Views { get { return s_views; } } [<#= GeneratedCode #>, DebuggerNonUserCode] public class ViewNames { <#RenderControllerViews(controller);#> } } <#if (!controller.NotRealController) { #> [<#= GeneratedCode #>, DebuggerNonUserCode] public class <#=controller.DerivedClassName #>: <#=controller.FullClassName #> { public <#=controller.DerivedClassName #>() : base(Dummy.Instance) { } <#foreach (var method in controller.ActionMethods) { #> public override <#=method.ReturnTypeFullName #> <#=method.Name #>(<#method.WriteFormalParameters(true); #>) { var callInfo = new T4MVC_<#=method.ReturnType #>(Area, Name, ActionNames.<#=method.ActionName #>); <#if (method.Parameters.Count > 0) { #> <#foreach (var p in method.Parameters) { #> callInfo.RouteValueDictionary.Add(<#=p.RouteNameExpression #>, <#=p.Name #>); <#} #> <#}#> return callInfo; } <#} #> } <#} #> <#if (!String.IsNullOrEmpty(controller.Namespace)) { #> } <#} #> <#manager.EndBlock(); #> <#} #> <# if (ExplicitHtmlHelpersForPartials) { manager.StartNewFile("T4MVC.ExplicitExtensions.cs"); #> namespace System.Web.Mvc { [<#= GeneratedCode #>] public static class HtmlHelpersForExplicitPartials { <# foreach(var partialView in GetPartials()) { string partialName = partialView.Key; string partialPath = partialView.Value; string partialRenderMethod = string.Format(ExplicitHtmlHelpersForPartialsFormat, partialName); #> /// ///Render the <#= partialName #> partial. /// public static void <#= partialRenderMethod #>(this HtmlHelper html) { html.RenderPartial("<#= partialPath #>"); } /// ///Render the <#= partialName #> partial. /// public static void <#= partialRenderMethod #>(this HtmlHelper html, object model) { html.RenderPartial("<#= partialPath #>", model); } <# } #> } } <# manager.EndBlock(); #> <# } #> <#manager.StartFooter(); #> #endregion T4MVC #pragma warning restore 1591 <#manager.EndBlock(); #> <#manager.Process(SplitIntoMultipleFiles); #> <#@ Include File="T4MVC.tt.settings.t4" #> <#+ const string ControllerSuffix = "Controller"; static DTE Dte; static Project Project; // static Project ViewProject; // static string AppRoot; static HashSet Areas; static AreaInfo DefaultArea; static Dictionary ResultTypes; static TextTransformation TT; static string T4FileName; static string T4Folder; static string GeneratedCode = @"GeneratedCode(""T4MVC"", ""2.0"")"; static float MvcVersion; static string HtmlStringType; static Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider(); IEnumerable GetControllers() { var controllers = new List(); foreach (var area in Areas) { controllers.AddRange(area.GetControllers()); } return controllers; } IEnumerable GetAbstractControllers() { var controllers = new List(); foreach (var area in Areas) { controllers.AddRange(area.GetAbstractControllers()); } return controllers; } IDictionary GetPartials() { var parts = GetControllers() .Select(m => m.ViewsFolder) .SelectMany(m => m.Views) .Where(m => IsPartialView(m.Value)); var partsDic = new Dictionary>(); foreach(var part in parts) { string viewName = Sanitize(part.Key); // Check if we already have a partial view by that name (e.g. if two Views folders have the same ascx) int keyCollisionCount = partsDic.Where(m => m.Key == viewName || m.Value.Key == viewName).Count(); if (keyCollisionCount > 0) { // Append a numbered suffix to avoid the conflict partsDic.Add(viewName + keyCollisionCount.ToString(), part); } else { partsDic.Add(viewName, part); } } return partsDic.ToDictionary(k => k.Key, v => v.Value.Value); } bool IsPartialView(string viewFilePath) { string viewFileName = Path.GetFileName(viewFilePath); if (viewFileName.EndsWith(".ascx")) return true; if ((viewFileName.EndsWith(".cshtml") || viewFileName.EndsWith(".vbhtml")) && viewFileName.StartsWith("_")) { return true; } return false; } void PrepareDataToRender(TextTransformation tt) { TT = tt; T4FileName = Path.GetFileName(Host.TemplateFile); T4Folder = Path.GetDirectoryName(Host.TemplateFile); Areas = new HashSet(); ResultTypes = new Dictionary(); // Get the DTE service from the host var serviceProvider = Host as IServiceProvider; if (serviceProvider != null) { Dte = serviceProvider.GetService(typeof(SDTE)) as DTE; } // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe if (Dte == null) { throw new Exception("T4MVC can only execute through the Visual Studio host"); } Project = GetProjectContainingT4File(Dte); if (Project == null) { Error("Could not find the VS Project containing the T4 file."); return; } // Get the path of the root folder of the app // //AppRoot = Path.GetDirectoryName(Project.FullName) + '\\'; // MvcVersion = GetMvcVersion(); // Use the proper return type of render helpers HtmlStringType = MvcVersion < 2 ? "string" : "MvcHtmlString"; // //ProcessAreas(Project); ViewProject = GetViewsProject(Dte); AppRoot = Path.GetDirectoryName(ViewProject.FullName) + '\\'; ProcessAreas(ViewProject); // } float GetMvcVersion() { var vsProject = (VSLangProj.VSProject)Project.Object; foreach (VSLangProj.Reference r in vsProject.References) { if (r.Name.Equals("System.Web.Mvc", StringComparison.OrdinalIgnoreCase)) { return r.MajorVersion + (r.MinorVersion / 10); } } // We should never get here, but default to v1 just in case return 1; } // Project GetViewsProject(DTE dte) { foreach (Project proj in dte.Solution.Projects) { if (proj.Name == ViewsProject) { return proj; } } return null; } // Project GetProjectContainingT4File(DTE dte) { // Find the .tt file's ProjectItem ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile); // If the .tt file is not opened, open it if (projectItem.Document == null) projectItem.Open(Constants.vsViewKindCode); if (AlwaysKeepTemplateDirty) { // Mark the .tt file as unsaved. This way it will be saved and update itself next time the // project is built. Basically, it keeps marking itself as unsaved to make the next build work. // Note: this is certainly hacky, but is the best I could come up with so far. projectItem.Document.Saved = false; } return projectItem.ContainingProject; } void ProcessAreas(Project project) { // Process the default area // ProcessDefaultArea(); // // Get the Areas folder ProjectItem areaProjectItem = GetProjectItem(project, AreasFolder); if (areaProjectItem == null) return; foreach (ProjectItem item in areaProjectItem.ProjectItems) { if (IsFolder(item)) { ProcessArea(item.ProjectItems, item.Name); } } // Process portable areas foreach (string portableArea in PortableAreas) { ProjectItem portableAreaProjectItem = GetProjectItem(project, portableArea); if (portableAreaProjectItem == null) return; if (IsFolder(portableAreaProjectItem)) { ProcessArea(portableAreaProjectItem.ProjectItems, portableAreaProjectItem.Name); } } } // void ProcessDefaultArea() { string name = null; var area = new AreaInfo() { Name = name }; var viewItems = ViewProject.ProjectItems; var controllerItems = Project.ProjectItems; ProcessAreaControllers(controllerItems, area); ProcessAreaViews(viewItems, area); Areas.Add(area); if (String.IsNullOrEmpty(name)) DefaultArea = area; } // void ProcessArea(ProjectItems areaFolderItems, string name) { var area = new AreaInfo() { Name = name }; ProcessAreaControllers(areaFolderItems, area); ProcessAreaViews(areaFolderItems, area); Areas.Add(area); if (String.IsNullOrEmpty(name)) DefaultArea = area; } void ProcessAreaControllers(ProjectItems areaFolderItems, AreaInfo area) { // Get area Controllers folder ProjectItem controllerProjectItem = GetProjectItem(areaFolderItems, ControllersFolder); if (controllerProjectItem == null) return; ProcessControllersRecursive(controllerProjectItem, area); } void ProcessAreaViews(ProjectItems areaFolderItems, AreaInfo area) { // Get area Views folder ProjectItem viewsProjectItem = GetProjectItem(areaFolderItems, ViewsRootFolder); if (viewsProjectItem == null) return; ProcessAllViews(viewsProjectItem, area); } void ProcessControllersRecursive(ProjectItem projectItem, AreaInfo area) { // Recurse into all the sub-items (both files and folder can have some - e.g. .tt files) foreach (ProjectItem item in projectItem.ProjectItems) { ProcessControllersRecursive(item, area); } if (projectItem.FileCodeModel != null) { DateTime controllerLastWriteTime = File.GetLastWriteTime(projectItem.get_FileNames(0)); foreach (var type in projectItem.FileCodeModel.CodeElements.OfType()) { ProcessControllerType(type, area, controllerLastWriteTime); } // Process all the elements that are namespaces foreach (var ns in projectItem.FileCodeModel.CodeElements.OfType()) { foreach (var type in ns.Members.OfType()) { ProcessControllerType(type, area, controllerLastWriteTime); } } } } void ProcessControllerType(CodeClass2 type, AreaInfo area, DateTime controllerLastWriteTime) { // Only process controllers if (!IsController(type)) return; // Don't process generic classes (their concrete derived classes will be processed) if (type.IsGeneric) return; // Make sure the class is partial if (type.ClassKind != vsCMClassKind.vsCMClassKindPartialClass) { try { type.ClassKind = vsCMClassKind.vsCMClassKindPartialClass; } catch { // If we couldn't make it partial, give a warning and skip it Warning(String.Format("{0} was not able to make the class {1} partial. Please change it manually if possible", T4FileName, type.Name)); return; } Warning(String.Format("{0} changed the class {1} to be partial", T4FileName, type.Name)); } // Collect misc info about the controller class and add it to the collection var controllerInfo = new ControllerInfo { Area = area, Namespace = type.Namespace != null ? type.Namespace.Name : String.Empty, ClassName = type.Name }; // Check if the controller has changed since the generated file was last created DateTime lastGenerationTime = File.GetLastWriteTime(controllerInfo.GeneratedFileFullPath); if (lastGenerationTime > controllerLastWriteTime) { controllerInfo.GeneratedCodeIsUpToDate = true; } // Either process new ControllerInfo or integrate results into existing object for partially defined controllers var target = area.Controllers.Add(controllerInfo) ? controllerInfo : area.Controllers.First(c => c.Equals(controllerInfo)); target.HasExplicitConstructor |= HasExplicitConstructor(type); target.HasExplicitDefaultConstructor |= HasExplicitDefaultConstructor(type); if (type.IsAbstract) { // If it's abstract, set a flag and don't process action methods (derived classes will) target.IsAbstract = true; } else { // Process all the action methods in the controller ProcessControllerActionMethods(target, type); } } void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current) { // We want to process not just the controller class itself, but also its parents, as they // may themselves define actions for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1)) { // If the type doesn't come from this project, some actions on it will fail. Try to get a real project type if possible. if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject) { // Go through all the projects in the solution //foreach (Project prj in Dte.Solution.Projects) { for (int i = 1; i <= Dte.Solution.Projects.Count; i++) { Project prj = null; try { prj = Dte.Solution.Projects.Item(i); } catch (System.Runtime.Serialization.SerializationException) { // Some project types (that we don't care about) cause a strange exception, so ingore it continue; } // Skip it if it's the current project or doesn't have a code model try { if (prj == Project || prj.CodeModel == null) continue; } catch (System.NotImplementedException) { // Installer project does not implement CodeModel property continue; } // If we can get a local project type, use it instead of the original var codeType = prj.CodeModel.CodeTypeFromFullName(type.FullName); if (codeType != null && codeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject) { type = (CodeClass2)codeType; break; } } } foreach (CodeFunction2 method in GetMethods(type)) { // Ignore non-public methods if (method.Access != vsCMAccess.vsCMAccessPublic) continue; // Ignore methods that are marked as not being actions if (GetAttribute(method.Attributes, "System.Web.Mvc.NonActionAttribute") != null) continue; // Ignore methods that are marked as Obsolete if (GetAttribute(method.Attributes, "System.ObsoleteAttribute") != null) continue; // Ignore generic methods if (method.IsGeneric) continue; // This takes care of avoiding generic types which cause method.Type.CodeType to blow up if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType) continue; // We only support action methods that return an ActionResult derived type if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult")) { Warning(String.Format("{0} doesn't support {1}.{2} because it doesn't return a supported ActionResult type", T4FileName, type.Name, method.Name)); continue; } // Ignore async completion methods as they can't really be used in T4MVC, and can cause issues. // See http://stackoverflow.com/questions/5419173/t4mvc-asynccontroller if (method.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase)) continue; // If we haven't yet seen this return type, keep track of it if (!ResultTypes.ContainsKey(method.Type.CodeType.Name)) { var resTypeInfo = new ResultTypeInfo(method.Type.CodeType); ResultTypes[method.Type.CodeType.Name] = resTypeInfo; } // Make sure the method is virtual if (!method.CanOverride && method.OverrideKind != vsCMOverrideKind.vsCMOverrideKindOverride) { try { method.CanOverride = true; } catch { // If we couldn't make it virtual, give a warning and skip it Warning(String.Format("{0} was not able to make the action method {1}.{2} virtual. Please change it manually if possible", T4FileName, type.Name, method.Name)); continue; } Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name)); } // Collect misc info about the action method and add it to the collection controllerInfo.ActionMethods.Add(new ActionMethodInfo(method)); } } } void ProcessAllViews(ProjectItem viewsProjectItem, AreaInfo area) { // Go through all the sub-folders in the Views folder foreach (ProjectItem item in viewsProjectItem.ProjectItems) { // We only care about sub-folders, not files if (!IsFolder(item)) continue; // Find the controller for this view folder ControllerInfo controller = area.Controllers.SingleOrDefault(c => c.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase)); if (controller == null) { // If it doesn't match a controller, treat as a pseudo-controller for consistency controller = new ControllerInfo { Area = area, NotRealController = true, Namespace = MakeClassName(T4MVCNamespace, area.Name), ClassName = item.Name + ControllerSuffix }; area.Controllers.Add(controller); } AddViewsRecursive(item.ProjectItems, controller.ViewsFolder); } } void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder) { AddViewsRecursive(items, viewsFolder, UseNonQualifiedViewNames); } void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder, bool useNonQualifiedViewNames) { // Go through all the files in the subfolder to get the view names foreach (ProjectItem item in items) { if (item.Kind == Constants.vsProjectItemKindPhysicalFile) { if (Path.GetExtension(item.Name).Equals(".master", StringComparison.OrdinalIgnoreCase)) continue; // ignore master files viewsFolder.AddView(item, useNonQualifiedViewNames); } else if (item.Kind == Constants.vsProjectItemKindPhysicalFolder) { string folderName = Path.GetFileName(item.Name); if (folderName.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase)) continue; // Use simple view names if we're already in that mode, or if the folder name is in the collection bool folderShouldUseNonQualifiedViewNames = useNonQualifiedViewNames || NonQualifiedViewFolders.Contains(folderName, StringComparer.OrdinalIgnoreCase); var subViewFolder = new ViewsFolderInfo() { Name = folderName }; viewsFolder.SubFolders.Add(subViewFolder); AddViewsRecursive(item.ProjectItems, subViewFolder, folderShouldUseNonQualifiedViewNames); } } } void RenderControllerViews(ControllerInfo controller) { PushIndent(" "); RenderViewsRecursive(controller.ViewsFolder, controller); PopIndent(); } void RenderViewsRecursive(ViewsFolderInfo viewsFolder, ControllerInfo controller) { // For each view, generate a readonly string foreach (var viewPair in viewsFolder.Views) { WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Value + "\";"); } // For each sub folder, generate a class and recurse foreach (var subFolder in viewsFolder.SubFolders) { string name = Sanitize(subFolder.Name); string className = "_" + name; // If the folder name is the same as the parent, add a modifier to avoid class name conflicts // http://mvccontrib.codeplex.com/workitem/7153 if (name == Sanitize(viewsFolder.Name)) { className += "_"; }#> static readonly <#=className#> s_<#=name#> = new <#=className#>(); public <#=className#> <#=EscapeID(name)#> { get { return s_<#=name#>; } } public partial class <#=className#>{ <#+ PushIndent(" "); RenderViewsRecursive(subFolder, controller); PopIndent(); WriteLine("}"); } } void ProcessStaticFiles(Project project, string folder) { ProjectItem folderProjectItem = GetProjectItem(project, folder); if (folderProjectItem != null) { ProcessStaticFilesRecursive(folderProjectItem, "~"); } } void ProcessStaticFilesRecursive(ProjectItem projectItem, string path) { ProcessStaticFilesRecursive(projectItem, path, false /*hasSameNameAsParent*/); } void ProcessStaticFilesRecursive(ProjectItem projectItem, string path, bool hasSameNameAsParent) { if (IsFolder(projectItem)) { string className = EscapeID(Sanitize(projectItem.Name)); // If the folder name is the same as the parent, add a modifier to avoid class name conflicts // http://mvccontrib.codeplex.com/workitem/7153 if (hasSameNameAsParent) { className += "_"; } #> [<#= GeneratedCode #>, DebuggerNonUserCode] public static class <#=className #> { private const string URLPATH = "<#=path#>/<#=projectItem.Name#>"; public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); } public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); } <#+ PushIndent(" "); // Recurse into all the items in the folder foreach (ProjectItem item in projectItem.ProjectItems) { ProcessStaticFilesRecursive( item, path + "/" + projectItem.Name, item.Name == projectItem.Name); } PopIndent(); #> } <#+ } else { #> <#+ if (!ExcludedStaticFileExtensions.Any(extension => projectItem.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))) { // if it's a non-minified javascript file if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js")) { if (AddTimestampToStaticLink(projectItem)) { #> public static readonly string <#=Sanitize(projectItem.Name)#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=projectItem.Name.Replace(".js", ".min.js")#>") ? Url("<#=projectItem.Name.Replace(".js", ".min.js")#>")+"?"+T4Extensions.TimestampString(URLPATH + "/<#=projectItem.Name#>") : Url("<#=projectItem.Name#>")+"?"+T4Extensions.TimestampString(URLPATH + "/<#=projectItem.Name#>"); <#+} else {#> public static readonly string <#=Sanitize(projectItem.Name)#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=projectItem.Name.Replace(".js", ".min.js")#>") ? Url("<#=projectItem.Name.Replace(".js", ".min.js")#>") : Url("<#=projectItem.Name#>"); <#+} #> <#+} else if (AddTimestampToStaticLink(projectItem)) { #> public static readonly string <#=Sanitize(projectItem.Name)#> = Url("<#=projectItem.Name#>")+"?"+T4Extensions.TimestampString(URLPATH + "/<#=projectItem.Name#>"); <#+} else { #> public static readonly string <#=Sanitize(projectItem.Name)#> = Url("<#=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); } } } ProjectItem GetProjectItem(Project project, string name) { return GetProjectItem(project.ProjectItems, name); } ProjectItem GetProjectItem(ProjectItems items, string subPath) { ProjectItem current = null; foreach (string name in subPath.Split('\\')) { try { // ProjectItems.Item() throws when it doesn't exist, so catch the exception // to return null instead. current = items.Item(name); } catch { // If any chunk couldn't be found, fail return null; } items = current.ProjectItems; } return current; } static bool IsController(CodeClass2 type) { // Ignore any class which name doesn't end with "Controller" if (!type.FullName.EndsWith(ControllerSuffix)) return false; for (; type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1)) { if (type.Bases.Count == 0) return false; } return true; } static string GetVirtualPath(ProjectItem item) { string fileFullPath = item.get_FileNames(0); if (!fileFullPath.StartsWith(AppRoot, StringComparison.OrdinalIgnoreCase)) throw new Exception(string.Format("File {0} is not under app root {1}. Please report issue.", fileFullPath, AppRoot)); // Make a virtual path from the physical path return "~/" + fileFullPath.Substring(AppRoot.Length).Replace('\\', '/'); } static string ProcessAreaOrControllerName(string name) { return UseLowercaseRoutes ? name.ToLowerInvariant() : name; } // Return all the CodeFunction2 in the CodeElements collection static IEnumerable GetMethods(CodeClass2 codeClass) { // Only look at regular method (e.g. ignore things like contructors) return codeClass.Members.OfType() .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction); } // Check if the class has any explicit constructor static bool HasExplicitConstructor(CodeClass2 codeClass) { return codeClass.Members.OfType().Any( f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor); } // Check if the class has a default (i.e. no params) constructor static bool HasExplicitDefaultConstructor(CodeClass2 codeClass) { return codeClass.Members.OfType().Any( f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor && f.Parameters.Count == 0); } // Find a method with a given name static CodeFunction2 GetMethod(CodeClass2 codeClass, string name) { return GetMethods(codeClass).FirstOrDefault(f => f.Name == name); } // Find an attribute of a given type on an attribute collection static CodeAttribute2 GetAttribute(CodeElements attributes, string attributeType) { for (int i = 1; i <= attributes.Count; i++) { var attrib = (CodeAttribute2)attributes.Item(i); if (attrib.FullName == attributeType) { return attrib; } } return null; } // Return whether a ProjectItem is a folder and not a file static bool IsFolder(ProjectItem item) { return (item.Kind == Constants.vsProjectItemKindPhysicalFolder); } static string MakeClassName(string ns, string classname) { return String.IsNullOrEmpty(ns) ? classname : String.IsNullOrEmpty(classname) ? ns : ns + "." + codeProvider.CreateEscapedIdentifier(classname); } static string Sanitize(string token) { if (token == null) return null; // Replace all invalid chars by underscores token = Regex.Replace(token, @"[\W\b]", "_", RegexOptions.IgnoreCase); // If it starts with a digit, prefix it with an underscore token = Regex.Replace(token, @"^\d", @"_$0"); // Check for reserved words // TODO: Clean this up and add other reserved words (keywords, etc) if (token == "Url") token = "_Url"; return token; } static string EscapeID(string id) { return codeProvider.CreateEscapedIdentifier(id); } // Data structure to collect data about an area class AreaInfo { public AreaInfo() { Controllers = new HashSet(); } public string Name { get; set; } public HashSet Controllers { get; set; } public string Namespace { get { // When *not* using an 'Areas' token, we need to disambiguate conflicts // between Area names and controller names (from the default Area) if (!IncludeAreasToken && DefaultArea.Controllers.Any(c => c.Name == Name)) return Name + "Area"; return Name; } } public IEnumerable GetControllers() { return Controllers.Where(c => !c.IsAbstract); } public IEnumerable GetAbstractControllers() { return Controllers.Where(c => c.IsAbstract); } } // Data structure to collect data about a controller class class ControllerInfo { public ControllerInfo() { ActionMethods = new HashSet(); ViewsFolder = new ViewsFolderInfo(); } public AreaInfo Area { get; set; } public string AreaName { get { return Area.Name ?? ""; } } public string T4MVCControllerFullName { get { string name = HelpersPrefix; if (!String.IsNullOrEmpty(AreaName)) { if (IncludeAreasToken) name += ".Areas." + EscapeID(Area.Namespace); else name += "." + EscapeID(Area.Namespace); } return name + "." + Name; ; } } public string ViewPath { get { if (string.IsNullOrEmpty(Area.Name)) return String.Format("~/{0}/{1}/", ViewsRootFolder, Name); else return String.Format("~/{0}/{1}/{2}/", AreasFolder, ViewsRootFolder, Name); } } // True when this is not a real controller, but a placeholder for views folders that don't match a controller public bool NotRealController { get; set; } public bool HasExplicitConstructor { get; set; } public bool HasExplicitDefaultConstructor { get; set; } public bool HasDefaultConstructor { get { return !HasExplicitConstructor || HasExplicitDefaultConstructor; } } public bool IsAbstract { get; set; } public bool GeneratedCodeIsUpToDate { get; set; } public string ClassName { get; set; } public string Name { get { // Trim the Controller suffix return ClassName.Substring(0, ClassName.Length - ControllerSuffix.Length); } } public string Namespace { get; set; } public string FullClassName { get { return MakeClassName(Namespace, ClassName); } } public string DerivedClassName { get { return "T4MVC_" + ClassName; } } public string FullDerivedClassName { get { if (NotRealController) return FullClassName; return MakeClassName(Namespace, DerivedClassName); } } public string GeneratedFileName { get { return MakeClassName(AreaName, ClassName + ".generated.cs"); } } public string GeneratedFileFullPath { get { return Path.Combine(T4Folder, GeneratedFileName); } } public HashSet ActionMethods { get; set; } IEnumerable ActionMethodsWithNoParameters { get { return ActionMethods.Where(m => m.CanBeCalledWithoutParameters); } } public IEnumerable ActionMethodsUniqueWithoutParameterlessOverload { get { return ActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer()); } } // Return a list of actions without duplicate names (even with multiple overloads) public IEnumerable ActionMethodsWithUniqueNames { get { return ActionMethods.Distinct(new ActionComparer()); } } class ActionComparer : IEqualityComparer { public bool Equals(ActionMethodInfo x, ActionMethodInfo y) { return x.ActionName == y.ActionName; } public int GetHashCode(ActionMethodInfo obj) { return obj.ActionName.GetHashCode(); } } public ViewsFolderInfo ViewsFolder { get; private set; } public override string ToString() { return Name; } public override bool Equals(object obj) { return obj != null && FullClassName == ((ControllerInfo)obj).FullClassName; } public override int GetHashCode() { return FullClassName.GetHashCode(); } } // Info about a view folder, its views and its sub view folders class ViewsFolderInfo { public ViewsFolderInfo() { Views = new Dictionary(); SubFolders = new List(); } public void AddView(ProjectItem item, bool useNonQualifiedViewName) { string viewName = Path.GetFileName(item.Name); string viewFieldName = Path.GetFileNameWithoutExtension(viewName); // If the simple view name is already in use, include the extension (e.g. foo_ascx instead of just foo) if (Views.ContainsKey(viewFieldName)) viewFieldName = Sanitize(viewName); Views[viewFieldName] = useNonQualifiedViewName ? Path.GetFileNameWithoutExtension(viewName) : GetVirtualPath(item); } public string Name { get; set; } public Dictionary Views { get; private set; } public List SubFolders { get; set; } } // Data structure to collect data about a method class FunctionInfo { protected CodeFunction2 _method; private string _signature; public FunctionInfo(CodeFunction2 method) { Parameters = new List(); // Can be null when an custom ActionResult has no ctor if (method == null) return; _method = method; // Build a unique signature for the method, used to avoid duplication _signature = method.Name; CanBeCalledWithoutParameters = true; // Process all the parameters foreach (var p in method.Parameters.OfType()) { // If any param is not optional, then the method can't be called without parameters if (p.ParameterKind != vsCMParameterKind.vsCMParameterKindOptional) { CanBeCalledWithoutParameters = false; } // Note: if the param name starts with @ (e.g. to escape a keyword), we need to trim that string routeNameExpression = "\"" + p.Name.TrimStart('@') + "\""; // If there is a [Bind(Prefix = "someName")] attribute, use it if (p.InfoLocation != vsCMInfoLocation.vsCMInfoLocationExternal) { var attrib = GetAttribute(p.Attributes, "System.Web.Mvc.BindAttribute"); if (attrib != null) { var arg = attrib.Arguments.OfType().FirstOrDefault(a => a.Name == "Prefix"); if (arg != null) routeNameExpression = arg.Value; } } Parameters.Add( new MethodParamInfo() { Name = p.Name, RouteNameExpression = routeNameExpression, Type = p.Type.AsString }); _signature += "," + p.Type.AsString; } } public string Name { get { return _method.Name; } } public string ReturnType { get { return _method.Type.CodeType.Name; } } public string ReturnTypeFullName { get { return _method.Type.CodeType.FullName; } } public bool IsPublic { get { return _method.Access == vsCMAccess.vsCMAccessPublic; } } public List Parameters { get; private set; } public bool CanBeCalledWithoutParameters { get; private set; } // Write out all the parameters as part of a method declaration public void WriteFormalParameters(bool first) { foreach (var p in Parameters) { if (first) first = false; else TT.Write(", "); TT.Write(p.Type + " " + p.Name); } } // Pass non-empty param values to make sure the ActionResult ctors don't complain // REVIEW: this is a bit dirty public void WriteNonEmptyParameterValues(bool first) { foreach (var p in Parameters) { if (first) first = false; else TT.Write(", "); switch (p.Type) { case "string": TT.Write("\" \""); break; case "byte[]": TT.Write("new byte[0]"); break; default: TT.Write("default(" + p.Type + ")"); break; } } } public override bool Equals(object obj) { return obj != null && _signature == ((FunctionInfo)obj)._signature; } public override int GetHashCode() { return _signature.GetHashCode(); } } // Data structure to collect data about an action method class ActionMethodInfo : FunctionInfo { public ActionMethodInfo(CodeFunction2 method) : base(method) { // Normally, the action name is the method name. But if there is an [ActionName] on // the method, get the expression from that instead ActionNameValueExpression = '"' + Name + '"'; var attrib = GetAttribute(method.Attributes, "System.Web.Mvc.ActionNameAttribute"); if (attrib != null) { var arg = (CodeAttributeArgument)attrib.Arguments.Item(1); ActionNameValueExpression = arg.Value; } } public string ActionName { get { return Name; } } public string ActionNameValueExpression { get; set; } } // Data about an ActionResult derived type class ResultTypeInfo { CodeType _codeType; public ResultTypeInfo(CodeType codeType) { _codeType = codeType; var ctor = _codeType.Members.OfType().FirstOrDefault( f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor); Constructor = new FunctionInfo(ctor); } public string Name { get { return _codeType.Name; } } public string FullName { get { return _codeType.FullName; } } public FunctionInfo Constructor { get; set; } public IEnumerable AbstractMethods { get { return _codeType.Members.OfType().Where( f => f.MustImplement).Select(f => new FunctionInfo(f)); } } } class MethodParamInfo { public string Name { get; set; } public string RouteNameExpression { get; set; } public string Type { get; set; } } /* Manager.tt from Damien Guard: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited */ // Manager class records the various blocks so it can split them up class Manager { private class Block { public String Name; public int Start, Length; } private Block currentBlock; private List files = new List(); private Block footer = new Block(); private Block header = new Block(); private ITextTemplatingEngineHost host; private StringBuilder template; protected List generatedFileNames = new List(); public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template) { return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template); } public void KeepGeneratedFile(String name) { name = Path.Combine(Path.GetDirectoryName(host.TemplateFile), name); generatedFileNames.Add(name); } public void StartNewFile(String name) { if (name == null) throw new ArgumentNullException("name"); CurrentBlock = new Block { Name = name }; } public void StartFooter() { CurrentBlock = footer; } public void StartHeader() { CurrentBlock = header; } public void EndBlock() { if (CurrentBlock == null) return; CurrentBlock.Length = template.Length - CurrentBlock.Start; if (CurrentBlock != header && CurrentBlock != footer) files.Add(CurrentBlock); currentBlock = null; } public virtual void Process(bool split) { if (split) { EndBlock(); String headerText = template.ToString(header.Start, header.Length); String footerText = template.ToString(footer.Start, footer.Length); String outputPath = Path.GetDirectoryName(host.TemplateFile); files.Reverse(); foreach (Block block in files) { String fileName = Path.Combine(outputPath, block.Name); String content = headerText + template.ToString(block.Start, block.Length) + footerText; generatedFileNames.Add(fileName); CreateFile(fileName, content); template.Remove(block.Start, block.Length); } } } protected virtual void CreateFile(String fileName, String content) { if (IsFileContentDifferent(fileName, content)) File.WriteAllText(fileName, content); } public virtual String GetCustomToolNamespace(String fileName) { return null; } public virtual String DefaultProjectNamespace { get { return null; } } protected bool IsFileContentDifferent(String fileName, String newContent) { return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent); } private Manager(ITextTemplatingEngineHost host, StringBuilder template) { this.host = host; this.template = template; } private Block CurrentBlock { get { return currentBlock; } set { if (CurrentBlock != null) EndBlock(); if (value != null) value.Start = template.Length; currentBlock = value; } } private class VSManager : Manager { private EnvDTE.ProjectItem templateProjectItem; private EnvDTE.DTE dte; private Action checkOutAction; private Action> projectSyncAction; public override String DefaultProjectNamespace { get { return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString(); } } public override String GetCustomToolNamespace(string fileName) { return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString(); } public override void Process(bool split) { if (templateProjectItem.ProjectItems == null) return; base.Process(split); projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null)); } protected override void CreateFile(String fileName, String content) { if (IsFileContentDifferent(fileName, content)) { CheckoutFileIfRequired(fileName); File.WriteAllText(fileName, content); } } internal VSManager(ITextTemplatingEngineHost host, StringBuilder template) : base(host, template) { var hostServiceProvider = (IServiceProvider)host; if (hostServiceProvider == null) throw new ArgumentNullException("Could not obtain IServiceProvider"); dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE)); if (dte == null) throw new ArgumentNullException("Could not obtain DTE from host"); templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); checkOutAction = (String fileName) => dte.SourceControl.CheckOutItem(fileName); projectSyncAction = (IEnumerable keepFileNames) => ProjectSync(templateProjectItem, keepFileNames); } private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable keepFileNames) { var keepFileNameSet = new HashSet(keepFileNames); var projectFiles = new Dictionary(); var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.get_FileNames(0)) + "."; foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) projectFiles.Add(projectItem.get_FileNames(0), projectItem); // Remove unused items from the project foreach (var pair in projectFiles) if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix)) pair.Value.Delete(); // Add missing files to the project foreach (String fileName in keepFileNameSet) if (!projectFiles.ContainsKey(fileName)) templateProjectItem.ProjectItems.AddFromFile(fileName); } private void CheckoutFileIfRequired(String fileName) { var sc = dte.SourceControl; if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName)) checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null)); } } } /* End of Manager.tt */ #>