diff --git a/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.settings.t4 b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.settings.t4 new file mode 100644 index 0000000..bd0fa49 --- /dev/null +++ b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.settings.t4 @@ -0,0 +1,96 @@ +<#+ +/* + +This file contains settings used by T4MVC.tt. The main goal is to avoid the need for users +to fork the 'official' template in order to achieve what they want. + +*/ + + +// The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID) +const string HelpersPrefix = "MVC"; + +// Namespaces to be referenced by the generated code +readonly string[] ReferencedNamespaces = new string[] { +}; + +// The folder under the project that contains the areas +const string AreasFolder = "Areas"; + +// Choose whether you want to include an 'Areas' token when referring to areas. +// e.g. Assume the Area is called Blog and the Controller is Post: +// - When false use MVC.Blog.Post.etc... +// - When true use MVC.Areas.Blog.Post.etc... +static bool IncludeAreasToken = false; + +// The folder under the project that contains the controllers +const string ControllersFolder = "Controllers"; + +// The folder under the project that contains the views +const string ViewsRootFolder = "Views"; + +// The name of the interface that all T4MVC action results will implement +const string ActionResultInterfaceName = "IT4MVCActionResult"; + +// If true, the T4MVC action result interface will be generated +// If false, the namespace of the interface must be referenced in the 'ReferencedNamespaces' setting +const bool GenerateActionResultInterface = true; + +// If true, use lower case tokens in routes for the area, controller and action names +const bool UseLowercaseRoutes = false; + +// The namespace that the links are generated in (e.g. "Links", as in Links.Content.nerd_jpg) +const string LinksNamespace = "Links"; + +// If true, links to static files include a query string containing the file's last change time. This way, +// when the static file changes, the link changes and guarantees that the client will re-request the resource. +// e.g. when true, the link looks like: "/Content/nerd.jpg?2009-09-04T12:25:48" +const bool AddTimestampToStaticLinks = false; + +// Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js) +readonly string[] StaticFilesFolders = new string[] { + "Scripts", + "Content", +}; + +// Static files to exclude from the generated links +readonly string[] ExcludedStaticFileExtensions = new string[] { + ".cs" +}; + +// If true, the template marks itself as unsaved as part of its execution. +// 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. +static bool AlwaysKeepTemplateDirty = false; + +// If true,the template output will be split into multiple files. +static bool SplitIntoMultipleFiles = true; + +void RenderAdditionalCode() { +#> +static class T4MVCHelpers { + // You can change the ProcessVirtualPath method to modify the path that gets returned to the client. + // e.g. you can prepend a domain, or append a query string: + // return "http://localhost" + path + "?foo=bar"; + private static string ProcessVirtualPathDefault(string virtualPath) { + // The path that comes in starts with ~/ and must first be made absolute + string path = VirtualPathUtility.ToAbsolute(virtualPath); + + // Add your own modifications here before returning the path + return path; + } + + // Calling ProcessVirtualPath through delegate to allow it to be replaced for unit testing + public static Func ProcessVirtualPath = ProcessVirtualPathDefault; + + + // Logic to determine if the app is running in production or dev environment + public static bool IsProduction() { + return (HttpContext.Current != null && !HttpContext.Current.IsDebuggingEnabled); + } +} + +<#+ +} +#> diff --git a/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.tt b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.tt new file mode 100644 index 0000000..ef305ca --- /dev/null +++ b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/T4MVC.tt @@ -0,0 +1,1418 @@ +<# +/* +T4MVC Version 2.6.14 +Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC +Discuss on the T4MVC forum: http://forums.asp.net/1215.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://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" #> +<#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.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 #>(); +<#} #> + } +<#} #> +} + +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, IDictionary htmlAttributes) { + return htmlHelper.RouteLink(linkText, result.GetRouteValueDictionary(), htmlAttributes); + } + + 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(result.GetRouteValueDictionary()); + } + + 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 Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result) { + return routes.MapRoute(name, url, result, (ActionResult)null); + } + + public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults) { + return MapRoute(routes, name, url, result, defaults, null); + } + + public static Route MapRoute(this RouteCollection routes, string name, string url, ActionResult result, object defaults, object constraints) { + // 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()); + routes.Add(name, route); + return route; + } + + public static <#=ActionResultInterfaceName #> GetT4MVCResult(this ActionResult result) { + var t4MVCResult = result as <#=ActionResultInterfaceName #>; + if (t4MVCResult == null) { + throw new InvalidOperationException("T4MVC methods can only be passed pseudo-action calls (e.g. MVC.Home.About()), and not real action calls."); + } + 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 (Areas.Count > 1) { #>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); + } + } +} + +<#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 + 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(); #> +<#} #> + + +namespace <#=T4MVCNamespace #> { + [<#= GeneratedCode #>, DebuggerNonUserCode] + public class Dummy { + private Dummy() { } + public static Dummy Instance = new Dummy(); + } +} + +<#manager.StartFooter(); #> +#endregion T4MVC +#pragma warning restore 1591 +<#manager.EndBlock(); #> +<#manager.Process(SplitIntoMultipleFiles); #> + +<#@ Include File="T4MVC.settings.t4" #> + +<#+ +const string T4MVCNamespace = "T4MVC"; +const string ControllerSuffix = "Controller"; + +static DTE Dte; +static Project Project; +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; +} + +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); +} + +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 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 + ProcessArea(project.ProjectItems, null); + + // 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); + } + } +} + +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 types that end with Controller + // REVIEW: this check is not super reliable. Should look at base class. + if (!type.Name.EndsWith(ControllerSuffix, StringComparison.OrdinalIgnoreCase)) + 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 + if (prj == Project || prj.CodeModel == null) + 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; + + // 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; + } + + // 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) { + // 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); + } + else if (item.Kind == Constants.vsProjectItemKindPhysicalFolder) { + string folderName = Path.GetFileName(item.Name); + if (folderName.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase)) + continue; + var subViewFolder = new ViewsFolderInfo() { Name = folderName }; + viewsFolder.SubFolders.Add(subViewFolder); + AddViewsRecursive(item.ProjectItems, subViewFolder); + } + } +} + +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 newClassName = Sanitize(subFolder.Name);#> +static readonly _<#=newClassName#> s_<#=newClassName#> = new _<#=newClassName#>(); +public _<#=newClassName#> <#=EscapeID(newClassName)#> { get { return s_<#=newClassName#>; } } +public partial class _<#=newClassName#>{ +<#+ +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) { + + if (IsFolder(projectItem)) { #> + [<#= GeneratedCode #>, DebuggerNonUserCode] + public static class <#=EscapeID(Sanitize(projectItem.Name)) #> { + 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); +} + +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 (AddTimestampToStaticLinks) { #> + 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 (AddTimestampToStaticLinks) { #> + 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 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) { + // 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)) + 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.Parameters.Count == 0); + } + } + + 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) { + 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] = 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; + + // Process all the parameters + foreach (var p in method.Parameters.OfType()) { + string routeNameExpression = "\"" + p.Name + "\""; + + // 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; } + + // 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 +*/ +#> diff --git a/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/link.txt b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/link.txt new file mode 100644 index 0000000..3979ec0 --- /dev/null +++ b/UsageDataCollector/Project/Analysis/MVCContrib/T4MVC/link.txt @@ -0,0 +1,5 @@ +http://mvccontrib.codeplex.com/wikipage?title=T4MVC + +Apache License + + diff --git a/UsageDataCollector/Project/Analysis/UseDataAnalysis.sln b/UsageDataCollector/Project/Analysis/UsageDataAnalysis.sln similarity index 100% rename from UsageDataCollector/Project/Analysis/UseDataAnalysis.sln rename to UsageDataCollector/Project/Analysis/UsageDataAnalysis.sln diff --git a/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/UsageDataAnalysisWebClient.csproj b/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/UsageDataAnalysisWebClient.csproj index e7496d0..371490d 100644 --- a/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/UsageDataAnalysisWebClient.csproj +++ b/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/UsageDataAnalysisWebClient.csproj @@ -13,7 +13,7 @@ UsageDataAnalysisWebClient UsageDataAnalysisWebClient v4.0 - false + true true diff --git a/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/Web.config b/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/Web.config index e0dd7ad..1c83645 100644 --- a/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/Web.config +++ b/UsageDataCollector/Project/Analysis/UsageDataAnalysisWebClient/Web.config @@ -12,7 +12,11 @@ - + + + + +