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: }