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 implicitLanguage;
71 bool strictOn = false;
72 bool explicitOn = false;
73 bool linePragmasOn = false;
76 string oc_header, oc_custom, oc_param, oc_controls;
78 OutputCacheLocation oc_location;
79 CultureInfo invariantCulture = CultureInfo.InvariantCulture;
82 string partialClassName;
83 string codeFileBaseClass;
84 string metaResourceKey;
85 Type codeFileBaseClassType;
86 List <UnknownAttributeDescriptor> unknownMainAttributes;
89 int appAssemblyIndex = -1;
91 internal TemplateParser ()
93 imports = new ArrayList ();
95 AddNamespaces (imports);
97 imports.Add ("System");
98 imports.Add ("System.Collections");
99 imports.Add ("System.Collections.Specialized");
100 imports.Add ("System.Configuration");
101 imports.Add ("System.Text");
102 imports.Add ("System.Text.RegularExpressions");
103 imports.Add ("System.Web");
104 imports.Add ("System.Web.Caching");
105 imports.Add ("System.Web.Security");
106 imports.Add ("System.Web.SessionState");
107 imports.Add ("System.Web.UI");
108 imports.Add ("System.Web.UI.WebControls");
109 imports.Add ("System.Web.UI.HtmlControls");
112 assemblies = new ArrayList ();
114 bool addAssembliesInBin = false;
115 foreach (AssemblyInfo info in CompilationConfig.Assemblies) {
116 if (info.Assembly == "*")
117 addAssembliesInBin = true;
119 AddAssemblyByName (info.Assembly);
121 if (addAssembliesInBin)
122 AddAssembliesInBin ();
124 foreach (NamespaceInfo info in PagesConfig.Namespaces) {
125 imports.Add (info.Namespace);
128 foreach (string a in CompilationConfig.Assemblies)
129 AddAssemblyByName (a);
130 if (CompilationConfig.AssembliesInBin)
131 AddAssembliesInBin ();
134 language = CompilationConfig.DefaultLanguage;
135 implicitLanguage = true;
138 internal void AddApplicationAssembly ()
140 if (Context.ApplicationInstance == null)
141 return; // this may happen if we have Global.asax and have
142 // controls registered from Web.Config
143 string location = Context.ApplicationInstance.AssemblyLocation;
144 if (location != typeof (TemplateParser).Assembly.Location) {
145 appAssemblyIndex = assemblies.Add (location);
149 protected abstract Type CompileIntoType ();
152 void AddNamespaces (ArrayList imports)
154 if (BuildManager.HaveResources)
155 imports.Add ("System.Resources");
157 PagesSection pages = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
161 NamespaceCollection namespaces = pages.Namespaces;
162 if (namespaces == null || namespaces.Count == 0)
165 foreach (NamespaceInfo nsi in namespaces)
166 imports.Add (nsi.Namespace);
170 internal void RegisterCustomControl (string tagPrefix, string tagName, string src)
172 string realpath = MapPath (src);
173 if (String.Compare (realpath, inputFile, false, invariantCulture) == 0)
176 if (!File.Exists (realpath))
177 throw new ParseException (Location, "Could not find file \"" + realpath + "\".");
178 string vpath = VirtualPathUtility.Combine (BaseVirtualDir, src);
180 AddDependency (realpath);
182 ArrayList other_deps = new ArrayList ();
183 type = UserControlParser.GetCompiledType (vpath, realpath, other_deps, Context);
184 foreach (string s in other_deps) {
187 } catch (ParseException pe) {
188 if (this is UserControlParser)
189 throw new ParseException (Location, pe.Message, pe);
193 AddAssembly (type.Assembly, true);
194 RootBuilder.Foundry.RegisterFoundry (tagPrefix, tagName, type);
197 internal void RegisterNamespace (string tagPrefix, string ns, string assembly)
202 if (assembly != null && assembly.Length > 0) {
203 ass = AddAssemblyByName (assembly);
204 AddDependency (ass.Location);
207 RootBuilder.Foundry.RegisterFoundry (tagPrefix, ass, ns);
210 internal virtual void HandleOptions (object obj)
214 internal static string GetOneKey (Hashtable tbl)
216 foreach (object key in tbl.Keys)
217 return key.ToString ();
222 internal virtual void AddDirective (string directive, Hashtable atts)
224 if (String.Compare (directive, DefaultDirectiveName, true) == 0) {
225 if (mainAttributes != null)
226 ThrowParseException ("Only 1 " + DefaultDirectiveName + " is allowed");
228 mainAttributes = atts;
229 ProcessMainAttributes (mainAttributes);
233 int cmp = String.Compare ("Assembly", directive, true);
235 string name = GetString (atts, "Name", null);
236 string src = GetString (atts, "Src", null);
239 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
241 if (name == null && src == null)
242 ThrowParseException ("You gotta specify Src or Name");
244 if (name != null && src != null)
245 ThrowParseException ("Src and Name cannot be used together");
248 AddAssemblyByName (name);
250 GetAssemblyFromSource (src);
256 cmp = String.Compare ("Import", directive, true);
258 string namesp = GetString (atts, "Namespace", null);
260 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
262 if (namesp != null && namesp != "")
267 cmp = String.Compare ("Implements", directive, true);
269 string ifacename = GetString (atts, "Interface", "");
272 ThrowParseException ("Attribute " + GetOneKey (atts) + " unknown.");
274 Type iface = LoadType (ifacename);
276 ThrowParseException ("Cannot find type " + ifacename);
278 if (!iface.IsInterface)
279 ThrowParseException (iface + " is not an interface");
281 AddInterface (iface.FullName);
285 cmp = String.Compare ("OutputCache", directive, true);
287 HttpResponse response = HttpContext.Current.Response;
288 if (response != null)
289 response.Cache.SetValidUntilExpires (true);
293 if (atts ["Duration"] == null)
294 ThrowParseException ("The directive is missing a 'duration' attribute.");
295 if (atts ["VaryByParam"] == null)
296 ThrowParseException ("This directive is missing a 'VaryByParam' " +
297 "attribute, which should be set to \"none\", \"*\", " +
298 "or a list of name/value pairs.");
300 foreach (DictionaryEntry entry in atts) {
301 string key = (string) entry.Key;
302 switch (key.ToLower ()) {
304 oc_duration = Int32.Parse ((string) entry.Value);
306 ThrowParseException ("The 'duration' attribute must be set " +
307 "to a positive integer value");
310 oc_param = (string) entry.Value;
311 if (String.Compare (oc_param, "none") == 0)
315 oc_header = (string) entry.Value;
318 oc_custom = (string) entry.Value;
321 if (!(this is PageParser))
325 oc_location = (OutputCacheLocation) Enum.Parse (
326 typeof (OutputCacheLocation), (string) entry.Value, true);
328 ThrowParseException ("The 'location' attribute is case sensitive and " +
329 "must be one of the following values: Any, Client, " +
330 "Downstream, Server, None, ServerAndClient.");
333 case "varybycontrol":
334 if (this is PageParser)
337 oc_controls = (string) entry.Value;
340 if (this is PageParser)
344 oc_shared = Boolean.Parse ((string) entry.Value);
346 ThrowParseException ("The 'shared' attribute is case sensitive" +
347 " and must be set to 'true' or 'false'.");
351 ThrowParseException ("The '" + key + "' attribute is not " +
352 "supported by the 'Outputcache' directive.");
361 ThrowParseException ("Unknown directive: " + directive);
364 internal Type LoadType (string typeName)
366 Type type = HttpApplication.LoadType (typeName);
369 Assembly asm = type.Assembly;
370 string location = asm.Location;
372 AddDependency (location);
373 if (Path.GetDirectoryName (location) != PrivateBinPath)
374 AddAssembly (asm, true);
379 void AddAssembliesInBin ()
381 if (!Directory.Exists (PrivateBinPath))
384 string [] binDlls = Directory.GetFiles (PrivateBinPath, "*.dll");
385 foreach (string s in binDlls)
389 internal virtual void AddInterface (string iface)
391 if (interfaces == null)
392 interfaces = new ArrayList ();
394 if (!interfaces.Contains (iface))
395 interfaces.Add (iface);
398 internal virtual void AddImport (string namesp)
401 imports = new ArrayList ();
403 if (!imports.Contains (namesp))
404 imports.Add (namesp);
407 internal virtual void AddSourceDependency (string filename)
409 if (dependencies != null && dependencies.Contains (filename)) {
410 ThrowParseException ("Circular file references are not allowed. File: " + filename);
413 AddDependency (filename);
416 internal virtual void AddDependency (string filename)
421 if (dependencies == null)
422 dependencies = new ArrayList ();
424 if (!dependencies.Contains (filename))
425 dependencies.Add (filename);
428 internal virtual void AddAssembly (Assembly assembly, bool fullPath)
430 if (assembly.Location == "")
434 anames = new Hashtable ();
436 string name = assembly.GetName ().Name;
437 string loc = assembly.Location;
439 if (!assemblies.Contains (loc)) {
440 assemblies.Add (loc);
444 anames [loc] = assembly;
446 if (!assemblies.Contains (name)) {
447 assemblies.Add (name);
450 anames [name] = assembly;
454 internal virtual Assembly AddAssemblyByFileName (string filename)
456 Assembly assembly = null;
457 Exception error = null;
460 assembly = Assembly.LoadFrom (filename);
461 } catch (Exception e) { error = e; }
463 if (assembly == null)
464 ThrowParseException ("Assembly " + filename + " not found", error);
466 AddAssembly (assembly, true);
470 internal virtual Assembly AddAssemblyByName (string name)
473 anames = new Hashtable ();
475 if (anames.Contains (name)) {
476 object o = anames [name];
483 Assembly assembly = null;
484 Exception error = null;
485 if (name.IndexOf (',') != -1) {
487 assembly = Assembly.Load (name);
488 } catch (Exception e) { error = e; }
491 if (assembly == null) {
493 assembly = Assembly.LoadWithPartialName (name);
494 } catch (Exception e) { error = e; }
497 if (assembly == null)
498 ThrowParseException ("Assembly " + name + " not found", error);
500 AddAssembly (assembly, true);
504 internal virtual void ProcessMainAttributes (Hashtable atts)
506 atts.Remove ("Description"); // ignored
508 atts.Remove ("CodeBehind"); // ignored
510 atts.Remove ("AspCompat"); // ignored
512 // these two are ignored for the moment
513 atts.Remove ("Async");
514 atts.Remove ("AsyncTimeOut");
517 debug = GetBool (atts, "Debug", true);
518 compilerOptions = GetString (atts, "CompilerOptions", "");
519 language = GetString (atts, "Language", "");
520 if (language.Length != 0)
521 implicitLanguage = false;
523 language = CompilationConfig.DefaultLanguage;
525 strictOn = GetBool (atts, "Strict", CompilationConfig.Strict);
526 explicitOn = GetBool (atts, "Explicit", CompilationConfig.Explicit);
527 linePragmasOn = GetBool (atts, "LinePragmas", false);
529 string inherits = GetString (atts, "Inherits", null);
531 // In ASP 2, the source file is actually integrated with
532 // the generated file via the use of partial classes. This
533 // means that the code file has to be confirmed, but not
534 // used at this point.
535 src = GetString (atts, "CodeFile", null);
536 codeFileBaseClass = GetString (atts, "CodeFileBaseClass", null);
538 if (src == null && codeFileBaseClass != null)
539 ThrowParseException ("The 'CodeFileBaseClass' attribute cannot be used without a 'CodeFile' attribute");
541 if (src != null && inherits != null) {
542 // Make sure the source exists
543 src = UrlUtils.Combine (BaseVirtualDir, src);
544 string realPath = MapPath (src, false);
545 if (!File.Exists (realPath))
546 ThrowParseException ("File " + src + " not found");
548 // We are going to create a partial class that shares
549 // the same name as the inherits tag, so reset the
550 // name. The base type is changed because it is the
551 // code file's responsibilty to extend the classes
553 partialClassName = inherits;
555 // Add the code file as an option to the
556 // compiler. This lets both files be compiled at once.
557 compilerOptions += " \"" + realPath + "\"";
559 if (codeFileBaseClass != null) {
561 codeFileBaseClassType = LoadType (codeFileBaseClass);
562 } catch (Exception) {
565 if (codeFileBaseClassType == null)
566 ThrowParseException ("Could not load type '{0}'", codeFileBaseClass);
568 } else if (inherits != null) {
569 // We just set the inherits directly because this is a
570 // Single-Page model.
571 SetBaseType (inherits);
574 string src = GetString (atts, "Src", null);
577 srcAssembly = GetAssemblyFromSource (src);
579 if (inherits != null)
580 SetBaseType (inherits);
582 className = GetString (atts, "ClassName", null);
583 if (className != null) {
585 string [] identifiers = className.Split ('.');
586 for (int i = 0; i < identifiers.Length; i++)
587 if (!CodeGenerator.IsValidLanguageIndependentIdentifier (identifiers [i]))
588 ThrowParseException (String.Format ("'{0}' is not a valid "
589 + "value for attribute 'classname'.", className));
591 if (!CodeGenerator.IsValidLanguageIndependentIdentifier (className))
592 ThrowParseException (String.Format ("'{0}' is not a valid "
593 + "value for attribute 'classname'.", className));
598 if (this is TemplateControlParser)
599 metaResourceKey = GetString (atts, "meta:resourcekey", null);
601 if (inherits != null && (this is PageParser || this is UserControlParser) && atts.Count > 0) {
602 if (unknownMainAttributes == null)
603 unknownMainAttributes = new List <UnknownAttributeDescriptor> ();
606 foreach (DictionaryEntry de in atts) {
607 key = de.Key as string;
608 val = de.Value as string;
610 if (String.IsNullOrEmpty (key) || String.IsNullOrEmpty (val))
612 CheckUnknownAttribute (key, val, inherits);
618 ThrowParseException ("Unknown attribute: " + GetOneKey (atts));
622 void CheckUnknownAttribute (string name, string val, string inherits)
624 MemberInfo mi = null;
625 bool missing = false;
626 string memberName = name.Trim ().ToLower (CultureInfo.InvariantCulture);
627 Type parent = codeFileBaseClassType;
633 MemberInfo[] infos = parent.GetMember (memberName,
634 MemberTypes.Field | MemberTypes.Property,
635 BindingFlags.Public | BindingFlags.Instance |
636 BindingFlags.IgnoreCase | BindingFlags.Static);
637 if (infos.Length != 0) {
638 // prefer public properties to public methods (it's what MS.NET does)
639 foreach (MemberInfo tmp in infos) {
640 if (tmp is PropertyInfo) {
649 } catch (Exception) {
653 ThrowParseException (
654 "Error parsing attribute '{0}': Type '{1}' does not have a public property named '{0}'",
655 memberName, inherits);
657 Type memberType = null;
658 if (mi is PropertyInfo) {
659 PropertyInfo pi = mi as PropertyInfo;
662 ThrowParseException (
663 "Error parsing attribute '{0}': The '{0}' property is read-only and cannot be set.",
665 memberType = pi.PropertyType;
666 } else if (mi is FieldInfo) {
667 memberType = ((FieldInfo)mi).FieldType;
669 ThrowParseException ("Could not determine member the kind of '{0}' in base type '{1}",
670 memberName, inherits);
671 TypeConverter converter = TypeDescriptor.GetConverter (memberType);
672 bool convertible = true;
675 if (converter == null || !converter.CanConvertFrom (typeof (string)))
680 value = converter.ConvertFromInvariantString (val);
681 } catch (Exception) {
687 ThrowParseException ("Error parsing attribute '{0}': Cannot create an object of type '{1}' from its string representation '{2}' for the '{3}' property.",
688 memberName, memberType, val, mi.Name);
690 UnknownAttributeDescriptor desc = new UnknownAttributeDescriptor (mi, value);
691 unknownMainAttributes.Add (desc);
695 internal void SetBaseType (string type)
697 if (type == DefaultBaseTypeName)
701 if (srcAssembly != null)
702 parent = srcAssembly.GetType (type);
705 parent = LoadType (type);
708 ThrowParseException ("Cannot find type " + type);
710 if (!DefaultBaseType.IsAssignableFrom (parent))
711 ThrowParseException ("The parent type does not derive from " + DefaultBaseType);
714 if (parent.FullName.IndexOf ('.') == -1)
715 baseTypeIsGlobal = true;
718 internal void SetLanguage (string language)
720 this.language = language;
721 implicitLanguage = false;
724 Assembly GetAssemblyFromSource (string vpath)
726 vpath = UrlUtils.Combine (BaseVirtualDir, vpath);
727 string realPath = MapPath (vpath, false);
728 if (!File.Exists (realPath))
729 ThrowParseException ("File " + vpath + " not found");
731 AddSourceDependency (realPath);
733 CompilerResults result = CachingCompiler.Compile (language, realPath, realPath, assemblies);
734 if (result.NativeCompilerReturnValue != 0) {
735 StreamReader reader = new StreamReader (realPath);
736 throw new CompilationException (realPath, result.Errors, reader.ReadToEnd ());
739 AddAssembly (result.CompiledAssembly, true);
740 return result.CompiledAssembly;
743 internal abstract Type DefaultBaseType { get; }
744 internal abstract string DefaultBaseTypeName { get; }
745 internal abstract string DefaultDirectiveName { get; }
747 internal string InputFile
749 get { return inputFile; }
750 set { inputFile = value; }
754 internal bool IsPartial {
755 get { return src != null; }
758 internal string PartialClassName {
759 get { return partialClassName; }
762 internal string CodeFileBaseClass {
763 get { return codeFileBaseClass; }
766 internal string MetaResourceKey {
767 get { return metaResourceKey; }
770 internal Type CodeFileBaseClassType
772 get { return codeFileBaseClassType; }
775 internal List <UnknownAttributeDescriptor> UnknownMainAttributes
777 get { return unknownMainAttributes; }
784 set { text = value; }
787 internal Type BaseType
790 if (baseType == null)
791 baseType = DefaultBaseType;
797 internal bool BaseTypeIsGlobal {
798 get { return baseTypeIsGlobal; }
801 internal string ClassName {
803 if (className != null)
807 string physPath = HttpContext.Current.Request.PhysicalApplicationPath;
809 if (StrUtils.StartsWith (inputFile, physPath)) {
810 className = inputFile.Substring (physPath.Length).ToLower (CultureInfo.InvariantCulture);
811 className = className.Replace ('.', '_');
812 className = className.Replace ('/', '_').Replace ('\\', '_');
815 className = Path.GetFileName (inputFile).Replace ('.', '_');
816 className = className.Replace ('-', '_');
817 className = className.Replace (' ', '_');
819 if (Char.IsDigit(className[0])) {
820 className = "_" + className;
827 internal string PrivateBinPath {
829 if (privateBinPath != null)
830 return privateBinPath;
832 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
833 privateBinPath = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
835 return privateBinPath;
839 internal ArrayList Scripts {
842 scripts = new ArrayList ();
848 internal ArrayList Imports {
849 get { return imports; }
852 internal ArrayList Assemblies {
854 if (appAssemblyIndex != -1) {
855 object o = assemblies [appAssemblyIndex];
856 assemblies.RemoveAt (appAssemblyIndex);
858 appAssemblyIndex = -1;
865 internal ArrayList Interfaces {
866 get { return interfaces; }
869 internal RootBuilder RootBuilder {
870 get { return rootBuilder; }
871 set { rootBuilder = value; }
874 internal ArrayList Dependencies {
875 get { return dependencies; }
876 set { dependencies = value; }
879 internal string CompilerOptions {
880 get { return compilerOptions; }
883 internal string Language {
884 get { return language; }
887 internal bool ImplicitLanguage {
888 get { return implicitLanguage; }
891 internal bool StrictOn {
892 get { return strictOn; }
895 internal bool ExplicitOn {
896 get { return explicitOn; }
899 internal bool Debug {
900 get { return debug; }
903 internal bool OutputCache {
904 get { return output_cache; }
907 internal int OutputCacheDuration {
908 get { return oc_duration; }
911 internal string OutputCacheVaryByHeader {
912 get { return oc_header; }
915 internal string OutputCacheVaryByCustom {
916 get { return oc_custom; }
919 internal string OutputCacheVaryByControls {
920 get { return oc_controls; }
923 internal bool OutputCacheShared {
924 get { return oc_shared; }
927 internal OutputCacheLocation OutputCacheLocation {
928 get { return oc_location; }
931 internal string OutputCacheVaryByParam {
932 get { return oc_param; }
936 internal PagesSection PagesConfig {
938 return WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
942 internal PagesConfiguration PagesConfig {
943 get { return PagesConfiguration.GetInstance (Context); }