2 // System.Web.Compilation.BuildManager
5 // Chris Toshok (toshok@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@novell.com)
7 // Marek Habersack (mhabersack@novell.com)
9 // (C) 2006-2008 Novell, Inc (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
37 using System.CodeDom.Compiler;
38 using System.Collections;
39 using System.Collections.Generic;
40 using System.Collections.Specialized;
42 using System.Reflection;
44 using System.Threading;
46 using System.Web.Caching;
47 using System.Web.Configuration;
48 using System.Web.Hosting;
49 using System.Web.Util;
51 namespace System.Web.Compilation {
52 public sealed class BuildManager {
55 public BuildProvider buildProvider;
56 public AssemblyBuilder assemblyBuilder;
57 public Type codeDomProviderType;
58 public bool codeGenerated;
59 public Assembly compiledAssembly;
61 public ParseException ParserException {
66 public bool ParsedFine {
71 public CompilerParameters CompilerOptions {
73 if (buildProvider == null)
74 throw new HttpException ("No build provider.");
75 return buildProvider.CodeCompilerType.CompilerParameters;
79 public string VirtualPath {
81 if (buildProvider == null)
82 throw new HttpException ("No build provider.");
83 return buildProvider.VirtualPath;
87 public CodeCompileUnit CodeUnit {
89 if (buildProvider == null)
90 throw new HttpException ("No build provider.");
91 return buildProvider.CodeUnit;
95 public BuildItem (BuildProvider provider)
97 this.buildProvider = provider;
100 codeDomProviderType = GetCodeDomProviderType (provider);
102 ParserException = null;
103 } catch (ParseException ex) {
105 ParserException = ex;
109 public void SetCompiledAssembly (AssemblyBuilder assemblyBuilder, Assembly compiledAssembly)
111 if (this.compiledAssembly != null || this.assemblyBuilder == null || this.assemblyBuilder != assemblyBuilder)
114 this.compiledAssembly = compiledAssembly;
117 public CodeDomProvider CreateCodeDomProvider ()
119 if (codeDomProviderType == null)
120 throw new HttpException ("Unable to create compilation provider, no provider type given.");
125 ret = Activator.CreateInstance (codeDomProviderType) as CodeDomProvider;
126 } catch (Exception ex) {
127 throw new HttpException ("Failed to create compilation provider.", ex);
131 throw new HttpException ("Unable to instantiate code DOM provider '" + codeDomProviderType + "'.");
136 public void GenerateCode ()
138 if (buildProvider == null)
139 throw new HttpException ("Cannot generate code - missing build provider.");
141 buildProvider.GenerateCode ();
142 codeGenerated = true;
145 public void StoreCodeUnit ()
147 if (buildProvider == null)
148 throw new HttpException ("Cannot generate code - missing build provider.");
149 if (assemblyBuilder == null)
150 throw new HttpException ("Cannot generate code - missing assembly builder.");
152 buildProvider.GenerateCode (assemblyBuilder);
155 public override string ToString ()
157 string ret = "BuildItem [";
158 string virtualPath = VirtualPath;
160 if (!String.IsNullOrEmpty (virtualPath))
171 public string compiledCustomString;
172 public Assembly assembly;
174 public string virtualPath;
176 public bool ValidBuild {
181 public BuildCacheItem ()
186 public BuildCacheItem (Assembly assembly, BuildProvider bp, CompilerResults results)
188 this.assembly = assembly;
189 this.compiledCustomString = bp.GetCustomString (results);
190 this.type = bp.GetGeneratedType (results);
191 this.virtualPath = bp.VirtualPath;
195 public override string ToString ()
197 StringBuilder sb = new StringBuilder ("BuildCacheItem [");
200 if (!String.IsNullOrEmpty (compiledCustomString)) {
201 sb.Append ("compiledCustomString: " + compiledCustomString);
205 if (assembly != null) {
206 sb.Append ((first ? "" : "; ") + "assembly: " + assembly.ToString ());
211 sb.Append ((first ? "" : "; ") + "type: " + type.ToString ());
215 if (!String.IsNullOrEmpty (virtualPath)) {
216 sb.Append ((first ? "" : "; ") + "virtualPath: " + virtualPath);
222 return sb.ToString ();
235 internal const string FAKE_VIRTUAL_PATH_PREFIX = "/@@MonoFakeVirtualPath@@";
236 const string BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX = "Build_Manager";
237 static int BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH = BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX.Length;
240 static object buildCacheLock = new object ();
242 static Stack <BuildKind> recursiveBuilds = new Stack <BuildKind> ();
245 // Disabled - see comment at the end of BuildAssembly below
247 // static object buildCountLock = new object ();
248 // static int buildCount = 0;
250 static List<Assembly> AppCode_Assemblies = new List<Assembly>();
251 static List<Assembly> TopLevel_Assemblies = new List<Assembly>();
252 static bool haveResources;
254 // The build cache which maps a virtual path to a build item with all the necessary
256 static Dictionary <string, BuildCacheItem> buildCache;
258 // Maps the virtual path of a non-page build to the assembly that contains the
260 static Dictionary <string, Assembly> nonPagesCache;
262 static List <Assembly> referencedAssemblies = new List <Assembly> ();
264 static Dictionary <string, object> compilationTickets;
266 static Assembly globalAsaxAssembly;
268 static Dictionary <string, BuildKind> knownFileTypes = new Dictionary <string, BuildKind> (StringComparer.OrdinalIgnoreCase) {
269 {".aspx", BuildKind.Pages},
270 {".asax", BuildKind.Application},
271 {".ashx", BuildKind.NonPages},
272 {".asmx", BuildKind.NonPages},
273 {".ascx", BuildKind.NonPages},
274 {".master", BuildKind.NonPages}
277 static Dictionary <string, bool> virtualPathsToIgnore;
278 static bool haveVirtualPathsToIgnore;
280 static BuildManager ()
282 IEqualityComparer <string> comparer;
284 if (HttpRuntime.CaseInsensitive)
285 comparer = StringComparer.CurrentCultureIgnoreCase;
287 comparer = StringComparer.CurrentCulture;
289 buildCache = new Dictionary <string, BuildCacheItem> (comparer);
290 nonPagesCache = new Dictionary <string, Assembly> (comparer);
291 compilationTickets = new Dictionary <string, object> (comparer);
293 AppDomain domain = AppDomain.CurrentDomain;
294 hosted = (domain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes";
297 internal static void ThrowNoProviderException (string extension)
299 string msg = "No registered provider for extension '{0}'.";
300 throw new HttpException (String.Format (msg, extension));
303 public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType)
305 // virtualPath + Exists done in GetCompiledType()
306 if (requiredBaseType == null)
307 throw new NullReferenceException (); // This is what MS does, but from somewhere else.
310 Type type = GetCompiledType (virtualPath);
312 //throw new HttpException ("Instance creation failed for virtual
313 //path '" + virtualPath + "'.");
316 if (!requiredBaseType.IsAssignableFrom (type)) {
317 string msg = String.Format ("Type '{0}' does not inherit from '{1}'.",
318 type.FullName, requiredBaseType.FullName);
319 throw new HttpException (500, msg);
322 return Activator.CreateInstance (type, null);
325 public static ICollection GetReferencedAssemblies ()
327 List <Assembly> al = new List <Assembly> ();
329 CompilationSection compConfig = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
330 if (compConfig == null)
333 bool addAssembliesInBin = false;
334 foreach (AssemblyInfo info in compConfig.Assemblies) {
335 if (info.Assembly == "*")
336 addAssembliesInBin = true;
338 LoadAssembly (info, al);
341 foreach (Assembly topLevelAssembly in TopLevel_Assemblies)
342 al.Add (topLevelAssembly);
344 foreach (string assLocation in WebConfigurationManager.ExtraAssemblies)
345 LoadAssembly (assLocation, al);
347 if (addAssembliesInBin)
348 foreach (string s in HttpApplication.BinDirectoryAssemblies)
349 LoadAssembly (s, al);
351 lock (buildCacheLock) {
352 foreach (Assembly asm in referencedAssemblies)
353 if (!al.Contains (asm))
356 if (globalAsaxAssembly != null)
357 al.Add (globalAsaxAssembly);
363 static void LoadAssembly (string path, List <Assembly> al)
365 AddAssembly (Assembly.LoadFrom (path), al);
368 static void LoadAssembly (AssemblyInfo info, List <Assembly> al)
370 AddAssembly (Assembly.Load (info.Assembly), al);
373 static void AddAssembly (Assembly asm, List <Assembly> al)
375 if (al.Contains (asm))
381 [MonoTODO ("Not implemented, always returns null")]
382 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath)
384 return null; // null is ok here until we store the dependency set in the Cache.
387 internal static BuildProvider GetBuildProviderForPath (VirtualPath virtualPath, bool throwOnMissing)
389 return GetBuildProviderForPath (virtualPath, null, throwOnMissing);
392 internal static BuildProvider GetBuildProviderForPath (VirtualPath virtualPath, CompilationSection section, bool throwOnMissing)
394 string extension = virtualPath.Extension;
395 CompilationSection c = section;
398 c = WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Original) as CompilationSection;
402 ThrowNoProviderException (extension);
406 BuildProviderCollection coll = c.BuildProviders;
407 if (coll == null || coll.Count == 0)
408 ThrowNoProviderException (extension);
410 BuildProvider provider = coll.GetProviderForExtension (extension);
411 if (provider == null)
413 ThrowNoProviderException (extension);
417 provider.SetVirtualPath (virtualPath);
421 static VirtualPath GetAbsoluteVirtualPath (string virtualPath)
425 if (!VirtualPathUtility.IsRooted (virtualPath)) {
426 HttpContext ctx = HttpContext.Current;
427 HttpRequest req = ctx != null ? ctx.Request : null;
430 vp = VirtualPathUtility.GetDirectory (req.FilePath) + virtualPath;
432 throw new HttpException ("No context, cannot map paths.");
436 return new VirtualPath (vp);
439 static BuildCacheItem GetCachedItem (VirtualPath virtualPath)
443 lock (buildCacheLock) {
444 if (buildCache.TryGetValue (virtualPath.Absolute, out ret))
451 public static Assembly GetCompiledAssembly (string virtualPath)
453 VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
454 BuildCacheItem ret = GetCachedItem (vp);
459 ret = GetCachedItem (vp);
466 public static Type GetCompiledType (string virtualPath)
468 VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
469 BuildCacheItem ret = GetCachedItem (vp);
475 ret = GetCachedItem (vp);
483 public static string GetCompiledCustomString (string virtualPath)
485 VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
486 BuildCacheItem ret = GetCachedItem (vp);
488 return ret.compiledCustomString;
491 ret = GetCachedItem (vp);
493 return ret.compiledCustomString;
498 static List <VirtualFile> GetFilesForBuild (VirtualPath virtualPath, out BuildKind kind)
500 string extension = virtualPath.Extension;
501 var ret = new List <VirtualFile> ();
503 if (virtualPath.StartsWith (FAKE_VIRTUAL_PATH_PREFIX)) {
504 kind = BuildKind.Fake;
508 if (!knownFileTypes.TryGetValue (extension, out kind)) {
509 if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/"))
510 kind = BuildKind.Theme;
512 kind = BuildKind.Unknown;
515 if (kind == BuildKind.Theme || kind == BuildKind.Application)
518 bool doBatch = BatchMode;
520 lock (buildCacheLock) {
521 if (recursiveBuilds.Count > 0 && recursiveBuilds.Peek () == kind)
523 recursiveBuilds.Push (kind);
526 string vpAbsolute = virtualPath.Absolute;
527 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
528 VirtualDirectory dir = vpp.GetDirectory (vpAbsolute);
530 if (doBatch && HostingEnvironment.HaveCustomVPP && dir != null && dir is DefaultVirtualDirectory)
535 throw new HttpException (404, "Virtual directory '" + virtualPath.Directory + "' does not exist.");
538 foreach (VirtualFile file in dir.Files) {
539 if (!knownFileTypes.TryGetValue (VirtualPathUtility.GetExtension (file.Name), out fileKind))
542 if (kind == fileKind)
546 VirtualFile vf = vpp.GetFile (vpAbsolute);
548 throw new HttpException (404, "Virtual file '" + virtualPath + "' does not exist.");
555 static void SetCommonParameters (CompilationSection config, CompilerParameters p)
557 p.IncludeDebugInformation = config.Debug;
560 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection)
562 // MS throws when accesing a Hashtable, we do here.
563 if (language == null || language == "")
564 throw new ArgumentNullException ("language");
566 CompilationSection config;
567 if (configSection == null)
568 config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
570 config = configSection;
572 Compiler compiler = config.Compilers.Get (language);
573 CompilerParameters p;
574 if (compiler != null) {
575 Type type = HttpApplication.LoadType (compiler.Type, true);
576 p = new CompilerParameters ();
577 p.CompilerOptions = compiler.CompilerOptions;
578 p.WarningLevel = compiler.WarningLevel;
579 SetCommonParameters (config, p);
580 return new CompilerType (type, p);
583 if (CodeDomProvider.IsDefinedLanguage (language)) {
584 CompilerInfo info = CodeDomProvider.GetCompilerInfo (language);
585 CompilerParameters par = info.CreateDefaultCompilerParameters ();
586 SetCommonParameters (config, par);
587 return new CompilerType (info.CodeDomProviderType, par);
590 throw new HttpException (String.Concat ("No compiler for language '", language, "'."));
593 internal static Type GetCodeDomProviderType (BuildProvider provider)
595 CompilerType codeCompilerType;
596 Type codeDomProviderType = null;
598 codeCompilerType = provider.CodeCompilerType;
599 if (codeCompilerType != null)
600 codeDomProviderType = codeCompilerType.CodeDomProviderType;
602 if (codeDomProviderType == null)
603 throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type."));
605 return codeDomProviderType;
608 static List <BuildItem> LoadBuildProviders (VirtualPath virtualPath, string virtualDir, Dictionary <string, bool> vpCache,
609 out BuildKind kind, out string assemblyBaseName)
611 HttpContext ctx = HttpContext.Current;
612 HttpRequest req = ctx != null ? ctx.Request : null;
615 throw new HttpException ("No context available, cannot build.");
617 string vpAbsolute = virtualPath.Absolute;
618 CompilationSection section = WebConfigurationManager.GetSection ("system.web/compilation", vpAbsolute) as CompilationSection;
619 List <VirtualFile> files;
622 files = GetFilesForBuild (virtualPath, out kind);
623 } catch (Exception ex) {
624 throw new HttpException ("Error loading build providers for path '" + virtualDir + "'.", ex);
627 List <BuildItem> ret = new List <BuildItem> ();
628 BuildProvider provider = null;
631 case BuildKind.Theme:
632 assemblyBaseName = "App_Theme_";
633 provider = new ThemeDirectoryBuildProvider ();
634 provider.SetVirtualPath (virtualPath);
637 case BuildKind.Application:
638 assemblyBaseName = "App_global.asax.";
639 provider = new ApplicationFileBuildProvider ();
640 provider.SetVirtualPath (virtualPath);
644 provider = GetBuildProviderForPath (virtualPath, section, false);
645 assemblyBaseName = null;
649 assemblyBaseName = null;
653 if (provider != null) {
654 ret.Add (new BuildItem (provider));
658 string fileVirtualPath;
661 lock (buildCacheLock) {
662 foreach (VirtualFile f in files) {
663 fileVirtualPath = f.VirtualPath;
664 if (IgnoreVirtualPath (fileVirtualPath))
667 if (buildCache.ContainsKey (fileVirtualPath) || vpCache.ContainsKey (fileVirtualPath))
670 vpCache.Add (fileVirtualPath, true);
671 provider = GetBuildProviderForPath (new VirtualPath (fileVirtualPath), section, false);
672 if (provider == null)
675 ret.Add (new BuildItem (provider));
682 static bool IgnoreVirtualPath (string virtualPath)
684 if (!haveVirtualPathsToIgnore)
687 if (virtualPathsToIgnore.ContainsKey (virtualPath))
693 static void AddPathToIgnore (string vp)
695 if (virtualPathsToIgnore == null)
696 virtualPathsToIgnore = new Dictionary <string, bool> ();
698 VirtualPath path = GetAbsoluteVirtualPath (vp);
699 string vpAbsolute = path.Absolute;
700 if (virtualPathsToIgnore.ContainsKey (vpAbsolute))
703 virtualPathsToIgnore.Add (vpAbsolute, true);
704 haveVirtualPathsToIgnore = true;
707 static char[] virtualPathsToIgnoreSplitChars = {','};
708 static void LoadVirtualPathsToIgnore ()
710 if (virtualPathsToIgnore != null)
713 NameValueCollection appSettings = WebConfigurationManager.AppSettings;
714 if (appSettings == null)
717 string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"];
718 string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"];
720 if (!String.IsNullOrEmpty (pathsFromConfig)) {
721 string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars);
724 foreach (string p in paths) {
726 if (path.Length == 0)
729 AddPathToIgnore (path);
733 if (!String.IsNullOrEmpty (pathsFromFile)) {
735 HttpContext ctx = HttpContext.Current;
736 HttpRequest req = ctx != null ? ctx.Request : null;
739 throw new HttpException ("Missing context, cannot continue.");
741 realpath = req.MapPath (pathsFromFile);
742 if (!File.Exists (realpath))
745 string[] paths = File.ReadAllLines (realpath);
746 if (paths == null || paths.Length == 0)
750 foreach (string p in paths) {
752 if (path.Length == 0)
755 AddPathToIgnore (path);
760 static AssemblyBuilder CreateAssemblyBuilder (string assemblyBaseName, VirtualPath virtualPath, BuildItem buildItem)
762 buildItem.assemblyBuilder = new AssemblyBuilder (virtualPath, buildItem.CreateCodeDomProvider (), assemblyBaseName);
763 buildItem.assemblyBuilder.CompilerOptions = buildItem.CompilerOptions;
765 return buildItem.assemblyBuilder;
768 static Dictionary <string, CompileUnitPartialType> GetUnitPartialTypes (CodeCompileUnit unit)
770 Dictionary <string, CompileUnitPartialType> ret = null;
772 CompileUnitPartialType pt;
773 foreach (CodeNamespace ns in unit.Namespaces) {
774 foreach (CodeTypeDeclaration type in ns.Types) {
775 if (type.IsPartial) {
776 pt = new CompileUnitPartialType (unit, ns, type);
779 ret = new Dictionary <string, CompileUnitPartialType> ();
781 ret.Add (pt.TypeName, pt);
789 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeMemberMethod member)
791 if (type == null || member == null)
794 CodeMemberMethod method;
795 string methodName = member.Name;
798 foreach (CodeTypeMember m in type.Members) {
799 if (m.Name != methodName)
802 method = m as CodeMemberMethod;
806 if ((count = method.Parameters.Count) != member.Parameters.Count)
809 CodeParameterDeclarationExpressionCollection methodA = method.Parameters;
810 CodeParameterDeclarationExpressionCollection methodB = member.Parameters;
812 for (int i = 0; i < count; i++)
813 if (methodA [i].Type != methodB [i].Type)
822 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeMemberField member)
824 if (type == null || member == null)
827 CodeMemberField field = FindMemberByName (type, member.Name) as CodeMemberField;
831 if (field.Type == member.Type)
832 return false; // This will get "flattened" by AssemblyBuilder
837 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeTypeMember member)
839 if (type == null || member == null)
842 return (FindMemberByName (type, member.Name) != null);
845 static CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
847 foreach (CodeTypeMember m in type.Members) {
848 if (m == null || m.Name != name)
856 static bool PartialTypesConflict (CodeTypeDeclaration typeA, CodeTypeDeclaration typeB)
861 foreach (CodeTypeMember member in typeB.Members) {
863 type = member.GetType ();
864 if (type == typeof (CodeMemberMethod))
865 conflict = TypeHasConflictingMember (typeA, (CodeMemberMethod) member);
866 else if (type == typeof (CodeMemberField))
867 conflict = TypeHasConflictingMember (typeA, (CodeMemberField) member);
869 conflict = TypeHasConflictingMember (typeA, member);
878 static bool CanAcceptCode (AssemblyBuilder assemblyBuilder, BuildItem buildItem)
880 CodeCompileUnit newUnit = buildItem.CodeUnit;
884 Dictionary <string, CompileUnitPartialType> unitPartialTypes = GetUnitPartialTypes (newUnit);
886 if (unitPartialTypes == null)
889 if (assemblyBuilder.Units.Count > CompilationConfig.MaxBatchSize)
892 CompileUnitPartialType pt;
893 foreach (List <CompileUnitPartialType> partialTypes in assemblyBuilder.PartialTypes.Values)
894 foreach (CompileUnitPartialType cupt in partialTypes)
895 if (unitPartialTypes.TryGetValue (cupt.TypeName, out pt) && PartialTypesConflict (cupt.PartialType, pt.PartialType))
901 static void AssignToAssemblyBuilder (string assemblyBaseName, VirtualPath virtualPath, BuildItem buildItem,
902 Dictionary <Type, List <AssemblyBuilder>> assemblyBuilders)
904 if (!buildItem.codeGenerated)
905 buildItem.GenerateCode ();
907 List <AssemblyBuilder> builders;
909 if (!assemblyBuilders.TryGetValue (buildItem.codeDomProviderType, out builders)) {
910 builders = new List <AssemblyBuilder> ();
911 assemblyBuilders.Add (buildItem.codeDomProviderType, builders);
914 // Put it in the first assembly builder that doesn't have conflicting
916 foreach (AssemblyBuilder assemblyBuilder in builders) {
917 if (CanAcceptCode (assemblyBuilder, buildItem)) {
918 buildItem.assemblyBuilder = assemblyBuilder;
919 buildItem.StoreCodeUnit ();
924 // None of the existing builders can accept this unit, get it a new builder
925 builders.Add (CreateAssemblyBuilder (assemblyBaseName, virtualPath, buildItem));
926 buildItem.StoreCodeUnit ();
929 static void AssertVirtualPathExists (VirtualPath virtualPath)
932 bool dothrow = false;
934 if (virtualPath.StartsWith (FAKE_VIRTUAL_PATH_PREFIX)) {
935 realpath = virtualPath.Original.Substring (FAKE_VIRTUAL_PATH_PREFIX.Length);
936 if (!File.Exists (realpath) && !Directory.Exists (realpath))
939 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
940 string vpAbsolute = virtualPath.Absolute;
942 if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute))
947 throw new HttpException (404, "The file '" + virtualPath + "' does not exist.");
950 static void BuildAssembly (VirtualPath virtualPath)
952 AssertVirtualPathExists (virtualPath);
953 LoadVirtualPathsToIgnore ();
957 string virtualDir = virtualPath.Directory;
958 BuildKind buildKind = BuildKind.Unknown;
959 bool kindPushed = false;
960 string vpAbsolute = virtualPath.Absolute;
961 BuildItem vpBuildItem = null;
963 acquired = AcquireCompilationTicket (virtualDir, out ticket);
965 Monitor.Enter (ticket);
966 lock (buildCacheLock) {
967 if (buildCache.ContainsKey (vpAbsolute))
971 string assemblyBaseName;
972 Dictionary <string, bool> vpCache = new Dictionary <string, bool> ();
973 List <BuildItem> buildItems = LoadBuildProviders (virtualPath, virtualDir, vpCache, out buildKind, out assemblyBaseName);
976 if (buildItems.Count == 0)
979 Dictionary <Type, List <AssemblyBuilder>> assemblyBuilders = new Dictionary <Type, List <AssemblyBuilder>> ();
980 bool checkForRecursion = buildKind == BuildKind.NonPages;
982 foreach (BuildItem buildItem in buildItems) {
983 if (buildItem.VirtualPath == vpAbsolute) {
984 if (!buildItem.ParsedFine)
985 throw buildItem.ParserException;
986 vpBuildItem = buildItem;
987 } else if (!buildItem.ParsedFine)
990 if (checkForRecursion) {
991 // Expensive but, alas, necessary - the builder in
992 // our list might've been put into a different
993 // assembly in a recursive call.
994 lock (buildCacheLock) {
995 if (buildCache.ContainsKey (buildItem.VirtualPath))
1000 if (buildItem.assemblyBuilder == null)
1001 AssignToAssemblyBuilder (assemblyBaseName, virtualPath, buildItem, assemblyBuilders);
1003 CompilerResults results;
1004 Assembly compiledAssembly;
1008 foreach (List <AssemblyBuilder> abuilders in assemblyBuilders.Values) {
1009 foreach (AssemblyBuilder abuilder in abuilders) {
1010 abuilder.AddAssemblyReference (GetReferencedAssemblies () as List <Assembly>);
1011 results = abuilder.BuildAssembly (virtualPath);
1013 // No results is not an error - it is possible that the assembly builder contained only .asmx and
1014 // .ashx files which had no body, just the directive. In such case, no code unit or code file is added
1015 // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
1016 // to be added to the cache.
1017 compiledAssembly = results != null ? results.CompiledAssembly : null;
1019 lock (buildCacheLock) {
1020 switch (buildKind) {
1021 case BuildKind.NonPages:
1022 if (compiledAssembly != null)
1023 AddToReferencedAssemblies (compiledAssembly);
1026 case BuildKind.Application:
1027 globalAsaxAssembly = compiledAssembly;
1031 foreach (BuildItem buildItem in buildItems) {
1032 if (buildItem.assemblyBuilder != abuilder)
1035 vp = buildItem.VirtualPath;
1036 bp = buildItem.buildProvider;
1037 buildItem.SetCompiledAssembly (abuilder, compiledAssembly);
1039 if (!buildCache.ContainsKey (vp)) {
1040 AddToCache (vp, bp);
1041 buildCache.Add (vp, new BuildCacheItem (compiledAssembly, bp, results));
1044 if (compiledAssembly != null && !nonPagesCache.ContainsKey (vp))
1045 nonPagesCache.Add (vp, compiledAssembly);
1051 // WARNING: enabling this code breaks the test suite - it stays
1052 // disabled until I figure out what to do about it.
1053 // See http://support.microsoft.com/kb/319947
1054 // lock (buildCountLock) {
1056 // if (buildCount > CompilationConfig.NumRecompilesBeforeAppRestart)
1057 // HttpRuntime.UnloadAppDomain ();
1060 if (kindPushed && buildKind == BuildKind.Pages || buildKind == BuildKind.NonPages) {
1061 lock (buildCacheLock) {
1062 recursiveBuilds.Pop ();
1066 Monitor.Exit (ticket);
1068 ReleaseCompilationTicket (virtualDir);
1072 internal static void AddToReferencedAssemblies (Assembly asm)
1074 lock (buildCacheLock) {
1075 if (referencedAssemblies.Contains (asm))
1078 referencedAssemblies.Add (asm);
1082 internal static void AddToCache (string virtualPath, BuildProvider bp)
1084 HttpContext ctx = HttpContext.Current;
1085 HttpRequest req = ctx != null ? ctx.Request : null;
1088 throw new HttpException ("No current context.");
1090 CacheItemRemovedCallback cb = new CacheItemRemovedCallback (OnVirtualPathChanged);
1091 CacheDependency dep;
1092 ICollection col = bp.VirtualPathDependencies;
1095 if (col != null && (count = col.Count) > 0) {
1096 string[] files = new string [count];
1100 foreach (object o in col) {
1102 if (String.IsNullOrEmpty (file))
1104 files [fileCount++] = req.MapPath (file);
1107 dep = new CacheDependency (files);
1111 HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath,
1114 Cache.NoAbsoluteExpiration,
1115 Cache.NoSlidingExpiration,
1116 CacheItemPriority.High,
1121 static int RemoveVirtualPathFromCaches (VirtualPath virtualPath)
1123 lock (buildCacheLock) {
1124 // This is expensive, but we must do it - we must not leave
1125 // the assembly in which the invalidated type lived. At the same
1126 // time, we must remove the virtual paths which were in that
1127 // assembly from the other caches, so that they get recompiled.
1128 BuildCacheItem item = GetCachedItem (virtualPath);
1132 string vpAbsolute = virtualPath.Absolute;
1133 if (buildCache.ContainsKey (vpAbsolute))
1134 buildCache.Remove (vpAbsolute);
1138 if (nonPagesCache.TryGetValue (vpAbsolute, out asm)) {
1139 nonPagesCache.Remove (vpAbsolute);
1140 if (referencedAssemblies.Contains (asm))
1141 referencedAssemblies.Remove (asm);
1143 ArrayList extraAssemblies = WebConfigurationManager.ExtraAssemblies;
1144 if (extraAssemblies != null && extraAssemblies.Contains (asm.Location))
1145 extraAssemblies.Remove (asm.Location);
1147 List <string> keysToRemove = new List <string> ();
1148 foreach (KeyValuePair <string, Assembly> kvp in nonPagesCache)
1149 if (kvp.Value == asm)
1150 keysToRemove.Add (kvp.Key);
1152 foreach (string key in keysToRemove) {
1153 nonPagesCache.Remove (key);
1155 if (buildCache.ContainsKey (key))
1156 buildCache.Remove (key);
1164 static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason)
1168 if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX))
1169 virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH);
1173 RemoveVirtualPathFromCaches (new VirtualPath (virtualPath));
1176 static bool AcquireCompilationTicket (string key, out object ticket)
1178 lock (((ICollection)compilationTickets).SyncRoot) {
1179 if (!compilationTickets.TryGetValue (key, out ticket)) {
1180 ticket = new Mutex ();
1181 compilationTickets.Add (key, ticket);
1189 static void ReleaseCompilationTicket (string key)
1191 lock (((ICollection)compilationTickets).SyncRoot) {
1192 if (compilationTickets.ContainsKey (key))
1193 compilationTickets.Remove (key);
1197 // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers
1198 public static Type GetType (string typeName, bool throwOnError)
1200 return GetType (typeName, throwOnError, false);
1203 public static Type GetType (string typeName, bool throwOnError, bool ignoreCase)
1207 foreach (Assembly asm in TopLevel_Assemblies) {
1208 ret = asm.GetType (typeName, throwOnError, ignoreCase);
1212 } catch (Exception ex) {
1213 throw new HttpException ("Failed to find the specified type.", ex);
1218 internal static ICollection GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider)
1220 BuildProvider provider = bprovider;
1221 if (provider == null)
1222 provider = GetBuildProviderForPath (new VirtualPath (virtualPath), false);
1223 if (provider == null)
1225 return provider.VirtualPathDependencies;
1228 public static ICollection GetVirtualPathDependencies (string virtualPath)
1230 return GetVirtualPathDependencies (virtualPath, null);
1233 // Assemblies built from the App_Code directory
1234 public static IList CodeAssemblies {
1235 get { return AppCode_Assemblies; }
1238 internal static IList TopLevelAssemblies {
1239 get { return TopLevel_Assemblies; }
1242 internal static bool HaveResources {
1243 get { return haveResources; }
1244 set { haveResources = value; }
1247 internal static bool BatchMode {
1250 return false; // Fix for bug #380985
1252 return CompilationConfig.Batch;
1256 internal static CompilationSection CompilationConfig {
1257 get { return WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection; }