PageRenderTime 47ms CodeModel.GetById 7ms RepoModel.GetById 0ms app.codeStats 0ms

/Web/T4MVC.tt

https://bitbucket.org/mgreuel/noblog
Unknown | 2473 lines | 2129 code | 344 blank | 0 comment | 0 complexity | 04e577062b8954c3c566f7e74749498f MD5 | raw file
  1. <#
  2. /*
  3. T4MVC Version 3.8.2
  4. Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC
  5. Discuss on StackOverflow or on Codeplex (https://t4mvc.codeplex.com/discussions)
  6. T4MVC is part of the MvcContrib project, but in a different Codeplex location (http://t4mvc.codeplex.com)
  7. Maintained by David Ebbo, with much feedback from the MVC community (thanks all!)
  8. david.ebbo@microsoft.com
  9. http://twitter.com/davidebbo
  10. http://blog.davidebbo.com/ (previously: http://blogs.msdn.com/davidebb)
  11. Related blog posts: http://blogs.msdn.com/davidebb/archive/tags/T4MVC/default.aspx
  12. Please use in accordance to the MvcContrib license (http://mvccontrib.codeplex.com/license)
  13. */
  14. #>
  15. <#@ template language="C#" debug="true" hostspecific="true" #>
  16. <#@ assembly name="System.Core" #>
  17. <#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
  18. <#@ assembly name="EnvDTE" #>
  19. <#@ assembly name="EnvDTE80" #>
  20. <#@ assembly name="VSLangProj" #>
  21. <#@ assembly name="System.Xml" #>
  22. <#@ assembly name="System.Xml.Linq" #>
  23. <#@ import namespace="System.Collections.Generic" #>
  24. <#@ import namespace="System.IO" #>
  25. <#@ import namespace="System.Linq" #>
  26. <#@ import namespace="System.Text" #>
  27. <#@ import namespace="System.Text.RegularExpressions" #>
  28. <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
  29. <#@ import namespace="EnvDTE" #>
  30. <#@ import namespace="EnvDTE80" #>
  31. <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
  32. <# // To debug, uncomment the next two lines !!
  33. // System.Diagnostics.Debugger.Launch();
  34. // System.Diagnostics.Debugger.Break();
  35. #>
  36. <#settings=MvcSettings.Load(Host);#>
  37. <#PrepareDataToRender(this); #>
  38. <#var manager = Manager.Create(Host, GenerationEnvironment); #>
  39. <#manager.StartHeader(); #>// <auto-generated />
  40. // This file was generated by a T4 template.
  41. // Don't change it directly as your change would get overwritten. Instead, make changes
  42. // to the .tt file (i.e. the T4 template) and save it to regenerate this file.
  43. // Make sure the compiler doesn't complain about missing Xml comments
  44. #pragma warning disable 1591
  45. #region T4MVC
  46. using System;
  47. using System.Diagnostics;
  48. using System.CodeDom.Compiler;
  49. using System.Collections.Generic;
  50. using System.Linq;
  51. using System.Runtime.CompilerServices;
  52. using System.Threading.Tasks;
  53. using System.Web;
  54. using System.Web.Hosting;
  55. using System.Web.Mvc;
  56. using System.Web.Mvc.Ajax;
  57. using System.Web.Mvc.Html;
  58. using System.Web.Routing;
  59. using <#=settings.T4MVCNamespace #>;
  60. <#foreach (var referencedNamespace in settings.ReferencedNamespaces) { #>
  61. using <#=referencedNamespace #>;
  62. <#} #>
  63. <#manager.EndBlock(); #>
  64. [<#= GeneratedCode #>, DebuggerNonUserCode]
  65. public static partial class <#=settings.HelpersPrefix #>
  66. {
  67. <#if (settings.IncludeAreasToken) { #>
  68. public static class Areas
  69. {
  70. <#} #>
  71. <#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
  72. static readonly <#=area.Name #>Class s_<#=area.Name #> = new <#=area.Name #>Class();
  73. public static <#=area.Name #>Class <#=EscapeID(area.Namespace) #> { get { return s_<#=area.Name #>; } }
  74. <#} #>
  75. <#if (settings.IncludeAreasToken) { #>
  76. }
  77. <#} #>
  78. <#foreach (var controller in DefaultArea.GetControllers()) { #>
  79. public static <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
  80. <#} #>
  81. }
  82. namespace <#=settings.T4MVCNamespace #>
  83. {
  84. <#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
  85. [<#= GeneratedCode #>, DebuggerNonUserCode]
  86. public class <#=area.Name #>Class
  87. {
  88. public readonly string Name = "<#=ProcessAreaOrControllerName(area.Name) #>";
  89. <#foreach (var controller in area.GetControllers()) { #>
  90. public <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
  91. <#} #>
  92. }
  93. <#} #>
  94. }
  95. namespace <#=settings.T4MVCNamespace #>
  96. {
  97. [<#= GeneratedCode #>, DebuggerNonUserCode]
  98. public class Dummy
  99. {
  100. private Dummy() { }
  101. public static Dummy Instance = new Dummy();
  102. }
  103. }
  104. <#foreach (var resultType in ResultTypes.Values) { #>
  105. [<#= GeneratedCode #>, DebuggerNonUserCode]
  106. internal partial class T4MVC_<#=resultType.UniqueName #> : <#=resultType.FullName #>, IT4MVCActionResult
  107. {
  108. public T4MVC_<#=resultType.UniqueName #>(string area, string controller, string action, string protocol = null): base(<#resultType.Constructor.WriteNonEmptyParameterValues(true); #>)
  109. {
  110. this.InitMVCT4Result(area, controller, action, protocol);
  111. }
  112. <#foreach (var method in resultType.AbstractMethods) { #>
  113. <#=method.IsPublic ? "public" : "protected" #> override <#=method.ReturnType#> <#=method.Name #>(<#method.WriteFormalParameters(true); #>) {<# if(method.ReturnType != "void") {#> return default(<#=method.ReturnType#>); <#} #> }
  114. <#} #>
  115. public string Controller { get; set; }
  116. public string Action { get; set; }
  117. public string Protocol { get; set; }
  118. public RouteValueDictionary RouteValueDictionary { get; set; }
  119. }
  120. <#} #>
  121. namespace <#=settings.LinksNamespace #>
  122. {
  123. <#
  124. foreach (string folder in settings.StaticFilesFolders.Concat(GetStaticFilesViewFolders())) {
  125. ProcessStaticFiles(Project, folder);
  126. }
  127. #>
  128. [<#= GeneratedCode #>, DebuggerNonUserCode]
  129. public static partial class Bundles
  130. {
  131. [<#= GeneratedCode #>, DebuggerNonUserCode]
  132. public static partial class Scripts {}
  133. [<#= GeneratedCode #>, DebuggerNonUserCode]
  134. public static partial class Styles {}
  135. }
  136. }
  137. <#
  138. RenderAdditionalCode();
  139. #>
  140. <#foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #>
  141. <#manager.StartNewFile(controller.GeneratedFileName); #>
  142. namespace <#=controller.Namespace #>
  143. {
  144. [<#= GeneratedCode #>, DebuggerNonUserCode]
  145. public partial class <#=controller.ClassName #>
  146. {
  147. protected <#=controller.ClassName #>() { }
  148. }
  149. }
  150. <#manager.EndBlock(); #>
  151. <#} #>
  152. <#foreach (var controller in GetControllers()) { #>
  153. <#
  154. // Don't generate the file at all if the existing one is up to date
  155. // NOTE: disable this optimization since it doesn't catch view changes! It can be re-enabled later if smarter change detection is added
  156. //if (controller.GeneratedCodeIsUpToDate) {
  157. // manager.KeepGeneratedFile(controller.GeneratedFileName);
  158. // continue;
  159. //}
  160. #>
  161. <#manager.StartNewFile(controller.GeneratedFileName); #>
  162. <#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
  163. namespace <#=controller.Namespace #>
  164. {
  165. <#} #>
  166. public <#if (!controller.NotRealController) { #>partial <#} #>class <#=controller.ClassName #>
  167. {
  168. <#if (!controller.NotRealController) { #>
  169. <#if (!controller.HasExplicitConstructor) { #>
  170. [<#= GeneratedCode #>, DebuggerNonUserCode]
  171. public <#=controller.ClassName #>() { }
  172. <#} #>
  173. [<#= GeneratedCode #>, DebuggerNonUserCode]
  174. protected <#=controller.ClassName #>(Dummy d) { }
  175. [<#= GeneratedCode #>, DebuggerNonUserCode]
  176. protected RedirectToRouteResult RedirectToAction(ActionResult result)
  177. {
  178. var callInfo = result.GetT4MVCResult();
  179. return RedirectToRoute(callInfo.RouteValueDictionary);
  180. }
  181. [<#= GeneratedCode #>, DebuggerNonUserCode]
  182. protected RedirectToRouteResult RedirectToAction(Task<ActionResult> taskResult)
  183. {
  184. return RedirectToAction(taskResult.Result);
  185. }
  186. [<#= GeneratedCode #>, DebuggerNonUserCode]
  187. protected RedirectToRouteResult RedirectToActionPermanent(ActionResult result)
  188. {
  189. var callInfo = result.GetT4MVCResult();
  190. return RedirectToRoutePermanent(callInfo.RouteValueDictionary);
  191. }
  192. [<#= GeneratedCode #>, DebuggerNonUserCode]
  193. protected RedirectToRouteResult RedirectToActionPermanent(Task<ActionResult> taskResult)
  194. {
  195. return RedirectToActionPermanent(taskResult.Result);
  196. }
  197. <#foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #>
  198. [NonAction]
  199. [<#= GeneratedCode #>, DebuggerNonUserCode]
  200. public virtual <#=method.ReturnTypeFullName #> <#=method.Name #>()
  201. {
  202. <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
  203. var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
  204. return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
  205. <#} else { #>
  206. return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
  207. <#} #>
  208. }
  209. <#} #>
  210. <#foreach (var method in controller.CustomActionMethodsUniqueWithoutParameterlessOverload) { #>
  211. [NonAction]
  212. [<#= GeneratedCode #>, DebuggerNonUserCode]
  213. public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>()
  214. {
  215. return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
  216. }
  217. <#} #>
  218. <#foreach (var method in controller.CustomActionMethods) { #>
  219. [NonAction]
  220. [<#= GeneratedCode #>, DebuggerNonUserCode]
  221. public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>(<#method.WriteFormalParameters(true, true); #>)
  222. {
  223. var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
  224. <#if (method.Parameters.Count > 0) { #>
  225. <#foreach (var p in method.Parameters) { #>
  226. ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
  227. <#} #>
  228. <#}#>
  229. <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
  230. return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
  231. <#} else { #>
  232. return callInfo;
  233. <#} #>
  234. }
  235. <#} #>
  236. [<#= GeneratedCode #>, DebuggerNonUserCode]
  237. public <#=controller.ClassName #> Actions { get { return <#=controller.T4MVCControllerFullName #>; } }
  238. [<#= GeneratedCode #>]
  239. public readonly string Area = "<#=ProcessAreaOrControllerName(controller.AreaName) #>";
  240. [<#= GeneratedCode #>]
  241. public readonly string Name = "<#=ProcessAreaOrControllerName(controller.Name) #>";
  242. [<#= GeneratedCode #>]
  243. public const string NameConst = "<#=ProcessAreaOrControllerName(controller.Name) #>";
  244. static readonly ActionNamesClass s_actions = new ActionNamesClass();
  245. [<#= GeneratedCode #>, DebuggerNonUserCode]
  246. public ActionNamesClass ActionNames { get { return s_actions; } }
  247. [<#= GeneratedCode #>, DebuggerNonUserCode]
  248. public class ActionNamesClass
  249. {
  250. <#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
  251. <# if (settings.UseLowercaseRoutes) { #>
  252. public readonly string <#=method.ActionName #> = (<#=method.ActionNameValueExpression #>).ToLowerInvariant();
  253. <# } else { #>
  254. public readonly string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
  255. <# }
  256. } #>
  257. }
  258. <#
  259. // Issue: we can't honor UseLowercaseRoutes here because ToLowerInvariant() is not valid in constants!
  260. if (!settings.UseLowercaseRoutes) { #>
  261. [<#= GeneratedCode #>, DebuggerNonUserCode]
  262. public class ActionNameConstants
  263. {
  264. <#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
  265. public const string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
  266. <#
  267. } #>
  268. }
  269. <#}
  270. } #>
  271. <#if (settings.GenerateParamsForActionMethods && !settings.GenerateParamsAsConstantsForActionMethods){
  272. foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
  273. static readonly ActionParamsClass_<#=group.Key #> s_params_<#=group.Key #> = new ActionParamsClass_<#=group.Key #>();
  274. [<#= GeneratedCode #>, DebuggerNonUserCode]
  275. public ActionParamsClass_<#=group.Key #> <#=group.Key + settings.ParamsPropertySuffix #> { get { return s_params_<#=group.Key #>; } }
  276. [<#= GeneratedCode #>, DebuggerNonUserCode]
  277. public class ActionParamsClass_<#=group.Key #>
  278. {
  279. <#foreach (var param in group) { #>
  280. <# if (settings.UseLowercaseRoutes) { #>
  281. public readonly string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
  282. <# } else { #>
  283. public readonly string <#=param.Name #> = <#=param.RouteNameExpression #>;
  284. <# }
  285. } #>
  286. }
  287. <# } #>
  288. <#} #>
  289. <#if (settings.GenerateParamsAsConstantsForActionMethods){
  290. foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
  291. [<#= GeneratedCode #>, DebuggerNonUserCode]
  292. public class <#=group.Key + settings.ParamsPropertySuffix#>
  293. {
  294. <#foreach (var param in group) { #>
  295. <# if (settings.UseLowercaseRoutes) { #>
  296. public const string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
  297. <# } else { #>
  298. public const string <#=param.Name #> = <#=param.RouteNameExpression #>;
  299. <# }
  300. } #>
  301. }
  302. <# } #>
  303. <#} #>
  304. static readonly ViewsClass s_views = new ViewsClass();
  305. [<#= GeneratedCode #>, DebuggerNonUserCode]
  306. public ViewsClass Views { get { return s_views; } }
  307. [<#= GeneratedCode #>, DebuggerNonUserCode]
  308. public class ViewsClass
  309. {
  310. <#RenderControllerViews(controller);#>
  311. }
  312. }
  313. <#if (!controller.NotRealController) { #>
  314. [<#= GeneratedCode #>, DebuggerNonUserCode]
  315. public partial class <#=controller.DerivedClassName #> : <#=controller.FullClassName #>
  316. {
  317. public <#=controller.DerivedClassName #>() : base(Dummy.Instance) { }
  318. <#foreach (var method in controller.ActionMethods.Where(m => !m.IsCustomReturnType)) { #>
  319. [NonAction]
  320. partial void <#=method.Name #>Override(T4MVC_<#=method.ReturnTypeUniqueName #> callInfo<#if (method.Parameters.Count > 0) { #>, <#method.WriteFormalParameters(true); #><#}#>);
  321. [NonAction]
  322. public override <#=method.ReturnTypeFullName #> <#=method.Name #>(<#method.WriteFormalParameters(true); #>)
  323. {
  324. var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
  325. <#if (method.Parameters.Count > 0) { #>
  326. <#foreach (var p in method.Parameters) { #>
  327. ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
  328. <#} #>
  329. <#}#>
  330. <#=method.Name #>Override(callInfo<#if (method.Parameters.Count > 0) { #><#foreach (var p in method.Parameters) { #>, <#=p.Name #><#}}#>);
  331. <#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
  332. return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
  333. <#} else { #>
  334. return callInfo;
  335. <#} #>
  336. }
  337. <#} #>
  338. }
  339. <#} #>
  340. <#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
  341. }
  342. <#} #>
  343. <#manager.EndBlock(); #>
  344. <#} #>
  345. <# if (settings.ExplicitHtmlHelpersForPartials) {
  346. manager.StartNewFile("T4MVC.ExplicitExtensions.cs"); #>
  347. namespace System.Web.Mvc {
  348. [<#= GeneratedCode #>]
  349. public static class HtmlHelpersForExplicitPartials
  350. {
  351. <#
  352. foreach(var partialView in GetPartials()) {
  353. string partialName = partialView.Key;
  354. string partialPath = partialView.Value;
  355. string partialRenderMethod = string.Format(settings.ExplicitHtmlHelpersForPartialsFormat, partialName);
  356. #>
  357. ///<summary>
  358. ///Render the <b><#= partialName #></b> partial.
  359. ///</summary>
  360. public static void <#= partialRenderMethod #>(this HtmlHelper html) {
  361. html.RenderPartial("<#= partialPath #>");
  362. }
  363. ///<summary>
  364. ///Render the <b><#= partialName #></b> partial.
  365. ///</summary>
  366. public static void <#= partialRenderMethod #>(this HtmlHelper html, object model) {
  367. html.RenderPartial("<#= partialPath #>", model);
  368. }
  369. <# } #>
  370. }
  371. }
  372. <# manager.EndBlock(); #>
  373. <# } #>
  374. <#manager.StartFooter(); #>
  375. #endregion T4MVC
  376. #pragma warning restore 1591
  377. <#manager.EndBlock(); #>
  378. <#settings.SaveChanges(manager); #>
  379. <#manager.Process(settings.SplitIntoMultipleFiles); #>
  380. <#@ Include File="T4MVC.tt.hooks.t4" #>
  381. <#+
  382. static MvcSettings settings;
  383. const string ControllerSuffix = "Controller";
  384. static DTE Dte;
  385. static Project Project;
  386. static string AppRoot;
  387. static HashSet<AreaInfo> Areas;
  388. static AreaInfo DefaultArea;
  389. static Dictionary<string, ResultTypeInfo> ResultTypes;
  390. static TextTransformation TT;
  391. static string T4FileName;
  392. static string T4Folder;
  393. static string GeneratedCode = @"GeneratedCode(""T4MVC"", ""2.0"")";
  394. static Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
  395. List<string> virtualPathesForStaticFiles = new List<string>();
  396. IEnumerable<ControllerInfo> GetControllers()
  397. {
  398. var controllers = new List<ControllerInfo>();
  399. foreach (var area in Areas)
  400. {
  401. controllers.AddRange(area.GetControllers());
  402. }
  403. return controllers;
  404. }
  405. IEnumerable<ControllerInfo> GetAbstractControllers()
  406. {
  407. var controllers = new List<ControllerInfo>();
  408. foreach (var area in Areas)
  409. {
  410. controllers.AddRange(area.GetAbstractControllers());
  411. }
  412. return controllers;
  413. }
  414. IDictionary<string, string> GetPartials()
  415. {
  416. var parts = GetControllers()
  417. .Select(m => m.ViewsFolder)
  418. .SelectMany(m => m.Views)
  419. .Where(m => IsPartialView(m.Value));
  420. var partsDic = new Dictionary<string, KeyValuePair<string, string>>();
  421. foreach(var part in parts)
  422. {
  423. string viewName = Sanitize(part.Key);
  424. // Check if we already have a partial view by that name (e.g. if two Views folders have the same ascx)
  425. int keyCollisionCount = partsDic.Where(m => m.Key == viewName || m.Value.Key == viewName).Count();
  426. if (keyCollisionCount > 0)
  427. {
  428. // Append a numbered suffix to avoid the conflict
  429. partsDic.Add(viewName + keyCollisionCount.ToString(), part);
  430. }
  431. else
  432. {
  433. partsDic.Add(viewName, part);
  434. }
  435. }
  436. return partsDic.ToDictionary(k => k.Key, v => v.Value.Value);
  437. }
  438. bool IsPartialView(string viewFilePath)
  439. {
  440. string viewFileName = Path.GetFileName(viewFilePath);
  441. if (viewFileName.EndsWith(".ascx")) return true;
  442. if ((viewFileName.EndsWith(".cshtml") || viewFileName.EndsWith(".vbhtml")) && viewFileName.StartsWith("_"))
  443. {
  444. return true;
  445. }
  446. return false;
  447. }
  448. void PrepareDataToRender(TextTransformation tt)
  449. {
  450. TT = tt;
  451. T4FileName = Path.GetFileName(Host.TemplateFile);
  452. T4Folder = Path.GetDirectoryName(Host.TemplateFile);
  453. Areas = new HashSet<AreaInfo>();
  454. ResultTypes = new Dictionary<string, ResultTypeInfo>();
  455. // Get the DTE service from the host
  456. var serviceProvider = Host as IServiceProvider;
  457. if (serviceProvider != null)
  458. {
  459. Dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
  460. }
  461. // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
  462. if (Dte == null)
  463. {
  464. throw new Exception("T4MVC can only execute through the Visual Studio host");
  465. }
  466. Project = GetProjectContainingT4File(Dte);
  467. if (Project == null)
  468. {
  469. Error("Could not find the VS Project containing the T4 file.");
  470. return;
  471. }
  472. // Get the path of the root folder of the app
  473. AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
  474. ProcessAreas(Project);
  475. }
  476. Project GetProjectContainingT4File(DTE dte)
  477. {
  478. // Find the .tt file's ProjectItem
  479. ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
  480. // If the .tt file is not opened, open it
  481. if (projectItem.Document == null)
  482. projectItem.Open(EnvDTE.Constants.vsViewKindCode);
  483. return projectItem.ContainingProject;
  484. }
  485. void ProcessAreas(Project project)
  486. {
  487. // Process the default area
  488. ProcessArea(project.ProjectItems, null);
  489. // Get the Areas folder
  490. ProjectItem areaProjectItem = GetProjectItem(project, settings.AreasFolder);
  491. // Process areas folder
  492. if (areaProjectItem != null)
  493. {
  494. foreach (ProjectItem item in areaProjectItem.ProjectItems)
  495. {
  496. if (IsFolder(item))
  497. {
  498. ProcessArea(item.ProjectItems, item.Name);
  499. }
  500. }
  501. }
  502. // Process portable areas
  503. foreach (string portableArea in settings.PortableAreas)
  504. {
  505. ProjectItem portableAreaProjectItem = GetProjectItem(project, portableArea);
  506. if (portableAreaProjectItem == null)
  507. return;
  508. if (IsFolder(portableAreaProjectItem))
  509. {
  510. ProcessArea(portableAreaProjectItem.ProjectItems, portableAreaProjectItem.Name);
  511. }
  512. }
  513. }
  514. void ProcessArea(ProjectItems areaFolderItems, string name)
  515. {
  516. var area = new AreaInfo() { Name = name };
  517. ProcessAreaControllers(areaFolderItems, area);
  518. ProcessAreaViews(areaFolderItems, area);
  519. Areas.Add(area);
  520. if (String.IsNullOrEmpty(name))
  521. DefaultArea = area;
  522. }
  523. void ProcessAreaControllers(ProjectItems areaFolderItems, AreaInfo area)
  524. {
  525. // Get area Controllers folder
  526. ProjectItem controllerProjectItem = GetProjectItem(areaFolderItems, settings.ControllersFolder);
  527. if (controllerProjectItem == null)
  528. return;
  529. ProcessControllersRecursive(controllerProjectItem, area);
  530. }
  531. void ProcessAreaViews(ProjectItems areaFolderItems, AreaInfo area)
  532. {
  533. // Get area Views folder
  534. ProjectItem viewsProjectItem = GetProjectItem(areaFolderItems, settings.ViewsRootFolder);
  535. if (viewsProjectItem == null)
  536. return;
  537. ProcessAllViews(viewsProjectItem, area);
  538. }
  539. void ProcessControllersRecursive(ProjectItem projectItem, AreaInfo area)
  540. {
  541. // Recurse into all the sub-items (both files and folder can have some - e.g. .tt files)
  542. foreach (ProjectItem item in projectItem.ProjectItems)
  543. {
  544. ProcessControllersRecursive(item, area);
  545. }
  546. if (projectItem.FileCodeModel != null)
  547. {
  548. DateTime controllerLastWriteTime = File.GetLastWriteTime(projectItem.get_FileNames(0));
  549. foreach (var type in projectItem.FileCodeModel.CodeElements.OfType<CodeClass2>())
  550. {
  551. ProcessControllerType(type, area, controllerLastWriteTime);
  552. }
  553. // Process all the elements that are namespaces
  554. foreach (var ns in projectItem.FileCodeModel.CodeElements.OfType<CodeNamespace>())
  555. {
  556. foreach (var type in ns.Members.OfType<CodeClass2>())
  557. {
  558. ProcessControllerType(type, area, controllerLastWriteTime);
  559. }
  560. }
  561. }
  562. }
  563. void ProcessControllerType(CodeClass2 type, AreaInfo area, DateTime controllerLastWriteTime)
  564. {
  565. // Only process controllers
  566. if (!IsController(type))
  567. return;
  568. // Don't process generic classes (their concrete derived classes will be processed)
  569. if (type.IsGeneric)
  570. return;
  571. //Ignore references to controllers we create
  572. if(area.Controllers.Any(c => c.DerivedClassName == type.Name))
  573. return;
  574. // Make sure the class is partial
  575. if (type.ClassKind != vsCMClassKind.vsCMClassKindPartialClass)
  576. {
  577. try
  578. {
  579. type.ClassKind = vsCMClassKind.vsCMClassKindPartialClass;
  580. }
  581. catch
  582. {
  583. // If we couldn't make it partial, give a warning and skip it
  584. Warning(String.Format("{0} was not able to make the class {1} partial. Please change it manually if possible", T4FileName, type.Name));
  585. return;
  586. }
  587. Warning(String.Format("{0} changed the class {1} to be partial", T4FileName, type.Name));
  588. }
  589. // Collect misc info about the controller class and add it to the collection
  590. var controllerInfo = new ControllerInfo
  591. {
  592. Area = area,
  593. Namespace = type.Namespace != null ? type.Namespace.Name : String.Empty,
  594. ClassName = type.Name
  595. };
  596. //Filter references to controllers we create
  597. foreach(var derived in area.Controllers.Where(c => c.ClassName == controllerInfo.DerivedClassName).ToArray())
  598. area.Controllers.Remove(derived);
  599. // Check if the controller has changed since the generated file was last created
  600. DateTime lastGenerationTime = File.GetLastWriteTime(controllerInfo.GeneratedFileFullPath);
  601. if (lastGenerationTime > controllerLastWriteTime)
  602. {
  603. controllerInfo.GeneratedCodeIsUpToDate = true;
  604. }
  605. // Either process new ControllerInfo or integrate results into existing object for partially defined controllers
  606. var target = area.Controllers.Add(controllerInfo) ? controllerInfo : area.Controllers.First(c => c.Equals(controllerInfo));
  607. target.HasExplicitConstructor |= HasExplicitConstructor(type);
  608. target.HasExplicitDefaultConstructor |= HasExplicitDefaultConstructor(type);
  609. if (type.IsAbstract)
  610. {
  611. // If it's abstract, set a flag and don't process action methods (derived classes will)
  612. target.IsAbstract = true;
  613. }
  614. else
  615. {
  616. // Process all the action methods in the controller
  617. ProcessControllerActionMethods(target, type);
  618. }
  619. }
  620. void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current)
  621. {
  622. bool isAsyncController = IsAsyncController(current);
  623. // We want to process not just the controller class itself, but also its parents, as they
  624. // may themselves define actions
  625. for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
  626. {
  627. // If the type doesn't come from this project, some actions on it will fail. Try to get a real project type if possible.
  628. if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject)
  629. {
  630. // Go through all the projects in the solution
  631. for (int i = 1; i <= Dte.Solution.Projects.Count; i++)
  632. {
  633. Project prj = null;
  634. try
  635. {
  636. prj = Dte.Solution.Projects.Item(i);
  637. }
  638. catch (System.Runtime.Serialization.SerializationException)
  639. {
  640. // Some project types (that we don't care about) cause a strange exception, so ingore it
  641. continue;
  642. }
  643. // Skip it if it's the current project or doesn't have a code model
  644. try
  645. {
  646. if (prj == Project || prj.CodeModel == null)
  647. continue;
  648. }
  649. catch (System.NotImplementedException)
  650. {
  651. // Installer project does not implement CodeModel property
  652. continue;
  653. }
  654. try
  655. {
  656. // If we can get a local project type, use it instead of the original
  657. var codeType = prj.CodeModel.CodeTypeFromFullName(type.FullName);
  658. if (codeType != null && codeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject)
  659. {
  660. type = (CodeClass2)codeType;
  661. break;
  662. }
  663. }
  664. catch (System.ArgumentException)
  665. {
  666. // CodeTypeFromFullName throws when called on VB projects with a type it doesn't know
  667. // (instead of returning null), so ignore those exceptions (See http://t4mvc.codeplex.com/workitem/7)
  668. }
  669. }
  670. }
  671. foreach (CodeFunction2 method in GetMethods(type))
  672. {
  673. // Ignore non-public methods
  674. if (method.Access != vsCMAccess.vsCMAccessPublic)
  675. continue;
  676. // Ignore methods that are marked as not being actions
  677. if (GetAttribute(method.Attributes, "System.Web.Mvc.NonActionAttribute") != null)
  678. continue;
  679. // Ignore methods that are marked as Obsolete
  680. if (GetAttribute(method.Attributes, "System.ObsoleteAttribute") != null)
  681. continue;
  682. // Ignore generic methods
  683. if (method.IsGeneric)
  684. continue;
  685. if(isAsyncController && settings.SupportAsyncActions && (method.Type.TypeKind == vsCMTypeRef.vsCMTypeRefVoid) && method.Name.EndsWith("Async"))
  686. {
  687. //Async methods return void and there could be multiple matching Completed methods, so we will use
  688. //the generic ActionResult as the return type for the method.
  689. var resultType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
  690. // If we haven't yet seen this return type, keep track of it
  691. if (!ResultTypes.ContainsKey(resultType.AsFullName))
  692. {
  693. var resTypeInfo = new ResultTypeInfo(resultType);
  694. ResultTypes[resultType.AsFullName] = resTypeInfo;
  695. }
  696. // Collect misc info about the action method and add it to the collection
  697. controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current, resultType));
  698. continue;
  699. }
  700. // This takes care of avoiding generic types which cause method.Type.CodeType to blow up
  701. if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType || !(method.Type.CodeType is CodeClass2))
  702. continue;
  703. // We only support action methods that return an ActionResult and Task<ActionResult> derived types
  704. if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult") && method.Type.CodeType.FullName !="System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
  705. {
  706. 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));
  707. continue;
  708. }
  709. // Ignore async completion methods as they can't really be used in T4MVC, and can cause issues.
  710. // See http://stackoverflow.com/questions/5419173/t4mvc-asynccontroller
  711. if (isAsyncController && method.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
  712. continue;
  713. var methodType = method.Type;
  714. if(method.Type.CodeType.FullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
  715. methodType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
  716. // If we haven't yet seen this return type, keep track of it
  717. var resTypeInfo2 = new ResultTypeInfo(methodType);
  718. if (!ResultTypes.ContainsKey(resTypeInfo2.FullName))
  719. {
  720. ResultTypes[resTypeInfo2.FullName] = resTypeInfo2;
  721. }
  722. // Make sure the method is virtual
  723. if (!method.CanOverride && method.OverrideKind != vsCMOverrideKind.vsCMOverrideKindOverride)
  724. {
  725. try
  726. {
  727. method.CanOverride = true;
  728. }
  729. catch
  730. {
  731. // If we couldn't make it virtual, give a warning and skip it
  732. 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));
  733. continue;
  734. }
  735. Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name));
  736. }
  737. // Collect misc info about the action method and add it to the collection
  738. controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current));
  739. }
  740. }
  741. }
  742. void ProcessAllViews(ProjectItem viewsProjectItem, AreaInfo area)
  743. {
  744. // Go through all the sub-folders in the Views folder
  745. foreach (ProjectItem item in viewsProjectItem.ProjectItems)
  746. {
  747. // We only care about sub-folders, not files
  748. if (!IsFolder(item))
  749. continue;
  750. // Find the controller for this view folder
  751. ControllerInfo controller = area.Controllers.SingleOrDefault(c => c.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase));
  752. if (controller == null)
  753. {
  754. // If it doesn't match a controller, treat as a pseudo-controller for consistency
  755. controller = new ControllerInfo
  756. {
  757. Area = area,
  758. NotRealController = true,
  759. Namespace = MakeClassName(settings.T4MVCNamespace, area.Name),
  760. ClassName = item.Name + ControllerSuffix
  761. };
  762. area.Controllers.Add(controller);
  763. }
  764. AddViewsRecursive(item.ProjectItems, controller.ViewsFolder);
  765. }
  766. }
  767. void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder)
  768. {
  769. AddViewsRecursive(items, viewsFolder, false);
  770. }
  771. void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder, bool useNonQualifiedViewNames)
  772. {
  773. // Go through all the files in the subfolder to get the view names
  774. foreach (ProjectItem item in items)
  775. {
  776. if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
  777. {
  778. // Ignore some extensions that are normally not views
  779. if (settings.ExcludedViewExtensions.Any(extension => Path.GetExtension(item.Name).Equals(extension, StringComparison.OrdinalIgnoreCase)))
  780. continue;
  781. viewsFolder.AddView(item, useNonQualifiedViewNames);
  782. }
  783. else if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
  784. {
  785. string folderName = Path.GetFileName(item.Name);
  786. if (folderName.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase))
  787. continue;
  788. // Use simple view names if we're already in that mode, or if the folder name is in the collection
  789. bool folderShouldUseNonQualifiedViewNames = useNonQualifiedViewNames || settings.NonQualifiedViewFolders.Contains(folderName, StringComparer.OrdinalIgnoreCase);
  790. var subViewFolder = new ViewsFolderInfo() { Name = folderName };
  791. viewsFolder.SubFolders.Add(subViewFolder);
  792. AddViewsRecursive(item.ProjectItems, subViewFolder, folderShouldUseNonQualifiedViewNames);
  793. }
  794. }
  795. }
  796. void RenderControllerViews(ControllerInfo controller)
  797. {
  798. PushIndent(" ");
  799. RenderViewsRecursive(controller.ViewsFolder, controller);
  800. PopIndent();
  801. }
  802. void RenderViewsRecursive(ViewsFolderInfo viewsFolder, ControllerInfo controller)
  803. {
  804. if(!viewsFolder.HasNonQualifiedViewNames)
  805. {
  806. #>
  807. static readonly _ViewNamesClass s_ViewNames = new _ViewNamesClass();
  808. public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
  809. public class _ViewNamesClass
  810. {
  811. <#+
  812. PushIndent(" ");
  813. foreach (var viewPair in viewsFolder.Views)
  814. {
  815. WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Key + "\";");
  816. }
  817. PopIndent();
  818. #>
  819. }
  820. <#+}
  821. // For each view, generate a readonly string
  822. foreach (var viewPair in viewsFolder.Views)
  823. {
  824. WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Value + "\";");
  825. }
  826. // For each sub folder, generate a class and recurse
  827. foreach (var subFolder in viewsFolder.SubFolders)
  828. {
  829. string name = Sanitize(subFolder.Name);
  830. string className = "_" + name;
  831. // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
  832. // http://mvccontrib.codeplex.com/workitem/7153
  833. if (name == Sanitize(viewsFolder.Name))
  834. {
  835. className += "_";
  836. }#>
  837. static readonly <#=className#>Class s_<#=name#> = new <#=className#>Class();
  838. public <#=className#>Class <#=EscapeID(name)#> { get { return s_<#=name#>; } }
  839. [<#= GeneratedCode #>, DebuggerNonUserCode]
  840. public partial class <#=className#>Class
  841. {
  842. <#+
  843. PushIndent(" ");
  844. RenderViewsRecursive(subFolder, controller);
  845. PopIndent();
  846. WriteLine("}");
  847. }
  848. }
  849. IEnumerable<string> GetStaticFilesViewFolders()
  850. {
  851. if (settings.AddAllViewsFoldersToStaticFilesFolders)
  852. {
  853. foreach (var area in Areas)
  854. {
  855. yield return area.Name == null ?
  856. settings.ViewsRootFolder :
  857. settings.AreasFolder + "\\" + area.Name + "\\" + settings.ViewsRootFolder;
  858. }
  859. }
  860. }
  861. void ProcessStaticFiles(Project project, string folder)
  862. {
  863. ProjectItem folderProjectItem = GetProjectItem(project, folder);
  864. if (folderProjectItem != null)
  865. {
  866. var rootPath = "~";
  867. if (folder.Contains("\\"))
  868. {
  869. rootPath += "/" + folder.Replace("\\", "/");
  870. rootPath = rootPath.Substring(0, rootPath.LastIndexOf("/"));
  871. }
  872. ProcessStaticFilesRecursive(folderProjectItem, rootPath);
  873. }
  874. }
  875. void ProcessStaticFilesRecursive(ProjectItem projectItem, string path)
  876. {
  877. int nestedLevel = BuildClassStructureForProvidedPath(path);
  878. ProcessStaticFilesRecursive(projectItem, path, new HashSet<String>());
  879. for(int i = 0; i < nestedLevel; ++i) {#>
  880. }
  881. <#+
  882. PopIndent();
  883. }
  884. }
  885. void ProcessStaticFilesRecursive(ProjectItem projectItem, string path, HashSet<String> nameSet)
  886. {
  887. // The passed in HashSet is to guarantee uniqueness with our parent and siblings
  888. string name = SanitizeWithNoConflicts(projectItem.Name, nameSet);
  889. // This HashSet is to guarantee uniqueness of our direct children
  890. // We add our own name to it to avoid class name conflicts (http://mvccontrib.codeplex.com/workitem/7153)
  891. var childrenNameSet = new HashSet<String>();
  892. childrenNameSet.Add(name);
  893. if (IsFolder(projectItem))
  894. {
  895. #>
  896. [<#= GeneratedCode #>, DebuggerNonUserCode]
  897. public static class <#=EscapeID(name)#> {
  898. private const string URLPATH = "<#=path#>/<#=projectItem.Name#>";
  899. public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
  900. public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
  901. <#+
  902. PushIndent(" ");
  903. // Recurse into all the items in the folder
  904. foreach (ProjectItem item in projectItem.ProjectItems)
  905. {
  906. ProcessStaticFilesRecursive(
  907. item,
  908. path + "/" + projectItem.Name,
  909. childrenNameSet);
  910. }
  911. PopIndent();
  912. #>
  913. }
  914. <#+
  915. }
  916. else { #>
  917. <#+
  918. if (!settings.ExcludedStaticFileExtensions.Any(extension => projectItem.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))) {
  919. // if it's a Typescript file
  920. if (projectItem.Name.EndsWith(".ts")) {
  921. string tsJavascriptName = projectItem.Name.Replace(".ts", ".js");
  922. string minifiedName = projectItem.Name.Replace(".ts", ".min.js");
  923. if (AddTimestampToStaticLink(projectItem)) { #>
  924. public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=tsJavascriptName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=tsJavascriptName#>");
  925. <#+} else {#>
  926. public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=tsJavascriptName#>");
  927. <#+} #>
  928. <#+}
  929. // if it's a non-minified javascript file
  930. else if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js")) {
  931. string minifiedName = projectItem.Name.Replace(".js", ".min.js");
  932. if (AddTimestampToStaticLink(projectItem)) { #>
  933. 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#>");
  934. <#+} else {#>
  935. public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
  936. <#+} #>
  937. <#+}
  938. else if (projectItem.Name.EndsWith(".css") && !projectItem.Name.EndsWith(".min.css")) {
  939. string minifiedName = projectItem.Name.Replace(".css", ".min.css");
  940. if (AddTimestampToStaticLink(projectItem)) { #>
  941. 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#>");
  942. <#+} else {#>
  943. public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
  944. <#+} #>
  945. <#+}
  946. else if (AddTimestampToStaticLink(projectItem)) { #>
  947. public static readonly string <#=name#> = Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
  948. <#+}
  949. else { #>
  950. public static readonly string <#=name#> = Url("<#=projectItem.Name#>");
  951. <#+}
  952. } #>
  953. <#+
  954. // Non folder items may also have children (virtual folders, Class.cs -> Class.Designer.cs, template output)
  955. // Just register them on the same path as their parent item
  956. foreach (ProjectItem item in projectItem.ProjectItems)
  957. {
  958. ProcessStaticFilesRecursive(item, path, childrenNameSet);
  959. }
  960. }
  961. }
  962. int BuildClassStructureForProvidedPath(string path)
  963. {
  964. var folders = path.Split(new char[] {'/', '~'}, StringSplitOptions.RemoveEmptyEntries);
  965. var parentFolder = String.Empty;
  966. var currentPath = "~";
  967. foreach(var folder in folders)
  968. {
  969. currentPath += "/" + folder;
  970. string className = EscapeID(Sanitize(folder));
  971. // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
  972. // http://mvccontrib.codeplex.com/workitem/7153
  973. if (parentFolder == folder)
  974. {
  975. className += "_";
  976. }
  977. if(!virtualPathesForStaticFiles.Contains(currentPath))
  978. {
  979. virtualPathesForStaticFiles.Add(currentPath);#>
  980. [<#= GeneratedCode #>, DebuggerNonUserCode]
  981. public static partial class <#=className #> {
  982. private const string URLPATH = "<#=currentPath#>";
  983. public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
  984. public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
  985. <#+ } else {
  986. #>
  987. public static partial class <#=className #> {
  988. <#+ }
  989. PushIndent(" ");
  990. parentFolder = folder;
  991. }
  992. return folders.Length;
  993. }
  994. ProjectItem GetProjectItem(Project project, string name)
  995. {
  996. return GetProjectItem(project.ProjectItems, name);
  997. }
  998. ProjectItem GetProjectItem(ProjectItems items, string subPath)
  999. {
  1000. ProjectItem current = null;
  1001. foreach (string name in subPath.Split('\\'))
  1002. {
  1003. try
  1004. {
  1005. // ProjectItems.Item() throws when it doesn't exist, so catch the exception
  1006. // to return null instead.
  1007. current = items.Item(name);
  1008. }
  1009. catch
  1010. {
  1011. // If any chunk couldn't be found, fail
  1012. return null;
  1013. }
  1014. items = current.ProjectItems;
  1015. }
  1016. return current;
  1017. }
  1018. static bool IsController(CodeClass2 type)
  1019. {
  1020. // Ignore any class which name doesn't end with "Controller"
  1021. if (!type.FullName.EndsWith(ControllerSuffix)) return false;
  1022. for (; type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
  1023. {
  1024. if (type.Bases.Count == 0)
  1025. return false;
  1026. }
  1027. return true;
  1028. }
  1029. static bool IsAsyncController(CodeClass2 type)
  1030. {
  1031. for (; type.FullName != "System.Web.Mvc.AsyncController"; type = (CodeClass2)type.Bases.Item(1))
  1032. {
  1033. if (type.Bases.Count == 0)
  1034. return false;
  1035. }
  1036. return true;
  1037. }
  1038. static string GetVirtualPath(ProjectItem item)
  1039. {
  1040. string fileFullPath = item.get_FileNames(0);
  1041. // Ignore files that are not under the app root (e.g. they could be linked files)
  1042. if (!fileFullPath.StartsWith(AppRoot, StringComparison.OrdinalIgnoreCase))
  1043. return null;
  1044. // Make a virtual path from the physical path
  1045. return "~/" + fileFullPath.Substring(AppRoot.Length).Replace('\\', '/');
  1046. }
  1047. static string ProcessAreaOrControllerName(string name)
  1048. {
  1049. return settings.UseLowercaseRoutes ? name.ToLowerInvariant() : name;
  1050. }
  1051. // Return all the CodeFunction2 in the CodeElements collection
  1052. static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass)
  1053. {
  1054. // Only look at regular method (e.g. ignore things like contructors)
  1055. return codeClass.Members.OfType<CodeFunction2>()
  1056. .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
  1057. }
  1058. // Check if the class has any explicit constructor
  1059. static bool HasExplicitConstructor(CodeClass2 codeClass)
  1060. {
  1061. return codeClass.Members.OfType<CodeFunction2>().Any(
  1062. f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor);
  1063. }
  1064. // Check if the class has a default (i.e. no params) constructor
  1065. static bool HasExplicitDefaultConstructor(CodeClass2 codeClass)
  1066. {
  1067. return codeClass.Members.OfType<CodeFunction2>().Any(
  1068. f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor && f.Parameters.Count == 0);
  1069. }
  1070. // Find a method with a given name
  1071. static CodeFunction2 GetMethod(CodeClass2 codeClass, string name)
  1072. {
  1073. return GetMethods(codeClass).FirstOrDefault(f => f.Name == name);
  1074. }
  1075. // Find an attribute of a given type on an attribute collection
  1076. static CodeAttribute2 GetAttribute(CodeElements attributes, string attributeType)
  1077. {
  1078. for (int i = 1; i <= attributes.Count; i++)
  1079. {
  1080. try
  1081. {
  1082. var attrib = (CodeAttribute2)attributes.Item(i);
  1083. if (attributeType.Split(',').Contains(attrib.FullName, StringComparer.OrdinalIgnoreCase))
  1084. {
  1085. return attrib;
  1086. }
  1087. }
  1088. catch
  1089. {
  1090. // FullName can throw in some cases, so just ignore those attributes
  1091. continue;
  1092. }
  1093. }
  1094. return null;
  1095. }
  1096. static CodeAttribute2 GetAttribute(CodeClass2 type, string attributeType)
  1097. {
  1098. while(type != null) {
  1099. var attribute = GetAttribute(type.Attributes, attributeType);
  1100. if(attribute != null)
  1101. return attribute;
  1102. if (type.Bases.Count == 0)
  1103. return null;
  1104. type = (CodeClass2)type.Bases.Item(1);
  1105. }
  1106. return null;
  1107. }
  1108. static string UniqueFullName(CodeTypeRef codeType)
  1109. {
  1110. return UniqueFullName(codeType.CodeType);
  1111. }
  1112. static string UniqueFullName(CodeType codeType)
  1113. {
  1114. var uniqueName = codeType.FullName;
  1115. // Match characters not allowed in class names.
  1116. uniqueName = Regex.Replace(uniqueName, @"[^\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\d]", "_");
  1117. // Remove duplicate '_' characters
  1118. uniqueName = Regex.Replace(uniqueName, @"__+", "_");
  1119. // Remove trailing '_' characters
  1120. uniqueName = uniqueName.TrimEnd('_');
  1121. return uniqueName;
  1122. }
  1123. // Return whether a ProjectItem is a folder and not a file
  1124. static bool IsFolder(ProjectItem item)
  1125. {
  1126. return (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
  1127. }
  1128. static string MakeClassName(string ns, string classname)
  1129. {
  1130. return String.IsNullOrEmpty(ns) ? classname :
  1131. String.IsNullOrEmpty(classname) ? ns : ns + "." + codeProvider.CreateEscapedIdentifier(classname);
  1132. }
  1133. static string SanitizeWithNoConflicts(string token, HashSet<string> names)
  1134. {
  1135. string name = Sanitize(token);
  1136. while (names.Contains(name))
  1137. {
  1138. name += "_";
  1139. }
  1140. names.Add(name);
  1141. return name;
  1142. }
  1143. static string Sanitize(string token)
  1144. {
  1145. if (token == null) return null;
  1146. // Replace all invalid chars by underscores
  1147. token = Regex.Replace(token, @"[\W\b]", "_", RegexOptions.IgnoreCase);
  1148. // If it starts with a digit, prefix it with an underscore
  1149. token = Regex.Replace(token, @"^\d", @"_$0");
  1150. // Check for reserved words
  1151. // TODO: Clean this up and add other reserved words (keywords, etc)
  1152. if (token == "Url") token = "_Url";
  1153. return token;
  1154. }
  1155. static string EscapeID(string id)
  1156. {
  1157. return codeProvider.CreateEscapedIdentifier(id);
  1158. }
  1159. // Data structure to collect data about an area
  1160. class AreaInfo
  1161. {
  1162. public AreaInfo()
  1163. {
  1164. Controllers = new HashSet<ControllerInfo>();
  1165. }
  1166. public string Name { get; set; }
  1167. public HashSet<ControllerInfo> Controllers { get; set; }
  1168. public string Namespace
  1169. {
  1170. get
  1171. {
  1172. // When *not* using an 'Areas' token, we need to disambiguate conflicts
  1173. // between Area names and controller names (from the default Area)
  1174. if (!settings.IncludeAreasToken && DefaultArea.Controllers.Any(c => c.Name == Name))
  1175. return Name + "Area";
  1176. return Name;
  1177. }
  1178. }
  1179. public IEnumerable<ControllerInfo> GetControllers()
  1180. {
  1181. return Controllers.Where(c => !c.IsAbstract);
  1182. }
  1183. public IEnumerable<ControllerInfo> GetAbstractControllers()
  1184. {
  1185. return Controllers.Where(c => c.IsAbstract);
  1186. }
  1187. }
  1188. // Data structure to collect data about a controller class
  1189. class ControllerInfo
  1190. {
  1191. public ControllerInfo()
  1192. {
  1193. ActionMethods = new HashSet<ActionMethodInfo>();
  1194. ViewsFolder = new ViewsFolderInfo();
  1195. }
  1196. public AreaInfo Area { get; set; }
  1197. public string AreaName
  1198. {
  1199. get { return Area.Name ?? ""; }
  1200. }
  1201. public string T4MVCControllerFullName
  1202. {
  1203. get
  1204. {
  1205. string name = settings.HelpersPrefix;
  1206. if (!String.IsNullOrEmpty(AreaName))
  1207. {
  1208. if (settings.IncludeAreasToken)
  1209. name += ".Areas." + EscapeID(Area.Namespace);
  1210. else
  1211. name += "." + EscapeID(Area.Namespace);
  1212. }
  1213. return name + "." + Name;
  1214. }
  1215. }
  1216. public string ViewPath
  1217. {
  1218. get
  1219. {
  1220. if (string.IsNullOrEmpty(Area.Name))
  1221. return String.Format("~/{0}/{1}/", settings.ViewsRootFolder, Name);
  1222. else
  1223. return String.Format("~/{0}/{1}/{2}/", settings.AreasFolder, settings.ViewsRootFolder, Name);
  1224. }
  1225. }
  1226. // True when this is not a real controller, but a placeholder for views folders that don't match a controller
  1227. public bool NotRealController { get; set; }
  1228. public bool HasExplicitConstructor { get; set; }
  1229. public bool HasExplicitDefaultConstructor { get; set; }
  1230. public bool HasDefaultConstructor { get { return !HasExplicitConstructor || HasExplicitDefaultConstructor; } }
  1231. public bool IsAbstract { get; set; }
  1232. public bool GeneratedCodeIsUpToDate { get; set; }
  1233. public string ClassName { get; set; }
  1234. public string Name
  1235. {
  1236. get
  1237. {
  1238. // Trim the Controller suffix
  1239. return ClassName.Substring(0, ClassName.Length - ControllerSuffix.Length);
  1240. }
  1241. }
  1242. public string Namespace { get; set; }
  1243. public string FullClassName
  1244. {
  1245. get
  1246. {
  1247. return MakeClassName(Namespace, ClassName);
  1248. }
  1249. }
  1250. public string DerivedClassName
  1251. {
  1252. get
  1253. {
  1254. return "T4MVC_" + ClassName;
  1255. }
  1256. }
  1257. public string FullDerivedClassName
  1258. {
  1259. get
  1260. {
  1261. if (NotRealController)
  1262. return FullClassName;
  1263. return MakeClassName(Namespace, DerivedClassName);
  1264. }
  1265. }
  1266. public string GeneratedFileName
  1267. {
  1268. get
  1269. {
  1270. return MakeClassName(AreaName, ClassName + ".generated.cs");
  1271. }
  1272. }
  1273. public string GeneratedFileFullPath
  1274. {
  1275. get
  1276. {
  1277. return Path.Combine(T4Folder, GeneratedFileName);
  1278. }
  1279. }
  1280. public HashSet<ActionMethodInfo> ActionMethods { get; set; }
  1281. public IEnumerable<ActionMethodInfo> CustomActionMethods
  1282. {
  1283. get
  1284. {
  1285. return ActionMethods.Where(m => m.IsCustomReturnType);
  1286. }
  1287. }
  1288. IEnumerable<ActionMethodInfo> ActionMethodsWithNoParameters
  1289. {
  1290. get
  1291. {
  1292. return ActionMethods.Where(m => m.CanBeCalledWithoutParameters);
  1293. }
  1294. }
  1295. public IEnumerable<ActionMethodInfo> ActionMethodsUniqueWithoutParameterlessOverload
  1296. {
  1297. get
  1298. {
  1299. return ActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
  1300. }
  1301. }
  1302. public IEnumerable<ActionMethodInfo> CustomActionMethodsUniqueWithoutParameterlessOverload
  1303. {
  1304. get
  1305. {
  1306. return CustomActionMethodsWithUniqueNames.Except(ActionMethodsWithNoParameters, new ActionComparer());
  1307. }
  1308. }
  1309. // Return a list of actions without duplicate names (even with multiple overloads)
  1310. public IEnumerable<ActionMethodInfo> ActionMethodsWithUniqueNames
  1311. {
  1312. get
  1313. {
  1314. return ActionMethods.Distinct(new ActionComparer());
  1315. }
  1316. }
  1317. // Return a list of actions without duplicate names (even with multiple overloads)
  1318. public IEnumerable<ActionMethodInfo> CustomActionMethodsWithUniqueNames
  1319. {
  1320. get
  1321. {
  1322. return CustomActionMethods.Distinct(new ActionComparer());
  1323. }
  1324. }
  1325. class ActionComparer : IEqualityComparer<ActionMethodInfo>
  1326. {
  1327. public bool Equals(ActionMethodInfo x, ActionMethodInfo y)
  1328. {
  1329. return x.ActionName == y.ActionName;
  1330. }
  1331. public int GetHashCode(ActionMethodInfo obj)
  1332. {
  1333. return obj.ActionName.GetHashCode();
  1334. }
  1335. }
  1336. public IEnumerable<IGrouping<string, MethodParamInfo>> UniqueParameterNamesGroupedByActionName
  1337. {
  1338. get
  1339. {
  1340. var comp = new ActionParameterComparer();
  1341. return ActionMethods
  1342. .SelectMany(m => m.Parameters, (m, p) => new { Method = m, Parameter = p })
  1343. .GroupBy(m => m.Method.ActionName, m => m.Parameter)
  1344. .Select(g => new { g.Key, Items = g.Distinct(comp) })
  1345. .SelectMany(g => g.Items, (g, i) => new { g.Key, Item = i })
  1346. .GroupBy(i => i.Key, i => i.Item);
  1347. }
  1348. }
  1349. class ActionParameterComparer : IEqualityComparer<MethodParamInfo>
  1350. {
  1351. public bool Equals(MethodParamInfo x, MethodParamInfo y)
  1352. {
  1353. return x.Name == y.Name;
  1354. }
  1355. public int GetHashCode(MethodParamInfo obj){
  1356. return obj.Name.GetHashCode();
  1357. }
  1358. }
  1359. public ViewsFolderInfo ViewsFolder { get; private set; }
  1360. public override string ToString()
  1361. {
  1362. return Name;
  1363. }
  1364. public override bool Equals(object obj)
  1365. {
  1366. return obj != null && FullClassName == ((ControllerInfo)obj).FullClassName;
  1367. }
  1368. public override int GetHashCode()
  1369. {
  1370. return FullClassName.GetHashCode();
  1371. }
  1372. }
  1373. // Info about a view folder, its views and its sub view folders
  1374. class ViewsFolderInfo
  1375. {
  1376. public ViewsFolderInfo()
  1377. {
  1378. Views = new Dictionary<string, string>();
  1379. SubFolders = new List<ViewsFolderInfo>();
  1380. }
  1381. public void AddView(ProjectItem item, bool useNonQualifiedViewName)
  1382. {
  1383. string viewName = Path.GetFileName(item.Name);
  1384. string viewFieldName = Path.GetFileNameWithoutExtension(viewName);
  1385. // If the simple view name is already in use, include the extension (e.g. foo_ascx instead of just foo)
  1386. if (Views.ContainsKey(viewFieldName))
  1387. viewFieldName = Sanitize(viewName);
  1388. HasNonQualifiedViewNames = HasNonQualifiedViewNames | useNonQualifiedViewName;
  1389. string virtualPath = GetVirtualPath(item);
  1390. if (virtualPath != null)
  1391. {
  1392. Views[viewFieldName] = useNonQualifiedViewName ? Path.GetFileNameWithoutExtension(viewName) : virtualPath;
  1393. }
  1394. }
  1395. public bool HasNonQualifiedViewNames { get; private set; }
  1396. public string Name { get; set; }
  1397. public Dictionary<string, string> Views { get; private set; }
  1398. public List<ViewsFolderInfo> SubFolders { get; set; }
  1399. }
  1400. // Data structure to collect data about a method
  1401. class FunctionInfo
  1402. {
  1403. protected CodeFunction2 _method;
  1404. private string _signature;
  1405. public FunctionInfo(CodeFunction2 method)
  1406. {
  1407. Parameters = new List<MethodParamInfo>();
  1408. // Can be null when an custom ActionResult has no ctor
  1409. if (method == null)
  1410. return;
  1411. _method = method;
  1412. // Build a unique signature for the method, used to avoid duplication
  1413. _signature = method.Name;
  1414. CanBeCalledWithoutParameters = true;
  1415. // Process all the parameters
  1416. foreach (var p in method.Parameters.OfType<CodeParameter2>())
  1417. {
  1418. // If any param is not optional, then the method can't be called without parameters
  1419. if (p.ParameterKind != vsCMParameterKind.vsCMParameterKindOptional)
  1420. {
  1421. CanBeCalledWithoutParameters = false;
  1422. }
  1423. // Note: if the param name starts with @ (e.g. to escape a keyword), we need to trim that
  1424. string routeNameExpression = "\"" + p.Name.TrimStart('@') + "\"";
  1425. // If there is a [Bind(Prefix = "someName")] attribute, use it
  1426. if (p.InfoLocation != vsCMInfoLocation.vsCMInfoLocationExternal)
  1427. {
  1428. var attrib = GetAttribute(p.Attributes, "System.Web.Mvc.BindAttribute");
  1429. if (attrib != null)
  1430. {
  1431. var arg = attrib.Arguments.OfType<CodeAttributeArgument>().FirstOrDefault(a => a.Name == "Prefix");
  1432. if (arg != null)
  1433. routeNameExpression = arg.Value;
  1434. }
  1435. }
  1436. Parameters.Add(
  1437. new MethodParamInfo()
  1438. {
  1439. Name = p.Name,
  1440. RouteNameExpression = routeNameExpression,
  1441. Type = p.Type.AsString,
  1442. DefaultValue = p.DefaultValue
  1443. });
  1444. _signature += "," + p.Type.AsString;
  1445. }
  1446. }
  1447. protected virtual CodeTypeRef ReturnTypeImpl { get { return _method.Type; } }
  1448. public string Name { get { return _method.Name; } }
  1449. public string ReturnType { get { return ReturnTypeImpl.AsString; } }
  1450. public string ReturnTypeFullName { get { return ReturnTypeImpl.AsFullName; } }
  1451. public string ReturnTypeUniqueName { get { return IsTaskBased ? "System_Web_Mvc_ActionResult" : UniqueFullName(ReturnTypeImpl); } }
  1452. public bool IsPublic { get { return _method.Access == vsCMAccess.vsCMAccessPublic; } }
  1453. public List<MethodParamInfo> Parameters { get; private set; }
  1454. public bool CanBeCalledWithoutParameters { get; private set; }
  1455. private bool IsTaskBased { get {return ReturnTypeImpl.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }
  1456. // Write out all the parameters as part of a method declaration
  1457. public void WriteFormalParameters(bool first, bool includeDefaults = false)
  1458. {
  1459. foreach (var p in Parameters)
  1460. {
  1461. if (first)
  1462. first = false;
  1463. else
  1464. TT.Write(", ");
  1465. TT.Write(p.Type + " " + p.Name);
  1466. if(includeDefaults && !string.IsNullOrEmpty(p.DefaultValue))
  1467. TT.Write(" = " + p.DefaultValue);
  1468. }
  1469. }
  1470. // Pass non-empty param values to make sure the ActionResult ctors don't complain
  1471. // REVIEW: this is a bit dirty
  1472. public void WriteNonEmptyParameterValues(bool first)
  1473. {
  1474. foreach (var p in Parameters)
  1475. {
  1476. if (first)
  1477. first = false;
  1478. else
  1479. TT.Write(", ");
  1480. if(!string.IsNullOrEmpty(p.DefaultValue))
  1481. TT.Write(p.DefaultValue);
  1482. else
  1483. {
  1484. switch (p.Type)
  1485. {
  1486. case "string":
  1487. TT.Write("\" \"");
  1488. break;
  1489. case "byte[]":
  1490. TT.Write("new byte[0]");
  1491. break;
  1492. default:
  1493. TT.Write("default(" + p.Type + ")");
  1494. break;
  1495. }
  1496. }
  1497. }
  1498. }
  1499. public override bool Equals(object obj)
  1500. {
  1501. return obj != null && _signature == ((FunctionInfo)obj)._signature;
  1502. }
  1503. public override int GetHashCode()
  1504. {
  1505. return _signature.GetHashCode();
  1506. }
  1507. }
  1508. // Data structure to collect data about an action method
  1509. class ActionMethodInfo : FunctionInfo
  1510. {
  1511. public ActionMethodInfo(CodeFunction2 method, CodeClass2 controller, CodeTypeRef asyncType = null)
  1512. : base(method)
  1513. {
  1514. if(asyncType != null)
  1515. {
  1516. // Remove the Async from the end of the name to match the actual Action routing would use.
  1517. // This also separates the Action Calls from the implementation
  1518. _actionName = method.Name.Remove(method.Name.Length - 5);
  1519. _returnType = asyncType;
  1520. }
  1521. // Normally, the action name is the method name. But if there is an [ActionName] on
  1522. // the method, get the expression from that instead
  1523. ActionNameValueExpression = '"' + ActionName + '"';
  1524. var attrib = GetAttribute(method.Attributes, "System.Web.Mvc.ActionNameAttribute");
  1525. if (attrib != null)
  1526. {
  1527. var arg = (CodeAttributeArgument)attrib.Arguments.Item(1);
  1528. ActionNameValueExpression = arg.Value;
  1529. }
  1530. if (GetAttribute(method.Attributes, settings.AttributeIndicatingHttps) != null || GetAttribute(controller, settings.AttributeIndicatingHttps) != null)
  1531. {
  1532. ActionUrlHttps = true;
  1533. }
  1534. }
  1535. string _actionName;
  1536. CodeTypeRef _returnType;
  1537. protected override CodeTypeRef ReturnTypeImpl { get { return _returnType ?? base.ReturnTypeImpl; } }
  1538. public string ActionName { get { return _actionName ?? base.Name; } }
  1539. public string ActionNameValueExpression { get; set; }
  1540. public bool ActionUrlHttps {get; set; }
  1541. public bool IsCustomReturnType { get { return _returnType != null; } }
  1542. }
  1543. // Data about an ActionResult derived type
  1544. class ResultTypeInfo
  1545. {
  1546. CodeTypeRef _codeType;
  1547. public ResultTypeInfo(CodeTypeRef codeType)
  1548. {
  1549. _codeType = codeType;
  1550. // Use the constructor with the least number of parameters
  1551. var ctor = _codeType.CodeType.Members.OfType<CodeFunction2>()
  1552. .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionConstructor)
  1553. .OrderBy(f => f.Parameters.Count)
  1554. .FirstOrDefault();
  1555. Constructor = new FunctionInfo(ctor);
  1556. }
  1557. public string Name { get { return _codeType.AsString; } }
  1558. public string FullName { get { return this.IsTaskBased ? "System.Web.Mvc.ActionResult" : _codeType.AsFullName; } }
  1559. public string UniqueName { get { return this.IsTaskBased ? "System_Web_Mvc_ActionResult" : UniqueFullName(_codeType); } }
  1560. public FunctionInfo Constructor { get; set; }
  1561. public IEnumerable<FunctionInfo> AbstractMethods
  1562. {
  1563. get
  1564. {
  1565. return _codeType.CodeType.Members.OfType<CodeFunction2>().Where(
  1566. f => f.MustImplement).Select(f => new FunctionInfo(f));
  1567. }
  1568. }
  1569. private bool IsTaskBased { get {return _codeType.AsFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>"; } }
  1570. }
  1571. class MethodParamInfo
  1572. {
  1573. public string Name { get; set; }
  1574. public string RouteNameExpression { get; set; }
  1575. public string Type { get; set; }
  1576. public string DefaultValue { get; set; }
  1577. }
  1578. class MvcSettings : XmlSettings
  1579. {
  1580. public static MvcSettings Load(ITextTemplatingEngineHost host)
  1581. {
  1582. return Load<MvcSettings>(host);
  1583. }
  1584. public MvcSettings()
  1585. {
  1586. this.T4MVCNamespace = "T4MVC";
  1587. this.HelpersPrefix = "MVC";
  1588. this.ReferencedNamespaces = new XmlStringArray(new string[] {
  1589. }, "Namespace");
  1590. this.AreasFolder = "Areas";
  1591. this.PortableAreas = new XmlStringArray(new string[] {
  1592. }, "Area");
  1593. this.IncludeAreasToken = false;
  1594. this.ControllersFolder = "Controllers";
  1595. this.ViewsRootFolder = "Views";
  1596. this.NonQualifiedViewFolders = new XmlStringArray(new string[] {
  1597. "DisplayTemplates",
  1598. "EditorTemplates"
  1599. }, "ViewFolder");
  1600. this.GenerateActionResultInterface = true;
  1601. this.GenerateParamsAsConstantsForActionMethods = false;
  1602. this.GenerateParamsForActionMethods = true;
  1603. this.SupportAsyncActions = false;
  1604. this.UseLowercaseRoutes = false;
  1605. this.LinksNamespace = "Links";
  1606. this.AddTimestampToStaticLinks = false;
  1607. this.StaticFilesFolders = new XmlStringArray(new string[] {
  1608. "Scripts",
  1609. "Content",
  1610. }, "FileFolder");
  1611. this.ExcludedStaticFileExtensions = new XmlStringArray(new string[] {
  1612. ".cs",
  1613. ".cshtml",
  1614. ".aspx",
  1615. ".ascx"
  1616. }, "Extension");
  1617. this.ExcludedViewExtensions = new XmlStringArray(new string[] {
  1618. ".master",
  1619. ".js",
  1620. ".css"
  1621. }, "Extension");
  1622. this.AttributeIndicatingHttps = "System.Web.Mvc.RequireHttpsAttribute";
  1623. this.GenerateSecureLinksInDebugMode = false;
  1624. this.ParamsPropertySuffix = "Params";
  1625. this.ExplicitHtmlHelpersForPartialsFormat = "Render{0}";
  1626. this.SplitIntoMultipleFiles = true;
  1627. }
  1628. [System.ComponentModel.Description("The namespace used by some of T4MVC's generated code")]
  1629. public string T4MVCNamespace { get; set; }
  1630. [System.ComponentModel.Description("The prefix used for things like MVC.Dinners.Name and MVC.Dinners.Delete(Model.DinnerID)")]
  1631. public string HelpersPrefix { get; set; }
  1632. [System.ComponentModel.Description("Namespaces to be referenced by the generated code")]
  1633. public XmlStringArray ReferencedNamespaces { get; set; }
  1634. [System.ComponentModel.Description("The folder under the project that contains the areas")]
  1635. public string AreasFolder { get; set; }
  1636. [System.ComponentModel.Description("You can list folders containing portable areas here")]
  1637. public IEnumerable<string> PortableAreas { get; set; }
  1638. [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...")]
  1639. public bool IncludeAreasToken { get; set; }
  1640. [System.ComponentModel.Description("The folder under the project that contains the controllers")]
  1641. public string ControllersFolder { get; set; }
  1642. [System.ComponentModel.Description("The folder under the project that contains the views")]
  1643. public string ViewsRootFolder { get; set; }
  1644. [System.ComponentModel.Description("Views in DisplayTemplates and EditorTemplates folders shouldn't be fully qualifed\r\nas it breaks the templated helper code")]
  1645. public XmlStringArray NonQualifiedViewFolders { get; set; }
  1646. [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")]
  1647. public bool GenerateActionResultInterface { get; set; }
  1648. [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.")]
  1649. public bool SupportAsyncActions { get; set; }
  1650. [System.ComponentModel.Description("If true, use lower case tokens in routes for the area, controller and action names")]
  1651. public bool UseLowercaseRoutes { get; set; }
  1652. [System.ComponentModel.Description("The namespace that the links are generated in (e.g. \"Links\", as in Links.Content.nerd_jpg)")]
  1653. public string LinksNamespace { get; set; }
  1654. [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")]
  1655. public bool AddTimestampToStaticLinks { get; set; }
  1656. [System.ComponentModel.Description("Folders containing static files for which links are generated (e.g. Links.Scripts.Map_js)")]
  1657. public XmlStringArray StaticFilesFolders { get; set; }
  1658. [System.ComponentModel.Description("If true, static file helpers are generated for all view folders. See https://t4mvc.codeplex.com/discussions/445358")]
  1659. public bool AddAllViewsFoldersToStaticFilesFolders { get; set; }
  1660. [System.ComponentModel.Description("Static files to exclude from the generated links")]
  1661. public XmlStringArray ExcludedStaticFileExtensions { get; set; }
  1662. [System.ComponentModel.Description("Files to exclude from the generated views")]
  1663. public XmlStringArray ExcludedViewExtensions { get; set; }
  1664. [System.ComponentModel.Description("When creating links with T4MVC, it can force them to HTTPS if the action method you are linking to requires Http.")]
  1665. public string AttributeIndicatingHttps { get; set; }
  1666. public bool GenerateSecureLinksInDebugMode { get; set; }
  1667. [System.ComponentModel.Description("Set this to false to omit the generation of parameters for action methods.")]
  1668. public bool GenerateParamsForActionMethods { get; set; }
  1669. [System.ComponentModel.Description("Set this to true to omit the generation of parameters for action methods as constants. Choose this or GenerateParamsForActionMethods.")]
  1670. public bool GenerateParamsAsConstantsForActionMethods { get; set; }
  1671. [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.")]
  1672. public string ParamsPropertySuffix { get; set; }
  1673. [System.ComponentModel.Description("create explicit HtmlHelpers for rendering partials")]
  1674. public bool ExplicitHtmlHelpersForPartials { get; set; }
  1675. public string ExplicitHtmlHelpersForPartialsFormat { get; set; }
  1676. [System.ComponentModel.Description("If true,the template output will be split into multiple files.")]
  1677. public bool SplitIntoMultipleFiles { get; set; }
  1678. }
  1679. /*
  1680. XmlSettings base classes, if you need to modify the T4MVC properties edit the MvcSettings Class Above
  1681. */
  1682. /// Base XmlSettings class, responsible for reading/writing the settigns file contents, all settings other
  1683. /// than string convertable types should decend from this class
  1684. abstract class XmlSettingsBase
  1685. {
  1686. protected XmlSettingsBase()
  1687. {
  1688. this.NeedsSave = true;
  1689. }
  1690. protected virtual void Init()
  1691. {
  1692. }
  1693. protected bool SaveAsChild { get; private set; }
  1694. protected bool NeedsSave { get; private set; }
  1695. protected static void SetSaveAsChild(XmlSettingsBase settings, bool value)
  1696. {
  1697. settings.SaveAsChild = value;
  1698. }
  1699. protected static void SetNeedsSave(XmlSettingsBase settings, bool value)
  1700. {
  1701. settings.NeedsSave = value;
  1702. }
  1703. protected static void WriteCommentedProperty(System.Xml.XmlWriter writer, string name)
  1704. {
  1705. writer.WriteComment(string.Concat("<", name, "></", name, ">"));
  1706. }
  1707. protected static void WritePropertyDesc(System.Xml.XmlWriter writer, System.ComponentModel.PropertyDescriptor property)
  1708. {
  1709. var desc = property.Attributes.OfType<System.ComponentModel.DescriptionAttribute>().FirstOrDefault();
  1710. if(desc != null)
  1711. {
  1712. writer.WriteComment(desc.Description);
  1713. }
  1714. }
  1715. protected virtual void Load(System.Xml.Linq.XElement xml)
  1716. {
  1717. this.NeedsSave = false;
  1718. int matched = 0;
  1719. int read = 0;
  1720. foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
  1721. {
  1722. object pvalue;
  1723. if(typeof(XmlSettingsBase).IsAssignableFrom(property.PropertyType) || (((pvalue = property.GetValue(this)) != null) && typeof(XmlSettingsBase).IsAssignableFrom(pvalue.GetType())))
  1724. {
  1725. read++;
  1726. var value = xml.Element(property.Name);
  1727. if(value != null)
  1728. {
  1729. var settings = (XmlSettingsBase)property.GetValue(this);
  1730. settings.Load(value);
  1731. if(!settings.NeedsSave)
  1732. matched++;
  1733. settings.SaveAsChild = true;
  1734. }
  1735. }
  1736. else if(!property.IsReadOnly)
  1737. {
  1738. read++;
  1739. var value = xml.Element(property.Name);
  1740. if(value != null)
  1741. {
  1742. if(property.Converter.CanConvertFrom(typeof(string)))
  1743. {
  1744. matched++;
  1745. property.SetValue(this, property.Converter.ConvertFromString(value.Value));
  1746. }
  1747. else
  1748. {
  1749. System.Reflection.MethodBase parser = property.PropertyType.GetMethod("Parse", new Type[] { typeof(string) });
  1750. if(parser == null)
  1751. parser = property.PropertyType.GetConstructor(new Type[] { typeof(string) });
  1752. if(parser != null)
  1753. {
  1754. matched++;
  1755. property.SetValue(this, parser.Invoke(null, new Object[] { value.Value }));
  1756. }
  1757. }
  1758. }
  1759. }
  1760. }
  1761. this.NeedsSave = this.NeedsSave || (matched < read);
  1762. }
  1763. protected virtual void Save(System.Xml.XmlWriter writer)
  1764. {
  1765. foreach(System.ComponentModel.PropertyDescriptor property in System.ComponentModel.TypeDescriptor.GetProperties(this))
  1766. {
  1767. var value = property.GetValue(this);
  1768. WritePropertyDesc(writer, property);
  1769. if(value != null)
  1770. {
  1771. if(typeof(XmlSettingsBase).IsAssignableFrom(value.GetType()))
  1772. {
  1773. var settings = (XmlSettingsBase)property.GetValue(this);
  1774. if((settings != null) && settings.SaveAsChild)
  1775. {
  1776. writer.WriteStartElement(property.Name);
  1777. settings.Save(writer);
  1778. writer.WriteEndElement();
  1779. }
  1780. } else if(!property.IsReadOnly)
  1781. {
  1782. writer.WriteElementString(property.Name, property.Converter.ConvertToString(value));
  1783. }
  1784. }
  1785. else
  1786. {
  1787. WriteCommentedProperty(writer, property.Name);
  1788. }
  1789. }
  1790. }
  1791. }
  1792. /// Custom class to allow string arrays to be read and written to/from settings
  1793. class XmlStringArray : XmlSettingsBase, IEnumerable<string>
  1794. {
  1795. public XmlStringArray(IEnumerable<string> items, string name)
  1796. {
  1797. this._items = items;
  1798. this._name = name;
  1799. SetSaveAsChild(this, true);
  1800. }
  1801. string _name;
  1802. IEnumerable<string> _items;
  1803. protected override void Load(System.Xml.Linq.XElement xml)
  1804. {
  1805. var items = new List<string>();
  1806. foreach(var item in xml.Elements(this._name))
  1807. {
  1808. items.Add(item.Value);
  1809. }
  1810. this._items = items;
  1811. SetNeedsSave(this, false);
  1812. }
  1813. protected override void Save(System.Xml.XmlWriter writer)
  1814. {
  1815. if(this._items == null || !this._items.Any())
  1816. {
  1817. WriteCommentedProperty(writer, this._name);
  1818. return;
  1819. }
  1820. foreach(var item in this._items)
  1821. {
  1822. writer.WriteElementString(this._name, item);
  1823. }
  1824. }
  1825. public IEnumerator<string> GetEnumerator()
  1826. {
  1827. return this._items.GetEnumerator();
  1828. }
  1829. System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
  1830. {
  1831. return this.GetEnumerator();
  1832. }
  1833. }
  1834. /// This is the base class for the standard settings, the main settigns class should inherit from this
  1835. /// one since it provides the methods to interact with the T4 system and EnvDTE. Sub-properties can
  1836. /// just inherit from XmlSettingsBase.
  1837. abstract class XmlSettings : XmlSettingsBase
  1838. {
  1839. protected static T Load<T>(ITextTemplatingEngineHost host) where T : XmlSettings, new()
  1840. {
  1841. T settings = new T();
  1842. settings.Init(host);
  1843. return settings;
  1844. }
  1845. void Init(ITextTemplatingEngineHost host)
  1846. {
  1847. this.TemplateFile = Path.GetFileName(host.TemplateFile);
  1848. this.TemplateFolder = Path.GetDirectoryName(host.TemplateFile);
  1849. // Get the DTE service from the host
  1850. var serviceProvider = host as IServiceProvider;
  1851. if (serviceProvider != null)
  1852. {
  1853. this.DTE = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
  1854. }
  1855. // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
  1856. if (this.DTE == null)
  1857. {
  1858. throw new Exception("T4Build can only execute through the Visual Studio host");
  1859. }
  1860. this.ProjectItem = this.DTE.Solution.FindProjectItem(host.TemplateFile);
  1861. // If the .tt file is not opened, open it
  1862. if (this.ProjectItem.Document == null)
  1863. this.ProjectItem.Open(EnvDTE.Constants.vsViewKindCode);
  1864. this.Project = this.ProjectItem.ContainingProject;
  1865. if (Project == null)
  1866. {
  1867. throw new Exception("Could not find the VS Project containing the T4 file.");
  1868. }
  1869. this.Load();
  1870. this.Init();
  1871. }
  1872. public string TemplateFile { get; private set; }
  1873. public string TemplateFolder { get; private set; }
  1874. public DTE DTE { get; private set; }
  1875. public ProjectItem ProjectItem { get; private set; }
  1876. public Project Project { get; private set; }
  1877. ProjectItem FindProjectItemRecursive(ProjectItems items, string name)
  1878. {
  1879. if(items == null)
  1880. return null;
  1881. foreach(ProjectItem item in items)
  1882. {
  1883. if(item.Name.Equals(name) || item.Name.StartsWith(name + "."))
  1884. return item;
  1885. var found = FindProjectItemRecursive(item.ProjectItems, name);
  1886. if(found != null)
  1887. return found;
  1888. }
  1889. return null;
  1890. }
  1891. protected ProjectItem FindProjectItem(string name)
  1892. {
  1893. return this.FindProjectItemRecursive(this.Project.ProjectItems, name);
  1894. }
  1895. protected string SettingsFile
  1896. {
  1897. get
  1898. {
  1899. return Path.Combine(this.TemplateFolder, string.Concat(this.TemplateFile, ".settings.xml"));
  1900. }
  1901. }
  1902. void Load()
  1903. {
  1904. if(System.IO.File.Exists(this.SettingsFile))
  1905. try
  1906. {
  1907. this.Load(System.Xml.Linq.XElement.Load(this.SettingsFile));
  1908. } catch { throw; }
  1909. }
  1910. public void SaveChanges(Manager manager)
  1911. {
  1912. // Avoid saving if we dont need to;
  1913. if(!this.NeedsSave)
  1914. return;
  1915. if(manager.FileOkToWrite(this.SettingsFile))
  1916. {
  1917. var settings = new System.Xml.XmlWriterSettings
  1918. {
  1919. Indent = true
  1920. };
  1921. using(var writer = System.Xml.XmlWriter.Create(this.SettingsFile, settings))
  1922. {
  1923. writer.WriteStartDocument();
  1924. writer.WriteStartElement(this.GetType().Name);
  1925. this.Save(writer);
  1926. writer.WriteEndElement();
  1927. writer.WriteEndDocument();
  1928. }
  1929. var item = this.ProjectItem.Collection.AddFromFile(this.SettingsFile);
  1930. item.Properties.Item("ItemType").Value = "None";
  1931. } else
  1932. TT.Error("Cannot save settings file! " + this.SettingsFile);
  1933. }
  1934. }
  1935. /*
  1936. Manager.tt from Damien Guard: http://damieng.com/blog/2009/11/06/multiple-outputs-from-t4-made-easy-revisited
  1937. */
  1938. // Manager class records the various blocks so it can split them up
  1939. class Manager
  1940. {
  1941. private class Block
  1942. {
  1943. public String Name;
  1944. public int Start, Length;
  1945. }
  1946. private Block currentBlock;
  1947. private List<Block> files = new List<Block>();
  1948. private Block footer = new Block();
  1949. private Block header = new Block();
  1950. private ITextTemplatingEngineHost host;
  1951. private StringBuilder template;
  1952. protected List<String> generatedFileNames = new List<String>();
  1953. public static Manager Create(ITextTemplatingEngineHost host, StringBuilder template)
  1954. {
  1955. return (host is IServiceProvider) ? new VSManager(host, template) : new Manager(host, template);
  1956. }
  1957. public virtual bool FileOkToWrite(String fileName)
  1958. {
  1959. return true;
  1960. }
  1961. public void KeepGeneratedFile(String name)
  1962. {
  1963. name = Path.Combine(Path.GetDirectoryName(host.TemplateFile), name);
  1964. generatedFileNames.Add(name);
  1965. }
  1966. public void StartNewFile(String name)
  1967. {
  1968. if (name == null)
  1969. throw new ArgumentNullException("name");
  1970. CurrentBlock = new Block { Name = name };
  1971. }
  1972. public void StartFooter()
  1973. {
  1974. CurrentBlock = footer;
  1975. }
  1976. public void StartHeader()
  1977. {
  1978. CurrentBlock = header;
  1979. }
  1980. public void EndBlock()
  1981. {
  1982. if (CurrentBlock == null)
  1983. return;
  1984. CurrentBlock.Length = template.Length - CurrentBlock.Start;
  1985. if (CurrentBlock != header && CurrentBlock != footer)
  1986. files.Add(CurrentBlock);
  1987. currentBlock = null;
  1988. }
  1989. public virtual void Process(bool split)
  1990. {
  1991. if (split)
  1992. {
  1993. EndBlock();
  1994. String headerText = template.ToString(header.Start, header.Length);
  1995. String footerText = template.ToString(footer.Start, footer.Length);
  1996. String outputPath = Path.GetDirectoryName(host.TemplateFile);
  1997. files.Reverse();
  1998. foreach (Block block in files)
  1999. {
  2000. String fileName = Path.Combine(outputPath, block.Name);
  2001. String content = headerText + template.ToString(block.Start, block.Length) + footerText;
  2002. generatedFileNames.Add(fileName);
  2003. CreateFile(fileName, content);
  2004. template.Remove(block.Start, block.Length);
  2005. }
  2006. }
  2007. }
  2008. protected virtual void CreateFile(String fileName, String content)
  2009. {
  2010. if (IsFileContentDifferent(fileName, content))
  2011. File.WriteAllText(fileName, content);
  2012. }
  2013. public virtual String GetCustomToolNamespace(String fileName)
  2014. {
  2015. return null;
  2016. }
  2017. public virtual String DefaultProjectNamespace
  2018. {
  2019. get { return null; }
  2020. }
  2021. protected bool IsFileContentDifferent(String fileName, String newContent)
  2022. {
  2023. return !(File.Exists(fileName) && File.ReadAllText(fileName) == newContent);
  2024. }
  2025. private Manager(ITextTemplatingEngineHost host, StringBuilder template)
  2026. {
  2027. this.host = host;
  2028. this.template = template;
  2029. }
  2030. private Block CurrentBlock
  2031. {
  2032. get { return currentBlock; }
  2033. set
  2034. {
  2035. if (CurrentBlock != null)
  2036. EndBlock();
  2037. if (value != null)
  2038. value.Start = template.Length;
  2039. currentBlock = value;
  2040. }
  2041. }
  2042. private class VSManager : Manager
  2043. {
  2044. private EnvDTE.ProjectItem templateProjectItem;
  2045. private EnvDTE.DTE dte;
  2046. private Action<String> checkOutAction;
  2047. private Action<IEnumerable<String>> projectSyncAction;
  2048. private IVsQueryEditQuerySave2 queryEditSave;
  2049. public override String DefaultProjectNamespace
  2050. {
  2051. get
  2052. {
  2053. return templateProjectItem.ContainingProject.Properties.Item("DefaultNamespace").Value.ToString();
  2054. }
  2055. }
  2056. public override String GetCustomToolNamespace(string fileName)
  2057. {
  2058. return dte.Solution.FindProjectItem(fileName).Properties.Item("CustomToolNamespace").Value.ToString();
  2059. }
  2060. public override void Process(bool split)
  2061. {
  2062. if (templateProjectItem.ProjectItems == null)
  2063. return;
  2064. base.Process(split);
  2065. projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(generatedFileNames, null, null));
  2066. }
  2067. public override bool FileOkToWrite(String fileName)
  2068. {
  2069. CheckoutFileIfRequired(fileName);
  2070. return base.FileOkToWrite(fileName);
  2071. }
  2072. protected override void CreateFile(String fileName, String content)
  2073. {
  2074. if (IsFileContentDifferent(fileName, content))
  2075. {
  2076. CheckoutFileIfRequired(fileName);
  2077. File.WriteAllText(fileName, content);
  2078. }
  2079. }
  2080. internal VSManager(ITextTemplatingEngineHost host, StringBuilder template)
  2081. : base(host, template)
  2082. {
  2083. var hostServiceProvider = (IServiceProvider)host;
  2084. if (hostServiceProvider == null)
  2085. throw new ArgumentNullException("Could not obtain IServiceProvider");
  2086. dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE));
  2087. if (dte == null)
  2088. throw new ArgumentNullException("Could not obtain DTE from host");
  2089. templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile);
  2090. checkOutAction = (String fileName) => dte.SourceControl.CheckOutItem(fileName);
  2091. projectSyncAction = (IEnumerable<String> keepFileNames) => ProjectSync(templateProjectItem, keepFileNames);
  2092. queryEditSave = (IVsQueryEditQuerySave2)hostServiceProvider.GetService(typeof(SVsQueryEditQuerySave));
  2093. }
  2094. private static void ProjectSync(EnvDTE.ProjectItem templateProjectItem, IEnumerable<String> keepFileNames)
  2095. {
  2096. var keepFileNameSet = new HashSet<String>(keepFileNames);
  2097. var projectFiles = new Dictionary<String, EnvDTE.ProjectItem>();
  2098. var originalFilePrefix = Path.GetFileNameWithoutExtension(templateProjectItem.get_FileNames(0)) + ".";
  2099. foreach (EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems)
  2100. projectFiles.Add(projectItem.get_FileNames(0), projectItem);
  2101. // Remove unused items from the project
  2102. foreach (var pair in projectFiles)
  2103. if (!keepFileNames.Contains(pair.Key) && !(Path.GetFileNameWithoutExtension(pair.Key) + ".").StartsWith(originalFilePrefix))
  2104. pair.Value.Delete();
  2105. // Add missing files to the project
  2106. foreach (String fileName in keepFileNameSet)
  2107. if (!projectFiles.ContainsKey(fileName))
  2108. templateProjectItem.ProjectItems.AddFromFile(fileName);
  2109. }
  2110. private void CheckoutFileIfRequired(String fileName)
  2111. {
  2112. if (queryEditSave != null)
  2113. {
  2114. uint pfEditVerdict;
  2115. queryEditSave.QuerySaveFile(fileName, 0, null, out pfEditVerdict);
  2116. }
  2117. else
  2118. {
  2119. var sc = dte.SourceControl;
  2120. if (sc != null && sc.IsItemUnderSCC(fileName) && !sc.IsItemCheckedOut(fileName))
  2121. checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
  2122. }
  2123. }
  2124. }
  2125. }
  2126. /*
  2127. End of Manager.tt
  2128. */
  2129. #>