PageRenderTime 19ms CodeModel.GetById 3ms app.highlight 5ms RepoModel.GetById 0ms app.codeStats 1ms

/Web/T4MVC.tt

https://bitbucket.org/mgreuel/noblog
Unknown | 2473 lines | 2129 code | 344 blank | 0 comment | 0 complexity | 04e577062b8954c3c566f7e74749498f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1<#
   2/*
   3T4MVC Version 3.8.2
   4Find latest version and documentation at http://mvccontrib.codeplex.com/wikipage?title=T4MVC
   5Discuss on StackOverflow or on Codeplex (https://t4mvc.codeplex.com/discussions)
   6
   7T4MVC is part of the MvcContrib project, but in a different Codeplex location (http://t4mvc.codeplex.com)
   8Maintained by David Ebbo, with much feedback from the MVC community (thanks all!)
   9david.ebbo@microsoft.com
  10http://twitter.com/davidebbo
  11http://blog.davidebbo.com/ (previously: http://blogs.msdn.com/davidebb)
  12
  13Related blog posts: http://blogs.msdn.com/davidebb/archive/tags/T4MVC/default.aspx
  14
  15Please use in accordance to the MvcContrib license (http://mvccontrib.codeplex.com/license)
  16*/
  17#>
  18<#@ template language="C#" debug="true" hostspecific="true" #>
  19<#@ assembly name="System.Core" #>
  20<#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
  21<#@ assembly name="EnvDTE" #>
  22<#@ assembly name="EnvDTE80" #>
  23<#@ assembly name="VSLangProj" #>
  24<#@ assembly name="System.Xml" #>
  25<#@ assembly name="System.Xml.Linq" #>
  26<#@ import namespace="System.Collections.Generic" #>
  27<#@ import namespace="System.IO" #>
  28<#@ import namespace="System.Linq" #>
  29<#@ import namespace="System.Text" #>
  30<#@ import namespace="System.Text.RegularExpressions" #>
  31<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
  32<#@ import namespace="EnvDTE" #>
  33<#@ import namespace="EnvDTE80" #>
  34<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
  35<# // To debug, uncomment the next two lines !! 
  36// System.Diagnostics.Debugger.Launch();
  37// System.Diagnostics.Debugger.Break();
  38#>
  39<#settings=MvcSettings.Load(Host);#>
  40<#PrepareDataToRender(this); #>
  41<#var manager = Manager.Create(Host, GenerationEnvironment); #>
  42<#manager.StartHeader(); #>// <auto-generated />
  43// This file was generated by a T4 template.
  44// Don't change it directly as your change would get overwritten.  Instead, make changes
  45// to the .tt file (i.e. the T4 template) and save it to regenerate this file.
  46
  47// Make sure the compiler doesn't complain about missing Xml comments
  48#pragma warning disable 1591
  49#region T4MVC
  50
  51using System;
  52using System.Diagnostics;
  53using System.CodeDom.Compiler;
  54using System.Collections.Generic;
  55using System.Linq;
  56using System.Runtime.CompilerServices;
  57using System.Threading.Tasks;
  58using System.Web;
  59using System.Web.Hosting;
  60using System.Web.Mvc;
  61using System.Web.Mvc.Ajax;
  62using System.Web.Mvc.Html;
  63using System.Web.Routing;
  64using <#=settings.T4MVCNamespace #>;
  65<#foreach (var referencedNamespace in settings.ReferencedNamespaces) { #>
  66using <#=referencedNamespace #>;
  67<#} #>
  68<#manager.EndBlock(); #>
  69
  70[<#= GeneratedCode #>, DebuggerNonUserCode]
  71public static partial class <#=settings.HelpersPrefix #>
  72{
  73<#if (settings.IncludeAreasToken) { #>
  74public static class Areas
  75{
  76<#} #>
  77<#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
  78    static readonly <#=area.Name #>Class s_<#=area.Name #> = new <#=area.Name #>Class();
  79    public static <#=area.Name #>Class <#=EscapeID(area.Namespace) #> { get { return s_<#=area.Name #>; } }
  80<#} #>
  81<#if (settings.IncludeAreasToken) { #>
  82}
  83<#} #>
  84<#foreach (var controller in DefaultArea.GetControllers()) { #>
  85    public static <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
  86<#} #>
  87}
  88
  89namespace <#=settings.T4MVCNamespace #>
  90{
  91<#foreach (var area in Areas.Where(a => !string.IsNullOrEmpty(a.Name))) { #>
  92    [<#= GeneratedCode #>, DebuggerNonUserCode]
  93    public class <#=area.Name #>Class
  94    {
  95        public readonly string Name = "<#=ProcessAreaOrControllerName(area.Name) #>";
  96<#foreach (var controller in area.GetControllers()) { #>
  97        public <#=controller.FullClassName #> <#=controller.Name #> = new <#=controller.FullDerivedClassName #>();
  98<#} #>
  99    }
 100<#} #>
 101}
 102
 103namespace <#=settings.T4MVCNamespace #>
 104{
 105    [<#= GeneratedCode #>, DebuggerNonUserCode]
 106    public class Dummy
 107    {
 108        private Dummy() { }
 109        public static Dummy Instance = new Dummy();
 110    }
 111}
 112
 113<#foreach (var resultType in ResultTypes.Values) { #>
 114[<#= GeneratedCode #>, DebuggerNonUserCode]
 115internal partial class T4MVC_<#=resultType.UniqueName #> : <#=resultType.FullName #>, IT4MVCActionResult
 116{
 117    public T4MVC_<#=resultType.UniqueName #>(string area, string controller, string action, string protocol = null): base(<#resultType.Constructor.WriteNonEmptyParameterValues(true); #>)
 118    {
 119        this.InitMVCT4Result(area, controller, action, protocol);
 120    }
 121    <#foreach (var method in resultType.AbstractMethods) { #> 
 122    <#=method.IsPublic ? "public" : "protected" #> override <#=method.ReturnType#> <#=method.Name #>(<#method.WriteFormalParameters(true); #>) {<# if(method.ReturnType != "void") {#> return default(<#=method.ReturnType#>); <#} #> }
 123    <#} #>
 124
 125    public string Controller { get; set; }
 126    public string Action { get; set; }
 127    public string Protocol { get; set; }
 128    public RouteValueDictionary RouteValueDictionary { get; set; }
 129}
 130<#} #>
 131
 132
 133
 134namespace <#=settings.LinksNamespace #>
 135{
 136<#
 137foreach (string folder in settings.StaticFilesFolders.Concat(GetStaticFilesViewFolders())) {
 138    ProcessStaticFiles(Project, folder);
 139}
 140#>
 141    [<#= GeneratedCode #>, DebuggerNonUserCode]
 142    public static partial class Bundles
 143    {
 144        [<#= GeneratedCode #>, DebuggerNonUserCode]
 145        public static partial class Scripts {}
 146        [<#= GeneratedCode #>, DebuggerNonUserCode]
 147        public static partial class Styles {}
 148    }
 149}
 150
 151<#
 152RenderAdditionalCode();
 153#>
 154<#foreach (var controller in GetAbstractControllers().Where(c => !c.HasDefaultConstructor)) { #>
 155<#manager.StartNewFile(controller.GeneratedFileName); #>
 156namespace <#=controller.Namespace #>
 157{
 158    [<#= GeneratedCode #>, DebuggerNonUserCode]
 159    public partial class <#=controller.ClassName #>
 160    {
 161        protected <#=controller.ClassName #>() { }
 162    }
 163}
 164<#manager.EndBlock(); #>
 165<#} #>
 166
 167<#foreach (var controller in GetControllers()) { #>
 168<#
 169    // Don't generate the file at all if the existing one is up to date
 170    // NOTE: disable this optimization since it doesn't catch view changes! It can be re-enabled later if smarter change detection is added
 171    //if (controller.GeneratedCodeIsUpToDate) {
 172    //    manager.KeepGeneratedFile(controller.GeneratedFileName);
 173    //    continue;
 174    //}
 175#>
 176<#manager.StartNewFile(controller.GeneratedFileName); #>
 177<#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
 178namespace <#=controller.Namespace #>
 179{
 180<#} #>
 181    public <#if (!controller.NotRealController) { #>partial <#} #>class <#=controller.ClassName #>
 182    {
 183<#if (!controller.NotRealController) { #>
 184<#if (!controller.HasExplicitConstructor) { #>
 185        [<#= GeneratedCode #>, DebuggerNonUserCode]
 186        public <#=controller.ClassName #>() { }
 187
 188<#} #>
 189        [<#= GeneratedCode #>, DebuggerNonUserCode]
 190        protected <#=controller.ClassName #>(Dummy d) { }
 191
 192        [<#= GeneratedCode #>, DebuggerNonUserCode]
 193        protected RedirectToRouteResult RedirectToAction(ActionResult result)
 194        {
 195            var callInfo = result.GetT4MVCResult();
 196            return RedirectToRoute(callInfo.RouteValueDictionary);
 197        }
 198
 199        [<#= GeneratedCode #>, DebuggerNonUserCode]
 200        protected RedirectToRouteResult RedirectToAction(Task<ActionResult> taskResult)
 201        {
 202            return RedirectToAction(taskResult.Result);
 203        }
 204
 205        [<#= GeneratedCode #>, DebuggerNonUserCode]
 206        protected RedirectToRouteResult RedirectToActionPermanent(ActionResult result)
 207        {
 208            var callInfo = result.GetT4MVCResult();
 209            return RedirectToRoutePermanent(callInfo.RouteValueDictionary);
 210        }
 211
 212        [<#= GeneratedCode #>, DebuggerNonUserCode]
 213        protected RedirectToRouteResult RedirectToActionPermanent(Task<ActionResult> taskResult)
 214        {
 215            return RedirectToActionPermanent(taskResult.Result);
 216        }
 217
 218<#foreach (var method in controller.ActionMethodsUniqueWithoutParameterlessOverload) { #>
 219        [NonAction]
 220        [<#= GeneratedCode #>, DebuggerNonUserCode]
 221        public virtual <#=method.ReturnTypeFullName #> <#=method.Name #>()
 222        {
 223<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
 224            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
 225            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
 226<#} else { #>
 227            return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
 228<#} #>
 229        }
 230<#} #>
 231<#foreach (var method in controller.CustomActionMethodsUniqueWithoutParameterlessOverload) { #>
 232        [NonAction]
 233        [<#= GeneratedCode #>, DebuggerNonUserCode]
 234        public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>()
 235        {
 236            return new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
 237        }
 238<#} #>
 239<#foreach (var method in controller.CustomActionMethods) { #>
 240        [NonAction]
 241        [<#= GeneratedCode #>, DebuggerNonUserCode]
 242        public virtual <#=method.ReturnTypeFullName #> <#=method.ActionName #>(<#method.WriteFormalParameters(true, true); #>)
 243        {
 244            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
 245<#if (method.Parameters.Count > 0) { #>
 246<#foreach (var p in method.Parameters) { #>
 247            ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
 248<#} #>
 249<#}#>
 250<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
 251            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
 252<#} else { #>
 253            return callInfo;
 254<#} #>
 255        }
 256<#} #>
 257
 258        [<#= GeneratedCode #>, DebuggerNonUserCode]
 259        public <#=controller.ClassName #> Actions { get { return <#=controller.T4MVCControllerFullName #>; } }
 260        [<#= GeneratedCode #>]
 261        public readonly string Area = "<#=ProcessAreaOrControllerName(controller.AreaName) #>";
 262        [<#= GeneratedCode #>]
 263        public readonly string Name = "<#=ProcessAreaOrControllerName(controller.Name) #>";
 264        [<#= GeneratedCode #>]
 265        public const string NameConst = "<#=ProcessAreaOrControllerName(controller.Name) #>";
 266
 267        static readonly ActionNamesClass s_actions = new ActionNamesClass();
 268        [<#= GeneratedCode #>, DebuggerNonUserCode]
 269        public ActionNamesClass ActionNames { get { return s_actions; } }
 270        [<#= GeneratedCode #>, DebuggerNonUserCode]
 271        public class ActionNamesClass
 272        {
 273<#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
 274<#  if (settings.UseLowercaseRoutes) { #>
 275            public readonly string <#=method.ActionName #> = (<#=method.ActionNameValueExpression #>).ToLowerInvariant();
 276<#  } else { #>
 277            public readonly string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
 278<#  }
 279} #>
 280        }
 281
 282<#
 283// Issue: we can't honor UseLowercaseRoutes here because ToLowerInvariant() is not valid in constants!
 284if (!settings.UseLowercaseRoutes) { #>
 285        [<#= GeneratedCode #>, DebuggerNonUserCode]
 286        public class ActionNameConstants
 287        {
 288<#foreach (var method in controller.ActionMethodsWithUniqueNames) { #>
 289            public const string <#=method.ActionName #> = <#=method.ActionNameValueExpression #>;
 290<#
 291} #>
 292        }
 293
 294<#}
 295} #>
 296
 297<#if (settings.GenerateParamsForActionMethods && !settings.GenerateParamsAsConstantsForActionMethods){
 298foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
 299        static readonly ActionParamsClass_<#=group.Key #> s_params_<#=group.Key #> = new ActionParamsClass_<#=group.Key #>();
 300        [<#= GeneratedCode #>, DebuggerNonUserCode]
 301        public ActionParamsClass_<#=group.Key #> <#=group.Key + settings.ParamsPropertySuffix #> { get { return s_params_<#=group.Key #>; } }
 302        [<#= GeneratedCode #>, DebuggerNonUserCode]
 303        public class ActionParamsClass_<#=group.Key #>
 304        {
 305<#foreach (var param in group) { #>
 306<#  if (settings.UseLowercaseRoutes) { #>
 307            public readonly string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
 308<#  } else { #>
 309            public readonly string <#=param.Name #> = <#=param.RouteNameExpression #>;
 310<#  }
 311} #>
 312        }
 313<# } #>
 314<#} #>
 315<#if (settings.GenerateParamsAsConstantsForActionMethods){
 316
 317foreach (var group in controller.UniqueParameterNamesGroupedByActionName) if (group.Any()) { #>
 318        [<#= GeneratedCode #>, DebuggerNonUserCode]
 319        public class <#=group.Key + settings.ParamsPropertySuffix#>
 320        {
 321<#foreach (var param in group) { #>
 322<#  if (settings.UseLowercaseRoutes) { #>
 323            public const string <#=param.Name #> = (<#=param.RouteNameExpression #>).ToLowerInvariant();
 324<#  } else { #>
 325            public const string <#=param.Name #> = <#=param.RouteNameExpression #>;
 326<#  }
 327} #>
 328        }
 329<# } #>
 330<#} #>
 331        static readonly ViewsClass s_views = new ViewsClass();
 332        [<#= GeneratedCode #>, DebuggerNonUserCode]
 333        public ViewsClass Views { get { return s_views; } }
 334        [<#= GeneratedCode #>, DebuggerNonUserCode]
 335        public class ViewsClass
 336        {
 337<#RenderControllerViews(controller);#>
 338        }
 339    }
 340
 341<#if (!controller.NotRealController) { #>
 342    [<#= GeneratedCode #>, DebuggerNonUserCode]
 343    public partial class <#=controller.DerivedClassName #> : <#=controller.FullClassName #>
 344    {
 345        public <#=controller.DerivedClassName #>() : base(Dummy.Instance) { }
 346
 347<#foreach (var method in controller.ActionMethods.Where(m => !m.IsCustomReturnType)) { #>
 348        [NonAction]
 349        partial void <#=method.Name #>Override(T4MVC_<#=method.ReturnTypeUniqueName #> callInfo<#if (method.Parameters.Count > 0) { #>, <#method.WriteFormalParameters(true); #><#}#>);
 350
 351        [NonAction]
 352        public override <#=method.ReturnTypeFullName #> <#=method.Name #>(<#method.WriteFormalParameters(true); #>)
 353        {
 354            var callInfo = new T4MVC_<#=method.ReturnTypeUniqueName #>(Area, Name, ActionNames.<#=method.ActionName #><# if (method.ActionUrlHttps) {#>, "https"<#}#>);
 355<#if (method.Parameters.Count > 0) { #>
 356<#foreach (var p in method.Parameters) { #>
 357            ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, <#=p.RouteNameExpression #>, <#=p.Name #>);
 358<#} #>
 359<#}#>
 360            <#=method.Name #>Override(callInfo<#if (method.Parameters.Count > 0) { #><#foreach (var p in method.Parameters) { #>, <#=p.Name #><#}}#>);
 361<#if (method.ReturnTypeFullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>") { #>
 362            return System.Threading.Tasks.Task.FromResult(callInfo as ActionResult);
 363<#} else { #>
 364            return callInfo;
 365<#} #>
 366        }
 367
 368<#} #>
 369    }
 370<#} #>
 371<#if (!String.IsNullOrEmpty(controller.Namespace)) { #>
 372}
 373<#} #>
 374
 375<#manager.EndBlock(); #>
 376<#} #>
 377
 378
 379<# if (settings.ExplicitHtmlHelpersForPartials) {
 380        manager.StartNewFile("T4MVC.ExplicitExtensions.cs"); #>
 381
 382namespace System.Web.Mvc {
 383    [<#= GeneratedCode #>]
 384    public static class HtmlHelpersForExplicitPartials
 385    {
 386    <#  
 387        foreach(var partialView in GetPartials()) {
 388            string partialName = partialView.Key;
 389            string partialPath = partialView.Value;
 390            string partialRenderMethod = string.Format(settings.ExplicitHtmlHelpersForPartialsFormat, partialName);
 391    #> 
 392        ///<summary>
 393        ///Render the <b><#= partialName #></b> partial.
 394        ///</summary>
 395        public static void <#= partialRenderMethod #>(this HtmlHelper html) {
 396            html.RenderPartial("<#= partialPath #>");
 397        }
 398        
 399        ///<summary>
 400        ///Render the <b><#= partialName #></b> partial.
 401        ///</summary>
 402        public static void <#= partialRenderMethod #>(this HtmlHelper html, object model) {
 403            html.RenderPartial("<#= partialPath #>", model);
 404        }
 405    <# } #> 
 406    }
 407}
 408<#  manager.EndBlock(); #>
 409<#  }   #>
 410
 411<#manager.StartFooter(); #>
 412#endregion T4MVC
 413#pragma warning restore 1591
 414<#manager.EndBlock(); #>
 415<#settings.SaveChanges(manager); #>
 416<#manager.Process(settings.SplitIntoMultipleFiles); #>
 417
 418<#@ Include File="T4MVC.tt.hooks.t4" #>
 419
 420<#+ 
 421static MvcSettings settings;
 422const string ControllerSuffix = "Controller";
 423
 424static DTE Dte;
 425static Project Project;
 426static string AppRoot;
 427static HashSet<AreaInfo> Areas;
 428static AreaInfo DefaultArea;
 429static Dictionary<string, ResultTypeInfo> ResultTypes;
 430static TextTransformation TT;
 431static string T4FileName;
 432static string T4Folder;
 433static string GeneratedCode = @"GeneratedCode(""T4MVC"", ""2.0"")";
 434static Microsoft.CSharp.CSharpCodeProvider codeProvider = new Microsoft.CSharp.CSharpCodeProvider();
 435List<string> virtualPathesForStaticFiles = new List<string>();
 436
 437IEnumerable<ControllerInfo> GetControllers()
 438{
 439    var controllers = new List<ControllerInfo>();
 440
 441    foreach (var area in Areas)
 442    {
 443        controllers.AddRange(area.GetControllers());
 444    }
 445
 446    return controllers;
 447}
 448
 449IEnumerable<ControllerInfo> GetAbstractControllers()
 450{
 451    var controllers = new List<ControllerInfo>();
 452
 453    foreach (var area in Areas)
 454    {
 455        controllers.AddRange(area.GetAbstractControllers());
 456    }
 457
 458    return controllers;
 459}
 460
 461IDictionary<string, string> GetPartials()
 462{
 463    var parts = GetControllers()
 464        .Select(m => m.ViewsFolder)
 465        .SelectMany(m => m.Views)
 466        .Where(m => IsPartialView(m.Value));
 467
 468    var partsDic = new Dictionary<string, KeyValuePair<string, string>>();
 469
 470    foreach(var part in parts)
 471    {
 472        string viewName = Sanitize(part.Key);
 473        
 474        // Check if we already have a partial view by that name (e.g. if two Views folders have the same ascx)
 475        int keyCollisionCount = partsDic.Where(m => m.Key == viewName || m.Value.Key == viewName).Count();
 476
 477        if (keyCollisionCount > 0)
 478        {
 479            // Append a numbered suffix to avoid the conflict
 480            partsDic.Add(viewName + keyCollisionCount.ToString(), part);
 481        }
 482        else
 483        {
 484            partsDic.Add(viewName, part);
 485        }
 486    }
 487
 488    return partsDic.ToDictionary(k => k.Key, v => v.Value.Value);
 489}
 490
 491bool IsPartialView(string viewFilePath)
 492{
 493    string viewFileName = Path.GetFileName(viewFilePath);
 494
 495    if (viewFileName.EndsWith(".ascx")) return true;
 496    
 497    if ((viewFileName.EndsWith(".cshtml") || viewFileName.EndsWith(".vbhtml")) && viewFileName.StartsWith("_"))
 498    {
 499        return true;
 500    }
 501
 502    return false;
 503}
 504
 505void PrepareDataToRender(TextTransformation tt)
 506{
 507    TT = tt;
 508    T4FileName = Path.GetFileName(Host.TemplateFile);
 509    T4Folder = Path.GetDirectoryName(Host.TemplateFile);
 510    Areas = new HashSet<AreaInfo>();
 511    ResultTypes = new Dictionary<string, ResultTypeInfo>();
 512
 513    // Get the DTE service from the host
 514    var serviceProvider = Host as IServiceProvider;
 515    if (serviceProvider != null)
 516    {
 517        Dte = (EnvDTE.DTE)serviceProvider.GetService(typeof(EnvDTE.DTE));
 518    }
 519
 520    // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
 521    if (Dte == null)
 522    {
 523        throw new Exception("T4MVC can only execute through the Visual Studio host");
 524    }
 525
 526    Project = GetProjectContainingT4File(Dte);
 527
 528    if (Project == null)
 529    {
 530        Error("Could not find the VS Project containing the T4 file.");
 531        return;
 532    }
 533
 534    // Get the path of the root folder of the app
 535    AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
 536
 537    ProcessAreas(Project);
 538}
 539
 540Project GetProjectContainingT4File(DTE dte)
 541{
 542
 543    // Find the .tt file's ProjectItem
 544    ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile);
 545
 546    // If the .tt file is not opened, open it
 547    if (projectItem.Document == null)
 548        projectItem.Open(EnvDTE.Constants.vsViewKindCode);
 549
 550    return projectItem.ContainingProject;
 551}
 552
 553void ProcessAreas(Project project)
 554{
 555    // Process the default area
 556    ProcessArea(project.ProjectItems, null);
 557
 558    // Get the Areas folder
 559    ProjectItem areaProjectItem = GetProjectItem(project, settings.AreasFolder);
 560    
 561    // Process areas folder
 562    if (areaProjectItem != null)
 563    {
 564        foreach (ProjectItem item in areaProjectItem.ProjectItems)
 565        {
 566            if (IsFolder(item))
 567            {
 568                ProcessArea(item.ProjectItems, item.Name);
 569            }
 570        }
 571    }
 572
 573    // Process portable areas
 574    foreach (string portableArea in settings.PortableAreas)
 575    {
 576        ProjectItem portableAreaProjectItem = GetProjectItem(project, portableArea);
 577
 578        if (portableAreaProjectItem == null)
 579            return;
 580
 581        if (IsFolder(portableAreaProjectItem))
 582        {
 583            ProcessArea(portableAreaProjectItem.ProjectItems, portableAreaProjectItem.Name);
 584        }
 585    }
 586}
 587
 588void ProcessArea(ProjectItems areaFolderItems, string name)
 589{
 590    var area = new AreaInfo() { Name = name };
 591    ProcessAreaControllers(areaFolderItems, area);
 592    ProcessAreaViews(areaFolderItems, area);
 593    Areas.Add(area);
 594
 595    if (String.IsNullOrEmpty(name))
 596        DefaultArea = area;
 597}
 598
 599void ProcessAreaControllers(ProjectItems areaFolderItems, AreaInfo area)
 600{
 601    // Get area Controllers folder
 602    ProjectItem controllerProjectItem = GetProjectItem(areaFolderItems, settings.ControllersFolder);
 603    if (controllerProjectItem == null)
 604        return;
 605
 606    ProcessControllersRecursive(controllerProjectItem, area);
 607}
 608
 609void ProcessAreaViews(ProjectItems areaFolderItems, AreaInfo area)
 610{
 611    // Get area Views folder
 612    ProjectItem viewsProjectItem = GetProjectItem(areaFolderItems, settings.ViewsRootFolder);
 613    if (viewsProjectItem == null)
 614        return;
 615
 616    ProcessAllViews(viewsProjectItem, area);
 617}
 618
 619void ProcessControllersRecursive(ProjectItem projectItem, AreaInfo area)
 620{
 621
 622    // Recurse into all the sub-items (both files and folder can have some - e.g. .tt files)
 623    foreach (ProjectItem item in projectItem.ProjectItems)
 624    {
 625        ProcessControllersRecursive(item, area);
 626    }
 627
 628    if (projectItem.FileCodeModel != null)
 629    {
 630        DateTime controllerLastWriteTime = File.GetLastWriteTime(projectItem.get_FileNames(0));
 631        foreach (var type in projectItem.FileCodeModel.CodeElements.OfType<CodeClass2>())
 632        {
 633            ProcessControllerType(type, area, controllerLastWriteTime);
 634        }
 635        // Process all the elements that are namespaces
 636        foreach (var ns in projectItem.FileCodeModel.CodeElements.OfType<CodeNamespace>())
 637        {
 638            foreach (var type in ns.Members.OfType<CodeClass2>())
 639            {
 640                ProcessControllerType(type, area, controllerLastWriteTime);
 641            }
 642        }
 643    }
 644}
 645
 646void ProcessControllerType(CodeClass2 type, AreaInfo area, DateTime controllerLastWriteTime)
 647{
 648    // Only process controllers
 649    if (!IsController(type))
 650        return;
 651
 652    // Don't process generic classes (their concrete derived classes will be processed)
 653    if (type.IsGeneric)
 654        return;
 655
 656    //Ignore references to controllers we create
 657    if(area.Controllers.Any(c => c.DerivedClassName == type.Name))
 658        return;
 659
 660    // Make sure the class is partial
 661    if (type.ClassKind != vsCMClassKind.vsCMClassKindPartialClass)
 662    {
 663        try
 664        {
 665            type.ClassKind = vsCMClassKind.vsCMClassKindPartialClass;
 666        }
 667        catch
 668        {
 669            // If we couldn't make it partial, give a warning and skip it
 670            Warning(String.Format("{0} was not able to make the class {1} partial. Please change it manually if possible", T4FileName, type.Name));
 671            return;
 672        }
 673        Warning(String.Format("{0} changed the class {1} to be partial", T4FileName, type.Name));
 674    }
 675
 676    // Collect misc info about the controller class and add it to the collection
 677    var controllerInfo = new ControllerInfo
 678    {
 679        Area = area,
 680        Namespace = type.Namespace != null ? type.Namespace.Name : String.Empty,
 681        ClassName = type.Name
 682    };
 683
 684    //Filter references to controllers we create
 685    foreach(var derived in area.Controllers.Where(c => c.ClassName == controllerInfo.DerivedClassName).ToArray())
 686        area.Controllers.Remove(derived);
 687
 688    // Check if the controller has changed since the generated file was last created
 689    DateTime lastGenerationTime = File.GetLastWriteTime(controllerInfo.GeneratedFileFullPath);
 690    if (lastGenerationTime > controllerLastWriteTime)
 691    {
 692        controllerInfo.GeneratedCodeIsUpToDate = true;
 693    }
 694
 695    // Either process new ControllerInfo or integrate results into existing object for partially defined controllers
 696    var target = area.Controllers.Add(controllerInfo) ? controllerInfo : area.Controllers.First(c => c.Equals(controllerInfo));
 697    target.HasExplicitConstructor |= HasExplicitConstructor(type);
 698    target.HasExplicitDefaultConstructor |= HasExplicitDefaultConstructor(type);
 699
 700    if (type.IsAbstract)
 701    {
 702        // If it's abstract, set a flag and don't process action methods (derived classes will)
 703        target.IsAbstract = true;
 704    }
 705    else
 706    {
 707        // Process all the action methods in the controller
 708        ProcessControllerActionMethods(target, type);
 709    }
 710}
 711
 712void ProcessControllerActionMethods(ControllerInfo controllerInfo, CodeClass2 current)
 713{
 714
 715    bool isAsyncController = IsAsyncController(current);
 716
 717    // We want to process not just the controller class itself, but also its parents, as they
 718    // may themselves define actions
 719    for (CodeClass2 type = current; type != null && type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
 720    {
 721
 722        // If the type doesn't come from this project, some actions on it will fail. Try to get a real project type if possible.
 723        if (type.InfoLocation != vsCMInfoLocation.vsCMInfoLocationProject)
 724        {
 725            // Go through all the projects in the solution
 726            for (int i = 1; i <= Dte.Solution.Projects.Count; i++)
 727            {
 728                Project prj = null;
 729                try
 730                {
 731                    prj = Dte.Solution.Projects.Item(i);
 732                }
 733                catch (System.Runtime.Serialization.SerializationException)
 734                {
 735                    // Some project types (that we don't care about) cause a strange exception, so ingore it
 736                    continue;
 737                }
 738
 739                // Skip it if it's the current project or doesn't have a code model
 740                try
 741                {
 742                   if (prj == Project || prj.CodeModel == null)
 743                      continue;
 744                }
 745                catch (System.NotImplementedException)
 746                {
 747                   // Installer project does not implement CodeModel property
 748                   continue;
 749                }
 750
 751                try
 752                {
 753                    // If we can get a local project type, use it instead of the original
 754                    var codeType = prj.CodeModel.CodeTypeFromFullName(type.FullName);
 755                    if (codeType != null && codeType.InfoLocation == vsCMInfoLocation.vsCMInfoLocationProject)
 756                    {
 757                        type = (CodeClass2)codeType;
 758                        break;
 759                    }
 760                }
 761                catch (System.ArgumentException)
 762                {
 763                    // CodeTypeFromFullName throws when called on VB projects with a type it doesn't know
 764                    // (instead of returning null), so ignore those exceptions (See http://t4mvc.codeplex.com/workitem/7)
 765                }
 766            }
 767        }
 768
 769        foreach (CodeFunction2 method in GetMethods(type))
 770        {
 771            // Ignore non-public methods
 772            if (method.Access != vsCMAccess.vsCMAccessPublic)
 773                continue;
 774
 775            // Ignore methods that are marked as not being actions
 776            if (GetAttribute(method.Attributes, "System.Web.Mvc.NonActionAttribute") != null)
 777                continue;
 778
 779            // Ignore methods that are marked as Obsolete
 780            if (GetAttribute(method.Attributes, "System.ObsoleteAttribute") != null)
 781                continue;
 782
 783            // Ignore generic methods
 784            if (method.IsGeneric)
 785                continue;
 786
 787            if(isAsyncController && settings.SupportAsyncActions && (method.Type.TypeKind == vsCMTypeRef.vsCMTypeRefVoid) && method.Name.EndsWith("Async")) 
 788            {
 789                //Async methods return void and there could be multiple matching Completed methods, so we will use
 790                //the generic ActionResult as the return type for the method.
 791                var resultType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
 792                // If we haven't yet seen this return type, keep track of it
 793                if (!ResultTypes.ContainsKey(resultType.AsFullName))
 794                {
 795                    var resTypeInfo = new ResultTypeInfo(resultType);
 796                    ResultTypes[resultType.AsFullName] = resTypeInfo;
 797                }
 798
 799                // Collect misc info about the action method and add it to the collection
 800                controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current, resultType));
 801
 802                continue;
 803            }
 804
 805            // This takes care of avoiding generic types which cause method.Type.CodeType to blow up
 806            if (method.Type.TypeKind != vsCMTypeRef.vsCMTypeRefCodeType || !(method.Type.CodeType is CodeClass2))
 807                continue;
 808
 809            // We only support action methods that return an ActionResult and Task<ActionResult> derived types
 810            if (!method.Type.CodeType.get_IsDerivedFrom("System.Web.Mvc.ActionResult") && method.Type.CodeType.FullName !="System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
 811            {
 812                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));
 813                continue;
 814            }
 815
 816            // Ignore async completion methods as they can't really be used in T4MVC, and can cause issues.
 817            // See http://stackoverflow.com/questions/5419173/t4mvc-asynccontroller
 818            if (isAsyncController && method.Name.EndsWith("Completed", StringComparison.OrdinalIgnoreCase))
 819                continue;
 820
 821            var methodType = method.Type;
 822            if(method.Type.CodeType.FullName == "System.Threading.Tasks.Task<System.Web.Mvc.ActionResult>")
 823                methodType = Project.CodeModel.CreateCodeTypeRef("System.Web.Mvc.ActionResult");
 824
 825            // If we haven't yet seen this return type, keep track of it
 826            var resTypeInfo2 = new ResultTypeInfo(methodType);
 827            if (!ResultTypes.ContainsKey(resTypeInfo2.FullName))
 828            {
 829                ResultTypes[resTypeInfo2.FullName] = resTypeInfo2;
 830            }
 831
 832            // Make sure the method is virtual
 833            if (!method.CanOverride && method.OverrideKind != vsCMOverrideKind.vsCMOverrideKindOverride)
 834            {
 835                try
 836                {
 837                    method.CanOverride = true;
 838                }
 839                catch
 840                {
 841                    // If we couldn't make it virtual, give a warning and skip it
 842                    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));
 843                    continue;
 844                }
 845                Warning(String.Format("{0} changed the action method {1}.{2} to be virtual", T4FileName, type.Name, method.Name));
 846            }
 847
 848            // Collect misc info about the action method and add it to the collection
 849            controllerInfo.ActionMethods.Add(new ActionMethodInfo(method, current));
 850        }
 851    }
 852}
 853
 854void ProcessAllViews(ProjectItem viewsProjectItem, AreaInfo area)
 855{
 856    // Go through all the sub-folders in the Views folder
 857    foreach (ProjectItem item in viewsProjectItem.ProjectItems)
 858    {
 859
 860        // We only care about sub-folders, not files
 861        if (!IsFolder(item))
 862            continue;
 863
 864        // Find the controller for this view folder
 865        ControllerInfo controller = area.Controllers.SingleOrDefault(c => c.Name.Equals(item.Name, StringComparison.OrdinalIgnoreCase));
 866
 867        if (controller == null)
 868        {
 869            // If it doesn't match a controller, treat as a pseudo-controller for consistency
 870            controller = new ControllerInfo
 871            {
 872                Area = area,
 873                NotRealController = true,
 874                Namespace = MakeClassName(settings.T4MVCNamespace, area.Name),
 875                ClassName = item.Name + ControllerSuffix
 876            };
 877            area.Controllers.Add(controller);
 878        }
 879
 880        AddViewsRecursive(item.ProjectItems, controller.ViewsFolder);
 881    }
 882}
 883
 884void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder)
 885{
 886  AddViewsRecursive(items, viewsFolder, false);
 887}
 888
 889void AddViewsRecursive(ProjectItems items, ViewsFolderInfo viewsFolder, bool useNonQualifiedViewNames)
 890{
 891    // Go through all the files in the subfolder to get the view names
 892    foreach (ProjectItem item in items)
 893    {
 894        if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFile)
 895        {
 896            // Ignore some extensions that are normally not views
 897            if (settings.ExcludedViewExtensions.Any(extension => Path.GetExtension(item.Name).Equals(extension, StringComparison.OrdinalIgnoreCase)))
 898                continue;
 899
 900            viewsFolder.AddView(item, useNonQualifiedViewNames);
 901        }
 902        else if (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder)
 903        {
 904            string folderName = Path.GetFileName(item.Name);
 905            if (folderName.Equals("App_LocalResources", StringComparison.OrdinalIgnoreCase))
 906                continue;
 907            // Use simple view names if we're already in that mode, or if the folder name is in the collection
 908            bool folderShouldUseNonQualifiedViewNames = useNonQualifiedViewNames || settings.NonQualifiedViewFolders.Contains(folderName, StringComparer.OrdinalIgnoreCase);
 909            var subViewFolder = new ViewsFolderInfo() { Name = folderName };
 910            viewsFolder.SubFolders.Add(subViewFolder);
 911            AddViewsRecursive(item.ProjectItems, subViewFolder, folderShouldUseNonQualifiedViewNames);
 912        }
 913    }
 914}
 915
 916void RenderControllerViews(ControllerInfo controller)
 917{
 918    PushIndent("            ");
 919    RenderViewsRecursive(controller.ViewsFolder, controller);
 920    PopIndent();
 921}
 922
 923void RenderViewsRecursive(ViewsFolderInfo viewsFolder, ControllerInfo controller)
 924{
 925    if(!viewsFolder.HasNonQualifiedViewNames)
 926    {
 927#>
 928static readonly _ViewNamesClass s_ViewNames = new _ViewNamesClass();
 929public _ViewNamesClass ViewNames { get { return s_ViewNames; } }
 930public class _ViewNamesClass
 931{
 932<#+
 933    PushIndent("    ");
 934    foreach (var viewPair in viewsFolder.Views)
 935    {
 936        WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Key + "\";");
 937    }
 938    PopIndent();
 939#>
 940}
 941<#+}
 942    // For each view, generate a readonly string
 943    foreach (var viewPair in viewsFolder.Views)
 944    {
 945        WriteLine("public readonly string " + EscapeID(Sanitize(viewPair.Key)) + " = \"" + viewPair.Value + "\";");
 946    }
 947
 948    // For each sub folder, generate a class and recurse
 949    foreach (var subFolder in viewsFolder.SubFolders)
 950    {
 951        string name = Sanitize(subFolder.Name);
 952        string className = "_" + name;
 953
 954        // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
 955        // http://mvccontrib.codeplex.com/workitem/7153
 956        if (name == Sanitize(viewsFolder.Name))
 957        {
 958            className += "_";
 959        }#>
 960static readonly <#=className#>Class s_<#=name#> = new <#=className#>Class();
 961public <#=className#>Class <#=EscapeID(name)#> { get { return s_<#=name#>; } }
 962[<#= GeneratedCode #>, DebuggerNonUserCode]
 963public partial class <#=className#>Class
 964{
 965<#+
 966PushIndent("    ");
 967RenderViewsRecursive(subFolder, controller);
 968PopIndent();
 969
 970WriteLine("}");
 971    }
 972}
 973
 974IEnumerable<string> GetStaticFilesViewFolders()
 975{
 976    if (settings.AddAllViewsFoldersToStaticFilesFolders)
 977    {
 978        foreach (var area in Areas)
 979        {
 980            yield return area.Name == null ?
 981                settings.ViewsRootFolder :
 982                settings.AreasFolder + "\\" + area.Name + "\\" + settings.ViewsRootFolder;
 983        }
 984    }
 985}
 986
 987void ProcessStaticFiles(Project project, string folder)
 988{
 989    ProjectItem folderProjectItem = GetProjectItem(project, folder);
 990    if (folderProjectItem != null)
 991    {
 992        var rootPath = "~";
 993        if (folder.Contains("\\"))
 994        {
 995            rootPath += "/" + folder.Replace("\\", "/");
 996            rootPath = rootPath.Substring(0, rootPath.LastIndexOf("/"));
 997        }
 998        ProcessStaticFilesRecursive(folderProjectItem, rootPath);
 999    }
1000}
1001
1002void ProcessStaticFilesRecursive(ProjectItem projectItem, string path)
1003{
1004    int nestedLevel = BuildClassStructureForProvidedPath(path);
1005    ProcessStaticFilesRecursive(projectItem, path, new HashSet<String>());
1006    for(int i = 0; i < nestedLevel; ++i) {#>
1007}
1008<#+
1009    PopIndent();
1010    }
1011}
1012
1013void ProcessStaticFilesRecursive(ProjectItem projectItem, string path, HashSet<String> nameSet)
1014{
1015    // The passed in HashSet is to guarantee uniqueness with our parent and siblings
1016    string name = SanitizeWithNoConflicts(projectItem.Name, nameSet);
1017
1018    // This HashSet is to guarantee uniqueness of our direct children
1019    // We add our own name to it to avoid class name conflicts (http://mvccontrib.codeplex.com/workitem/7153)
1020    var childrenNameSet = new HashSet<String>();
1021    childrenNameSet.Add(name);
1022
1023    if (IsFolder(projectItem))
1024    {
1025        #>
1026    [<#= GeneratedCode #>, DebuggerNonUserCode]
1027    public static class <#=EscapeID(name)#> {
1028        private const string URLPATH = "<#=path#>/<#=projectItem.Name#>";
1029        public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
1030        public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
1031<#+
1032PushIndent("    ");
1033
1034// Recurse into all the items in the folder
1035foreach (ProjectItem item in projectItem.ProjectItems)
1036{
1037    ProcessStaticFilesRecursive(
1038        item,
1039        path + "/" + projectItem.Name,
1040        childrenNameSet);
1041}
1042
1043PopIndent();
1044#>
1045    }
1046
1047<#+
1048}
1049    else { #>
1050<#+
1051if (!settings.ExcludedStaticFileExtensions.Any(extension => projectItem.Name.EndsWith(extension, StringComparison.OrdinalIgnoreCase))) {
1052    // if it's a Typescript file
1053    if (projectItem.Name.EndsWith(".ts")) {
1054        string tsJavascriptName =  projectItem.Name.Replace(".ts", ".js");
1055        string minifiedName = projectItem.Name.Replace(".ts", ".min.js");
1056        if (AddTimestampToStaticLink(projectItem)) { #>
1057    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=minifiedName#>") : Url("<#=tsJavascriptName#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=tsJavascriptName#>");
1058        <#+} else {#>
1059    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=tsJavascriptName#>");
1060<#+}  #>
1061<#+}
1062    // if it's a non-minified javascript file
1063    else if (projectItem.Name.EndsWith(".js") && !projectItem.Name.EndsWith(".min.js")) {
1064        string minifiedName = projectItem.Name.Replace(".js", ".min.js");
1065        if (AddTimestampToStaticLink(projectItem)) { #>
1066    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#>");
1067        <#+} else {#>
1068    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
1069<#+}  #>
1070<#+}
1071    else if (projectItem.Name.EndsWith(".css") && !projectItem.Name.EndsWith(".min.css")) { 
1072        string minifiedName = projectItem.Name.Replace(".css", ".min.css");
1073        if (AddTimestampToStaticLink(projectItem)) { #>
1074    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#>");
1075        <#+} else {#>
1076    public static readonly string <#=name#> = T4MVCHelpers.IsProduction() && T4Extensions.FileExists(URLPATH + "/<#=minifiedName#>") ? Url("<#=minifiedName#>") : Url("<#=projectItem.Name#>");
1077        <#+}  #> 
1078<#+}
1079    else if (AddTimestampToStaticLink(projectItem)) { #>
1080    public static readonly string <#=name#> = Url("<#=projectItem.Name#>")+"?"+T4MVCHelpers.TimestampString(URLPATH + "/<#=projectItem.Name#>");
1081<#+}
1082    else { #>
1083    public static readonly string <#=name#> = Url("<#=projectItem.Name#>");
1084<#+}
1085} #>
1086<#+
1087// Non folder items may also have children (virtual folders, Class.cs -> Class.Designer.cs, template output)
1088// Just register them on the same path as their parent item
1089foreach (ProjectItem item in projectItem.ProjectItems)
1090{
1091    ProcessStaticFilesRecursive(item, path, childrenNameSet);
1092}
1093    }
1094}
1095
1096int BuildClassStructureForProvidedPath(string path)
1097{
1098    var folders = path.Split(new char[] {'/', '~'}, StringSplitOptions.RemoveEmptyEntries);
1099    var parentFolder = String.Empty;
1100    var currentPath = "~";
1101    foreach(var folder in folders)
1102    {
1103        currentPath += "/" + folder;
1104        string className = EscapeID(Sanitize(folder));
1105        // If the folder name is the same as the parent, add a modifier to avoid class name conflicts
1106        // http://mvccontrib.codeplex.com/workitem/7153
1107        if (parentFolder == folder)
1108        {
1109            className += "_";
1110        }
1111
1112        if(!virtualPathesForStaticFiles.Contains(currentPath))
1113        {
1114            virtualPathesForStaticFiles.Add(currentPath);#>
1115
1116    [<#= GeneratedCode #>, DebuggerNonUserCode]
1117    public static partial class <#=className #> {
1118        private const string URLPATH = "<#=currentPath#>";
1119        public static string Url() { return T4MVCHelpers.ProcessVirtualPath(URLPATH); }
1120        public static string Url(string fileName) { return T4MVCHelpers.ProcessVirtualPath(URLPATH + "/" + fileName); }
1121<#+     } else {
1122            #>
1123
1124    public static partial class <#=className #> {
1125<#+     }
1126        PushIndent("    ");
1127        parentFolder = folder;
1128    }
1129    return folders.Length;
1130}
1131
1132ProjectItem GetProjectItem(Project project, string name)
1133{
1134    return GetProjectItem(project.ProjectItems, name);
1135}
1136
1137ProjectItem GetProjectItem(ProjectItems items, string subPath)
1138{
1139
1140    ProjectItem current = null;
1141    foreach (string name in subPath.Split('\\'))
1142    {
1143        try
1144        {
1145            // ProjectItems.Item() throws when it doesn't exist, so catch the exception
1146            // to return null instead.
1147            current = items.Item(name);
1148        }
1149        catch
1150        {
1151            // If any chunk couldn't be found, fail
1152            return null;
1153        }
1154        items = current.ProjectItems;
1155    }
1156
1157    return current;
1158}
1159
1160static bool IsController(CodeClass2 type)
1161{
1162    // Ignore any class which name doesn't end with "Controller"
1163    if (!type.FullName.EndsWith(ControllerSuffix)) return false;
1164
1165    for (; type.FullName != "System.Web.Mvc.Controller"; type = (CodeClass2)type.Bases.Item(1))
1166    {
1167        if (type.Bases.Count == 0)
1168            return false;
1169    }
1170    return true;
1171}
1172
1173static bool IsAsyncController(CodeClass2 type)
1174{
1175    for (; type.FullName != "System.Web.Mvc.AsyncController"; type = (CodeClass2)type.Bases.Item(1))
1176    {
1177        if (type.Bases.Count == 0)
1178            return false;
1179    }
1180    return true;
1181}
1182
1183static string GetVirtualPath(ProjectItem item)
1184{
1185    string fileFullPath = item.get_FileNames(0);
1186
1187    // Ignore files that are not under the app root (e.g. they could be linked files)
1188    if (!fileFullPath.StartsWith(AppRoot, StringComparison.OrdinalIgnoreCase))
1189        return null;
1190
1191    // Make a virtual path from the physical path
1192    return "~/" + fileFullPath.Substring(AppRoot.Length).Replace('\\', '/');
1193}
1194
1195static string ProcessAreaOrControllerName(string name)
1196{
1197    return settings.UseLowercaseRoutes ? name.ToLowerInvariant() : name;
1198}
1199
1200// Return all the CodeFunction2 in the CodeElements collection
1201static IEnumerable<CodeFunction2> GetMethods(CodeClass2 codeClass)
1202{
1203    // Only look at regular method (e.g. ignore things like contructors)
1204    return codeClass.Members.OfType<CodeFunction2>()
1205        .Where(f => f.FunctionKind == vsCMFunction.vsCMFunctionFunction);
1206}
1207
1208// Check if the class has any explicit constructor
1209static bool HasExplicitConstructor(CodeClass2 codeClass)
1210{
1211    return codeClass.Members.OfType<CodeFunction2>().Any(
1212        f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor);
1213}
1214
1215// Check if the class has a default (i.e. no params) constructor
1216static bool HasExplicitDefaultConstructor(CodeClass2 codeClass)
1217{
1218    return codeClass.Members.OfType<CodeFunction2>().Any(
1219        f => !f.IsShared && f.FunctionKind == vsCMFunction.vsCMFunctionConstructor && f.Parameters.Count == 0);
1220}
1221
1222// Find a method with a given name
1223static CodeFunction2 GetMethod(CodeClass2 codeClass, string name)
1224{
1225    return GetMethods(codeClass).FirstOrDefault(f => f.Name == name);
1226}
1227
1228// Find an attribute of a given type on an attribute collection
1229static CodeAttribute2 GetAttribute(CodeElements attributes, string attributeType)
1230{
1231    for (int i = 1; i <= attributes.Count; i++)
1232    {
1233        try
1234        {
1235            var attrib = (CodeAttribute2)attributes.Item(i);
1236            if (attributeType.Split(',').Contains(attrib.FullName, StringComparer.OrdinalIgnoreCase))
1237            {
1238                return attrib;
1239            }
1240        }
1241        catch
1242        {
1243            // FullName can throw in some cases, so just ignore those attributes
1244            continue;
1245        }
1246    }
1247    return null;
1248}
1249
1250static CodeAttribute2 GetAttribute(CodeClass2 type, string attributeType)
1251{
1252    while(type != null) {
1253        var attribute = GetAttribute(type.Attributes, attributeType);
1254        if(attribute != null)
1255            return attribute;
1256        if (type.Bases.Count == 0)
1257            return null;
1258        type = (CodeClass2)type.Bases.Item(1);
1259    }
1260    return null;
1261}
1262
1263static string UniqueFullName(CodeTypeRef codeType)
1264{
1265	return UniqueFullName(codeType.CodeType);
1266}
1267
1268static string UniqueFullName(CodeType codeType)
1269{
1270    var uniqueName = codeType.FullName;
1271
1272    // Match characters not allowed in class names.
1273    uniqueName = Regex.Replace(uniqueName, @"[^\p{Ll}\p{Lu}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\d]", "_");
1274
1275    // Remove duplicate '_' characters
1276    uniqueName = Regex.Replace(uniqueName, @"__+", "_");
1277
1278    // Remove trailing '_' characters
1279    uniqueName = uniqueName.TrimEnd('_');
1280
1281    return uniqueName;
1282}
1283
1284// Return whether a ProjectItem is a folder and not a file
1285static bool IsFolder(ProjectItem item)
1286{
1287    return (item.Kind == EnvDTE.Constants.vsProjectItemKindPhysicalFolder);
1288}
1289
1290static string MakeClassName(string ns, string classname)
1291{
1292    return String.IsNullOrEmpty(ns) ? classname :
1293        String.IsNullOrEmpty(classname) ? ns : ns + "." + codeProvider.CreateEscapedIdentifier(classname);
1294}
1295
1296static string SanitizeWithNoConflicts(string token, HashSet<string> names)
1297{
1298    string name = Sanitize(token);
1299
1300    while (names.Contains(name))
1301    {
1302        name += "_";
1303    }
1304
1305    names.Add(name);
1306
1307    return name;
1308}
1309
1310static string Sanitize(string token)
1311{
1312    if (token == null) return null;
1313
1314    // Replace all invalid chars by underscores
1315    token = Regex.Replace(token, @"[\W\b]", "_", RegexOptions.IgnoreCase);
1316
1317    // If it starts with a digit, prefix it with an underscore
1318    token = Regex.Replace(token, @"^\d", @"_$0");
1319
1320    // Check for reserved…

Large files files are truncated, but you can click here to view the full file