/Web/T4MVC.tt
Unknown | 2473 lines | 2129 code | 344 blank | 0 comment | 0 complexity | 04e577062b8954c3c566f7e74749498f MD5 | raw file
- <#
- /*
- T4MVC Version 3.8.2
- Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC
- Discuss on StackOverflow or on Codeplex (https://t4mvc.codeplex.com/discussions)
-
- T4MVC is part of the MvcContrib project, but in a different Codeplex location (http://t4mvc.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#" debug="true" hostspecific="true" #>
- <#@ assembly name="System.Core" #>
- <#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
- <#@ assembly name="EnvDTE" #>
- <#@ assembly name="EnvDTE80" #>
- <#@ assembly name="VSLangProj" #>
- <#@ assembly name="System.Xml" #>
- <#@ assembly name="System.Xml.Linq" #>
- <#@ 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();
- #>
- <#settings=MvcSettings.Load(Host);#>
- <#PrepareDataToRender(this); #>
- <#var manager = Manager.Create(Host, GenerationEnvironment); #>
- <#manager.StartHeader(); #>// <auto-generated />
- // 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.Threading.Tasks;
- 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 <#=settings.T4MVCNamespace #>;
- <#foreach (var referencedNamespace in settings.ReferencedNamespaces) { #>
- using <#=referencedNamespace #>;
- <#} #>
- <#manager.EndBlock(); #>
-
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static partial class <#=settings.HelpersPrefix #>
- {
- <#if (settings.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 (settings.IncludeAreasToken) { #>
- }
- <#} #>
- <#foreach (var controller in DefaultArea.GetControllers()) { #>
- public static <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
- <#} #>
- }
-
- namespace <#=settings.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 <#=settings.T4MVCNamespace #>
- {
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public class Dummy
- {
- private Dummy() { }
- public static Dummy Instance = new Dummy();
- }
- }
-
- <#foreach (var resultType in ResultTypes.Values) { #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- internal partial class T4MVC_<#=resultType.UniqueName #> : <#=resultType.FullName #>, IT4MVCActionResult
- {
- public T4MVC_<#=resultType.UniqueName #>(string area, string controller, string action, string protocol = null): base(<#resultType.Constructor.WriteNonEmptyParameterValues(true); #>)
- {
- this.InitMVCT4Result(area, controller, action, protocol);
- }
- <#foreach (var method in resultType.AbstractMethods) { #>
- <#=method.IsPublic ? "public" : "protected" #> override <#=method.ReturnType#> <#=method.Name #>(<#method.WriteFormalParameters(true); #>) {<# if(method.ReturnType != "void") {#> return default(<#=method.ReturnType#>); <#} #> }
- <#} #>
-
- public string Controller { get; set; }
- public string Action { get; set; }
- public string Protocol { get; set; }
- public RouteValueDictionary RouteValueDictionary { get; set; }
- }
- <#} #>
-
-
-
- namespace <#=settings.LinksNamespace #>
- {
- <#
- foreach (string folder in settings.StaticFilesFolders.Concat(GetStaticFilesViewFolders())) {
- ProcessStaticFiles(Project, folder);
- }
- #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static partial class Bundles
- {
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static partial class Scripts {}
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static partial class Styles {}
- }
- }
-
- <#
- RenderAdditionalCode();
- #>
- <#foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #>
- <#manager.StartNewFile(controller.GeneratedFileName); #>
- namespace <#=controller.Namespace #>
- {
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- 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);
- }
-
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- protected RedirectToRouteResult RedirectToAction(Task<ActionResult> taskResult)
- {
- return RedirectToAction(taskResult.Result);
- }
-
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- protected RedirectToRouteResult RedirectToActionPermanent(ActionResult result)
- {
- var callInfo = result.GetT4MVCResult();
- return RedirectToRoutePermanent(callInfo.RouteValueDictionary);
- }
-
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- protected RedirectToRouteResult RedirectToActionPermanent(Task<ActionResult> taskResult)
- {
- return RedirectToActionPermanent(taskResult.Result);
- }
-
- <#foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #>
- [NonAction]
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public virtual <#=method.ReturnTypeFullName #> <#=method.Name #>()
- {
- <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
- var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
- return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
- <#} else { #>
- return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
- <#} #>
- }
- <#} #>
- <#foreach (var method in controller.CustomActionMethodsUniqueWithoutParameterlessOverload) { #>
- [NonAction]
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>()
- {
- return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
- }
- <#} #>
- <#foreach (var method in controller.CustomActionMethods) { #>
- [NonAction]
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>(<#method.WriteFormalParameters(true, true); #>)
- {
- var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
- <#if (method.Parameters.Count > 0) { #>
- <#foreach (var p in method.Parameters) { #>
- ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
- <#} #>
- <#}#>
- <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
- return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
- <#} else { #>
- return callInfo;
- <#} #>
- }
- <#} #>
-
- [<#= 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) #>";
- [<#= GeneratedCode #>]
- public const string NameConst = "<#=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 (settings.UseLowercaseRoutes) { #>
- public readonly string <#=method.ActionName #> = (<#=method.ActionNameValueExpression #>).ToLowerInvariant();
- <# } else { #>
- public readonly string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
- <# }
- } #>
- }
-
- <#
- // Issue: we can't honor UseLowercaseRoutes here because ToLowerInvariant() is not valid in constants!
- if (!settings.UseLowercaseRoutes) { #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public class ActionNameConstants
- {
- <#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
- public const string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
- <#
- } #>
- }
-
- <#}
- } #>
-
- <#if (settings.GenerateParamsForActionMethods && !settings.GenerateParamsAsConstantsForActionMethods){
- foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
- static readonly ActionParamsClass_<#=group.Key #> s_params_<#=group.Key #> = new ActionParamsClass_<#=group.Key #>();
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public ActionParamsClass_<#=group.Key #> <#=group.Key + settings.ParamsPropertySuffix #> { get { return s_params_<#=group.Key #>; } }
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public class ActionParamsClass_<#=group.Key #>
- {
- <#foreach (var param in group) { #>
- <# if (settings.UseLowercaseRoutes) { #>
- public readonly string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
- <# } else { #>
- public readonly string <#=param.Name #> = <#=param.RouteNameExpression #>;
- <# }
- } #>
- }
- <# } #>
- <#} #>
- <#if (settings.GenerateParamsAsConstantsForActionMethods){
-
- foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public class <#=group.Key + settings.ParamsPropertySuffix#>
- {
- <#foreach (var param in group) { #>
- <# if (settings.UseLowercaseRoutes) { #>
- public const string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
- <# } else { #>
- public const string <#=param.Name #> = <#=param.RouteNameExpression #>;
- <# }
- } #>
- }
- <# } #>
- <#} #>
- static readonly ViewsClass s_views = new ViewsClass();
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public ViewsClass Views { get { return s_views; } }
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public class ViewsClass
- {
- <#RenderControllerViews(controller);#>
- }
- }
-
- <#if (!controller.NotRealController) { #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public partial class <#=controller.DerivedClassName #> : <#=controller.FullClassName #>
- {
- public <#=controller.DerivedClassName #>() : base(Dummy.Instance) { }
-
- <#foreach (var method in controller.ActionMethods.Where(m => !m.IsCustomReturnType)) { #>
- [NonAction]
- partial void <#=method.Name #>Override(T4MVC_<#=method.ReturnTypeUniqueName #> callInfo<#if (method.Parameters.Count > 0) { #>, <#method.WriteFormalParameters(true); #><#}#>);
-
- [NonAction]
- public override <#=method.ReturnTypeFullName #> <#=method.Name #>(<#method.WriteFormalParameters(true); #>)
- {
- var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
- <#if (method.Parameters.Count > 0) { #>
- <#foreach (var p in method.Parameters) { #>
- ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
- <#} #>
- <#}#>
- <#=method.Name #>Override(callInfo<#if (method.Parameters.Count > 0) { #><#foreach (var p in method.Parameters) { #>, <#=p.Name #><#}}#>);
- <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
- return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
- <#} else { #>
- return callInfo;
- <#} #>
- }
-
- <#} #>
- }
- <#} #>
- <#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
- }
- <#} #>
-
- <#manager.EndBlock(); #>
- <#} #>
-
-
- <# if (settings.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(settings.ExplicitHtmlHelpersForPartialsFormat, partialName);
- #>
- ///<summary>
- ///Render the <b><#= partialName #></b> partial.
- ///</summary>
- public static void <#= partialRenderMethod #>(this HtmlHelper html) {
- html.RenderPartial("<#= partialPath #>");
- }
-
- ///<summary>
- ///Render the <b><#= partialName #></b> partial.
- ///</summary>
- 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(); #>
- <#settings.SaveChanges(manager); #>
- <#manager.Process(settings.SplitIntoMultipleFiles); #>
-
- <#@ Include File="T4MVC.tt.hooks.t4" #>
-
- <#+
- static MvcSettings settings;
- const string ControllerSuffix = "Controller";
-
- static DTE Dte;
- static Project Project;
- static string AppRoot;
- static HashSet<AreaInfo> Areas;
- static AreaInfo DefaultArea;
- static Dictionary<string, ResultTypeInfo> ResultTypes;
- static TextTransformation TT;
- static string T4FileName;
- static string T4Folder;
- static string GeneratedCode = @"GeneratedCode(""T4MVC"", ""2.0"")";
- static Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
- List<string> virtualPathesForStaticFiles = new List<string>();
-
- IEnumerable<ControllerInfo> GetControllers()
- {
- var controllers = new List<ControllerInfo>();
-
- foreach (var area in Areas)
- {
- controllers.AddRange(area.GetControllers());
- }
-
- return controllers;
- }
-
- IEnumerable<ControllerInfo> GetAbstractControllers()
- {
- var controllers = new List<ControllerInfo>();
-
- foreach (var area in Areas)
- {
- controllers.AddRange(area.GetAbstractControllers());
- }
-
- return controllers;
- }
-
- IDictionary<string, string> GetPartials()
- {
- var parts = GetControllers()
- .Select(m => m.ViewsFolder)
- .SelectMany(m => m.Views)
- .Where(m => IsPartialView(m.Value));
-
- var partsDic = new Dictionary<string, KeyValuePair<string, string>>();
-
- 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<AreaInfo>();
- ResultTypes = new Dictionary<string, ResultTypeInfo>();
-
- // Get the DTE service from the host
- var serviceProvider = Host as IServiceProvider;
- if (serviceProvider != null)
- {
- Dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.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) + '\\';
-
- ProcessAreas(Project);
- }
-
- 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(EnvDTE.Constants.vsViewKindCode);
-
- return projectItem.ContainingProject;
- }
-
- void ProcessAreas(Project project)
- {
- // Process the default area
- ProcessArea(project.ProjectItems, null);
-
- // Get the Areas folder
- ProjectItem areaProjectItem = GetProjectItem(project, settings.AreasFolder);
-
- // Process areas folder
- if (areaProjectItem != null)
- {
- foreach (ProjectItem item in areaProjectItem.ProjectItems)
- {
- if (IsFolder(item))
- {
- ProcessArea(item.ProjectItems, item.Name);
- }
- }
- }
-
- // Process portable areas
- foreach (string portableArea in settings.PortableAreas)
- {
- ProjectItem portableAreaProjectItem = GetProjectItem(project, portableArea);
-
- if (portableAreaProjectItem == null)
- return;
-
- if (IsFolder(portableAreaProjectItem))
- {
- ProcessArea(portableAreaProjectItem.ProjectItems, portableAreaProjectItem.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, settings.ControllersFolder);
- if (controllerProjectItem == null)
- return;
-
- ProcessControllersRecursive(controllerProjectItem, area);
- }
-
- void ProcessAreaViews(ProjectItems areaFolderItems, AreaInfo area)
- {
- // Get area Views folder
- ProjectItem viewsProjectItem = GetProjectItem(areaFolderItems, settings.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<CodeClass2>())
- {
- ProcessControllerType(type, area, controllerLastWriteTime);
- }
- // Process all the elements that are namespaces
- foreach (var ns in projectItem.FileCodeModel.CodeElements.OfType<CodeNamespace>())
- {
- foreach (var type in ns.Members.OfType<CodeClass2>())
- {
- 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;
-
- //Ignore references to controllers we create
- if(area.Controllers.Any(c => c.DerivedClassName == type.Name))
- 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
- };
-
- //Filter references to controllers we create
- foreach(var derived in area.Controllers.Where(c => c.ClassName == controllerInfo.DerivedClassName).ToArray())
- area.Controllers.Remove(derived);
-
- // 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)
- {
-
- bool isAsyncController = IsAsyncController(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
- 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;
- }
-
- try
- {
- // 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;
- }
- }
- catch (System.ArgumentException)
- {
- // CodeTypeFromFullName throws when called on VB projects with a type it doesn't know
- // (instead of returning null), so ignore those exceptions (See http://t4mvc.codeplex.com/workitem/7)
- }
- }
- }
-
- 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;
-
- if(isAsyncController && settings.SupportAsyncActions && (method.Type.TypeKind == vsCMTypeRef.vsCMTypeRefVoid) && method.Name.EndsWith("Async"))
- {
- //Async methods return void and there could be multiple matching Completed methods, so we will use
- //the generic ActionResult as the return type for the method.
- var resultType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
- // If we haven't yet seen this return type, keep track of it
- if (!ResultTypes.ContainsKey(resultType.AsFullName))
- {
- var resTypeInfo = new ResultTypeInfo(resultType);
- ResultTypes[resultType.AsFullName] = resTypeInfo;
- }
-
- // Collect misc info about the action method and add it to the collection
- controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current, resultType));
-
- continue;
- }
-
- // This takes care of avoiding generic types which cause method.Type.CodeType to blow up
- if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType || !(method.Type.CodeType is CodeClass2))
- continue;
-
- // We only support action methods that return an ActionResult and Task<ActionResult> derived types
- if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult") && method.Type.CodeType.FullName !="System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
- {
- Warning(String.Format("{0} doesn't support {1}.{2} because it doesn't return a supported {3} type", T4FileName, type.Name, method.Name, method.Type.CodeType.FullName));
- 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 (isAsyncController && method.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
- continue;
-
- var methodType = method.Type;
- if(method.Type.CodeType.FullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
- methodType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
-
- // If we haven't yet seen this return type, keep track of it
- var resTypeInfo2 = new ResultTypeInfo(methodType);
- if (!ResultTypes.ContainsKey(resTypeInfo2.FullName))
- {
- ResultTypes[resTypeInfo2.FullName] = resTypeInfo2;
- }
-
- // 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, current));
- }
- }
- }
-
- 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(settings.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, false);
- }
-
- 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 == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
- {
- // Ignore some extensions that are normally not views
- if (settings.ExcludedViewExtensions.Any(extension => Path.GetExtension(item.Name).Equals(extension, StringComparison.OrdinalIgnoreCase)))
- continue;
-
- viewsFolder.AddView(item, useNonQualifiedViewNames);
- }
- else if (item.Kind == EnvDTE.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 || settings.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)
- {
- if(!viewsFolder.HasNonQualifiedViewNames)
- {
- #>
- static readonly _ViewNamesClass s_ViewNames = new _ViewNamesClass();
- public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
- public class _ViewNamesClass
- {
- <#+
- PushIndent(" ");
- foreach (var viewPair in viewsFolder.Views)
- {
- WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Key + "\";");
- }
- PopIndent();
- #>
- }
- <#+}
- // 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#>Class s_<#=name#> = new <#=className#>Class();
- public <#=className#>Class <#=EscapeID(name)#> { get { return s_<#=name#>; } }
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public partial class <#=className#>Class
- {
- <#+
- PushIndent(" ");
- RenderViewsRecursive(subFolder, controller);
- PopIndent();
-
- WriteLine("}");
- }
- }
-
- IEnumerable<string> GetStaticFilesViewFolders()
- {
- if (settings.AddAllViewsFoldersToStaticFilesFolders)
- {
- foreach (var area in Areas)
- {
- yield return area.Name == null ?
- settings.ViewsRootFolder :
- settings.AreasFolder + "\\" + area.Name + "\\" + settings.ViewsRootFolder;
- }
- }
- }
-
- void ProcessStaticFiles(Project project, string folder)
- {
- ProjectItem folderProjectItem = GetProjectItem(project, folder);
- if (folderProjectItem != null)
- {
- var rootPath = "~";
- if (folder.Contains("\\"))
- {
- rootPath += "/" + folder.Replace("\\", "/");
- rootPath = rootPath.Substring(0, rootPath.LastIndexOf("/"));
- }
- ProcessStaticFilesRecursive(folderProjectItem, rootPath);
- }
- }
-
- void ProcessStaticFilesRecursive(ProjectItem projectItem, string path)
- {
- int nestedLevel = BuildClassStructureForProvidedPath(path);
- ProcessStaticFilesRecursive(projectItem, path, new HashSet<String>());
- for(int i = 0; i < nestedLevel; ++i) {#>
- }
- <#+
- PopIndent();
- }
- }
-
- void ProcessStaticFilesRecursive(ProjectItem projectItem, string path, HashSet<String> nameSet)
- {
- // The passed in HashSet is to guarantee uniqueness with our parent and siblings
- string name = SanitizeWithNoConflicts(projectItem.Name, nameSet);
-
- // This HashSet is to guarantee uniqueness of our direct children
- // We add our own name to it to avoid class name conflicts (http://mvccontrib.codeplex.com/workitem/7153)
- var childrenNameSet = new HashSet<String>();
- childrenNameSet.Add(name);
-
- if (IsFolder(projectItem))
- {
- #>
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static class <#=EscapeID(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,
- childrenNameSet);
- }
-
- PopIndent();
- #>
- }
-
- <#+
- }
- else { #>
- <#+
- if (!settings.ExcludedStaticFileExtensions.Any(extension => projectItem.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))) {
- // if it's a Typescript file
- if (projectItem.Name.EndsWith(".ts")) {
- string tsJavascriptName = projectItem.Name.Replace(".ts", ".js");
- string minifiedName = projectItem.Name.Replace(".ts", ".min.js");
- if (AddTimestampToStaticLink(projectItem)) { #>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=tsJavascriptName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=tsJavascriptName#>");
- <#+} else {#>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=tsJavascriptName#>");
- <#+} #>
- <#+}
- // if it's a non-minified javascript file
- else if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js")) {
- string minifiedName = projectItem.Name.Replace(".js", ".min.js");
- if (AddTimestampToStaticLink(projectItem)) { #>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
- <#+} else {#>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
- <#+} #>
- <#+}
- else if (projectItem.Name.EndsWith(".css") && !projectItem.Name.EndsWith(".min.css")) {
- string minifiedName = projectItem.Name.Replace(".css", ".min.css");
- if (AddTimestampToStaticLink(projectItem)) { #>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
- <#+} else {#>
- public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
- <#+} #>
- <#+}
- else if (AddTimestampToStaticLink(projectItem)) { #>
- public static readonly string <#=name#> = Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
- <#+}
- else { #>
- public static readonly string <#=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, childrenNameSet);
- }
- }
- }
-
- int BuildClassStructureForProvidedPath(string path)
- {
- var folders = path.Split(new char[] {'/', '~'}, StringSplitOptions.RemoveEmptyEntries);
- var parentFolder = String.Empty;
- var currentPath = "~";
- foreach(var folder in folders)
- {
- currentPath += "/" + folder;
- string className = EscapeID(Sanitize(folder));
- // 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 (parentFolder == folder)
- {
- className += "_";
- }
-
- if(!virtualPathesForStaticFiles.Contains(currentPath))
- {
- virtualPathesForStaticFiles.Add(currentPath);#>
-
- [<#= GeneratedCode #>, DebuggerNonUserCode]
- public static partial class <#=className #> {
- private const string URLPATH = "<#=currentPath#>";
- public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
- public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
- <#+ } else {
- #>
-
- public static partial class <#=className #> {
- <#+ }
- PushIndent(" ");
- parentFolder = folder;
- }
- return folders.Length;
- }
-
- 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 bool IsAsyncController(CodeClass2 type)
- {
- for (; type.FullName != "System.Web.Mvc.AsyncController"; 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);
-
- // Ignore files that are not under the app root (e.g. they could be linked files)
- if (!fileFullPath.StartsWith(AppRoot, StringComparison.OrdinalIgnoreCase))
- return null;
-
- // Make a virtual path from the physical path
- return "~/" + fileFullPath.Substring(AppRoot.Length).Replace('\\', '/');
- }
-
- static string ProcessAreaOrControllerName(string name)
- {
- return settings.UseLowercaseRoutes ? name.ToLowerInvariant() : name;
- }
-
- // Return all the CodeFunction2 in the CodeElements collection
- static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass)
- {
- // Only look at regular method (e.g. ignore things like contructors)
- return codeClass.Members.OfType<CodeFunction2>()
- .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
- }
-
- // Check if the class has any explicit constructor
- static bool HasExplicitConstructor(CodeClass2 codeClass)
- {
- return codeClass.Members.OfType<CodeFunction2>().Any(
- f => !f.IsShared && 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<CodeFunction2>().Any(
- f => !f.IsShared && 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++)
- {
- try
- {
- var attrib = (CodeAttribute2)attributes.Item(i);
- if (attributeType.Split(',').Contains(attrib.FullName, StringComparer.OrdinalIgnoreCase))
- {
- return attrib;
- }
- }
- catch
- {
- // FullName can throw in some cases, so just ignore those attributes
- continue;
- }
- }
- return null;
- }
-
- static CodeAttribute2 GetAttribute(CodeClass2 type, string attributeType)
- {
- while(type != null) {
- var attribute = GetAttribute(type.Attributes, attributeType);
- if(attribute != null)
- return attribute;
- if (type.Bases.Count == 0)
- return null;
- type = (CodeClass2)type.Bases.Item(1);
- }
- return null;
- }
-
- static string UniqueFullName(CodeTypeRef codeType)
- {
- return UniqueFullName(codeType.CodeType);
- }
-
- static string UniqueFullName(CodeType codeType)
- {
- var uniqueName = codeType.FullName;
-
- // Match characters not allowed in class names.
- uniqueName = Regex.Replace(uniqueName, @"[^\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\d]", "_");
-
- // Remove duplicate '_' characters
- uniqueName = Regex.Replace(uniqueName, @"__+", "_");
-
- // Remove trailing '_' characters
- uniqueName = uniqueName.TrimEnd('_');
-
- return uniqueName;
- }
-
- // Return whether a ProjectItem is a folder and not a file
- static bool IsFolder(ProjectItem item)
- {
- return (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
- }
-
- static string MakeClassName(string ns, string classname)
- {
- return String.IsNullOrEmpty(ns) ? classname :
- String.IsNullOrEmpty(classname) ? ns : ns + "." + codeProvider.CreateEscapedIdentifier(classname);
- }
-
- static string SanitizeWithNoConflicts(string token, HashSet<string> names)
- {
- string name = Sanitize(token);
-
- while (names.Contains(name))
- {
- name += "_";
- }
-
- names.Add(name);
-
- return name;
- }
-
- 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<ControllerInfo>();
- }
-
- public string Name { get; set; }
- public HashSet<ControllerInfo> 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 (!settings.IncludeAreasToken && DefaultArea.Controllers.Any(c => c.Name == Name))
- return Name + "Area";
-
- return Name;
- }
- }
-
- public IEnumerable<ControllerInfo> GetControllers()
- {
- return Controllers.Where(c => !c.IsAbstract);
- }
-
- public IEnumerable<ControllerInfo> GetAbstractControllers()
- {
- return Controllers.Where(c => c.IsAbstract);
- }
- }
-
- // Data structure to collect data about a controller class
- class ControllerInfo
- {
- public ControllerInfo()
- {
- ActionMethods = new HashSet<ActionMethodInfo>();
- ViewsFolder = new ViewsFolderInfo();
- }
-
- public AreaInfo Area { get; set; }
- public string AreaName
- {
- get { return Area.Name ?? ""; }
- }
-
- public string T4MVCControllerFullName
- {
- get
- {
- string name = settings.HelpersPrefix;
- if (!String.IsNullOrEmpty(AreaName))
- {
- if (settings.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}/", settings.ViewsRootFolder, Name);
- else
- return String.Format("~/{0}/{1}/{2}/", settings.AreasFolder, settings.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<ActionMethodInfo> ActionMethods { get; set; }
-
- public IEnumerable<ActionMethodInfo> CustomActionMethods
- {
- get
- {
- return ActionMethods.Where(m => m.IsCustomReturnType);
- }
- }
-
- IEnumerable<ActionMethodInfo> ActionMethodsWithNoParameters
- {
- get
- {
- return ActionMethods.Where(m => m.CanBeCalledWithoutParameters);
- }
- }
-
- public IEnumerable<ActionMethodInfo> ActionMethodsUniqueWithoutParameterlessOverload
- {
- get
- {
- return ActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
- }
- }
-
- public IEnumerable<ActionMethodInfo> CustomActionMethodsUniqueWithoutParameterlessOverload
- {
- get
- {
- return CustomActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
- }
- }
-
- // Return a list of actions without duplicate names (even with multiple overloads)
- public IEnumerable<ActionMethodInfo> ActionMethodsWithUniqueNames
- {
- get
- {
- return ActionMethods.Distinct(new ActionComparer());
- }
- }
-
- // Return a list of actions without duplicate names (even with multiple overloads)
- public IEnumerable<ActionMethodInfo> CustomActionMethodsWithUniqueNames
- {
- get
- {
- return CustomActionMethods.Distinct(new ActionComparer());
- }
- }
-
- class ActionComparer : IEqualityComparer<ActionMethodInfo>
- {
- public bool Equals(ActionMethodInfo x, ActionMethodInfo y)
- {
- return x.ActionName == y.ActionName;
- }
-
- public int GetHashCode(ActionMethodInfo obj)
- {
- return obj.ActionName.GetHashCode();
- }
- }
-
- public IEnumerable<IGrouping<string, MethodParamInfo>> UniqueParameterNamesGroupedByActionName
- {
- get
- {
- var comp = new ActionParameterComparer();
- return ActionMethods
- .SelectMany(m => m.Parameters, (m, p) => new { Method = m, Parameter = p })
- .GroupBy(m => m.Method.ActionName, m => m.Parameter)
- .Select(g => new { g.Key, Items = g.Distinct(comp) })
- .SelectMany(g => g.Items, (g, i) => new { g.Key, Item = i })
- .GroupBy(i => i.Key, i => i.Item);
- }
- }
-
- class ActionParameterComparer : IEqualityComparer<MethodParamInfo>
- {
- public bool Equals(MethodParamInfo x, MethodParamInfo y)
- {
- return x.Name == y.Name;
- }
-
- public int GetHashCode(MethodParamInfo obj){
- return obj.Name.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<string, string>();
- SubFolders = new List<ViewsFolderInfo>();
- }
-
- 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);
-
- HasNonQualifiedViewNames = HasNonQualifiedViewNames | useNonQualifiedViewName;
- string virtualPath = GetVirtualPath(item);
- if (virtualPath != null)
- {
- Views[viewFieldName] = useNonQualifiedViewName ? Path.GetFileNameWithoutExtension(viewName) : virtualPath;
- }
- }
-
- public bool HasNonQualifiedViewNames { get; private set; }
- public string Name { get; set; }
- public Dictionary<string, string> Views { get; private set; }
- public List<ViewsFolderInfo> 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<MethodParamInfo>();
-
- // 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<CodeParameter2>())
- {
- // 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<CodeAttributeArgument>().FirstOrDefault(a => a.Name == "Prefix");
- if (arg != null)
- routeNameExpression = arg.Value;
- }
- }
-
- Parameters.Add(
- new MethodParamInfo()
- {
- Name = p.Name,
- RouteNameExpression = routeNameExpression,
- Type = p.Type.AsString,
- DefaultValue = p.DefaultValue
- });
- _signature += "," + p.Type.AsString;
- }
- }
-
- protected virtual CodeTypeRef ReturnTypeImpl { get { return _method.Type; } }
-
- public string Name { get { return _method.Name; } }
- public string ReturnType { get { return ReturnTypeImpl.AsString; } }
- public string ReturnTypeFullName { get { return ReturnTypeImpl.AsFullName; } }
- public string ReturnTypeUniqueName { get { return IsTaskBased ? "System_Web_Mvc_ActionResult" : UniqueFullName(ReturnTypeImpl); } }
- public bool IsPublic { get { return _method.Access == vsCMAccess.vsCMAccessPublic; } }
- public List<MethodParamInfo> Parameters { get; private set; }
- public bool CanBeCalledWithoutParameters { get; private set; }
-
- private bool IsTaskBased { get {return ReturnTypeImpl.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }
-
- // Write out all the parameters as part of a method declaration
- public void WriteFormalParameters(bool first, bool includeDefaults = false)
- {
- foreach (var p in Parameters)
- {
- if (first)
- first = false;
- else
- TT.Write(", ");
-
- TT.Write(p.Type + " " + p.Name);
- if(includeDefaults && !string.IsNullOrEmpty(p.DefaultValue))
- TT.Write(" = " + p.DefaultValue);
- }
- }
-
- // 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(", ");
-
- if(!string.IsNullOrEmpty(p.DefaultValue))
- TT.Write(p.DefaultValue);
- else
- {
- 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, CodeClass2 controller, CodeTypeRef asyncType = null)
- : base(method)
- {
- if(asyncType != null)
- {
- // Remove the Async from the end of the name to match the actual Action routing would use.
- // This also separates the Action Calls from the implementation
- _actionName = method.Name.Remove(method.Name.Length - 5);
- _returnType = asyncType;
- }
- // Normally, the action name is the method name. But if there is an [ActionName] on
- // the method, get the expression from that instead
- ActionNameValueExpression = '"' + ActionName + '"';
- var attrib = GetAttribute(method.Attributes, "System.Web.Mvc.ActionNameAttribute");
- if (attrib != null)
- {
- var arg = (CodeAttributeArgument)attrib.Arguments.Item(1);
- ActionNameValueExpression = arg.Value;
- }
-
- if (GetAttribute(method.Attributes, settings.AttributeIndicatingHttps) != null || GetAttribute(controller, settings.AttributeIndicatingHttps) != null)
- {
- ActionUrlHttps = true;
- }
- }
-
- string _actionName;
- CodeTypeRef _returnType;
- protected override CodeTypeRef ReturnTypeImpl { get { return _returnType ?? base.ReturnTypeImpl; } }
-
- public string ActionName { get { return _actionName ?? base.Name; } }
- public string ActionNameValueExpression { get; set; }
- public bool ActionUrlHttps {get; set; }
-
- public bool IsCustomReturnType { get { return _returnType != null; } }
-
- }
-
- // Data about an ActionResult derived type
- class ResultTypeInfo
- {
- CodeTypeRef _codeType;
- public ResultTypeInfo(CodeTypeRef codeType)
- {
- _codeType = codeType;
-
- // Use the constructor with the least number of parameters
- var ctor = _codeType.CodeType.Members.OfType<CodeFunction2>()
- .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
- .OrderBy(f => f.Parameters.Count)
- .FirstOrDefault();
- Constructor = new FunctionInfo(ctor);
- }
-
- public string Name { get { return _codeType.AsString; } }
- public string FullName { get { return this.IsTaskBased ? "System.Web.Mvc.ActionResult" : _codeType.AsFullName; } }
- public string UniqueName { get { return this.IsTaskBased ? "System_Web_Mvc_ActionResult" : UniqueFullName(_codeType); } }
- public FunctionInfo Constructor { get; set; }
- public IEnumerable<FunctionInfo> AbstractMethods
- {
- get
- {
- return _codeType.CodeType.Members.OfType<CodeFunction2>().Where(
- f => f.MustImplement).Select(f => new FunctionInfo(f));
- }
- }
-
- private bool IsTaskBased { get {return _codeType.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }
- }
-
- class MethodParamInfo
- {
- public string Name { get; set; }
- public string RouteNameExpression { get; set; }
- public string Type { get; set; }
- public string DefaultValue { get; set; }
- }
-
- class MvcSettings : XmlSettings
- {
- public static MvcSettings Load(ITextTemplatingEngineHost host)
- {
- return Load<MvcSettings>(host);
- }
-
- public MvcSettings()
- {
- this.T4MVCNamespace = "T4MVC";
- this.HelpersPrefix = "MVC";
- this.ReferencedNamespaces = new XmlStringArray(new string[] {
- }, "Namespace");
- this.AreasFolder = "Areas";
- this.PortableAreas = new XmlStringArray(new string[] {
- }, "Area");
- this.IncludeAreasToken = false;
- this.ControllersFolder = "Controllers";
- this.ViewsRootFolder = "Views";
- this.NonQualifiedViewFolders = new XmlStringArray(new string[] {
- "DisplayTemplates",
- "EditorTemplates"
- }, "ViewFolder");
- this.GenerateActionResultInterface = true;
- this.GenerateParamsAsConstantsForActionMethods = false;
- this.GenerateParamsForActionMethods = true;
- this.SupportAsyncActions = false;
- this.UseLowercaseRoutes = false;
- this.LinksNamespace = "Links";
- this.AddTimestampToStaticLinks = false;
- this.StaticFilesFolders = new XmlStringArray(new string[] {
- "Scripts",
- "Content",
- }, "FileFolder");
- this.ExcludedStaticFileExtensions = new XmlStringArray(new string[] {
- ".cs",
- ".cshtml",
- ".aspx",
- ".ascx"
- }, "Extension");
- this.ExcludedViewExtensions = new XmlStringArray(new string[] {
- ".master",
- ".js",
- ".css"
- }, "Extension");
- this.AttributeIndicatingHttps = "System.Web.Mvc.RequireHttpsAttribute";
- this.GenerateSecureLinksInDebugMode = false;
- this.ParamsPropertySuffix = "Params";
- this.ExplicitHtmlHelpersForPartialsFormat = "Render{0}";
- this.SplitIntoMultipleFiles = true;
-
- }
-
- [System.ComponentModel.Description("The namespace used by some of T4MVC's generated code")]
- public string T4MVCNamespace { get; set; }
-
- [System.ComponentModel.Description("The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID)")]
- public string HelpersPrefix { get; set; }
-
- [System.ComponentModel.Description("Namespaces to be referenced by the generated code")]
- public XmlStringArray ReferencedNamespaces { get; set; }
-
- [System.ComponentModel.Description("The folder under the project that contains the areas")]
- public string AreasFolder { get; set; }
-
- [System.ComponentModel.Description("You can list folders containing portable areas here")]
- public IEnumerable<string> PortableAreas { get; set; }
-
- [System.ComponentModel.Description("Choose whether you want to include an 'Areas' token when referring to areas.\r\ne.g. Assume the Area is called Blog and the Controller is Post:\r\n- When false use MVC.Blog.Post.etc...\r\n- When true use MVC.Areas.Blog.Post.etc...")]
- public bool IncludeAreasToken { get; set; }
-
- [System.ComponentModel.Description("The folder under the project that contains the controllers")]
- public string ControllersFolder { get; set; }
-
- [System.ComponentModel.Description("The folder under the project that contains the views")]
- public string ViewsRootFolder { get; set; }
-
- [System.ComponentModel.Description("Views in DisplayTemplates and EditorTemplates folders shouldn't be fully qualifed\r\nas it breaks the templated helper code")]
- public XmlStringArray NonQualifiedViewFolders { get; set; }
-
- [System.ComponentModel.Description("If true, the T4MVC action result interface will be generated\r\nIf false, the namespace of the interface must be referenced in the 'ReferencedNamespaces' setting")]
- public bool GenerateActionResultInterface { get; set; }
-
- [System.ComponentModel.Description("If true, [new] overrides will be created for async actions on AsyncControllers\r\nThis breaks the Go To Definition function for async actions.")]
- public bool SupportAsyncActions { get; set; }
-
- [System.ComponentModel.Description("If true, use lower case tokens in routes for the area, controller and action names")]
- public bool UseLowercaseRoutes { get; set; }
-
- [System.ComponentModel.Description("The namespace that the links are generated in (e.g. \"Links\", as in Links.Content.nerd_jpg)")]
- public string LinksNamespace { get; set; }
-
- [System.ComponentModel.Description("If true, links to static files include a query string containing the file's last change time.\r\nThis way, when the static file changes, the link changes and guarantees that the client will re-request the resource.\r\ne.g. when true, the link looks like: \"/Content/nerd.jpg?2009-09-04T12:25:48\"\r\nSee http://mvccontrib.codeplex.com/workitem/7163 for potential issues with this feature")]
- public bool AddTimestampToStaticLinks { get; set; }
-
- [System.ComponentModel.Description("Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js)")]
- public XmlStringArray StaticFilesFolders { get; set; }
-
- [System.ComponentModel.Description("If true, static file helpers are generated for all view folders. See https://t4mvc.codeplex.com/discussions/445358")]
- public bool AddAllViewsFoldersToStaticFilesFolders { get; set; }
-
- [System.ComponentModel.Description("Static files to exclude from the generated links")]
- public XmlStringArray ExcludedStaticFileExtensions { get; set; }
-
- [System.ComponentModel.Description("Files to exclude from the generated views")]
- public XmlStringArray ExcludedViewExtensions { get; set; }
-
- [System.ComponentModel.Description("When creating links with T4MVC, it can force them to HTTPS if the action method you are linking to requires Http.")]
- public string AttributeIndicatingHttps { get; set; }
- public bool GenerateSecureLinksInDebugMode { get; set; }
-
- [System.ComponentModel.Description("Set this to false to omit the generation of parameters for action methods.")]
- public bool GenerateParamsForActionMethods { get; set; }
-
- [System.ComponentModel.Description("Set this to true to omit the generation of parameters for action methods as constants. Choose this or GenerateParamsForActionMethods.")]
- public bool GenerateParamsAsConstantsForActionMethods { get; set; }
-
- [System.ComponentModel.Description("The suffix added to action method names for the property containing the parameters, for example ImportParams\r\nfor the Import action method.")]
- public string ParamsPropertySuffix { get; set; }
-
- [System.ComponentModel.Description("create explicit HtmlHelpers for rendering partials")]
- public bool ExplicitHtmlHelpersForPartials { get; set; }
- public string ExplicitHtmlHelpersForPartialsFormat { get; set; }
-
- [System.ComponentModel.Description("If true,the template output will be split into multiple files.")]
- public bool SplitIntoMultipleFiles { get; set; }
-
- }
-
- /*
- XmlSettings base classes, if you need to modify the T4MVC properties edit the MvcSettings Class Above
- */
-
- /// Base XmlSettings class, responsible for reading/writing the settigns file contents, all settings other
- /// than string convertable types should decend from this class
- abstract class XmlSettingsBase
- {
- protected XmlSettingsBase()
- {
- this.NeedsSave = true;
- }
-
- protected virtual void Init()
- {
- }
-
- protected bool SaveAsChild { get; private set; }
-
- protected bool NeedsSave { get; private set; }
-
- protected static void SetSaveAsChild(XmlSettingsBase settings, bool value)
- {
- settings.SaveAsChild = value;
- }
-
- protected static void SetNeedsSave(XmlSettingsBase settings, bool value)
- {
- settings.NeedsSave = value;
- }
-
- protected static void WriteCommentedProperty(System.Xml.XmlWriter writer, string name)
- {
- writer.WriteComment(string.Concat("<", name, "></", name, ">"));
- }
-
- protected static void WritePropertyDesc(System.Xml.XmlWriter writer, System.ComponentModel.PropertyDescriptor property)
- {
- var desc = property.Attributes.OfType<System.ComponentModel.DescriptionAttribute>().FirstOrDefault();
- if(desc != null)
- {
- writer.WriteComment(desc.Description);
- }
- }
-
- protected virtual void Load(System.Xml.Linq.XElement xml)
- {
- this.NeedsSave = false;
- int matched = 0;
- int read = 0;
- foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
- {
- object pvalue;
- if(typeof(XmlSettingsBase).IsAssignableFrom(property.PropertyType) || (((pvalue = property.GetValue(this)) != null) && typeof(XmlSettingsBase).IsAssignableFrom(pvalue.GetType())))
- {
- read++;
- var value = xml.Element(property.Name);
- if(value != null)
- {
- var settings = (XmlSettingsBase)property.GetValue(this);
- settings.Load(value);
- if(!settings.NeedsSave)
- matched++;
- settings.SaveAsChild = true;
- }
- }
- else if(!property.IsReadOnly)
- {
- read++;
- var value = xml.Element(property.Name);
- if(value != null)
- {
- if(property.Converter.CanConvertFrom(typeof(string)))
- {
- matched++;
- property.SetValue(this, property.Converter.ConvertFromString(value.Value));
- }
- else
- {
- System.Reflection.MethodBase parser = property.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
- if(parser == null)
- parser = property.PropertyType.GetConstructor(new Type[] { typeof(string) });
-
- if(parser != null)
- {
- matched++;
- property.SetValue(this, parser.Invoke(null, new Object[] { value.Value }));
- }
- }
- }
- }
- }
- this.NeedsSave = this.NeedsSave || (matched < read);
- }
-
- protected virtual void Save(System.Xml.XmlWriter writer)
- {
- foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
- {
- var value = property.GetValue(this);
- WritePropertyDesc(writer, property);
- if(value != null)
- {
- if(typeof(XmlSettingsBase).IsAssignableFrom(value.GetType()))
- {
- var settings = (XmlSettingsBase)property.GetValue(this);
- if((settings != null) && settings.SaveAsChild)
- {
- writer.WriteStartElement(property.Name);
- settings.Save(writer);
- writer.WriteEndElement();
- }
- } else if(!property.IsReadOnly)
- {
- writer.WriteElementString(property.Name, property.Converter.ConvertToString(value));
- }
- }
- else
- {
- WriteCommentedProperty(writer, property.Name);
- }
- }
- }
- }
-
- /// Custom class to allow string arrays to be read and written to/from settings
- class XmlStringArray : XmlSettingsBase, IEnumerable<string>
- {
- public XmlStringArray(IEnumerable<string> items, string name)
- {
- this._items = items;
- this._name = name;
- SetSaveAsChild(this, true);
- }
-
- string _name;
- IEnumerable<string> _items;
-
- protected override void Load(System.Xml.Linq.XElement xml)
- {
- var items = new List<string>();
- foreach(var item in xml.Elements(this._name))
- {
- items.Add(item.Value);
- }
- this._items = items;
- SetNeedsSave(this, false);
- }
-
- protected override void Save(System.Xml.XmlWriter writer)
- {
- if(this._items == null || !this._items.Any())
- {
- WriteCommentedProperty(writer, this._name);
- return;
- }
-
- foreach(var item in this._items)
- {
- writer.WriteElementString(this._name, item);
- }
- }
-
- public IEnumerator<string> GetEnumerator()
- {
- return this._items.GetEnumerator();
- }
-
- System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
- {
- return this.GetEnumerator();
- }
- }
-
- /// This is the base class for the standard settings, the main settigns class should inherit from this
- /// one since it provides the methods to interact with the T4 system and EnvDTE. Sub-properties can
- /// just inherit from XmlSettingsBase.
- abstract class XmlSettings : XmlSettingsBase
- {
- protected static T Load<T>(ITextTemplatingEngineHost host) where T : XmlSettings, new()
- {
- T settings = new T();
- settings.Init(host);
- return settings;
- }
-
- void Init(ITextTemplatingEngineHost host)
- {
-
- this.TemplateFile = Path.GetFileName(host.TemplateFile);
- this.TemplateFolder = Path.GetDirectoryName(host.TemplateFile);
-
- // Get the DTE service from the host
- var serviceProvider = host as IServiceProvider;
- if (serviceProvider != null)
- {
- this.DTE = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
- }
-
- // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
- if (this.DTE == null)
- {
- throw new Exception("T4Build can only execute through the Visual Studio host");
- }
-
- this.ProjectItem = this.DTE.Solution.FindProjectItem(host.TemplateFile);
-
- // If the .tt file is not opened, open it
- if (this.ProjectItem.Document == null)
- this.ProjectItem.Open(EnvDTE.Constants.vsViewKindCode);
-
- this.Project = this.ProjectItem.ContainingProject;
-
- if (Project == null)
- {
- throw new Exception("Could not find the VS Project containing the T4 file.");
- }
-
- this.Load();
- this.Init();
- }
-
- public string TemplateFile { get; private set; }
-
- public string TemplateFolder { get; private set; }
-
- public DTE DTE { get; private set; }
-
- public ProjectItem ProjectItem { get; private set; }
-
- public Project Project { get; private set; }
-
- ProjectItem FindProjectItemRecursive(ProjectItems items, string name)
- {
- if(items == null)
- return null;
-
- foreach(ProjectItem item in items)
- {
- if(item.Name.Equals(name) || item.Name.StartsWith(name + "."))
- return item;
- var found = FindProjectItemRecursive(item.ProjectItems, name);
- if(found != null)
- return found;
- }
-
- return null;
- }
-
- protected ProjectItem FindProjectItem(string name)
- {
- return this.FindProjectItemRecursive(this.Project.ProjectItems, name);
- }
-
- protected string SettingsFile
- {
- get
- {
- return Path.Combine(this.TemplateFolder, string.Concat(this.TemplateFile, ".settings.xml"));
- }
- }
-
- void Load()
- {
- if(System.IO.File.Exists(this.SettingsFile))
- try
- {
- this.Load(System.Xml.Linq.XElement.Load(this.SettingsFile));
- } catch { throw; }
- }
-
- public void SaveChanges(Manager manager)
- {
- // Avoid saving if we dont need to;
- if(!this.NeedsSave)
- return;
-
- if(manager.FileOkToWrite(this.SettingsFile))
- {
- var settings = new System.Xml.XmlWriterSettings
- {
- Indent = true
- };
- using(var writer = System.Xml.XmlWriter.Create(this.SettingsFile, settings))
- {
- writer.WriteStartDocument();
- writer.WriteStartElement(this.GetType().Name);
- this.Save(writer);
- writer.WriteEndElement();
- writer.WriteEndDocument();
- }
-
- var item = this.ProjectItem.Collection.AddFromFile(this.SettingsFile);
- item.Properties.Item("ItemType").Value = "None";
- } else
- TT.Error("Cannot save settings file! " + this.SettingsFile);
- }
- }
-
- /*
- 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<Block> files = new List<Block>();
- private Block footer = new Block();
- private Block header = new Block();
- private ITextTemplatingEngineHost host;
- private StringBuilder template;
- protected List<String> generatedFileNames = new List<String>();
-
- public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template)
- {
- return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template);
- }
-
- public virtual bool FileOkToWrite(String fileName)
- {
- return true;
- }
-
- 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<String> checkOutAction;
- private Action<IEnumerable<String>> projectSyncAction;
- private IVsQueryEditQuerySave2 queryEditSave;
-
- 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));
- }
-
- public override bool FileOkToWrite(String fileName)
- {
- CheckoutFileIfRequired(fileName);
- return base.FileOkToWrite(fileName);
- }
-
- 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<String> keepFileNames) => ProjectSync(templateProjectItem, keepFileNames);
- queryEditSave = (IVsQueryEditQuerySave2)hostServiceProvider.GetService(typeof(SVsQueryEditQuerySave));
- }
-
- private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<String> keepFileNames)
- {
- var keepFileNameSet = new HashSet<String>(keepFileNames);
- var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>();
- 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)
- {
- if (queryEditSave != null)
- {
- uint pfEditVerdict;
- queryEditSave.QuerySaveFile(fileName, 0, null, out pfEditVerdict);
- }
- else
- {
- var sc = dte.SourceControl;
- if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
- checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
- }
- }
- }
- }
-
- /*
- End of Manager.tt
- */
- #>