2 // System.Web.UI.TemplateParser
5 // Duncan Mak (duncan@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (C) 2002,2003 Ximian, Inc. (http://www.ximian.com)
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System.CodeDom.Compiler;
32 using System.Collections;
33 using System.Globalization;
35 using System.Reflection;
36 using System.Security.Permissions;
37 using System.Web.Compilation;
38 using System.Web.Configuration;
39 using System.Web.Util;
41 namespace System.Web.UI {
44 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
45 [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46 public abstract class TemplateParser : BaseParser
50 string privateBinPath;
51 Hashtable mainAttributes;
52 ArrayList dependencies;
59 bool baseTypeIsGlobal;
61 RootBuilder rootBuilder;
63 string compilerOptions;
65 bool strictOn = false;
66 bool explicitOn = false;
67 bool linePragmasOn = false;
70 string oc_header, oc_custom, oc_param, oc_controls;
72 OutputCacheLocation oc_location;
73 CultureInfo invariantCulture = CultureInfo.InvariantCulture;
76 string partialClassName;
79 int appAssemblyIndex = -1;
81 internal TemplateParser ()
83 imports = new ArrayList ();
85 AddNamespaces (imports);
87 imports.Add ("System");
88 imports.Add ("System.Collections");
89 imports.Add ("System.Collections.Specialized");
90 imports.Add ("System.Configuration");
91 imports.Add ("System.Text");
92 imports.Add ("System.Text.RegularExpressions");
93 imports.Add ("System.Web");
94 imports.Add ("System.Web.Caching");
95 imports.Add ("System.Web.Security");
96 imports.Add ("System.Web.SessionState");
97 imports.Add ("System.Web.UI");
98 imports.Add ("System.Web.UI.WebControls");
99 imports.Add ("System.Web.UI.HtmlControls");
102 assemblies = new ArrayList ();
104 bool addAssembliesInBin = false;
105 foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
106 if (info.Assembly == "*")
107 addAssembliesInBin = true;
109 AddAssemblyByName (info.Assembly);
111 if (addAssembliesInBin)
112 AddAssembliesInBin ();
114 foreach (NamespaceInfo info in PagesConfig.Namespaces) {
115 imports.Add (info.Namespace);
118 foreach (string a in CompilationConfig.Assemblies)
119 AddAssemblyByName (a);
120 if (CompilationConfig.AssembliesInBin)
121 AddAssembliesInBin ();
124 language = CompilationConfig.DefaultLanguage;
127 internal void AddApplicationAssembly ()
129 if (Context.ApplicationInstance == null)
130 return; // this may happen if we have Global.asax and have
131 // controls registered from Web.Config
132 string location = Context.ApplicationInstance.AssemblyLocation;
133 if (location != typeof (TemplateParser).Assembly.Location) {
134 appAssemblyIndex = assemblies.Add (location);
138 protected abstract Type CompileIntoType ();
141 void AddNamespaces (ArrayList imports)
143 if (BuildManager.HaveResources)
144 imports.Add ("System.Resources");
146 PagesSection pages = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
150 NamespaceCollection namespaces = pages.Namespaces;
151 if (namespaces == null || namespaces.Count == 0)
154 foreach (NamespaceInfo nsi in namespaces)
155 imports.Add (nsi.Namespace);
159 internal void RegisterCustomControl (string tagPrefix, string tagName, string src)
161 string realpath = MapPath (src);
162 if (String.Compare (realpath, inputFile, false, invariantCulture) == 0)
165 if (!File.Exists (realpath))
166 throw new ParseException (Location, "Could not find file \"" + realpath + "\".");
167 string vpath = UrlUtils.Combine (BaseVirtualDir, src);
169 AddDependency (realpath);
171 ArrayList other_deps = new ArrayList ();
172 type = UserControlParser.GetCompiledType (vpath, realpath, other_deps, Context);
173 foreach (string s in other_deps) {
176 } catch (ParseException pe) {
177 if (this is UserControlParser)
178 throw new ParseException (Location, pe.Message, pe);
182 AddAssembly (type.Assembly, true);
183 RootBuilder.Foundry.RegisterFoundry (tagPrefix, tagName, type);
186 internal void RegisterNamespace (string tagPrefix, string ns, string assembly)
189 Assembly ass = AddAssemblyByName (assembly);
190 AddDependency (ass.Location);
191 RootBuilder.Foundry.RegisterFoundry (tagPrefix, ass, ns);
194 internal virtual void HandleOptions (object obj)
198 internal static string GetOneKey (Hashtable tbl)
200 foreach (object key in tbl.Keys)
201 return key.ToString ();
206 internal virtual void AddDirective (string directive, Hashtable atts)
208 if (String.Compare (directive, DefaultDirectiveName, true) == 0) {
209 if (mainAttributes != null)
210 ThrowParseException ("Only 1 " + DefaultDirectiveName + " is allowed");
212 mainAttributes = atts;
213 ProcessMainAttributes (mainAttributes);
217 int cmp = String.Compare ("Assembly", directive, true);
219 string name = GetString (atts, "Name", null);
220 string src = GetString (atts, "Src", null);
223 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
225 if (name == null && src == null)
226 ThrowParseException ("You gotta specify Src or Name");
228 if (name != null && src != null)
229 ThrowParseException ("Src and Name cannot be used together");
232 AddAssemblyByName (name);
234 GetAssemblyFromSource (src);
240 cmp = String.Compare ("Import", directive, true);
242 string namesp = GetString (atts, "Namespace", null);
244 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
246 if (namesp != null && namesp != "")
251 cmp = String.Compare ("Implements", directive, true);
253 string ifacename = GetString (atts, "Interface", "");
256 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
258 Type iface = LoadType (ifacename);
260 ThrowParseException ("Cannot find type " + ifacename);
262 if (!iface.IsInterface)
263 ThrowParseException (iface + " is not an interface");
265 AddInterface (iface.FullName);
269 cmp = String.Compare ("OutputCache", directive, true);
271 HttpResponse response = HttpContext.Current.Response;
272 if (response != null)
273 response.Cache.SetValidUntilExpires (true);
277 if (atts ["Duration"] == null)
278 ThrowParseException ("The directive is missing a 'duration' attribute.");
279 if (atts ["VaryByParam"] == null)
280 ThrowParseException ("This directive is missing a 'VaryByParam' " +
281 "attribute, which should be set to \"none\", \"*\", " +
282 "or a list of name/value pairs.");
284 foreach (DictionaryEntry entry in atts) {
285 string key = (string) entry.Key;
286 switch (key.ToLower ()) {
288 oc_duration = Int32.Parse ((string) entry.Value);
290 ThrowParseException ("The 'duration' attribute must be set " +
291 "to a positive integer value");
294 oc_param = (string) entry.Value;
295 if (String.Compare (oc_param, "none") == 0)
299 oc_header = (string) entry.Value;
302 oc_custom = (string) entry.Value;
305 if (!(this is PageParser))
309 oc_location = (OutputCacheLocation) Enum.Parse (
310 typeof (OutputCacheLocation), (string) entry.Value, true);
312 ThrowParseException ("The 'location' attribute is case sensitive and " +
313 "must be one of the following values: Any, Client, " +
314 "Downstream, Server, None, ServerAndClient.");
317 case "varybycontrol":
318 if (this is PageParser)
321 oc_controls = (string) entry.Value;
324 if (this is PageParser)
328 oc_shared = Boolean.Parse ((string) entry.Value);
330 ThrowParseException ("The 'shared' attribute is case sensitive" +
331 " and must be set to 'true' or 'false'.");
335 ThrowParseException ("The '" + key + "' attribute is not " +
336 "supported by the 'Outputcache' directive.");
345 ThrowParseException ("Unknown directive: " + directive);
348 internal Type LoadType (string typeName)
350 // First try loaded assemblies, then try assemblies in Bin directory.
352 bool seenBin = false;
353 Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies ();
354 foreach (Assembly ass in assemblies) {
355 type = ass.GetType (typeName);
359 if (Path.GetDirectoryName (ass.Location) != PrivateBinPath) {
360 AddAssembly (ass, true);
365 AddDependency (ass.Location);
373 if (!Directory.Exists (PrivateBinPath))
376 string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
377 foreach (string s in binDlls) {
378 Assembly binA = Assembly.LoadFrom (s);
379 type = binA.GetType (typeName);
383 AddDependency (binA.Location);
390 void AddAssembliesInBin ()
392 if (!Directory.Exists (PrivateBinPath))
395 string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
396 foreach (string s in binDlls)
400 internal virtual void AddInterface (string iface)
402 if (interfaces == null)
403 interfaces = new ArrayList ();
405 if (!interfaces.Contains (iface))
406 interfaces.Add (iface);
409 internal virtual void AddImport (string namesp)
412 imports = new ArrayList ();
414 if (!imports.Contains (namesp))
415 imports.Add (namesp);
418 internal virtual void AddSourceDependency (string filename)
420 if (dependencies != null && dependencies.Contains (filename)) {
421 ThrowParseException ("Circular file references are not allowed. File: " + filename);
424 AddDependency (filename);
427 internal virtual void AddDependency (string filename)
432 if (dependencies == null)
433 dependencies = new ArrayList ();
435 if (!dependencies.Contains (filename))
436 dependencies.Add (filename);
439 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
441 if (assembly.Location == "")
445 anames = new Hashtable ();
447 string name = assembly.GetName ().Name;
448 string loc = assembly.Location;
450 if (!assemblies.Contains (loc)) {
451 assemblies.Add (loc);
455 anames [loc] = assembly;
457 if (!assemblies.Contains (name)) {
458 assemblies.Add (name);
461 anames [name] = assembly;
465 internal virtual Assembly AddAssemblyByFileName (string filename)
467 Assembly assembly = null;
468 Exception error = null;
471 assembly = Assembly.LoadFrom (filename);
472 } catch (Exception e) { error = e; }
474 if (assembly == null)
475 ThrowParseException ("Assembly " + filename + " not found", error);
477 AddAssembly (assembly, true);
481 internal virtual Assembly AddAssemblyByName (string name)
484 anames = new Hashtable ();
486 if (anames.Contains (name)) {
487 object o = anames [name];
494 Assembly assembly = null;
495 Exception error = null;
496 if (name.IndexOf (',') != -1) {
498 assembly = Assembly.Load (name);
499 } catch (Exception e) { error = e; }
502 if (assembly == null) {
504 assembly = Assembly.LoadWithPartialName (name);
505 } catch (Exception e) { error = e; }
508 if (assembly == null)
509 ThrowParseException ("Assembly " + name + " not found", error);
511 AddAssembly (assembly, true);
515 internal virtual void ProcessMainAttributes (Hashtable atts)
517 atts.Remove ("Description"); // ignored
519 atts.Remove ("CodeBehind"); // ignored
521 atts.Remove ("AspCompat"); // ignored
523 // these two are ignored for the moment
524 atts.Remove ("Async");
525 atts.Remove ("AsyncTimeOut");
528 debug = GetBool (atts, "Debug", true);
529 compilerOptions = GetString (atts, "CompilerOptions", "");
530 language = GetString (atts, "Language", CompilationConfig.DefaultLanguage);
531 strictOn = GetBool (atts, "Strict", CompilationConfig.Strict);
532 explicitOn = GetBool (atts, "Explicit", CompilationConfig.Explicit);
533 linePragmasOn = GetBool (atts, "LinePragmas", false);
535 string inherits = GetString (atts, "Inherits", null);
537 // In ASP 2, the source file is actually integrated with
538 // the generated file via the use of partial classes. This
539 // means that the code file has to be confirmed, but not
540 // used at this point.
541 src = GetString (atts, "CodeFile", null);
543 if (src != null && inherits != null) {
544 // Make sure the source exists
545 src = UrlUtils.Combine (BaseVirtualDir, src);
546 string realPath = MapPath (src, false);
547 if (!File.Exists (realPath))
548 ThrowParseException ("File " + src + " not found");
550 // We are going to create a partial class that shares
551 // the same name as the inherits tag, so reset the
552 // name. The base type is changed because it is the
553 // code file's responsibilty to extend the classes
555 partialClassName = inherits;
557 // Add the code file as an option to the
558 // compiler. This lets both files be compiled at once.
559 compilerOptions += " \"" + realPath + "\"";
560 } else if (inherits != null) {
561 // We just set the inherits directly because this is a
562 // Single-Page model.
563 SetBaseType (inherits);
566 string src = GetString (atts, "Src", null);
569 srcAssembly = GetAssemblyFromSource (src);
571 if (inherits != null)
572 SetBaseType (inherits);
574 className = GetString (atts, "ClassName", null);
575 if (className != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (className))
576 ThrowParseException (String.Format ("'{0}' is not valid for 'className'", className));
580 ThrowParseException ("Unknown attribute: " + GetOneKey (atts));
583 internal void SetBaseType (string type)
585 if (type == DefaultBaseTypeName)
589 if (srcAssembly != null)
590 parent = srcAssembly.GetType (type);
593 parent = LoadType (type);
596 ThrowParseException ("Cannot find type " + type);
598 if (!DefaultBaseType.IsAssignableFrom (parent))
599 ThrowParseException ("The parent type does not derive from " + DefaultBaseType);
602 if (parent.FullName.IndexOf ('.') == -1)
603 baseTypeIsGlobal = true;
606 Assembly GetAssemblyFromSource (string vpath)
608 vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
609 string realPath = MapPath (vpath, false);
610 if (!File.Exists (realPath))
611 ThrowParseException ("File " + vpath + " not found");
613 AddSourceDependency (realPath);
615 CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
616 if (result.NativeCompilerReturnValue != 0) {
617 StreamReader reader = new StreamReader (realPath);
618 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
621 AddAssembly (result.CompiledAssembly, true);
622 return result.CompiledAssembly;
625 internal abstract Type DefaultBaseType { get; }
626 internal abstract string DefaultBaseTypeName { get; }
627 internal abstract string DefaultDirectiveName { get; }
629 internal string InputFile
631 get { return inputFile; }
632 set { inputFile = value; }
636 internal bool IsPartial
638 get { return src != null; }
641 internal string PartialClassName
643 get { return partialClassName; }
650 set { text = value; }
653 internal Type BaseType
656 if (baseType == null)
657 baseType = DefaultBaseType;
663 internal bool BaseTypeIsGlobal {
664 get { return baseTypeIsGlobal; }
667 internal string ClassName {
669 if (className != null)
672 className = Path.GetFileName (inputFile).Replace ('.', '_');
673 className = className.Replace ('-', '_');
674 className = className.Replace (' ', '_');
676 if (Char.IsDigit(className[0])) {
677 className = "_" + className;
684 internal string PrivateBinPath {
686 if (privateBinPath != null)
687 return privateBinPath;
689 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
690 privateBinPath = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
692 return privateBinPath;
696 internal ArrayList Scripts {
699 scripts = new ArrayList ();
705 internal ArrayList Imports {
706 get { return imports; }
709 internal ArrayList Assemblies {
711 if (appAssemblyIndex != -1) {
712 object o = assemblies [appAssemblyIndex];
713 assemblies.RemoveAt (appAssemblyIndex);
715 appAssemblyIndex = -1;
722 internal ArrayList Interfaces {
723 get { return interfaces; }
726 internal RootBuilder RootBuilder {
727 get { return rootBuilder; }
728 set { rootBuilder = value; }
731 internal ArrayList Dependencies {
732 get { return dependencies; }
733 set { dependencies = value; }
736 internal string CompilerOptions {
737 get { return compilerOptions; }
740 internal string Language {
741 get { return language; }
744 internal bool StrictOn {
745 get { return strictOn; }
748 internal bool ExplicitOn {
749 get { return explicitOn; }
752 internal bool Debug {
753 get { return debug; }
756 internal bool OutputCache {
757 get { return output_cache; }
760 internal int OutputCacheDuration {
761 get { return oc_duration; }
764 internal string OutputCacheVaryByHeader {
765 get { return oc_header; }
768 internal string OutputCacheVaryByCustom {
769 get { return oc_custom; }
772 internal string OutputCacheVaryByControls {
773 get { return oc_controls; }
776 internal bool OutputCacheShared {
777 get { return oc_shared; }
780 internal OutputCacheLocation OutputCacheLocation {
781 get { return oc_location; }
784 internal string OutputCacheVaryByParam {
785 get { return oc_param; }
789 internal PagesSection PagesConfig {
791 return WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
795 internal PagesConfiguration PagesConfig {
796 get { return PagesConfiguration.GetInstance (Context); }