2009-02-28 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppCodeCompiler.cs
index d21e6e42e039453d5c05422b6070bae904963c8a..71ac1d1452e111e5f1ed05c970e0d84f14726e29 100644 (file)
 using System;
 using System.CodeDom;
 using System.CodeDom.Compiler;
+using System.Configuration;
 using System.Collections;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Reflection;
+using System.Web;
 using System.Web.Configuration;
+using System.Web.Profile;
 using System.Web.Util;
 
 namespace System.Web.Compilation
 {
+       class AssemblyPathResolver
+       {
+               static Dictionary <string, string> assemblyCache;
+
+               static AssemblyPathResolver ()
+               {
+                       assemblyCache = new Dictionary <string, string> ();
+               }
+
+               public static string GetAssemblyPath (string assemblyName)
+               {
+                       lock (assemblyCache) {
+                               if (assemblyCache.ContainsKey (assemblyName))
+                                       return assemblyCache [assemblyName];
+
+                               Assembly asm = null;
+                               Exception error = null;
+                               if (assemblyName.IndexOf (',') != -1) {
+                                       try {
+                                               asm = Assembly.Load (assemblyName);
+                                       } catch (Exception e) {
+                                               error = e;
+                                       }
+                               }
+
+                               if (asm == null) {
+                                       try {
+                                               asm = Assembly.LoadWithPartialName (assemblyName);
+                                       } catch (Exception e) {
+                                               error = e;
+                                       }
+                               }
+                        
+                               if (asm == null)
+                                       throw new HttpException (String.Format ("Unable to find assembly {0}", assemblyName), error);
+
+                               assemblyCache.Add (assemblyName, asm.Location);
+                               return asm.Location;
+                       }
+               }
+       }
+       
        internal class AppCodeAssembly
        {
-               private List<string> files;
-               private string name;
-               private string path;
-               private bool validAssembly;
+               List<string> files;
+               List<CodeCompileUnit> units;
+               
+               string name;
+               string path;
+               bool validAssembly;
+               string outputAssemblyName;
+
+               public string OutputAssemblyName
+               {
+                       get {
+                               return outputAssemblyName;
+                       }
+               }
                
                public bool IsValid
                {
@@ -71,7 +127,8 @@ namespace System.Web.Compilation
                
                public AppCodeAssembly (string name, string path)
                {
-                       this.files = new List<string>();
+                       this.files = new List<string> ();
+                       this.units = new List<CodeCompileUnit> ();
                        this.validAssembly = true;
                        this.name = name;
                        this.path = path;
@@ -82,6 +139,11 @@ namespace System.Web.Compilation
                        files.Add (path);
                }
 
+               public void AddUnit (CodeCompileUnit unit)
+               {
+                       units.Add (unit);
+               }
+               
                object OnCreateTemporaryAssemblyFile (string path)
                {
                        FileStream f = new FileStream (path, FileMode.CreateNew);
@@ -91,7 +153,7 @@ namespace System.Web.Compilation
                
                // Build and add the assembly to the BuildManager's
                // CodeAssemblies collection
-               public void Build ()
+               public void Build (string[] binAssemblies)
                {
                        Type compilerProvider = null;
                        CompilerInfo compilerInfo = null, cit;
@@ -101,13 +163,13 @@ namespace System.Web.Compilation
                        
                        // First make sure all the files are in the same
                        // language
-                       bool known;
+                       bool known = false;
                        foreach (string f in files) {
                                known = true;
                                language = null;
                                
                                extension = Path.GetExtension (f);
-                               if (!CodeDomProvider.IsDefinedExtension (extension))
+                               if (String.IsNullOrEmpty (extension) || !CodeDomProvider.IsDefinedExtension (extension))
                                        known = false;
                                if (known) {
                                        language = CodeDomProvider.GetLanguageFromExtension(extension);
@@ -136,16 +198,13 @@ namespace System.Web.Compilation
                        }
 
                        CodeDomProvider provider = null;
+                       CompilationSection compilationSection = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
                        if (compilerInfo == null) {
-                               CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
-                               if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
+                               if (!CodeDomProvider.IsDefinedLanguage (compilationSection.DefaultLanguage))
                                        throw new HttpException ("Failed to retrieve default source language");
-                               compilerInfo = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
+                               compilerInfo = CodeDomProvider.GetCompilerInfo (compilationSection.DefaultLanguage);
                                if (compilerInfo == null || !compilerInfo.IsCodeDomProviderTypeValid)
                                        throw new HttpException ("Internal error while initializing application");
-                               provider = compilerInfo.CreateProvider ();
-                               if (provider == null)
-                                       throw new HttpException ("A code provider error occurred while initializing application.");
                        }
 
                        provider = compilerInfo.CreateProvider ();
@@ -155,10 +214,29 @@ namespace System.Web.Compilation
                        AssemblyBuilder abuilder = new AssemblyBuilder (provider);
                        foreach (string file in knownfiles)
                                abuilder.AddCodeFile (file);
+                       foreach (CodeCompileUnit unit in units)
+                               abuilder.AddCodeCompileUnit (unit);
                        
                        BuildProvider bprovider;
-                       CompilationSection compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
+                       CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
+                       parameters.IncludeDebugInformation = compilationSection.Debug;
+                       
+                       if (binAssemblies != null && binAssemblies.Length > 0)
+                               parameters.ReferencedAssemblies.AddRange (binAssemblies);
+                       
                        if (compilationSection != null) {
+                               foreach (AssemblyInfo ai in compilationSection.Assemblies)
+                                       if (ai.Assembly != "*") {
+                                               try {
+                                                       parameters.ReferencedAssemblies.Add (
+                                                               AssemblyPathResolver.GetAssemblyPath (ai.Assembly));
+                                               } catch (Exception ex) {
+                                                       throw new HttpException (
+                                                               String.Format ("Could not find assembly {0}.", ai.Assembly),
+                                                               ex);
+                                               }
+                                       }
+                               
                                BuildProviderCollection buildProviders = compilationSection.BuildProviders;
                                
                                foreach (string file in unknownfiles) {
@@ -168,36 +246,51 @@ namespace System.Web.Compilation
                                        bprovider.GenerateCode (abuilder);
                                }
                        }
+
+                       if (knownfiles.Count == 0 && unknownfiles.Count == 0 && units.Count == 0)
+                               return;
                        
-                       string assemblyName = (string)FileUtils.CreateTemporaryFile (
+                       outputAssemblyName = (string)FileUtils.CreateTemporaryFile (
                                AppDomain.CurrentDomain.SetupInformation.DynamicBase,
                                name, "dll", OnCreateTemporaryAssemblyFile);
-                       CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
-                       parameters.OutputAssembly = assemblyName;
+                       parameters.OutputAssembly = outputAssemblyName;
+                       foreach (Assembly a in BuildManager.TopLevelAssemblies)
+                               parameters.ReferencedAssemblies.Add (a.Location);
                        CompilerResults results = abuilder.BuildAssembly (parameters);
-                       if (results.Errors.Count == 0) {
-                               BuildManager.CodeAssemblies.Add (results.PathToAssembly);
+                       if (results == null)
+                               return;
+                       
+                       if (results.NativeCompilerReturnValue == 0) {
+                               BuildManager.CodeAssemblies.Add (results.CompiledAssembly);
                                BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
+                               HttpRuntime.WritePreservationFile (results.CompiledAssembly, name);
                        } else {
                                if (HttpContext.Current.IsCustomErrorEnabled)
                                        throw new HttpException ("An error occurred while initializing application.");
                                throw new CompilationException (null, results.Errors, null);
                        }
                }
-
-               private BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
+               
+               VirtualPath PhysicalToVirtual (string file)
+               {
+                       return new VirtualPath (file.Replace (HttpRuntime.AppDomainAppPath, "/").Replace (Path.DirectorySeparatorChar, '/'));
+               }
+               
+               BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
                {
                        if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
                                return null;
 
-                       foreach (BuildProvider bp in buildProviders)
-                               if (IsCorrectBuilderType (bp))
-                                       return bp;
-
+                       BuildProvider ret = buildProviders.GetProviderForExtension (Path.GetExtension (file));
+                       if (ret != null && IsCorrectBuilderType (ret)) {
+                               ret.SetVirtualPath (PhysicalToVirtual (file));
+                               return ret;
+                       }
+                               
                        return null;
                }
 
-               private bool IsCorrectBuilderType (BuildProvider bp)
+               bool IsCorrectBuilderType (BuildProvider bp)
                {
                        if (bp == null)
                                return false;
@@ -230,6 +323,9 @@ namespace System.Web.Compilation
        
        internal class AppCodeCompiler
        {
+               static bool _alreadyCompiled;
+               internal static string DefaultAppCodeAssemblyName;
+               
                // A dictionary that contains an entry per an assembly that will
                // be produced by compiling App_Code. There's one main assembly
                // and an optional number of assemblies as defined by the
@@ -252,53 +348,389 @@ namespace System.Web.Compilation
                // Files for which exist BuildProviders but which have no
                // unambiguous language assigned to them (e.g. .wsdl files), are
                // built using the default website compiler.
-               private List<AppCodeAssembly> assemblies;
+               List<AppCodeAssembly> assemblies;
+               string providerTypeName = null;
                
                public AppCodeCompiler ()
                {
                        assemblies = new List<AppCodeAssembly>();
                }
 
-               public void Compile ()
+               bool ProcessAppCodeDir (string appCode, AppCodeAssembly defasm)
                {
-                       string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
-                       if (!Directory.Exists (appCode))
-                               return;
-                       
                        // First process the codeSubDirectories
-                       CompilationSection cs = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
+                       CompilationSection cs = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
                        
                        if (cs != null) {
                                string aname;
                                for (int i = 0; i < cs.CodeSubDirectories.Count; i++) {
-                                       aname = String.Format ("App_SubCode_{0}", cs.CodeSubDirectories[i].DirectoryName);
+                                       aname = String.Concat ("App_SubCode_", cs.CodeSubDirectories[i].DirectoryName);
                                        assemblies.Add (new AppCodeAssembly (
                                                                aname,
                                                                Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
                                }
                        }
+                       
+                       return CollectFiles (appCode, defasm);
+               }
+
+               CodeTypeReference GetProfilePropertyType (string type)
+               {
+                       if (String.IsNullOrEmpty (type))
+                               throw new ArgumentException ("String size cannot be 0", "type");
+                       return new CodeTypeReference (type);
+               }
+
+               string FindProviderTypeName (ProfileSection ps, string providerName)
+               {
+                       if (ps.Providers == null || ps.Providers.Count == 0)
+                               return null;
+                       
+                       ProviderSettings pset = ps.Providers [providerName];
+                       if (pset == null)
+                               return null;
+                       return pset.Type;
+               }
+               
+               void GetProfileProviderAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
+                                                 string providerName)
+               {
+                       if (String.IsNullOrEmpty (providerName))
+                               providerTypeName = FindProviderTypeName (ps, ps.DefaultProvider);
+                       else
+                               providerTypeName = FindProviderTypeName (ps, providerName);
+                       if (providerTypeName == null)
+                               throw new HttpException (String.Format ("Profile provider type not defined: {0}",
+                                                                       providerName));
+                       
+                       collection.Add (
+                               new CodeAttributeDeclaration (
+                                       "ProfileProvider",
+                                       new CodeAttributeArgument (
+                                               new CodePrimitiveExpression (providerTypeName)
+                                       )
+                               )
+                       );
+               }
+
+               void GetProfileSettingsSerializeAsAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
+                                                            SerializationMode mode)
+               {
+                       string parameter = String.Concat ("SettingsSerializeAs.", mode.ToString ());
+                       collection.Add (
+                               new CodeAttributeDeclaration (
+                                       "SettingsSerializeAs",
+                                       new CodeAttributeArgument (
+                                               new CodeSnippetExpression (parameter)
+                                       )
+                               )
+                       );
+                                       
+               }
+
+               void AddProfileClassGetProfileMethod (CodeTypeDeclaration profileClass)
+               {
+                       CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
+                               new CodeTypeReferenceExpression (typeof (System.Web.Profile.ProfileBase)),
+                               "Create");
+                       CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
+                               mref,
+                               new CodeExpression[] { new CodeVariableReferenceExpression ("username") }
+                       );
+                       CodeCastExpression cast = new CodeCastExpression ();
+                       cast.TargetType = new CodeTypeReference ("ProfileCommon");
+                       cast.Expression = minvoke;
+                       
+                       CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
+                       ret.Expression = cast;
+                       
+                       CodeMemberMethod method = new CodeMemberMethod ();
+                       method.Name = "GetProfile";
+                       method.ReturnType = new CodeTypeReference ("ProfileCommon");
+                       method.Parameters.Add (new CodeParameterDeclarationExpression("System.String", "username"));
+                       method.Statements.Add (ret);
+                       method.Attributes = MemberAttributes.Public;
+                       
+                       profileClass.Members.Add (method);
+               }
+               
+               void AddProfileClassProperty (ProfileSection ps, CodeTypeDeclaration profileClass, ProfilePropertySettings pset)
+               {
+                       string name = pset.Name;
+                       if (String.IsNullOrEmpty (name))
+                               throw new HttpException ("Profile property 'Name' attribute cannot be null.");
+                       CodeMemberProperty property = new CodeMemberProperty ();
+                       string typeName = pset.Type;
+                       if (typeName == "string")
+                               typeName = "System.String";
+                       property.Name = name;
+                       property.Type = GetProfilePropertyType (typeName);
+                       property.Attributes = MemberAttributes.Public;
+                       
+                       CodeAttributeDeclarationCollection collection = new CodeAttributeDeclarationCollection();
+                       GetProfileProviderAttribute (ps, collection, pset.Provider);
+                       GetProfileSettingsSerializeAsAttribute (ps, collection, pset.SerializeAs);
+
+                       property.CustomAttributes = collection;
+                       CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
+                       CodeCastExpression cast = new CodeCastExpression ();
+                       ret.Expression = cast;
+
+                       CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
+                               new CodeThisReferenceExpression (),
+                               "GetPropertyValue");
+                       CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
+                               mref,
+                               new CodeExpression[] { new CodePrimitiveExpression (name) }
+                       );
+                       cast.TargetType = new CodeTypeReference (typeName);
+                       cast.Expression = minvoke;
+                       property.GetStatements.Add (ret);
+
+                       if (!pset.ReadOnly) {
+                               mref = new CodeMethodReferenceExpression (
+                                       new CodeThisReferenceExpression (),
+                                       "SetPropertyValue");
+                               minvoke = new CodeMethodInvokeExpression (
+                                       mref,
+                                       new CodeExpression[] { new CodePrimitiveExpression (name), new CodeSnippetExpression ("value") }
+                               );
+                               property.SetStatements.Add (minvoke);
+                       }
+                       
+                       
+                       profileClass.Members.Add (property);
+               }
+
+               void AddProfileClassGroupProperty (string groupName, string memberName, CodeTypeDeclaration profileClass)
+               {                       
+                       CodeMemberProperty property = new CodeMemberProperty ();
+                       property.Name = memberName;
+                       property.Type = new CodeTypeReference (groupName);
+                       property.Attributes = MemberAttributes.Public;
+
+                       CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
+                       CodeCastExpression cast = new CodeCastExpression ();
+                       ret.Expression = cast;
+
+                       CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
+                               new CodeThisReferenceExpression (),
+                               "GetProfileGroup");
+                       CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
+                               mref,
+                               new CodeExpression[] { new CodePrimitiveExpression (memberName) }
+                       );
+                       cast.TargetType = new CodeTypeReference (groupName);
+                       cast.Expression = minvoke;
+                       property.GetStatements.Add (ret);
+                       
+                       profileClass.Members.Add (property);
+               }
+               
+               void BuildProfileClass (ProfileSection ps, string className, ProfilePropertySettingsCollection psc,
+                                       CodeNamespace ns, string baseClass, bool baseIsGlobal,
+                                       SortedList <string, string> groupProperties)
+               {
+                       CodeTypeDeclaration profileClass = new CodeTypeDeclaration (className);
+                       CodeTypeReference cref = new CodeTypeReference (baseClass);
+                       if (baseIsGlobal)
+                               cref.Options |= CodeTypeReferenceOptions.GlobalReference;
+                       profileClass.BaseTypes.Add (cref);
+                       profileClass.TypeAttributes = TypeAttributes.Public;
+                       ns.Types.Add (profileClass);
+                       
+                       foreach (ProfilePropertySettings pset in psc)
+                               AddProfileClassProperty (ps, profileClass, pset);
+                       if (groupProperties != null && groupProperties.Count > 0)
+                               foreach (KeyValuePair <string, string> group in groupProperties)
+                                       AddProfileClassGroupProperty (group.Key, group.Value, profileClass);
+                       AddProfileClassGetProfileMethod (profileClass);
+               }
+
+               string MakeGroupName (string name)
+               {
+                       return String.Concat ("ProfileGroup", name);
+               }
+               
+               // FIXME: there should be some validation of syntactic correctness of the member/class name
+               // for the groups/properties. For now it's left to the compiler to report errors.
+               //
+               // CodeGenerator.IsValidLanguageIndependentIdentifier (id) - use that
+               //
+               bool ProcessCustomProfile (ProfileSection ps, AppCodeAssembly defasm)
+               {
+                       CodeCompileUnit unit = new CodeCompileUnit ();
+                       CodeNamespace ns = new CodeNamespace (null);
+                       unit.Namespaces.Add (ns);
+                       defasm.AddUnit (unit);
+                       
+                       ns.Imports.Add (new CodeNamespaceImport ("System"));
+                       ns.Imports.Add (new CodeNamespaceImport ("System.Configuration"));
+                       ns.Imports.Add (new CodeNamespaceImport ("System.Web"));
+                       ns.Imports.Add (new CodeNamespaceImport ("System.Web.Profile"));
+                       
+                       RootProfilePropertySettingsCollection props = ps.PropertySettings;
+                       if (props == null)
+                               return true;
+
+                       SortedList<string, string> groupProperties = new SortedList<string, string> ();
+                       string groupName;
+                       foreach (ProfileGroupSettings pgs in props.GroupSettings) {
+                               groupName = MakeGroupName (pgs.Name);
+                               groupProperties.Add (groupName, pgs.Name);
+                               BuildProfileClass (ps, groupName, pgs.PropertySettings, ns,
+                                                  "System.Web.Profile.ProfileGroupBase", true, null);
+                       }
+                       
+                       string baseType = ps.Inherits;
+                       if (String.IsNullOrEmpty (baseType))
+                               baseType = "System.Web.Profile.ProfileBase";
+                       else {
+                               string[] parts = baseType.Split (new char[] {','});
+                               if (parts.Length > 1)
+                                       baseType = parts [0].Trim ();
+                       }
+                       
+                       bool baseIsGlobal;
+                       if (baseType.IndexOf ('.') != -1)
+                               baseIsGlobal = true;
+                       else
+                               baseIsGlobal = false;
+                       
+                       BuildProfileClass (ps, "ProfileCommon", props, ns, baseType, baseIsGlobal, groupProperties);
+                       return true;
+               }
+
+//             void PutCustomProfileInContext (HttpContext context, string assemblyName)
+//             {
+//                     Type type = Type.GetType (String.Format ("ProfileCommon, {0}",
+//                                                              Path.GetFileNameWithoutExtension (assemblyName)));
+//                     ProfileBase pb = Activator.CreateInstance (type) as ProfileBase;
+//                     if (pb != null)
+//                             context.Profile = pb;
+//             }
+
+               public static bool HaveCustomProfile (ProfileSection ps)
+               {
+                       if (ps == null || !ps.Enabled)
+                               return false;
+                       if (!String.IsNullOrEmpty (ps.Inherits) || (ps.PropertySettings != null && ps.PropertySettings.Count > 0))
+                               return true;
+
+                       return false;
+               }
+               
+               public void Compile ()
+               {
+                       if (_alreadyCompiled)
+                               return;
+                       
+                       string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
+                       ProfileSection ps = WebConfigurationManager.GetWebApplicationSection ("system.web/profile") as ProfileSection;
+                       bool haveAppCodeDir = Directory.Exists (appCode);
+                       bool haveCustomProfile = HaveCustomProfile (ps);
+                       
+                       if (!haveAppCodeDir && !haveCustomProfile)
+                               return;
+
                        AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
                        assemblies.Add (defasm);
-                       CollectFiles (appCode, defasm);
 
+                       bool haveCode = false;
+                       if (haveAppCodeDir)
+                               haveCode = ProcessAppCodeDir (appCode, defasm);
+                       if (haveCustomProfile)
+                               if (ProcessCustomProfile (ps, defasm))
+                                       haveCode = true;
+
+                       if (!haveCode)
+                               return;
+                       
+                       HttpRuntime.EnableAssemblyMapping (true);
+                       string[] binAssemblies = HttpApplication.BinDirectoryAssemblies;
+                       
                        foreach (AppCodeAssembly aca in assemblies)
-                               aca.Build ();
+                               aca.Build (binAssemblies);
+                       _alreadyCompiled = true;
+                       DefaultAppCodeAssemblyName = Path.GetFileNameWithoutExtension (defasm.OutputAssemblyName);
+
+                       RunAppInitialize ();
+                       
+                       if (haveCustomProfile && providerTypeName != null) {
+                               if (Type.GetType (providerTypeName, false) == null) {
+                                       foreach (Assembly asm in BuildManager.TopLevelAssemblies) {
+                                               if (asm == null)
+                                                       continue;
+                                               
+                                               if (asm.GetType (providerTypeName, false) != null)
+                                                       return;
+                                       }
+                               } else
+                                       return;
+
+                               if (HttpApplication.LoadTypeFromBin (providerTypeName) == null)
+                                       throw new HttpException (String.Format ("Profile provider type not found: {0}",
+                                                                               providerTypeName));
+                       }
                }
 
-               private void CollectFiles (string dir, AppCodeAssembly aca)
+               // Documented (sort of...) briefly in:
+               //
+               //   http://quickstarts.asp.net/QuickStartv20/aspnet/doc/extensibility.aspx
+               //   http://msdn2.microsoft.com/en-us/library/system.web.hosting.virtualpathprovider.aspx
+               void RunAppInitialize ()
+               {
+                       MethodInfo mi = null, tmi;
+                       Type[] types;
+                       
+                       foreach (Assembly asm in BuildManager.CodeAssemblies) {
+                               types = asm.GetExportedTypes ();
+                               if (types == null || types.Length == 0)
+                                       continue;
+
+                               foreach (Type type in types) {
+                                       tmi = type.GetMethod ("AppInitialize",
+                                                             BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
+                                                             null,
+                                                             Type.EmptyTypes,
+                                                             null);
+                                       if (tmi == null)
+                                               continue;
+
+                                       if (mi != null)
+                                               throw new HttpException ("The static AppInitialize method found in more than one type in the App_Code directory.");
+
+                                       mi = tmi;
+                               }
+                       }
+
+                       if (mi == null)
+                               return;
+
+                       mi.Invoke (null, null);
+               }
+               
+               bool CollectFiles (string dir, AppCodeAssembly aca)
                {
+                       bool haveFiles = false;
+                       
                        AppCodeAssembly curaca = aca;
-                       foreach (string f in Directory.GetFiles (dir))
+                       foreach (string f in Directory.GetFiles (dir)) {
                                aca.AddFile (f);
+                               haveFiles = true;
+                       }
+                       
                        foreach (string d in Directory.GetDirectories (dir)) {
                                foreach (AppCodeAssembly a in assemblies)
                                        if (a.SourcePath == d) {
                                                curaca = a;
                                                break;
                                        }
-                               CollectFiles (d, curaca);
+                               if (CollectFiles (d, curaca))
+                                       haveFiles = true;
                                curaca = aca;
                        }
+                       return haveFiles;
                }
        }
 }