2010-05-17 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppCodeCompiler.cs
1 //
2 // System.Web.Compilation.AppCodeCompiler: A compiler for the App_Code folder
3 //
4 // Authors:
5 //   Marek Habersack (grendello@gmail.com)
6 //
7 // (C) 2006 Marek Habersack
8 //
9
10 //
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:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
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.
29 //
30
31 using System;
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Configuration;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Globalization;
38 using System.IO;
39 using System.Reflection;
40 using System.Web;
41 using System.Web.Configuration;
42 using System.Web.Profile;
43 using System.Web.Util;
44
45 namespace System.Web.Compilation
46 {
47         class AssemblyPathResolver
48         {
49                 static Dictionary <string, string> assemblyCache;
50
51                 static AssemblyPathResolver ()
52                 {
53                         assemblyCache = new Dictionary <string, string> ();
54                 }
55
56                 public static string GetAssemblyPath (string assemblyName)
57                 {
58                         lock (assemblyCache) {
59                                 if (assemblyCache.ContainsKey (assemblyName))
60                                         return assemblyCache [assemblyName];
61
62                                 Assembly asm = null;
63                                 Exception error = null;
64                                 if (assemblyName.IndexOf (',') != -1) {
65                                         try {
66                                                 asm = Assembly.Load (assemblyName);
67                                         } catch (Exception e) {
68                                                 error = e;
69                                         }
70                                 }
71
72                                 if (asm == null) {
73                                         try {
74                                                 asm = Assembly.LoadWithPartialName (assemblyName);
75                                         } catch (Exception e) {
76                                                 error = e;
77                                         }
78                                 }
79                         
80                                 if (asm == null)
81                                         throw new HttpException (String.Format ("Unable to find assembly {0}", assemblyName), error);
82
83                                 assemblyCache.Add (assemblyName, asm.Location);
84                                 return asm.Location;
85                         }
86                 }
87         }
88         
89         internal class AppCodeAssembly
90         {
91                 List<string> files;
92                 List<CodeCompileUnit> units;
93                 
94                 string name;
95                 string path;
96                 bool validAssembly;
97                 string outputAssemblyName;
98
99                 public string OutputAssemblyName
100                 {
101                         get {
102                                 return outputAssemblyName;
103                         }
104                 }
105                 
106                 public bool IsValid
107                 {
108                         get { return validAssembly; }
109                 }
110
111                 public string SourcePath
112                 {
113                         get { return path; }
114                 }
115
116 // temporary
117                 public string Name
118                 {
119                         get { return name; }
120                 }
121                 
122                 public List<string> Files
123                 {
124                         get { return files; }
125                 }
126 // temporary
127                 
128                 public AppCodeAssembly (string name, string path)
129                 {
130                         this.files = new List<string> ();
131                         this.units = new List<CodeCompileUnit> ();
132                         this.validAssembly = true;
133                         this.name = name;
134                         this.path = path;
135                 }
136
137                 public void AddFile (string path)
138                 {
139                         files.Add (path);
140                 }
141
142                 public void AddUnit (CodeCompileUnit unit)
143                 {
144                         units.Add (unit);
145                 }
146                 
147                 object OnCreateTemporaryAssemblyFile (string path)
148                 {
149                         FileStream f = new FileStream (path, FileMode.CreateNew);
150                         f.Close ();
151                         return path;
152                 }
153                 
154                 // Build and add the assembly to the BuildManager's
155                 // CodeAssemblies collection
156                 public void Build (string[] binAssemblies)
157                 {
158                         Type compilerProvider = null;
159                         CompilerInfo compilerInfo = null, cit;
160                         string extension, language, cpfile = null;
161                         List<string> knownfiles = new List<string>();
162                         List<string> unknownfiles = new List<string>();
163                         
164                         // First make sure all the files are in the same
165                         // language
166                         bool known = false;
167                         foreach (string f in files) {
168                                 known = true;
169                                 language = null;
170                                 
171                                 extension = Path.GetExtension (f);
172                                 if (String.IsNullOrEmpty (extension) || !CodeDomProvider.IsDefinedExtension (extension))
173                                         known = false;
174                                 if (known) {
175                                         language = CodeDomProvider.GetLanguageFromExtension(extension);
176                                         if (!CodeDomProvider.IsDefinedLanguage (language))
177                                                 known = false;
178                                 }
179                                 if (!known || language == null) {
180                                         unknownfiles.Add (f);
181                                         continue;
182                                 }
183                                 
184                                 cit = CodeDomProvider.GetCompilerInfo (language);
185                                 if (cit == null || !cit.IsCodeDomProviderTypeValid)
186                                         continue;
187                                 if (compilerProvider == null) {
188                                         cpfile = f;
189                                         compilerProvider = cit.CodeDomProviderType;
190                                         compilerInfo = cit;
191                                 } else if (compilerProvider != cit.CodeDomProviderType)
192                                         throw new HttpException (
193                                                 String.Format (
194                                                         "Files {0} and {1} are in different languages - they cannot be compiled into the same assembly",
195                                                         Path.GetFileName (cpfile),
196                                                         Path.GetFileName (f)));
197                                 knownfiles.Add (f);
198                         }
199
200                         CodeDomProvider provider = null;
201                         CompilationSection compilationSection = WebConfigurationManager.GetWebApplicationSection ("system.web/compilation") as CompilationSection;
202                         if (compilerInfo == null) {
203                                 if (!CodeDomProvider.IsDefinedLanguage (compilationSection.DefaultLanguage))
204                                         throw new HttpException ("Failed to retrieve default source language");
205                                 compilerInfo = CodeDomProvider.GetCompilerInfo (compilationSection.DefaultLanguage);
206                                 if (compilerInfo == null || !compilerInfo.IsCodeDomProviderTypeValid)
207                                         throw new HttpException ("Internal error while initializing application");
208                         }
209
210                         provider = compilerInfo.CreateProvider ();
211                         if (provider == null)
212                                 throw new HttpException ("A code provider error occurred while initializing application.");
213
214                         AssemblyBuilder abuilder = new AssemblyBuilder (provider);
215                         foreach (string file in knownfiles)
216                                 abuilder.AddCodeFile (file);
217                         foreach (CodeCompileUnit unit in units)
218                                 abuilder.AddCodeCompileUnit (unit);
219                         
220                         BuildProvider bprovider;
221                         CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
222                         parameters.IncludeDebugInformation = compilationSection.Debug;
223                         
224                         if (binAssemblies != null && binAssemblies.Length > 0)
225                                 parameters.ReferencedAssemblies.AddRange (binAssemblies);
226                         
227                         if (compilationSection != null) {
228                                 foreach (AssemblyInfo ai in compilationSection.Assemblies)
229                                         if (ai.Assembly != "*") {
230                                                 try {
231                                                         parameters.ReferencedAssemblies.Add (
232                                                                 AssemblyPathResolver.GetAssemblyPath (ai.Assembly));
233                                                 } catch (Exception ex) {
234                                                         throw new HttpException (
235                                                                 String.Format ("Could not find assembly {0}.", ai.Assembly),
236                                                                 ex);
237                                                 }
238                                         }
239                                 
240                                 BuildProviderCollection buildProviders = compilationSection.BuildProviders;
241                                 
242                                 foreach (string file in unknownfiles) {
243                                         bprovider = GetBuildProviderFor (file, buildProviders);
244                                         if (bprovider == null)
245                                                 continue;
246                                         bprovider.GenerateCode (abuilder);
247                                 }
248                         }
249
250                         if (knownfiles.Count == 0 && unknownfiles.Count == 0 && units.Count == 0)
251                                 return;
252                         
253                         outputAssemblyName = (string)FileUtils.CreateTemporaryFile (
254                                 AppDomain.CurrentDomain.SetupInformation.DynamicBase,
255                                 name, "dll", OnCreateTemporaryAssemblyFile);
256                         parameters.OutputAssembly = outputAssemblyName;
257                         foreach (Assembly a in BuildManager.TopLevelAssemblies)
258                                 parameters.ReferencedAssemblies.Add (a.Location);
259                         CompilerResults results = abuilder.BuildAssembly (parameters);
260                         if (results == null)
261                                 return;
262                         
263                         if (results.NativeCompilerReturnValue == 0) {
264                                 BuildManager.CodeAssemblies.Add (results.CompiledAssembly);
265                                 BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
266                                 HttpRuntime.WritePreservationFile (results.CompiledAssembly, name);
267                         } else {
268                                 if (HttpContext.Current.IsCustomErrorEnabled)
269                                         throw new HttpException ("An error occurred while initializing application.");
270                                 throw new CompilationException (null, results.Errors, null);
271                         }
272                 }
273                 
274                 VirtualPath PhysicalToVirtual (string file)
275                 {
276                         return new VirtualPath (file.Replace (HttpRuntime.AppDomainAppPath, "/").Replace (Path.DirectorySeparatorChar, '/'));
277                 }
278                 
279                 BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
280                 {
281                         if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
282                                 return null;
283
284                         BuildProvider ret = buildProviders.GetProviderInstanceForExtension (Path.GetExtension (file));
285                         if (ret != null && IsCorrectBuilderType (ret)) {
286                                 ret.SetVirtualPath (PhysicalToVirtual (file));
287                                 return ret;
288                         }
289                                 
290                         return null;
291                 }
292
293                 bool IsCorrectBuilderType (BuildProvider bp)
294                 {
295                         if (bp == null)
296                                 return false;
297                         Type type;
298                         object[] attrs;
299
300                         type = bp.GetType ();
301                         attrs = type.GetCustomAttributes (true);
302                         if (attrs == null)
303                                 return false;
304                         
305                         BuildProviderAppliesToAttribute bpAppliesTo;
306                         bool attributeFound = false;
307                         foreach (object attr in attrs) {
308                                 bpAppliesTo = attr as BuildProviderAppliesToAttribute;
309                                 if (bpAppliesTo == null)
310                                         continue;
311                                 attributeFound = true;
312                                 if ((bpAppliesTo.AppliesTo & BuildProviderAppliesTo.All) == BuildProviderAppliesTo.All ||
313                                     (bpAppliesTo.AppliesTo & BuildProviderAppliesTo.Code) == BuildProviderAppliesTo.Code)
314                                         return true;
315                         }
316
317                         if (attributeFound)
318                                 return false;
319                         return true;
320                 }
321                 
322         }
323         
324         internal class AppCodeCompiler
325         {
326                 static bool _alreadyCompiled;
327                 internal static string DefaultAppCodeAssemblyName;
328                 
329                 // A dictionary that contains an entry per an assembly that will
330                 // be produced by compiling App_Code. There's one main assembly
331                 // and an optional number of assemblies as defined by the
332                 // codeSubDirectories sub-element of the compilation element in
333                 // the system.web section of the app's config file.
334                 // Each entry's value is an AppCodeAssembly instance.
335                 //
336                 // Assemblies are named as follows:
337                 //
338                 //  1. main assembly: App_Code.{HASH}
339                 //  2. subdir assemblies: App_SubCode_{DirName}.{HASH}
340                 //
341                 // If any of the assemblies contains files that would be
342                 // compiled with different compilers, a System.Web.HttpException
343                 // is thrown.
344                 //
345                 // Files for which there is no explicit builder are ignored
346                 // silently
347                 //
348                 // Files for which exist BuildProviders but which have no
349                 // unambiguous language assigned to them (e.g. .wsdl files), are
350                 // built using the default website compiler.
351                 List<AppCodeAssembly> assemblies;
352                 string providerTypeName = null;
353                 
354                 public AppCodeCompiler ()
355                 {
356                         assemblies = new List<AppCodeAssembly>();
357                 }
358
359                 bool ProcessAppCodeDir (string appCode, AppCodeAssembly defasm)
360                 {
361                         // First process the codeSubDirectories
362                         CompilationSection cs = (CompilationSection) WebConfigurationManager.GetWebApplicationSection ("system.web/compilation");
363                         
364                         if (cs != null) {
365                                 string aname;
366                                 for (int i = 0; i < cs.CodeSubDirectories.Count; i++) {
367                                         aname = String.Concat ("App_SubCode_", cs.CodeSubDirectories[i].DirectoryName);
368                                         assemblies.Add (new AppCodeAssembly (
369                                                                 aname,
370                                                                 Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
371                                 }
372                         }
373                         
374                         return CollectFiles (appCode, defasm);
375                 }
376
377                 CodeTypeReference GetProfilePropertyType (string type)
378                 {
379                         if (String.IsNullOrEmpty (type))
380                                 throw new ArgumentException ("String size cannot be 0", "type");
381                         return new CodeTypeReference (type);
382                 }
383
384                 string FindProviderTypeName (ProfileSection ps, string providerName)
385                 {
386                         if (ps.Providers == null || ps.Providers.Count == 0)
387                                 return null;
388                         
389                         ProviderSettings pset = ps.Providers [providerName];
390                         if (pset == null)
391                                 return null;
392                         return pset.Type;
393                 }
394                 
395                 void GetProfileProviderAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
396                                                   string providerName)
397                 {
398                         if (String.IsNullOrEmpty (providerName))
399                                 providerTypeName = FindProviderTypeName (ps, ps.DefaultProvider);
400                         else
401                                 providerTypeName = FindProviderTypeName (ps, providerName);
402                         if (providerTypeName == null)
403                                 throw new HttpException (String.Format ("Profile provider type not defined: {0}",
404                                                                         providerName));
405                         
406                         collection.Add (
407                                 new CodeAttributeDeclaration (
408                                         "ProfileProvider",
409                                         new CodeAttributeArgument (
410                                                 new CodePrimitiveExpression (providerTypeName)
411                                         )
412                                 )
413                         );
414                 }
415
416                 void GetProfileSettingsSerializeAsAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
417                                                              SerializationMode mode)
418                 {
419                         string parameter = String.Concat ("SettingsSerializeAs.", mode.ToString ());
420                         collection.Add (
421                                 new CodeAttributeDeclaration (
422                                         "SettingsSerializeAs",
423                                         new CodeAttributeArgument (
424                                                 new CodeSnippetExpression (parameter)
425                                         )
426                                 )
427                         );
428                                         
429                 }
430
431                 void AddProfileClassGetProfileMethod (CodeTypeDeclaration profileClass)
432                 {
433                         CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
434                                 new CodeTypeReferenceExpression (typeof (System.Web.Profile.ProfileBase)),
435                                 "Create");
436                         CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
437                                 mref,
438                                 new CodeExpression[] { new CodeVariableReferenceExpression ("username") }
439                         );
440                         CodeCastExpression cast = new CodeCastExpression ();
441                         cast.TargetType = new CodeTypeReference ("ProfileCommon");
442                         cast.Expression = minvoke;
443                         
444                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
445                         ret.Expression = cast;
446                         
447                         CodeMemberMethod method = new CodeMemberMethod ();
448                         method.Name = "GetProfile";
449                         method.ReturnType = new CodeTypeReference ("ProfileCommon");
450                         method.Parameters.Add (new CodeParameterDeclarationExpression("System.String", "username"));
451                         method.Statements.Add (ret);
452                         method.Attributes = MemberAttributes.Public;
453                         
454                         profileClass.Members.Add (method);
455                 }
456                 
457                 void AddProfileClassProperty (ProfileSection ps, CodeTypeDeclaration profileClass, ProfilePropertySettings pset)
458                 {
459                         string name = pset.Name;
460                         if (String.IsNullOrEmpty (name))
461                                 throw new HttpException ("Profile property 'Name' attribute cannot be null.");
462                         CodeMemberProperty property = new CodeMemberProperty ();
463                         string typeName = pset.Type;
464                         if (typeName == "string")
465                                 typeName = "System.String";
466                         property.Name = name;
467                         property.Type = GetProfilePropertyType (typeName);
468                         property.Attributes = MemberAttributes.Public;
469                         
470                         CodeAttributeDeclarationCollection collection = new CodeAttributeDeclarationCollection();
471                         GetProfileProviderAttribute (ps, collection, pset.Provider);
472                         GetProfileSettingsSerializeAsAttribute (ps, collection, pset.SerializeAs);
473
474                         property.CustomAttributes = collection;
475                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
476                         CodeCastExpression cast = new CodeCastExpression ();
477                         ret.Expression = cast;
478
479                         CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
480                                 new CodeThisReferenceExpression (),
481                                 "GetPropertyValue");
482                         CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
483                                 mref,
484                                 new CodeExpression[] { new CodePrimitiveExpression (name) }
485                         );
486                         cast.TargetType = new CodeTypeReference (typeName);
487                         cast.Expression = minvoke;
488                         property.GetStatements.Add (ret);
489
490                         if (!pset.ReadOnly) {
491                                 mref = new CodeMethodReferenceExpression (
492                                         new CodeThisReferenceExpression (),
493                                         "SetPropertyValue");
494                                 minvoke = new CodeMethodInvokeExpression (
495                                         mref,
496                                         new CodeExpression[] { new CodePrimitiveExpression (name), new CodeSnippetExpression ("value") }
497                                 );
498                                 property.SetStatements.Add (minvoke);
499                         }
500                         
501                         
502                         profileClass.Members.Add (property);
503                 }
504
505                 void AddProfileClassGroupProperty (string groupName, string memberName, CodeTypeDeclaration profileClass)
506                 {                       
507                         CodeMemberProperty property = new CodeMemberProperty ();
508                         property.Name = memberName;
509                         property.Type = new CodeTypeReference (groupName);
510                         property.Attributes = MemberAttributes.Public;
511
512                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
513                         CodeCastExpression cast = new CodeCastExpression ();
514                         ret.Expression = cast;
515
516                         CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
517                                 new CodeThisReferenceExpression (),
518                                 "GetProfileGroup");
519                         CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
520                                 mref,
521                                 new CodeExpression[] { new CodePrimitiveExpression (memberName) }
522                         );
523                         cast.TargetType = new CodeTypeReference (groupName);
524                         cast.Expression = minvoke;
525                         property.GetStatements.Add (ret);
526                         
527                         profileClass.Members.Add (property);
528                 }
529                 
530                 void BuildProfileClass (ProfileSection ps, string className, ProfilePropertySettingsCollection psc,
531                                         CodeNamespace ns, string baseClass, bool baseIsGlobal,
532                                         SortedList <string, string> groupProperties)
533                 {
534                         CodeTypeDeclaration profileClass = new CodeTypeDeclaration (className);
535                         CodeTypeReference cref = new CodeTypeReference (baseClass);
536                         if (baseIsGlobal)
537                                 cref.Options |= CodeTypeReferenceOptions.GlobalReference;
538                         profileClass.BaseTypes.Add (cref);
539                         profileClass.TypeAttributes = TypeAttributes.Public;
540                         ns.Types.Add (profileClass);
541                         
542                         foreach (ProfilePropertySettings pset in psc)
543                                 AddProfileClassProperty (ps, profileClass, pset);
544                         if (groupProperties != null && groupProperties.Count > 0)
545                                 foreach (KeyValuePair <string, string> group in groupProperties)
546                                         AddProfileClassGroupProperty (group.Key, group.Value, profileClass);
547                         AddProfileClassGetProfileMethod (profileClass);
548                 }
549
550                 string MakeGroupName (string name)
551                 {
552                         return String.Concat ("ProfileGroup", name);
553                 }
554                 
555                 // FIXME: there should be some validation of syntactic correctness of the member/class name
556                 // for the groups/properties. For now it's left to the compiler to report errors.
557                 //
558                 // CodeGenerator.IsValidLanguageIndependentIdentifier (id) - use that
559                 //
560                 bool ProcessCustomProfile (ProfileSection ps, AppCodeAssembly defasm)
561                 {
562                         CodeCompileUnit unit = new CodeCompileUnit ();
563                         CodeNamespace ns = new CodeNamespace (null);
564                         unit.Namespaces.Add (ns);
565                         defasm.AddUnit (unit);
566                         
567                         ns.Imports.Add (new CodeNamespaceImport ("System"));
568                         ns.Imports.Add (new CodeNamespaceImport ("System.Configuration"));
569                         ns.Imports.Add (new CodeNamespaceImport ("System.Web"));
570                         ns.Imports.Add (new CodeNamespaceImport ("System.Web.Profile"));
571                         
572                         RootProfilePropertySettingsCollection props = ps.PropertySettings;
573                         if (props == null)
574                                 return true;
575
576                         SortedList<string, string> groupProperties = new SortedList<string, string> ();
577                         string groupName;
578                         foreach (ProfileGroupSettings pgs in props.GroupSettings) {
579                                 groupName = MakeGroupName (pgs.Name);
580                                 groupProperties.Add (groupName, pgs.Name);
581                                 BuildProfileClass (ps, groupName, pgs.PropertySettings, ns,
582                                                    "System.Web.Profile.ProfileGroupBase", true, null);
583                         }
584                         
585                         string baseType = ps.Inherits;
586                         if (String.IsNullOrEmpty (baseType))
587                                 baseType = "System.Web.Profile.ProfileBase";
588                         else {
589                                 string[] parts = baseType.Split (new char[] {','});
590                                 if (parts.Length > 1)
591                                         baseType = parts [0].Trim ();
592                         }
593                         
594                         bool baseIsGlobal;
595                         if (baseType.IndexOf ('.') != -1)
596                                 baseIsGlobal = true;
597                         else
598                                 baseIsGlobal = false;
599                         
600                         BuildProfileClass (ps, "ProfileCommon", props, ns, baseType, baseIsGlobal, groupProperties);
601                         return true;
602                 }
603
604 //              void PutCustomProfileInContext (HttpContext context, string assemblyName)
605 //              {
606 //                      Type type = Type.GetType (String.Format ("ProfileCommon, {0}",
607 //                                                               Path.GetFileNameWithoutExtension (assemblyName)));
608 //                      ProfileBase pb = Activator.CreateInstance (type) as ProfileBase;
609 //                      if (pb != null)
610 //                              context.Profile = pb;
611 //              }
612
613                 public static bool HaveCustomProfile (ProfileSection ps)
614                 {
615                         if (ps == null || !ps.Enabled)
616                                 return false;
617
618                         RootProfilePropertySettingsCollection props = ps.PropertySettings;
619                         ProfileGroupSettingsCollection groups = props != null ? props.GroupSettings : null;
620                         
621                         if (!String.IsNullOrEmpty (ps.Inherits) || (props != null && props.Count > 0) || (groups != null && groups.Count > 0))
622                                 return true;
623
624                         return false;
625                 }
626                 
627                 public void Compile ()
628                 {
629                         if (_alreadyCompiled)
630                                 return;
631                         
632                         string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
633                         ProfileSection ps = WebConfigurationManager.GetWebApplicationSection ("system.web/profile") as ProfileSection;
634                         bool haveAppCodeDir = Directory.Exists (appCode);
635                         bool haveCustomProfile = HaveCustomProfile (ps);
636                         
637                         if (!haveAppCodeDir && !haveCustomProfile)
638                                 return;
639
640                         AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
641                         assemblies.Add (defasm);
642
643                         bool haveCode = false;
644                         if (haveAppCodeDir)
645                                 haveCode = ProcessAppCodeDir (appCode, defasm);
646                         if (haveCustomProfile)
647                                 if (ProcessCustomProfile (ps, defasm))
648                                         haveCode = true;
649
650                         if (!haveCode)
651                                 return;
652                         
653                         HttpRuntime.EnableAssemblyMapping (true);
654                         string[] binAssemblies = HttpApplication.BinDirectoryAssemblies;
655                         
656                         foreach (AppCodeAssembly aca in assemblies)
657                                 aca.Build (binAssemblies);
658                         _alreadyCompiled = true;
659                         DefaultAppCodeAssemblyName = Path.GetFileNameWithoutExtension (defasm.OutputAssemblyName);
660
661                         RunAppInitialize ();
662                         
663                         if (haveCustomProfile && providerTypeName != null) {
664                                 if (Type.GetType (providerTypeName, false) == null) {
665                                         foreach (Assembly asm in BuildManager.TopLevelAssemblies) {
666                                                 if (asm == null)
667                                                         continue;
668                                                 
669                                                 if (asm.GetType (providerTypeName, false) != null)
670                                                         return;
671                                         }
672                                 } else
673                                         return;
674
675                                 Exception noTypeException = null;
676                                 Type ptype = null;
677                                 
678                                 try {
679                                         ptype = HttpApplication.LoadTypeFromBin (providerTypeName);
680                                 } catch (Exception ex) {
681                                         noTypeException = ex;
682                                 }
683
684                                 if (ptype == null)
685                                         throw new HttpException (String.Format ("Profile provider type not found: {0}", providerTypeName), noTypeException);
686                         }
687                 }
688
689                 // Documented (sort of...) briefly in:
690                 //
691                 //   http://quickstarts.asp.net/QuickStartv20/aspnet/doc/extensibility.aspx
692                 //   http://msdn2.microsoft.com/en-us/library/system.web.hosting.virtualpathprovider.aspx
693                 void RunAppInitialize ()
694                 {
695                         MethodInfo mi = null, tmi;
696                         Type[] types;
697                         
698                         foreach (Assembly asm in BuildManager.CodeAssemblies) {
699                                 types = asm.GetExportedTypes ();
700                                 if (types == null || types.Length == 0)
701                                         continue;
702
703                                 foreach (Type type in types) {
704                                         tmi = type.GetMethod ("AppInitialize",
705                                                               BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase,
706                                                               null,
707                                                               Type.EmptyTypes,
708                                                               null);
709                                         if (tmi == null)
710                                                 continue;
711
712                                         if (mi != null)
713                                                 throw new HttpException ("The static AppInitialize method found in more than one type in the App_Code directory.");
714
715                                         mi = tmi;
716                                 }
717                         }
718
719                         if (mi == null)
720                                 return;
721
722                         mi.Invoke (null, null);
723                 }
724                 
725                 bool CollectFiles (string dir, AppCodeAssembly aca)
726                 {
727                         bool haveFiles = false;
728                         
729                         AppCodeAssembly curaca = aca;
730                         foreach (string f in Directory.GetFiles (dir)) {
731                                 aca.AddFile (f);
732                                 haveFiles = true;
733                         }
734                         
735                         foreach (string d in Directory.GetDirectories (dir)) {
736                                 foreach (AppCodeAssembly a in assemblies)
737                                         if (a.SourcePath == d) {
738                                                 curaca = a;
739                                                 break;
740                                         }
741                                 if (CollectFiles (d, curaca))
742                                         haveFiles = true;
743                                 curaca = aca;
744                         }
745                         return haveFiles;
746                 }
747         }
748 }
749