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