// System.Web.Compilation.AppResourceFilesCollection
//
// Authors:
-// Marek Habersack (grendello@gmail.com)
+// Marek Habersack (mhabersack@novell.com)
//
// (C) 2006 Marek Habersack
+// (C) 2007-2009 Novell, Inc http://novell.com/
//
//
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel.Design;
using System.Globalization;
using System.IO;
using System.Reflection;
namespace System.Web.Compilation
{
- internal class AppResourcesCompiler
+ class AppResourcesCompiler
{
+ // This class fixes bug #466059
+ class TypeResolutionService : ITypeResolutionService
+ {
+ List <Assembly> referencedAssemblies;
+ Dictionary <string, Type> mappedTypes;
+
+ public Assembly GetAssembly (AssemblyName name)
+ {
+ return GetAssembly (name, false);
+ }
+
+ public Assembly GetAssembly (AssemblyName name, bool throwOnError)
+ {
+ try {
+ return Assembly.Load (name);
+ } catch {
+ if (throwOnError)
+ throw;
+ }
+
+ return null;
+ }
+
+ public void ReferenceAssembly (AssemblyName name)
+ {
+ if (referencedAssemblies == null)
+ referencedAssemblies = new List <Assembly> ();
+
+ Assembly asm = GetAssembly (name, false);
+ if (asm == null)
+ return;
+
+ if (referencedAssemblies.Contains (asm))
+ return;
+
+ referencedAssemblies.Add (asm);
+ }
+
+ public string GetPathOfAssembly (AssemblyName name)
+ {
+ if (name == null)
+ return null;
+
+ Assembly asm = GetAssembly (name, false);
+ if (asm == null)
+ return null;
+
+ return asm.Location;
+ }
+
+ public Type GetType (string name)
+ {
+ return GetType (name, false, false);
+ }
+
+ public Type GetType (string name, bool throwOnError)
+ {
+ return GetType (name, throwOnError, false);
+ }
+
+ public Type GetType (string name, bool throwOnError, bool ignoreCase)
+ {
+ if (String.IsNullOrEmpty (name)) {
+ if (throwOnError)
+ throw new ArgumentNullException ("name");
+ else
+ return null;
+ }
+
+ int idx = name.IndexOf (',');
+ Type type = null;
+ if (idx == -1) {
+ type = MapType (name, false);
+ if (type != null)
+ return type;
+
+ type = FindInAssemblies (name, ignoreCase);
+ if (type == null) {
+ if (throwOnError)
+ throw new InvalidOperationException ("Type '" + name + "' is not fully qualified and there are no referenced assemblies.");
+ else
+ return null;
+ }
+
+ return type;
+ }
+
+ type = MapType (name, true);
+ if (type != null)
+ return type;
+
+ return Type.GetType (name, throwOnError, ignoreCase);
+ }
+
+ Type MapType (string name, bool full)
+ {
+ if (mappedTypes == null)
+ mappedTypes = new Dictionary <string, Type> (StringComparer.Ordinal);
+
+ Type ret;
+ if (mappedTypes.TryGetValue (name, out ret))
+ return ret;
+
+ if (!full) {
+ if (String.Compare (name, "ResXDataNode", StringComparison.Ordinal) == 0)
+ return AddMappedType (name, typeof (ResXDataNode));
+ if (String.Compare (name, "ResXFileRef", StringComparison.Ordinal) == 0)
+ return AddMappedType (name, typeof (ResXFileRef));
+ if (String.Compare (name, "ResXNullRef", StringComparison.Ordinal) == 0)
+ return AddMappedType (name, typeof (ResXNullRef));
+ if (String.Compare (name, "ResXResourceReader", StringComparison.Ordinal) == 0)
+ return AddMappedType (name, typeof (ResXResourceReader));
+ if (String.Compare (name, "ResXResourceWriter", StringComparison.Ordinal) == 0)
+ return AddMappedType (name, typeof (ResXResourceWriter));
+
+ return null;
+ }
+
+ if (name.IndexOf ("System.Windows.Forms") == -1)
+ return null;
+
+ if (name.IndexOf ("ResXDataNode", StringComparison.Ordinal) != -1)
+ return AddMappedType (name, typeof (ResXDataNode));
+ if (name.IndexOf ("ResXFileRef", StringComparison.Ordinal) != -1)
+ return AddMappedType (name, typeof (ResXFileRef));
+ if (name.IndexOf ("ResXNullRef", StringComparison.Ordinal) != -1)
+ return AddMappedType (name, typeof (ResXNullRef));
+ if (name.IndexOf ("ResXResourceReader", StringComparison.Ordinal) != -1)
+ return AddMappedType (name, typeof (ResXResourceReader));
+ if (name.IndexOf ("ResXResourceWriter", StringComparison.Ordinal) != -1)
+ return AddMappedType (name, typeof (ResXResourceWriter));
+
+ return null;
+ }
+
+ Type AddMappedType (string name, Type type)
+ {
+ mappedTypes.Add (name, type);
+ return type;
+ }
+
+ Type FindInAssemblies (string name, bool ignoreCase)
+ {
+ Type ret = Type.GetType (name, false);
+ if (ret != null)
+ return ret;
+
+ if (referencedAssemblies == null || referencedAssemblies.Count == 0)
+ return null;
+
+ foreach (Assembly asm in referencedAssemblies) {
+ ret = asm.GetType (name, false, ignoreCase);
+ if (ret != null)
+ return ret;
+ }
+
+ return null;
+ }
+ }
+
const string cachePrefix = "@@LocalResourcesAssemblies";
+ public const string DefaultCultureKey = ".:!DefaultCulture!:.";
bool isGlobal;
- HttpContext context;
AppResourceFilesCollection files;
string tempDirectory;
+ string virtualPath;
+ Dictionary <string, List <string>> cultureFiles;
string TempDirectory {
get {
return (tempDirectory = AppDomain.CurrentDomain.SetupInformation.DynamicBase);
}
}
+
+ public Dictionary <string, List <string>> CultureFiles {
+ get { return cultureFiles; }
+ }
- public AppResourcesCompiler (HttpContext context, bool isGlobal)
+ public AppResourcesCompiler (HttpContext context)
{
- this.context = context;
- this.isGlobal = isGlobal;
- this.files = new AppResourceFilesCollection (context, isGlobal);
+ this.isGlobal = true;
+ this.files = new AppResourceFilesCollection (context);
+ this.cultureFiles = new Dictionary <string, List <string>> ();
}
+ public AppResourcesCompiler (string virtualPath)
+ {
+
+ this.virtualPath = virtualPath;
+ this.isGlobal = false;
+ this.files = new AppResourceFilesCollection (HttpContext.Current.Request.MapPath (virtualPath));
+ this.cultureFiles = new Dictionary <string, List <string>> ();
+ }
- public void Compile ()
+ public Assembly Compile ()
{
files.Collect ();
if (!files.HasFiles)
- return;
+ return null;
if (isGlobal)
- CompileGlobal ();
+ return CompileGlobal ();
else
- CompileLocal ();
+ return CompileLocal ();
}
- void CompileGlobal ()
+ Assembly CompileGlobal ()
{
string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
"App_GlobalResources",
"dll",
OnCreateRandomFile) as string;
+
if (assemblyPath == null)
throw new ApplicationException ("Failed to create global resources assembly");
- CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
- if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
- throw new ApplicationException ("Could not get the default compiler.");
- CompilerInfo ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
- if (ci == null || !ci.IsCodeDomProviderTypeValid)
- throw new ApplicationException ("Failed to obtain the default compiler information.");
-
- CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
- cp.OutputAssembly = assemblyPath;
- cp.GenerateExecutable = false;
- cp.TreatWarningsAsErrors = true;
- cp.IncludeDebugInformation = config.Debug;
-
- List <string>[] fileGroups = GroupGlobalFiles (cp);
+ List <string>[] fileGroups = GroupGlobalFiles ();
if (fileGroups == null || fileGroups.Length == 0)
- return;
-
+ return null;
+
CodeCompileUnit unit = new CodeCompileUnit ();
CodeNamespace ns = new CodeNamespace (null);
ns.Imports.Add (new CodeNamespaceImport ("System"));
ns.Imports.Add (new CodeNamespaceImport ("System.Resources"));
unit.Namespaces.Add (ns);
- CodeDomProvider provider;
- provider = ci.CreateProvider ();
- if (provider == null)
- throw new ApplicationException ("Failed to instantiate the default compiler.");
+ AppResourcesAssemblyBuilder builder = new AppResourcesAssemblyBuilder ("App_GlobalResources", assemblyPath,
+ this);
+ CodeDomProvider provider = builder.Provider;
Dictionary <string,bool> assemblies = new Dictionary<string,bool> ();
foreach (List<string> ls in fileGroups)
DomFromResource (ls [0], unit, assemblies, provider);
+
foreach (KeyValuePair<string,bool> de in assemblies)
unit.ReferencedAssemblies.Add (de.Key);
- AssemblyBuilder abuilder = new AssemblyBuilder (provider);
- abuilder.AddCodeCompileUnit (unit);
-
- CompilerResults results = abuilder.BuildAssembly (cp);
- if (results.Errors.Count == 0) {
- BuildManager.TopLevelAssemblies.Add (results.CompiledAssembly);
- HttpContext.AppGlobalResourcesAssembly = results.CompiledAssembly;
- } else {
- if (context.IsCustomErrorEnabled)
- throw new ApplicationException ("An error occurred while compiling global resources.");
- throw new CompilationException (null, results.Errors, null);
- }
+ builder.Build (unit);
+ HttpContext.AppGlobalResourcesAssembly = builder.MainAssembly;
+
+ return builder.MainAssembly;
}
- void CompileLocal ()
+ Assembly CompileLocal ()
{
- string path = Path.GetDirectoryName (VirtualPathUtility.ToAbsolute (context.Request.CurrentExecutionFilePath));
+ if (String.IsNullOrEmpty (virtualPath))
+ return null;
- if (String.IsNullOrEmpty (path))
- throw new ApplicationException ("Unable to determine the request virtual path.");
-
- Assembly cached = GetCachedLocalResourcesAssembly (path);
+ Assembly cached = GetCachedLocalResourcesAssembly (virtualPath);
if (cached != null)
- return;
+ return cached;
string prefix;
- if (path == "/")
+ if (virtualPath == "/")
prefix = "App_LocalResources.root";
else
- prefix = "App_LocalResources" + path.Replace ('/', '.');
+ prefix = "App_LocalResources" + virtualPath.Replace ('/', '.');
string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
prefix,
"dll",
OnCreateRandomFile) as string;
if (assemblyPath == null)
- throw new ApplicationException ("Failed to create global resources assembly");
-
- CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
- if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
- throw new ApplicationException ("Could not get the default compiler.");
- CompilerInfo ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
- if (ci == null || !ci.IsCodeDomProviderTypeValid)
- throw new ApplicationException ("Failed to obtain the default compiler information.");
-
- CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
- cp.OutputAssembly = assemblyPath;
- cp.GenerateExecutable = false;
- cp.TreatWarningsAsErrors = true;
- cp.IncludeDebugInformation = config.Debug;
+ throw new ApplicationException ("Failed to create local resources assembly");
List<AppResourceFileInfo> files = this.files.Files;
foreach (AppResourceFileInfo arfi in files)
- GetResourceFile (arfi, cp);
+ GetResourceFile (arfi, true);
- CodeDomProvider provider;
- provider = ci.CreateProvider ();
- if (provider == null)
- throw new ApplicationException ("Failed to instantiate the default compiler.");
+ AppResourcesAssemblyBuilder builder = new AppResourcesAssemblyBuilder ("App_LocalResources", assemblyPath,
+ this);
+ builder.Build ();
+ Assembly ret = builder.MainAssembly;
- AssemblyBuilder abuilder = new AssemblyBuilder (provider);
- CompilerResults results = abuilder.BuildAssembly (cp);
- if (results.Errors.Count == 0) {
- AddAssemblyToCache (path, results.CompiledAssembly);
- } else {
- if (context.IsCustomErrorEnabled)
- throw new ApplicationException ("An error occurred while compiling global resources.");
- throw new CompilationException (null, results.Errors, null);
- }
- }
+ if (ret != null)
+ AddAssemblyToCache (virtualPath, ret);
+ return ret;
+ }
+
internal static Assembly GetCachedLocalResourcesAssembly (string path)
{
Dictionary <string, Assembly> cache;
- cache = HttpRuntime.Cache[cachePrefix] as Dictionary <string, Assembly>;
+ cache = HttpRuntime.InternalCache[cachePrefix] as Dictionary <string, Assembly>;
if (cache == null || !cache.ContainsKey (path))
return null;
return cache [path];
}
-
+
void AddAssemblyToCache (string path, Assembly asm)
{
- Cache runtimeCache = HttpRuntime.Cache;
+ Cache runtimeCache = HttpRuntime.InternalCache;
Dictionary <string, Assembly> cache;
cache = runtimeCache[cachePrefix] as Dictionary <string, Assembly>;
return ret;
}
- bool IsFileCultureValid (string fileName)
+ string IsFileCultureValid (string fileName)
{
string tmp = Path.GetFileNameWithoutExtension (fileName);
tmp = Path.GetExtension (tmp);
tmp = tmp.Substring (1);
try {
CultureInfo.GetCultureInfo (tmp);
- return true;
+ return tmp;
} catch {
- return false;
+ return null;
}
}
- return false;
+ return null;
}
-
- string GetResourceFile (AppResourceFileInfo arfi, CompilerParameters cp)
+
+ string GetResourceFile (AppResourceFileInfo arfi, bool local)
{
string resfile;
if (arfi.Kind == AppResourceFileKind.ResX)
- resfile = CompileResource (arfi);
+ resfile = CompileResource (arfi, local);
else
resfile = arfi.Info.FullName;
- if (!String.IsNullOrEmpty (resfile))
- cp.EmbeddedResources.Add (resfile);
+ if (!String.IsNullOrEmpty (resfile)) {
+ string culture = IsFileCultureValid (resfile);
+ if (culture == null)
+ culture = DefaultCultureKey;
+
+ List <string> cfiles;
+ if (cultureFiles.ContainsKey (culture))
+ cfiles = cultureFiles [culture];
+ else {
+ cfiles = new List <string> (1);
+ cultureFiles [culture] = cfiles;
+ }
+ cfiles.Add (resfile);
+ }
+
return resfile;
}
- List <string>[] GroupGlobalFiles (CompilerParameters cp)
+ List <string>[] GroupGlobalFiles ()
{
List<AppResourceFileInfo> files = this.files.Files;
List<List<string>> groups = new List<List<string>> ();
filedots = CountChars ('.', tmp);
if (filedots == basedots + 1 && tmp.StartsWith (basename)) {
- if (IsFileCultureValid (s2)) {
+ if (IsFileCultureValid (s2) != null) {
// A valid translated file for this name
defaultFile = arfi;
break;
}
if (defaultFile != null) {
List<string> al = new List<string> ();
- al.Add (GetResourceFile (arfi, cp));
+ al.Add (GetResourceFile (arfi, false));
arfi.Seen = true;
groups.Add (al);
foreach (List<string> al in groups) {
s = al [0];
tmp = Path.GetFileNameWithoutExtension (s);
+ if (tmp.StartsWith ("Resources."))
+ tmp = tmp.Substring (10);
foreach (AppResourceFileInfo arfi in files) {
if (arfi.Seen)
continue;
-
s = arfi.Info.FullName;
if (s == null)
continue;
tmp2 = arfi.Info.Name;
if (tmp2.StartsWith (tmp)) {
- al.Add (GetResourceFile (arfi, cp));
+ al.Add (GetResourceFile (arfi, false));
arfi.Seen = true;
}
}
// culture or not. If it is not a culture, then we have a
// default file that doesn't have any translations. Otherwise,
// the file is ignored (it's the same thing MS.NET does)
- string fullName;
foreach (AppResourceFileInfo arfi in files) {
if (arfi.Seen)
continue;
- if (IsFileCultureValid (arfi.Info.FullName))
+ if (IsFileCultureValid (arfi.Info.FullName) != null)
continue; // Culture found, we reject the file
// A single default file, create a group
List<string> al = new List<string> ();
- al.Add (GetResourceFile (arfi, cp));
+ al.Add (GetResourceFile (arfi, false));
groups.Add (al);
}
groups.Sort (lcList);
void DomFromResource (string resfile, CodeCompileUnit unit, Dictionary <string,bool> assemblies,
CodeDomProvider provider)
{
+ if (String.IsNullOrEmpty (resfile))
+ return;
+
string fname, nsname, classname;
fname = Path.GetFileNameWithoutExtension (resfile);
classname = nsname;
nsname = "Resources";
} else {
- nsname = String.Format ("Resources.{0}", nsname);
+ if (!nsname.StartsWith ("Resources", StringComparison.InvariantCulture))
+ nsname = String.Concat ("Resources.", nsname);
classname = classname.Substring(1);
}
+ if (!String.IsNullOrEmpty (classname))
+ classname = classname.Replace ('.', '_');
+ if (!String.IsNullOrEmpty (nsname))
+ nsname = nsname.Replace ('.', '_');
+
if (!provider.IsValidIdentifier (nsname) || !provider.IsValidIdentifier (classname))
throw new ApplicationException ("Invalid resource file name.");
+
+ ResourceReader res;
+ try {
+ res = new ResourceReader (resfile);
+ } catch (ArgumentException) {
+ // invalid stream, probably empty - ignore silently and abort
+ return;
+ }
CodeNamespace ns = new CodeNamespace (nsname);
CodeTypeDeclaration cls = new CodeTypeDeclaration (classname);
cls.IsClass = true;
cls.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
- CodeMemberField cmf = new CodeMemberField (typeof(CultureInfo), "culture");
+ CodeMemberField cmf = new CodeMemberField (typeof(CultureInfo), "_culture");
cmf.InitExpression = new CodePrimitiveExpression (null);
cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
cls.Members.Add (cmf);
- cmf = new CodeMemberField (typeof(ResourceManager), "resourceManager");
+ cmf = new CodeMemberField (typeof(ResourceManager), "_resourceManager");
cmf.InitExpression = new CodePrimitiveExpression (null);
cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
cls.Members.Add (cmf);
cmp.HasGet = true;
cmp.HasSet = true;
cmp.Type = new CodeTypeReference (typeof(CultureInfo));
- CodePropertyGenericGet (cmp.GetStatements, "culture", classname);
- CodePropertyGenericSet (cmp.SetStatements, "culture", classname);
+ CodePropertyGenericGet (cmp.GetStatements, "_culture", classname);
+ CodePropertyGenericSet (cmp.SetStatements, "_culture", classname);
cls.Members.Add (cmp);
// Add the resource properties
Dictionary<string,bool> imports = new Dictionary<string,bool> ();
try {
- ResourceReader res = new ResourceReader (resfile);
foreach (DictionaryEntry de in res) {
Type type = de.Value.GetType ();
cls.Members.Add (cmp);
}
} catch (Exception ex) {
- throw new ApplicationException ("Failed to comipile global resources.", ex);
+ throw new ApplicationException ("Failed to compile global resources.", ex);
}
foreach (KeyValuePair<string,bool> de in imports)
ns.Imports.Add (new CodeNamespaceImport(de.Key));
CodeStatement st;
CodeExpression exp;
- exp = new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), "resourceManager");
+ exp = new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), "_resourceManager");
st = new CodeConditionStatement (
new CodeBinaryOperatorExpression (
exp,
gotstr ? "GetString" : "GetObject",
new CodeExpression [] { new CodePrimitiveExpression (resname),
new CodeFieldReferenceExpression (
- new CodeTypeReferenceExpression (typename), "culture") });
+ new CodeTypeReferenceExpression (typename), "_culture") });
st = new CodeVariableDeclarationStatement (
restype,
"obj",
new CodeVariableReferenceExpression ("value")));
}
- string CompileResource (AppResourceFileInfo arfi)
+ string CompileResource (AppResourceFileInfo arfi, bool local)
{
string path = arfi.Info.FullName;
- string resource = Path.Combine (TempDirectory,
- Path.GetFileNameWithoutExtension (path) + ".resources");
+ string rname = Path.GetFileNameWithoutExtension (path) + ".resources";
+ if (!local)
+ rname = "Resources." + rname;
+
+ string resource = Path.Combine (TempDirectory, rname);
FileStream source = null, destination = null;
IResourceReader reader = null;
ResourceWriter writer = null;
try {
source = new FileStream (path, FileMode.Open, FileAccess.Read);
destination = new FileStream (resource, FileMode.Create, FileAccess.Write);
- reader = GetReaderForKind (arfi.Kind, source);
+ reader = GetReaderForKind (arfi.Kind, source, path);
writer = new ResourceWriter (destination);
foreach (DictionaryEntry de in reader) {
object val = de.Value;
return resource;
}
- IResourceReader GetReaderForKind (AppResourceFileKind kind, Stream stream)
+ IResourceReader GetReaderForKind (AppResourceFileKind kind, Stream stream, string path)
{
switch (kind) {
case AppResourceFileKind.ResX:
- return new ResXResourceReader (stream);
+ var ret = new ResXResourceReader (stream, new TypeResolutionService ());
+ if (!String.IsNullOrEmpty (path))
+ ret.BasePath = Path.GetDirectoryName (path);
+ return ret;
case AppResourceFileKind.Resource:
return new ResourceReader (stream);