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.ComponentModel;
34 using System.Globalization;
36 using System.Reflection;
37 using System.Security.Permissions;
38 using System.Web.Compilation;
39 using System.Web.Configuration;
40 using System.Web.Util;
43 using System.Collections.Generic;
46 namespace System.Web.UI {
49 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
50 [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
51 public abstract class TemplateParser : BaseParser
55 string privateBinPath;
56 Hashtable mainAttributes;
57 ArrayList dependencies;
64 bool baseTypeIsGlobal;
66 RootBuilder rootBuilder;
68 string compilerOptions;
70 bool strictOn = false;
71 bool explicitOn = false;
72 bool linePragmasOn = false;
75 string oc_header, oc_custom, oc_param, oc_controls;
77 OutputCacheLocation oc_location;
78 CultureInfo invariantCulture = CultureInfo.InvariantCulture;
81 string partialClassName;
82 string codeFileBaseClass;
83 string metaResourceKey;
84 Type codeFileBaseClassType;
85 List <UnknownAttributeDescriptor> unknownMainAttributes;
88 int appAssemblyIndex = -1;
90 internal TemplateParser ()
92 imports = new ArrayList ();
94 AddNamespaces (imports);
96 imports.Add ("System");
97 imports.Add ("System.Collections");
98 imports.Add ("System.Collections.Specialized");
99 imports.Add ("System.Configuration");
100 imports.Add ("System.Text");
101 imports.Add ("System.Text.RegularExpressions");
102 imports.Add ("System.Web");
103 imports.Add ("System.Web.Caching");
104 imports.Add ("System.Web.Security");
105 imports.Add ("System.Web.SessionState");
106 imports.Add ("System.Web.UI");
107 imports.Add ("System.Web.UI.WebControls");
108 imports.Add ("System.Web.UI.HtmlControls");
111 assemblies = new ArrayList ();
113 bool addAssembliesInBin = false;
114 foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
115 if (info.Assembly == "*")
116 addAssembliesInBin = true;
118 AddAssemblyByName (info.Assembly);
120 if (addAssembliesInBin)
121 AddAssembliesInBin ();
123 foreach (NamespaceInfo info in PagesConfig.Namespaces) {
124 imports.Add (info.Namespace);
127 foreach (string a in CompilationConfig.Assemblies)
128 AddAssemblyByName (a);
129 if (CompilationConfig.AssembliesInBin)
130 AddAssembliesInBin ();
133 language = CompilationConfig.DefaultLanguage;
136 internal void AddApplicationAssembly ()
138 if (Context.ApplicationInstance == null)
139 return; // this may happen if we have Global.asax and have
140 // controls registered from Web.Config
141 string location = Context.ApplicationInstance.AssemblyLocation;
142 if (location != typeof (TemplateParser).Assembly.Location) {
143 appAssemblyIndex = assemblies.Add (location);
147 protected abstract Type CompileIntoType ();
150 void AddNamespaces (ArrayList imports)
152 if (BuildManager.HaveResources)
153 imports.Add ("System.Resources");
155 PagesSection pages = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
159 NamespaceCollection namespaces = pages.Namespaces;
160 if (namespaces == null || namespaces.Count == 0)
163 foreach (NamespaceInfo nsi in namespaces)
164 imports.Add (nsi.Namespace);
168 internal void RegisterCustomControl (string tagPrefix, string tagName, string src)
170 string realpath = MapPath (src);
171 if (String.Compare (realpath, inputFile, false, invariantCulture) == 0)
174 if (!File.Exists (realpath))
175 throw new ParseException (Location, "Could not find file \"" + realpath + "\".");
176 string vpath = UrlUtils.Combine (BaseVirtualDir, src);
178 AddDependency (realpath);
180 ArrayList other_deps = new ArrayList ();
181 type = UserControlParser.GetCompiledType (vpath, realpath, other_deps, Context);
182 foreach (string s in other_deps) {
185 } catch (ParseException pe) {
186 if (this is UserControlParser)
187 throw new ParseException (Location, pe.Message, pe);
191 AddAssembly (type.Assembly, true);
192 RootBuilder.Foundry.RegisterFoundry (tagPrefix, tagName, type);
195 internal void RegisterNamespace (string tagPrefix, string ns, string assembly)
198 Assembly ass = AddAssemblyByName (assembly);
199 AddDependency (ass.Location);
200 RootBuilder.Foundry.RegisterFoundry (tagPrefix, ass, ns);
203 internal virtual void HandleOptions (object obj)
207 internal static string GetOneKey (Hashtable tbl)
209 foreach (object key in tbl.Keys)
210 return key.ToString ();
215 internal virtual void AddDirective (string directive, Hashtable atts)
217 if (String.Compare (directive, DefaultDirectiveName, true) == 0) {
218 if (mainAttributes != null)
219 ThrowParseException ("Only 1 " + DefaultDirectiveName + " is allowed");
221 mainAttributes = atts;
222 ProcessMainAttributes (mainAttributes);
226 int cmp = String.Compare ("Assembly", directive, true);
228 string name = GetString (atts, "Name", null);
229 string src = GetString (atts, "Src", null);
232 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
234 if (name == null && src == null)
235 ThrowParseException ("You gotta specify Src or Name");
237 if (name != null && src != null)
238 ThrowParseException ("Src and Name cannot be used together");
241 AddAssemblyByName (name);
243 GetAssemblyFromSource (src);
249 cmp = String.Compare ("Import", directive, true);
251 string namesp = GetString (atts, "Namespace", null);
253 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
255 if (namesp != null && namesp != "")
260 cmp = String.Compare ("Implements", directive, true);
262 string ifacename = GetString (atts, "Interface", "");
265 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
267 Type iface = LoadType (ifacename);
269 ThrowParseException ("Cannot find type " + ifacename);
271 if (!iface.IsInterface)
272 ThrowParseException (iface + " is not an interface");
274 AddInterface (iface.FullName);
278 cmp = String.Compare ("OutputCache", directive, true);
280 HttpResponse response = HttpContext.Current.Response;
281 if (response != null)
282 response.Cache.SetValidUntilExpires (true);
286 if (atts ["Duration"] == null)
287 ThrowParseException ("The directive is missing a 'duration' attribute.");
288 if (atts ["VaryByParam"] == null)
289 ThrowParseException ("This directive is missing a 'VaryByParam' " +
290 "attribute, which should be set to \"none\", \"*\", " +
291 "or a list of name/value pairs.");
293 foreach (DictionaryEntry entry in atts) {
294 string key = (string) entry.Key;
295 switch (key.ToLower ()) {
297 oc_duration = Int32.Parse ((string) entry.Value);
299 ThrowParseException ("The 'duration' attribute must be set " +
300 "to a positive integer value");
303 oc_param = (string) entry.Value;
304 if (String.Compare (oc_param, "none") == 0)
308 oc_header = (string) entry.Value;
311 oc_custom = (string) entry.Value;
314 if (!(this is PageParser))
318 oc_location = (OutputCacheLocation) Enum.Parse (
319 typeof (OutputCacheLocation), (string) entry.Value, true);
321 ThrowParseException ("The 'location' attribute is case sensitive and " +
322 "must be one of the following values: Any, Client, " +
323 "Downstream, Server, None, ServerAndClient.");
326 case "varybycontrol":
327 if (this is PageParser)
330 oc_controls = (string) entry.Value;
333 if (this is PageParser)
337 oc_shared = Boolean.Parse ((string) entry.Value);
339 ThrowParseException ("The 'shared' attribute is case sensitive" +
340 " and must be set to 'true' or 'false'.");
344 ThrowParseException ("The '" + key + "' attribute is not " +
345 "supported by the 'Outputcache' directive.");
354 ThrowParseException ("Unknown directive: " + directive);
357 internal Type LoadType (string typeName)
359 // First try loaded assemblies, then try assemblies in Bin directory.
361 bool seenBin = false;
362 Assembly [] assemblies = AppDomain.CurrentDomain.GetAssemblies ();
363 foreach (Assembly ass in assemblies) {
364 type = ass.GetType (typeName);
368 if (Path.GetDirectoryName (ass.Location) != PrivateBinPath) {
369 AddAssembly (ass, true);
374 AddDependency (ass.Location);
382 if (!Directory.Exists (PrivateBinPath))
385 string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
386 foreach (string s in binDlls) {
387 Assembly binA = Assembly.LoadFrom (s);
388 type = binA.GetType (typeName);
392 AddDependency (binA.Location);
399 void AddAssembliesInBin ()
401 if (!Directory.Exists (PrivateBinPath))
404 string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
405 foreach (string s in binDlls)
409 internal virtual void AddInterface (string iface)
411 if (interfaces == null)
412 interfaces = new ArrayList ();
414 if (!interfaces.Contains (iface))
415 interfaces.Add (iface);
418 internal virtual void AddImport (string namesp)
421 imports = new ArrayList ();
423 if (!imports.Contains (namesp))
424 imports.Add (namesp);
427 internal virtual void AddSourceDependency (string filename)
429 if (dependencies != null && dependencies.Contains (filename)) {
430 ThrowParseException ("Circular file references are not allowed. File: " + filename);
433 AddDependency (filename);
436 internal virtual void AddDependency (string filename)
441 if (dependencies == null)
442 dependencies = new ArrayList ();
444 if (!dependencies.Contains (filename))
445 dependencies.Add (filename);
448 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
450 if (assembly.Location == "")
454 anames = new Hashtable ();
456 string name = assembly.GetName ().Name;
457 string loc = assembly.Location;
459 if (!assemblies.Contains (loc)) {
460 assemblies.Add (loc);
464 anames [loc] = assembly;
466 if (!assemblies.Contains (name)) {
467 assemblies.Add (name);
470 anames [name] = assembly;
474 internal virtual Assembly AddAssemblyByFileName (string filename)
476 Assembly assembly = null;
477 Exception error = null;
480 assembly = Assembly.LoadFrom (filename);
481 } catch (Exception e) { error = e; }
483 if (assembly == null)
484 ThrowParseException ("Assembly " + filename + " not found", error);
486 AddAssembly (assembly, true);
490 internal virtual Assembly AddAssemblyByName (string name)
493 anames = new Hashtable ();
495 if (anames.Contains (name)) {
496 object o = anames [name];
503 Assembly assembly = null;
504 Exception error = null;
505 if (name.IndexOf (',') != -1) {
507 assembly = Assembly.Load (name);
508 } catch (Exception e) { error = e; }
511 if (assembly == null) {
513 assembly = Assembly.LoadWithPartialName (name);
514 } catch (Exception e) { error = e; }
517 if (assembly == null)
518 ThrowParseException ("Assembly " + name + " not found", error);
520 AddAssembly (assembly, true);
524 internal virtual void ProcessMainAttributes (Hashtable atts)
526 atts.Remove ("Description"); // ignored
528 atts.Remove ("CodeBehind"); // ignored
530 atts.Remove ("AspCompat"); // ignored
532 // these two are ignored for the moment
533 atts.Remove ("Async");
534 atts.Remove ("AsyncTimeOut");
537 debug = GetBool (atts, "Debug", true);
538 compilerOptions = GetString (atts, "CompilerOptions", "");
539 language = GetString (atts, "Language", CompilationConfig.DefaultLanguage);
540 strictOn = GetBool (atts, "Strict", CompilationConfig.Strict);
541 explicitOn = GetBool (atts, "Explicit", CompilationConfig.Explicit);
542 linePragmasOn = GetBool (atts, "LinePragmas", false);
544 string inherits = GetString (atts, "Inherits", null);
546 // In ASP 2, the source file is actually integrated with
547 // the generated file via the use of partial classes. This
548 // means that the code file has to be confirmed, but not
549 // used at this point.
550 src = GetString (atts, "CodeFile", null);
551 codeFileBaseClass = GetString (atts, "CodeFileBaseClass", null);
553 if (src == null && codeFileBaseClass != null)
554 ThrowParseException ("The 'CodeFileBaseClass' attribute cannot be used without a 'CodeFile' attribute");
556 if (src != null && inherits != null) {
557 // Make sure the source exists
558 src = UrlUtils.Combine (BaseVirtualDir, src);
559 string realPath = MapPath (src, false);
560 if (!File.Exists (realPath))
561 ThrowParseException ("File " + src + " not found");
563 // We are going to create a partial class that shares
564 // the same name as the inherits tag, so reset the
565 // name. The base type is changed because it is the
566 // code file's responsibilty to extend the classes
568 partialClassName = inherits;
570 // Add the code file as an option to the
571 // compiler. This lets both files be compiled at once.
572 compilerOptions += " \"" + realPath + "\"";
574 if (codeFileBaseClass != null) {
576 codeFileBaseClassType = LoadType (codeFileBaseClass);
577 } catch (Exception) {
580 if (codeFileBaseClassType == null)
581 ThrowParseException ("Could not load type '{0}'", codeFileBaseClass);
583 } else if (inherits != null) {
584 // We just set the inherits directly because this is a
585 // Single-Page model.
586 SetBaseType (inherits);
589 string src = GetString (atts, "Src", null);
592 srcAssembly = GetAssemblyFromSource (src);
594 if (inherits != null)
595 SetBaseType (inherits);
597 className = GetString (atts, "ClassName", null);
598 if (className != null && !CodeGenerator.IsValidLanguageIndependentIdentifier (className))
599 ThrowParseException (String.Format ("'{0}' is not valid for 'className'", className));
602 if (this is TemplateControlParser)
603 metaResourceKey = GetString (atts, "meta:resourcekey", null);
605 if (inherits != null && (this is PageParser || this is UserControlParser) && atts.Count > 0) {
606 if (unknownMainAttributes == null)
607 unknownMainAttributes = new List <UnknownAttributeDescriptor> ();
610 foreach (DictionaryEntry de in atts) {
611 key = de.Key as string;
612 val = de.Value as string;
614 if (String.IsNullOrEmpty (key) || String.IsNullOrEmpty (val))
616 CheckUnknownAttribute (key, val, inherits);
622 ThrowParseException ("Unknown attribute: " + GetOneKey (atts));
626 void CheckUnknownAttribute (string name, string val, string inherits)
628 MemberInfo mi = null;
629 bool missing = false;
630 string memberName = name.Trim ().ToLower (CultureInfo.InvariantCulture);
631 Type parent = codeFileBaseClassType;
637 MemberInfo[] infos = parent.GetMember (memberName,
638 MemberTypes.Field | MemberTypes.Property,
639 BindingFlags.Public | BindingFlags.Instance |
640 BindingFlags.IgnoreCase | BindingFlags.Static);
641 if (infos.Length != 0) {
642 // prefer public properties to public methods (it's what MS.NET does)
643 foreach (MemberInfo tmp in infos) {
644 if (tmp is PropertyInfo) {
653 } catch (Exception) {
657 ThrowParseException (
658 "Error parsing attribute '{0}': Type '{1}' does not have a public property named '{0}'",
659 memberName, inherits);
661 Type memberType = null;
662 if (mi is PropertyInfo) {
663 PropertyInfo pi = mi as PropertyInfo;
666 ThrowParseException (
667 "Error parsing attribute '{0}': The '{0}' property is read-only and cannot be set.",
669 memberType = pi.PropertyType;
670 } else if (mi is FieldInfo) {
671 memberType = ((FieldInfo)mi).FieldType;
673 ThrowParseException ("Could not determine member the kind of '{0}' in base type '{1}",
674 memberName, inherits);
675 TypeConverter converter = TypeDescriptor.GetConverter (memberType);
676 bool convertible = true;
679 if (converter == null || !converter.CanConvertFrom (typeof (string)))
684 value = converter.ConvertFromInvariantString (val);
685 } catch (Exception) {
691 ThrowParseException ("Error parsing attribute '{0}': Cannot create an object of type '{1}' from its string representation '{2}' for the '{3}' property.",
692 memberName, memberType, val, mi.Name);
694 UnknownAttributeDescriptor desc = new UnknownAttributeDescriptor (mi, value);
695 unknownMainAttributes.Add (desc);
699 internal void SetBaseType (string type)
701 if (type == DefaultBaseTypeName)
705 if (srcAssembly != null)
706 parent = srcAssembly.GetType (type);
709 parent = LoadType (type);
712 ThrowParseException ("Cannot find type " + type);
714 if (!DefaultBaseType.IsAssignableFrom (parent))
715 ThrowParseException ("The parent type does not derive from " + DefaultBaseType);
718 if (parent.FullName.IndexOf ('.') == -1)
719 baseTypeIsGlobal = true;
722 Assembly GetAssemblyFromSource (string vpath)
724 vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
725 string realPath = MapPath (vpath, false);
726 if (!File.Exists (realPath))
727 ThrowParseException ("File " + vpath + " not found");
729 AddSourceDependency (realPath);
731 CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
732 if (result.NativeCompilerReturnValue != 0) {
733 StreamReader reader = new StreamReader (realPath);
734 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
737 AddAssembly (result.CompiledAssembly, true);
738 return result.CompiledAssembly;
741 internal abstract Type DefaultBaseType { get; }
742 internal abstract string DefaultBaseTypeName { get; }
743 internal abstract string DefaultDirectiveName { get; }
745 internal string InputFile
747 get { return inputFile; }
748 set { inputFile = value; }
752 internal bool IsPartial {
753 get { return src != null; }
756 internal string PartialClassName {
757 get { return partialClassName; }
760 internal string CodeFileBaseClass {
761 get { return codeFileBaseClass; }
764 internal string MetaResourceKey {
765 get { return metaResourceKey; }
768 internal Type CodeFileBaseClassType
770 get { return codeFileBaseClassType; }
773 internal List <UnknownAttributeDescriptor> UnknownMainAttributes
775 get { return unknownMainAttributes; }
782 set { text = value; }
785 internal Type BaseType
788 if (baseType == null)
789 baseType = DefaultBaseType;
795 internal bool BaseTypeIsGlobal {
796 get { return baseTypeIsGlobal; }
799 internal string ClassName {
801 if (className != null)
805 string physPath = HttpContext.Current.Request.PhysicalApplicationPath;
807 if (StrUtils.StartsWith (inputFile, physPath)) {
808 className = inputFile.Substring (physPath.Length).ToLower (CultureInfo.InvariantCulture);
809 className = className.Replace ('.', '_');
810 className = className.Replace ('/', '_').Replace ('\\', '_');
813 className = Path.GetFileName (inputFile).Replace ('.', '_');
814 className = className.Replace ('-', '_');
815 className = className.Replace (' ', '_');
817 if (Char.IsDigit(className[0])) {
818 className = "_" + className;
825 internal string PrivateBinPath {
827 if (privateBinPath != null)
828 return privateBinPath;
830 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
831 privateBinPath = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
833 return privateBinPath;
837 internal ArrayList Scripts {
840 scripts = new ArrayList ();
846 internal ArrayList Imports {
847 get { return imports; }
850 internal ArrayList Assemblies {
852 if (appAssemblyIndex != -1) {
853 object o = assemblies [appAssemblyIndex];
854 assemblies.RemoveAt (appAssemblyIndex);
856 appAssemblyIndex = -1;
863 internal ArrayList Interfaces {
864 get { return interfaces; }
867 internal RootBuilder RootBuilder {
868 get { return rootBuilder; }
869 set { rootBuilder = value; }
872 internal ArrayList Dependencies {
873 get { return dependencies; }
874 set { dependencies = value; }
877 internal string CompilerOptions {
878 get { return compilerOptions; }
881 internal string Language {
882 get { return language; }
885 internal bool StrictOn {
886 get { return strictOn; }
889 internal bool ExplicitOn {
890 get { return explicitOn; }
893 internal bool Debug {
894 get { return debug; }
897 internal bool OutputCache {
898 get { return output_cache; }
901 internal int OutputCacheDuration {
902 get { return oc_duration; }
905 internal string OutputCacheVaryByHeader {
906 get { return oc_header; }
909 internal string OutputCacheVaryByCustom {
910 get { return oc_custom; }
913 internal string OutputCacheVaryByControls {
914 get { return oc_controls; }
917 internal bool OutputCacheShared {
918 get { return oc_shared; }
921 internal OutputCacheLocation OutputCacheLocation {
922 get { return oc_location; }
925 internal string OutputCacheVaryByParam {
926 get { return oc_param; }
930 internal PagesSection PagesConfig {
932 return WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
936 internal PagesConfiguration PagesConfig {
937 get { return PagesConfiguration.GetInstance (Context); }