2 // System.Web.Compilation.AppCodeCompiler: A compiler for the App_Code folder
5 // Marek Habersack (grendello@gmail.com)
7 // (C) 2006 Marek Habersack
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.CodeDom.Compiler;
34 using System.Configuration;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Globalization;
39 using System.Reflection;
41 using System.Web.Configuration;
42 using System.Web.Profile;
43 using System.Web.Util;
45 namespace System.Web.Compilation
47 internal class AppCodeAssembly
49 private List<string> files;
50 private List<CodeCompileUnit> units;
54 private bool validAssembly;
55 private string outputAssemblyName;
57 public string OutputAssemblyName
60 return outputAssemblyName;
66 get { return validAssembly; }
69 public string SourcePath
80 public List<string> Files
86 public AppCodeAssembly (string name, string path)
88 this.files = new List<string> ();
89 this.units = new List<CodeCompileUnit> ();
90 this.validAssembly = true;
95 public void AddFile (string path)
100 public void AddUnit (CodeCompileUnit unit)
105 object OnCreateTemporaryAssemblyFile (string path)
107 FileStream f = new FileStream (path, FileMode.CreateNew);
112 // Build and add the assembly to the BuildManager's
113 // CodeAssemblies collection
114 public void Build (string[] binAssemblies)
116 Type compilerProvider = null;
117 CompilerInfo compilerInfo = null, cit;
118 string extension, language, cpfile = null;
119 List<string> knownfiles = new List<string>();
120 List<string> unknownfiles = new List<string>();
122 // First make sure all the files are in the same
125 foreach (string f in files) {
129 extension = Path.GetExtension (f);
130 if (!CodeDomProvider.IsDefinedExtension (extension))
133 language = CodeDomProvider.GetLanguageFromExtension(extension);
134 if (!CodeDomProvider.IsDefinedLanguage (language))
137 if (!known || language == null) {
138 unknownfiles.Add (f);
142 cit = CodeDomProvider.GetCompilerInfo (language);
143 if (cit == null || !cit.IsCodeDomProviderTypeValid)
145 if (compilerProvider == null) {
147 compilerProvider = cit.CodeDomProviderType;
149 } else if (compilerProvider != cit.CodeDomProviderType)
150 throw new HttpException (
152 "Files {0} and {1} are in different languages - they cannot be compiled into the same assembly",
153 Path.GetFileName (cpfile),
154 Path.GetFileName (f)));
158 CodeDomProvider provider = null;
159 CompilationSection compilationSection = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
160 if (compilerInfo == null) {
161 if (!CodeDomProvider.IsDefinedLanguage (compilationSection.DefaultLanguage))
162 throw new HttpException ("Failed to retrieve default source language");
163 compilerInfo = CodeDomProvider.GetCompilerInfo (compilationSection.DefaultLanguage);
164 if (compilerInfo == null || !compilerInfo.IsCodeDomProviderTypeValid)
165 throw new HttpException ("Internal error while initializing application");
166 provider = compilerInfo.CreateProvider ();
167 if (provider == null)
168 throw new HttpException ("A code provider error occurred while initializing application.");
171 provider = compilerInfo.CreateProvider ();
172 if (provider == null)
173 throw new HttpException ("A code provider error occurred while initializing application.");
175 AssemblyBuilder abuilder = new AssemblyBuilder (provider);
176 foreach (string file in knownfiles)
177 abuilder.AddCodeFile (file);
178 foreach (CodeCompileUnit unit in units)
179 abuilder.AddCodeCompileUnit (unit);
181 BuildProvider bprovider;
182 CompilerParameters parameters = compilerInfo.CreateDefaultCompilerParameters ();
183 parameters.IncludeDebugInformation = compilationSection.Debug;
185 if (binAssemblies != null && binAssemblies.Length > 0)
186 parameters.ReferencedAssemblies.AddRange (binAssemblies);
188 if (compilationSection != null) {
189 AssemblyName asmName;
190 foreach (AssemblyInfo ai in compilationSection.Assemblies)
191 if (ai.Assembly != "*") {
193 asmName = new AssemblyName (ai.Assembly);
194 parameters.ReferencedAssemblies.Add (asmName.Name);
195 } catch (Exception ex) {
196 throw new HttpException (
197 String.Format ("Could not find assembly {0}.", ai.Assembly),
202 BuildProviderCollection buildProviders = compilationSection.BuildProviders;
204 foreach (string file in unknownfiles) {
205 bprovider = GetBuildProviderFor (file, buildProviders);
206 if (bprovider == null)
208 bprovider.GenerateCode (abuilder);
212 outputAssemblyName = (string)FileUtils.CreateTemporaryFile (
213 AppDomain.CurrentDomain.SetupInformation.DynamicBase,
214 name, "dll", OnCreateTemporaryAssemblyFile);
215 parameters.OutputAssembly = outputAssemblyName;
216 foreach (Assembly a in BuildManager.TopLevelAssemblies)
217 parameters.ReferencedAssemblies.Add (a.Location);
218 CompilerResults results = abuilder.BuildAssembly (parameters);
219 if (results.Errors.Count == 0) {
220 BuildManager.CodeAssemblies.Add (results.CompiledAssembly);
221 BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
222 HttpRuntime.WritePreservationFile (results.CompiledAssembly, name);
224 if (HttpContext.Current.IsCustomErrorEnabled)
225 throw new HttpException ("An error occurred while initializing application.");
226 throw new CompilationException (null, results.Errors, null);
230 private BuildProvider GetBuildProviderFor (string file, BuildProviderCollection buildProviders)
232 if (file == null || file.Length == 0 || buildProviders == null || buildProviders.Count == 0)
235 BuildProvider ret = buildProviders.GetProviderForExtension (Path.GetExtension (file));
236 if (ret != null && IsCorrectBuilderType (ret)) {
237 ret.SetVirtualPath (VirtualPathUtility.ToAppRelative (file));
244 private bool IsCorrectBuilderType (BuildProvider bp)
251 type = bp.GetType ();
252 attrs = type.GetCustomAttributes (true);
256 BuildProviderAppliesToAttribute bpAppliesTo;
257 bool attributeFound = false;
258 foreach (object attr in attrs) {
259 bpAppliesTo = attr as BuildProviderAppliesToAttribute;
260 if (bpAppliesTo == null)
262 attributeFound = true;
263 if ((bpAppliesTo.AppliesTo & BuildProviderAppliesTo.All) == BuildProviderAppliesTo.All ||
264 (bpAppliesTo.AppliesTo & BuildProviderAppliesTo.Code) == BuildProviderAppliesTo.Code)
275 internal class AppCodeCompiler
277 static private bool _alreadyCompiled;
278 internal static string DefaultAppCodeAssemblyName;
280 // A dictionary that contains an entry per an assembly that will
281 // be produced by compiling App_Code. There's one main assembly
282 // and an optional number of assemblies as defined by the
283 // codeSubDirectories sub-element of the compilation element in
284 // the system.web section of the app's config file.
285 // Each entry's value is an AppCodeAssembly instance.
287 // Assemblies are named as follows:
289 // 1. main assembly: App_Code.{HASH}
290 // 2. subdir assemblies: App_SubCode_{DirName}.{HASH}
292 // If any of the assemblies contains files that would be
293 // compiled with different compilers, a System.Web.HttpException
296 // Files for which there is no explicit builder are ignored
299 // Files for which exist BuildProviders but which have no
300 // unambiguous language assigned to them (e.g. .wsdl files), are
301 // built using the default website compiler.
302 private List<AppCodeAssembly> assemblies;
305 public AppCodeCompiler ()
307 assemblies = new List<AppCodeAssembly>();
310 bool ProcessAppCodeDir (string appCode, AppCodeAssembly defasm)
312 // First process the codeSubDirectories
313 CompilationSection cs = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
317 for (int i = 0; i < cs.CodeSubDirectories.Count; i++) {
318 aname = String.Format ("App_SubCode_{0}", cs.CodeSubDirectories[i].DirectoryName);
319 assemblies.Add (new AppCodeAssembly (
321 Path.Combine (appCode, cs.CodeSubDirectories[i].DirectoryName)));
325 return CollectFiles (appCode, defasm);
328 CodeTypeReference GetProfilePropertyType (string type)
330 if (String.IsNullOrEmpty (type))
331 throw new ArgumentException ("String size cannot be 0", "type");
332 return new CodeTypeReference (type);
335 Type GetTypeFromBin (string typeName)
337 string bindir = BinDir;
338 if (!Directory.Exists (bindir))
341 string [] binDlls = Directory.GetFiles (bindir, "*.dll");
343 foreach (string dll in binDlls) {
345 Assembly asm = Assembly.LoadFrom (dll);
346 ret = asm.GetType (typeName, false);
349 } catch (Exception) {
357 string FindProviderTypeName (ProfileSection ps, string providerName)
359 if (ps.Providers == null || ps.Providers.Count == 0)
362 ProviderSettings pset = ps.Providers [providerName];
368 void GetProfileProviderAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
371 string providerTypeName;
373 if (String.IsNullOrEmpty (providerName))
374 providerTypeName = FindProviderTypeName (ps, ps.DefaultProvider);
376 providerTypeName = FindProviderTypeName (ps, providerName);
377 if (providerTypeName == null)
378 throw new HttpException (String.Format ("Profile provider type not found: {0}",
381 Type type = Type.GetType (providerTypeName, false);
383 type = GetTypeFromBin (providerTypeName);
385 throw new HttpException (String.Format ("Profile provider type not found: {0}",
390 new CodeAttributeDeclaration (
392 new CodeAttributeArgument (
393 new CodePrimitiveExpression (providerTypeName)
399 void GetProfileSettingsSerializeAsAttribute (ProfileSection ps, CodeAttributeDeclarationCollection collection,
400 SerializationMode mode)
402 string parameter = String.Format ("SettingsSerializeAs.{0}", mode.ToString ());
404 new CodeAttributeDeclaration (
405 "SettingsSerializeAs",
406 new CodeAttributeArgument (
407 new CodeSnippetExpression (parameter)
414 void AddProfileClassGetProfileMethod (CodeTypeDeclaration profileClass)
416 CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
417 new CodeTypeReferenceExpression (typeof (System.Web.Profile.ProfileBase)),
419 CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
421 new CodeExpression[] { new CodeVariableReferenceExpression ("username") }
423 CodeCastExpression cast = new CodeCastExpression ();
424 cast.TargetType = new CodeTypeReference ("ProfileCommon");
425 cast.Expression = minvoke;
427 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
428 ret.Expression = cast;
430 CodeMemberMethod method = new CodeMemberMethod ();
431 method.Name = "GetProfile";
432 method.ReturnType = new CodeTypeReference ("ProfileCommon");
433 method.Parameters.Add (new CodeParameterDeclarationExpression("System.String", "username"));
434 method.Statements.Add (ret);
435 method.Attributes = MemberAttributes.Public;
437 profileClass.Members.Add (method);
440 void AddProfileClassProperty (ProfileSection ps, CodeTypeDeclaration profileClass, ProfilePropertySettings pset)
442 string name = pset.Name;
443 if (String.IsNullOrEmpty (name))
444 throw new HttpException ("Profile property 'Name' attribute cannot be null.");
445 CodeMemberProperty property = new CodeMemberProperty ();
446 string typeName = pset.Type;
447 if (typeName == "string")
448 typeName = "System.String";
449 property.Name = name;
450 property.Type = GetProfilePropertyType (typeName);
451 property.Attributes = MemberAttributes.Public;
453 CodeAttributeDeclarationCollection collection = new CodeAttributeDeclarationCollection();
454 GetProfileProviderAttribute (ps, collection, pset.Provider);
455 GetProfileSettingsSerializeAsAttribute (ps, collection, pset.SerializeAs);
457 property.CustomAttributes = collection;
458 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
459 CodeCastExpression cast = new CodeCastExpression ();
460 ret.Expression = cast;
462 CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
463 new CodeThisReferenceExpression (),
465 CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
467 new CodeExpression[] { new CodePrimitiveExpression (name) }
469 cast.TargetType = new CodeTypeReference (typeName);
470 cast.Expression = minvoke;
471 property.GetStatements.Add (ret);
473 if (!pset.ReadOnly) {
474 mref = new CodeMethodReferenceExpression (
475 new CodeThisReferenceExpression (),
477 minvoke = new CodeMethodInvokeExpression (
479 new CodeExpression[] { new CodePrimitiveExpression (name), new CodeSnippetExpression ("value") }
481 property.SetStatements.Add (minvoke);
485 profileClass.Members.Add (property);
488 void AddProfileClassGroupProperty (string groupName, string memberName, CodeTypeDeclaration profileClass)
490 CodeMemberProperty property = new CodeMemberProperty ();
491 property.Name = memberName;
492 property.Type = new CodeTypeReference (groupName);
493 property.Attributes = MemberAttributes.Public;
495 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
496 CodeCastExpression cast = new CodeCastExpression ();
497 ret.Expression = cast;
499 CodeMethodReferenceExpression mref = new CodeMethodReferenceExpression (
500 new CodeThisReferenceExpression (),
502 CodeMethodInvokeExpression minvoke = new CodeMethodInvokeExpression (
504 new CodeExpression[] { new CodePrimitiveExpression (memberName) }
506 cast.TargetType = new CodeTypeReference (groupName);
507 cast.Expression = minvoke;
508 property.GetStatements.Add (ret);
510 profileClass.Members.Add (property);
513 void BuildProfileClass (ProfileSection ps, string className, ProfilePropertySettingsCollection psc,
514 CodeNamespace ns, string baseClass, bool baseIsGlobal,
515 SortedList <string, string> groupProperties)
517 CodeTypeDeclaration profileClass = new CodeTypeDeclaration (className);
518 CodeTypeReference cref = new CodeTypeReference (baseClass);
520 cref.Options |= CodeTypeReferenceOptions.GlobalReference;
521 profileClass.BaseTypes.Add (cref);
522 profileClass.TypeAttributes = TypeAttributes.Public;
523 ns.Types.Add (profileClass);
525 foreach (ProfilePropertySettings pset in psc)
526 AddProfileClassProperty (ps, profileClass, pset);
527 if (groupProperties != null && groupProperties.Count > 0)
528 foreach (KeyValuePair <string, string> group in groupProperties)
529 AddProfileClassGroupProperty (group.Key, group.Value, profileClass);
530 AddProfileClassGetProfileMethod (profileClass);
533 string MakeGroupName (string name)
535 return String.Format ("ProfileGroup{0}", name);
538 // FIXME: there should be some validation of syntactic correctness of the member/class name
539 // for the groups/properties. For now it's left to the compiler to report errors.
541 // CodeGenerator.IsValidLanguageIndependentIdentifier (id) - use that
543 bool ProcessCustomProfile (ProfileSection ps, AppCodeAssembly defasm)
545 CodeCompileUnit unit = new CodeCompileUnit ();
546 CodeNamespace ns = new CodeNamespace (null);
547 unit.Namespaces.Add (ns);
548 defasm.AddUnit (unit);
550 ns.Imports.Add (new CodeNamespaceImport ("System"));
551 ns.Imports.Add (new CodeNamespaceImport ("System.Configuration"));
552 ns.Imports.Add (new CodeNamespaceImport ("System.Web"));
553 ns.Imports.Add (new CodeNamespaceImport ("System.Web.Profile"));
555 RootProfilePropertySettingsCollection props = ps.PropertySettings;
556 if (props == null || props.Count == 0)
559 SortedList<string, string> groupProperties = new SortedList<string, string> ();
561 foreach (ProfileGroupSettings pgs in props.GroupSettings) {
562 groupName = MakeGroupName (pgs.Name);
563 groupProperties.Add (groupName, pgs.Name);
564 BuildProfileClass (ps, groupName, pgs.PropertySettings, ns,
565 "System.Web.Profile.ProfileGroupBase", true, null);
568 string baseType = ps.Inherits;
569 bool baseIsGlobal = false;
570 if (String.IsNullOrEmpty (baseType)) {
571 baseType = "System.Web.Profile.ProfileBase";
575 BuildProfileClass (ps, "ProfileCommon", props, ns, baseType, baseIsGlobal, groupProperties);
579 // void PutCustomProfileInContext (HttpContext context, string assemblyName)
581 // Type type = Type.GetType (String.Format ("ProfileCommon, {0}",
582 // Path.GetFileNameWithoutExtension (assemblyName)));
583 // ProfileBase pb = Activator.CreateInstance (type) as ProfileBase;
585 // context.Profile = pb;
588 public void Compile ()
590 if (_alreadyCompiled)
592 _alreadyCompiled = false;
594 string appCode = Path.Combine (HttpRuntime.AppDomainAppPath, "App_Code");
595 ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
596 bool haveAppCodeDir = Directory.Exists (appCode);
597 bool haveCustomProfile = ps != null ? ps.PropertySettings.Count > 0 : false;
599 if (!haveAppCodeDir && !haveCustomProfile)
602 AppCodeAssembly defasm = new AppCodeAssembly ("App_Code", appCode);
603 assemblies.Add (defasm);
605 bool haveCode = false;
607 haveCode = ProcessAppCodeDir (appCode, defasm);
608 if (haveCustomProfile)
609 if (ProcessCustomProfile (ps, defasm))
615 HttpRuntime.EnableAssemblyMapping (true);
616 string bindir = BinDir;
617 string[] binAssemblies = null;
618 if (Directory.Exists (bindir))
619 binAssemblies = Directory.GetFiles (bindir, "*.dll");
620 foreach (AppCodeAssembly aca in assemblies)
621 aca.Build (binAssemblies);
622 DefaultAppCodeAssemblyName = Path.GetFileNameWithoutExtension (defasm.OutputAssemblyName);
625 private bool CollectFiles (string dir, AppCodeAssembly aca)
627 bool haveFiles = false;
629 AppCodeAssembly curaca = aca;
630 foreach (string f in Directory.GetFiles (dir)) {
635 foreach (string d in Directory.GetDirectories (dir)) {
636 foreach (AppCodeAssembly a in assemblies)
637 if (a.SourcePath == d) {
641 if (CollectFiles (d, curaca))
648 private string BinDir {
652 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
653 _bindir = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);