1: //CustomT4EngineHost.cs
       2:  
       3: using System;
       4: using System.Collections.Generic;
       5: using System.Linq;
       6: using System.Text;
       7: using Microsoft.VisualStudio.TextTemplating;
       8: using System.CodeDom.Compiler;
       9: using System.IO;
      10:  
      11: namespace CustomT4
      12: {      13:     //The text template transformation engine is responsible for running 
      14:     //the transformation process.
      15:     //The host is responsible for all input and output, locating files, 
      16:     //and anything else related to the external environment.
      17:     //-------------------------------------------------------------------------
      18:     [Serializable]
      19:     public class CustomT4EngineHost : MarshalByRefObject, ITextTemplatingEngineHost
      20:     {      21:  
      22:  
      23:         //the path and file name of the text template that is being processed
      24:         //---------------------------------------------------------------------
      25:         public string TemplateFile
      26:         {      27:             get { return ""; }      28:         }
      29:  
      30:  
      31:         //This will be the extension of the generated text output file.
      32:         //The host can provide a default by setting the value of the field here.
      33:         //The engine can change this value based on the optional output directive
      34:         //if the user specifies it in the text template.
      35:         //---------------------------------------------------------------------
      36:         private string fileExtensionValue = ".txt";
      37:         public string FileExtension
      38:         {      39:             get { return fileExtensionValue; }      40:         }
      41:  
      42:  
      43:         //This will be the encoding of the generated text output file.
      44:         //The host can provide a default by setting the value of the field here.
      45:         //The engine can change this value based on the optional output directive
      46:         //if the user specifies it in the text template.
      47:         //---------------------------------------------------------------------
      48:         private Encoding fileEncodingValue = Encoding.UTF8;
      49:         public Encoding FileEncoding
      50:         {      51:             get { return fileEncodingValue; }      52:         }
      53:  
      54:         //These are the errors that occur when the engine processes a template.
      55:         //The engine passes the errors to the host when it is done processing,
      56:         //and the host can decide how to display them. For example, the host 
      57:         //can display the errors in the UI or write them to a file.
      58:         //---------------------------------------------------------------------
      59:         private CompilerErrorCollection errorsValue;
      60:         public CompilerErrorCollection Errors
      61:         {      62:             get { return errorsValue; }      63:         }
      64:  
      65:  
      66:         //The host can provide standard assembly references.
      67:         //The engine will use these references when compiling and
      68:         //executing the generated transformation class.
      69:         //--------------------------------------------------------------
      70:         public IList<string> StandardAssemblyReferences
      71:         {      72:             get
      73:             {      74:                 return new string[]
      75:                 {      76:                     //If this host searches standard paths and the GAC,
      77:                     //we can specify the assembly name like this.
      78:                     //---------------------------------------------------------
      79:                     //"System"
      80:  
      81:                     //Because this host only resolves assemblies from the 
      82:                     //fully qualified path and name of the assembly,
      83:                     //this is a quick way to get the code to give us the
      84:                     //fully qualified path and name of the System assembly.
      85:                     //---------------------------------------------------------
      86:                     typeof(System.Uri).Assembly.Location
      87:                 };
      88:             }
      89:         }
      90:  
      91:  
      92:         //The host can provide standard imports or using statements.
      93:         //The engine will add these statements to the generated 
      94:         //transformation class.
      95:         //--------------------------------------------------------------
      96:         public IList<string> StandardImports
      97:         {      98:             get
      99:             {     100:                 return new string[]
     101:                 {     102:                     "System"
     103:                 };
     104:             }
     105:         }
     106:  
     107:  
     108:         //The engine calls this method based on the optional include directive
     109:         //if the user has specified it in the text template.
     110:         //This method can be called 0, 1, or more times.
     111:         //---------------------------------------------------------------------
     112:         //The included text is returned in the context parameter.
     113:         //If the host searches the registry for the location of include files,
     114:         //or if the host searches multiple locations by default, the host can
     115:         //return the final path of the include file in the location parameter.
     116:         //---------------------------------------------------------------------
     117:         public bool LoadIncludeText(string requestFileName, out string content, out string location)
     118:         {     119:             content = System.String.Empty;
     120:             location = System.String.Empty;
     121:  
     122:             //If the argument is the fully qualified path of an existing file,
     123:             //then we are done.
     124:             //----------------------------------------------------------------
     125:             if (File.Exists(requestFileName))
     126:             {     127:                 content = File.ReadAllText(requestFileName);
     128:                 return true;
     129:             }
     130:  
     131:             //This can be customized to search specific paths for the file.
     132:             //This can be customized to accept paths to search as command line
     133:             //arguments.
     134:             //----------------------------------------------------------------
     135:             else
     136:             {     137:                 return false;
     138:             }
     139:         }
     140:  
     141:  
     142:         //Passes in the name of a service. If you have that service, you need to 
     143:         //pass back a pointer to it. Otherwise, you need to pass back NULL to 
     144:         //indicate that you have no knowledge of that service.
     145:         //--------------------------------------------------------------------
     146:         public object GetHostOption(string optionName)
     147:         {     148:             object returnObject;
     149:             switch (optionName)
     150:             {     151:                 case "CacheAssemblies":
     152:                     returnObject = true;
     153:                     break;
     154:                 default:
     155:                     returnObject = null;
     156:                     break;
     157:             }
     158:             return returnObject;
     159:         }
     160:  
     161:  
     162:         //The engine calls this method to resolve assembly references used in
     163:         //the generated transformation class project and for the optional 
     164:         //assembly directive if the user has specified it in the text template.
     165:         //This method can be called 0, 1, or more times.
     166:         //---------------------------------------------------------------------
     167:         public string ResolveAssemblyReference(string assemblyReference)
     168:         {     169:             //If the argument is the fully qualified path of an existing file,
     170:             //then we are done. (This does not do any work.)
     171:             //----------------------------------------------------------------
     172:             var _assemblyRef = assemblyReference;
     173:             if (_assemblyRef.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))     174:                 _assemblyRef = _assemblyRef.Remove(_assemblyRef.Length - 4);
     175:             if (_assemblyRef.EndsWith(".exe", StringComparison.InvariantCultureIgnoreCase))     176:                 _assemblyRef = _assemblyRef.Remove(_assemblyRef.Length - 4);
     177:  
     178:             foreach (var _assembly in AppDomain.CurrentDomain.GetAssemblies())
     179:             {     180:                 var _assemblyName = _assembly.GetName();
     181:                 if (String.Compare(_assemblyName.Name, _assemblyRef, true) == 0 ||
     182:                     String.Compare(_assemblyName.FullName, _assemblyRef, true) == 0)
     183:                     return _assembly.Location;
     184:             }
     185:             if (File.Exists(assemblyReference))
     186:             {     187:                 return assemblyReference;
     188:             }
     189:  
     190:             //Maybe the assembly is in the same folder as the text template that 
     191:             //called the directive.
     192:             //----------------------------------------------------------------
     193:             string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
     194:             if (File.Exists(candidate))
     195:             {     196:                 return candidate;
     197:             }
     198:  
     199:             //This can be customized to search specific paths for the file
     200:             //or to search the GAC.
     201:             //----------------------------------------------------------------
     202:  
     203:             //This can be customized to accept paths to search as command line
     204:             //arguments.
     205:             //----------------------------------------------------------------
     206:  
     207:             //If we cannot do better, return the original file name.
     208:             return "";
     209:         }
     210:  
     211:  
     212:         //The engine calls this method based on the directives the user has 
     213:         //specified in the text template.
     214:         //This method can be called 0, 1, or more times.
     215:         //---------------------------------------------------------------------
     216:         public Type ResolveDirectiveProcessor(string processorName)
     217:         {     218:             //This host will not resolve any specific processors.
     219:  
     220:             //Check the processor name, and if it is the name of a processor the 
     221:             //host wants to support, return the type of the processor.
     222:             //---------------------------------------------------------------------
     223:             if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0)
     224:             {     225:                 //return typeof();
     226:             }
     227:  
     228:             //This can be customized to search specific paths for the file
     229:             //or to search the GAC
     230:  
     231:             //If the directive processor cannot be found, throw an error.
     232:             throw new Exception("Directive Processor not found");     233:         }
     234:  
     235:  
     236:         //A directive processor can call this method if a file name does not 
     237:         //have a path.
     238:         //The host can attempt to provide path information by searching 
     239:         //specific paths for the file and returning the file and path if found.
     240:         //This method can be called 0, 1, or more times.
     241:         //---------------------------------------------------------------------
     242:         public string ResolvePath(string fileName)
     243:         {     244:             if (fileName == null)
     245:             {     246:                 throw new ArgumentNullException("the file name cannot be null");     247:             }
     248:  
     249:             //If the argument is the fully qualified path of an existing file,
     250:             //then we are done
     251:             //----------------------------------------------------------------
     252:             if (File.Exists(fileName))
     253:             {     254:                 return fileName;
     255:             }
     256:  
     257:             //Maybe the file is in the same folder as the text template that 
     258:             //called the directive.
     259:             //----------------------------------------------------------------
     260:             string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
     261:             if (File.Exists(candidate))
     262:             {     263:                 return candidate;
     264:             }
     265:  
     266:             //Look more places.
     267:             //----------------------------------------------------------------
     268:             //More code can go here...
     269:  
     270:             //If we cannot do better, return the original file name.
     271:             return fileName;
     272:         }
     273:  
     274:  
     275:         //If a call to a directive in a text template does not provide a value
     276:         //for a required parameter, the directive processor can try to get it
     277:         //from the host by calling this method.
     278:         //This method can be called 0, 1, or more times.
     279:         //---------------------------------------------------------------------
     280:         public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
     281:         {     282:             if (directiveId == null)
     283:             {     284:                 throw new ArgumentNullException("the directiveId cannot be null");     285:             }
     286:             if (processorName == null)
     287:             {     288:                 throw new ArgumentNullException("the processorName cannot be null");     289:             }
     290:             if (parameterName == null)
     291:             {     292:                 throw new ArgumentNullException("the parameterName cannot be null");     293:             }
     294:  
     295:             //Code to provide "hard-coded" parameter values goes here.
     296:             //This code depends on the directive processors this host will interact with.
     297:  
     298:             //If we cannot do better, return the empty string.
     299:             return String.Empty;
     300:         }
     301:  
     302:  
     303:         //The engine calls this method to change the extension of the 
     304:         //generated text output file based on the optional output directive 
     305:         //if the user specifies it in the text template.
     306:         //---------------------------------------------------------------------
     307:         public void SetFileExtension(string extension)
     308:         {     309:             //The parameter extension has a '.' in front of it already.
     310:             //--------------------------------------------------------
     311:             fileExtensionValue = extension;
     312:         }
     313:  
     314:  
     315:         //The engine calls this method to change the encoding of the 
     316:         //generated text output file based on the optional output directive 
     317:         //if the user specifies it in the text template.
     318:         //----------------------------------------------------------------------
     319:         public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
     320:         {     321:             fileEncodingValue = encoding;
     322:         }
     323:  
     324:  
     325:         //The engine calls this method when it is done processing a text
     326:         //template to pass any errors that occurred to the host.
     327:         //The host can decide how to display them.
     328:         //---------------------------------------------------------------------
     329:         public void LogErrors(CompilerErrorCollection errors)
     330:         {     331:             errorsValue = errors;
     332:         }
     333:  
     334:  
     335:         //This is the application domain that is used to compile and run
     336:         //the generated transformation class to create the generated text output.
     337:         //----------------------------------------------------------------------
     338:         public AppDomain ProvideTemplatingAppDomain(string content)
     339:         {     340:             //This host will provide a new application domain each time the 
     341:             //engine processes a text template.
     342:             //-------------------------------------------------------------
     343:             return AppDomain.CreateDomain("Generation App Domain");     344:  
     345:             //This could be changed to return the current appdomain, but new 
     346:             //assemblies are loaded into this AppDomain on a regular basis.
     347:             //If the AppDomain lasts too long, it will grow indefintely, 
     348:             //which might be regarded as a leak.
     349:  
     350:             //This could be customized to cache the application domain for 
     351:             //a certain number of text template generations (for example, 10).
     352:  
     353:             //This could be customized based on the contents of the text 
     354:             //template, which are provided as a parameter for that purpose.
     355:         }
     356:  
     357:     }
     358:     
     359: }