I am trying to learn more about Reflection, and took some code already built and added to it. Now I am trying to query the GAC for other assemblies and build type instances, and etc. I modified the code I found here, but myAssemblyList is empty. Can you tell me what I am doing wrong? I debugged and placed a break at "var currentAssembly = value.GetAssembly(f);" and it returns null. All the code I have seen populates Assemblies from the current AppDomain, but I have seen methods like LoadFrom(), which should work with a directory path. I also saw this post, and compiled it.
class Program
{
static void Main(string[] args)
{
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = System.Environment.CurrentDirectory;
Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyDomain", adevidence, domaininfo);
Type type = typeof(Proxy);
var value = (Proxy)domain.CreateInstanceAndUnwrap(
type.Assembly.FullName,
type.FullName);
//String myDir = "C:\\Windows\\Microsoft.NET\\assembly\\GAC_64\\";
String myDir = "C:\\Windows\\Microsoft.NET\\assembly\\";
List<Assembly> myAssemblyList = new List<Assembly>();
foreach (String f in Directory.GetFiles(myDir, "*.dll", SearchOption.AllDirectories))
{
//Console.WriteLine($"Here is f: {f}");
var currentAssembly = value.GetAssembly(f);
if (currentAssembly != null)
{
myAssemblyList.Add(currentAssembly);
Console.WriteLine(currentAssembly.FullName);
//Console.ReadLine();
}
Console.WriteLine($"Total Assemblies found: {myAssemblyList.Count}");
}
Console.WriteLine($"Total Assemblies found: {myAssemblyList.Count}");
Console.ReadLine();
}
}
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
return Assembly.LoadFile(assemblyPath);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
}
I jumped one directory back, and tried to collect from GAC_* i.e. 32, 64, and MSIL. I added a test for null for currentAssembly to address an issue with GetAssembly(). But still some directories that contain dll and non-dll files cause exceptions.
modify this line:
foreach (String f in Directory.GetFiles(myDir))
with
foreach (String f in Directory.GetFiles(myDir, "*.dll", SearchOption.AllDirectories)
Related
I am trying to make a small C# tool to compare two svn revision builds and track properties changes in any classes. My goal is to use reflection to compare the properties of each class of my dll without using Momo.Cecil.
From experimenting, then reading this article Assembly Loading and a few threads found on Google, I learnt that two DLLs with same identities would get resolved as the same if we dont use Assembly.ReflectionOnlyLoadFrom.
Trying to use their code to create an AppDomain for each loading, and also try many variants from searches, I get this exception and I can't find any thread explaining how to solve this issue:
API restriction: The assembly 'file:///D:\somepath\82\MyLib.dll' has already loaded from a different location. It cannot be loaded from a new location within the same appdomain.
This error happen on the 2nd call (the path 82) on the following line:
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
Maybe I din't understand something very basic which make me unable to create a new AppDomain correctly?
Here is all the code used to reproduce this problem.
Code from my entry point
//Let say one of the property has been renamed between both commits
var svnRev81Assembly = ReflectionOnlyLoadFrom(#"D:\somepath\81\MyLib.dll");
var svnRev82Assembly = ReflectionOnlyLoadFrom(#"D:\somepath\82\MyLib.dll");
Implementation of the loader
private string _CurrentAssemblyKey;
private Assembly ReflectionOnlyLoadFrom(string assemblyPath)
{
var path = Path.GetDirectoryName(assemblyPath);
// Create application domain setup information.
AppDomainSetup domaininfo = new AppDomainSetup();
domaininfo.ApplicationBase = path;
domaininfo.PrivateBinPath = path;
_CurrentAssemblyKey = Guid.NewGuid().ToString();
AppDomain currentAd = AppDomain.CreateDomain(_CurrentAssemblyKey, null, domaininfo); //Everytime we create a new domain with a new name
//currentAd.ReflectionOnlyAssemblyResolve += CustomReflectionOnlyResolver; This doesnt work anymore since I added AppDomainSetup
currentAd.SetData(_CurrentAssemblyKey, path);
//Loading to specific location - folder 81 or 82
Assembly assembly = Assembly.ReflectionOnlyLoadFrom(assemblyPath);
//Preloading the
foreach (var assemblyName in assembly.GetReferencedAssemblies())
{
Assembly.ReflectionOnlyLoadFrom(Path.Combine(path, assemblyName.Name + ".dll"));
}
Type[] types = assembly.GetTypes();
// Lastly, reset the ALS entry and remove our handler
currentAd.SetData(_CurrentAssemblyKey, null);
//currentAd.ReflectionOnlyAssemblyResolve -= CustomReflectionOnlyResolver; This doesnt work anymore since I added AppDomainSetup
return assembly;
}
This can solved by loading dlls in seperate appdomain.
private static void ReflectionOnlyLoadFrom()
{
var appDomain = AppDomain.CreateDomain("Temporary");
var loader1 = (Loader)appDomain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader1.Execute(#"D:\somepath\81\MyLib.dll");
var loader2 = (Loader)appDomain.CreateInstanceAndUnwrap(typeof(Loader).Assembly.FullName, typeof(Loader).FullName);
loader2.Execute(#"D:\somepath\82\MyLib.dll");
loader1.CompareTwoDLLs(loader2);
AppDomain.Unload(appDomain);
}
Loader.cs
public class Loader : MarshalByRefObject
{
public Assembly TempAssembly { get; set; }
public string Execute(string dllPath)
{
TempAssembly = Assembly.LoadFile(dllPath);
return TempAssembly.FullName;
}
public void GetRefAssemblyTypes()
{
foreach (var refAssembly in TempAssembly.GetReferencedAssemblies())
{
var asm = Assembly.Load(refAssembly);
var asmTypes = asm.GetTypes();
}
}
public void CompareTwoDLLs(Loader l2)
{
var types1 = TempAssembly.GetTypes();
var types2= l2.TempAssembly.GetTypes();
GetRefAssemblyTypes();
//logic to return comparison result
}
}
so what I am creating dll type files, running them and then I want to delete them.
However when I try to delete them I get an exception as it claims they are still in use by another process.
I assuming that the code used to create the files does not dispose of resources correctly to allow the deleting of the file after, here is my code to create the file.
if (!Directory.Exists(PathToRobots + Generation))
{
Directory.CreateDirectory(PathToRobots + Generation);
}
File.WriteAllText(Path.Combine(PathToRobots + Generation, NameSpace + GetRobotName() + robotNumber + ".cs"), code);
CSharpCodeProvider provider = new CSharpCodeProvider();
CompilerParameters parameters = new CompilerParameters()
{
GenerateInMemory = false,
GenerateExecutable = false, // True = EXE, False = DLL
IncludeDebugInformation = true,
OutputAssembly = Path.Combine(FileName + ".dll") // Compilation name
};
parameters.ReferencedAssemblies.Add(#"robocode.dll");
CompilerResults results = provider.CompileAssemblyFromSource(parameters, code);
if (results.Errors.HasErrors)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError error in results.Errors)
{
sb.AppendLine(String.Format("Error ({0}): {1}", error.ErrorNumber, error.ErrorText));
}
throw new InvalidOperationException(sb.ToString());
}
Assembly assembly = results.CompiledAssembly;
provider.Dispose();
The code to delete the file is quite simple and is as follows,
var files = Directory.GetFiles(DirectoryPath);
foreach (var file in files)
{
File.Delete(file);
}
Any idea as to why I can't delete the files?
See the notes at CompilerResults.CompiledAssembly Property
The get accessor for the CompiledAssembly property calls the Load method to load the compiled assembly into the current application domain. After calling the get accessor, the compiled assembly cannot be deleted until the current AppDomain is unloaded.
So when you do this:
Assembly assembly = results.CompiledAssembly;
You have loaded the compiled assembly into the current app domain and thus have locked the file. To be able to delete the generated file, you would need to load it into a separate app domain (this answer may help with specifics for doing that).
I use Bundle. But it not send exception if file not found.
I need check exist file and catch exception if file not exist.
I tried:
var cssCommon = "/Common/common.css";
if (!System.IO.File.Exists(server.MapPath("~") + cssCommon))
{
throw new FileNotFoundException(cssCommon);
}
but always had exception
How check exist file on global asax or in bundle settings?
I prefer to use a BundleHelper for this task.
Herman has an excellent one here: https://stackoverflow.com/a/25784663/732377
Copied here for completeness, but all the kudos should go to Herman!
public static class BundleHelper
{
[Conditional("DEBUG")] // remove this attribute to validate bundles in production too
private static void CheckExistence(string virtualPath)
{
int i = virtualPath.LastIndexOf('/');
string path = HostingEnvironment.MapPath(virtualPath.Substring(0, i));
string fileName = virtualPath.Substring(i + 1);
bool found = Directory.Exists(path);
if (found)
{
if (fileName.Contains("{version}"))
{
var re = new Regex(fileName.Replace(".", #"\.").Replace("{version}", #"(\d+(?:\.\d+){1,3})"));
fileName = fileName.Replace("{version}", "*");
found = Directory.EnumerateFiles(path, fileName).Where(file => re.IsMatch(file)).FirstOrDefault() != null;
}
else // fileName may contain '*'
found = Directory.EnumerateFiles(path, fileName).FirstOrDefault() != null;
}
if (!found)
throw new ApplicationException(String.Format("Bundle resource '{0}' not found", virtualPath));
}
public static Bundle IncludeExisting(this Bundle bundle, params string[] virtualPaths)
{
foreach (string virtualPath in virtualPaths)
CheckExistence(virtualPath);
return bundle.Include(virtualPaths);
}
public static Bundle IncludeExisting(this Bundle bundle, string virtualPath, params IItemTransform[] transforms)
{
CheckExistence(virtualPath);
return bundle.Include(virtualPath, transforms);
}
}
Then in configuration:
bundles.Add(new ScriptBundle("~/test")
.IncludeExisting("~/Scripts/jquery/jquery-{version}.js")
.IncludeExisting("~/Scripts/lib*")
.IncludeExisting("~/Scripts/model.js")
);
However, you might also want to checkout other solutions to this common problem.
nrodic is pretty straight forward here: https://stackoverflow.com/a/24812225/732377
I have implemented the fusion.dll wrapper mentioned in many posts and now find that at least one dll I need to determine if it needs to be updated is not using build and revision numbers. Consequently I cannot compare on version numbers and need to compare on Last Modified date.
fusion.dll or it's wrappers have no such method which I guess is fair enough but how do I determine the 'real' path of the dll so that I can discovers it's last Modified date.
My code so far:
private DateTime getGACVersionLastModified(string DLLName)
{
FileInfo fi = new FileInfo(DLLName);
string dllName = fi.Name.Replace(fi.Extension, "");
DateTime versionDT = new DateTime(1960,01,01);
IAssemblyEnum ae = AssemblyCache.CreateGACEnum();
IAssemblyName an;
AssemblyName name;
while (AssemblyCache.GetNextAssembly(ae, out an) == 0)
{
try
{
name = GetAssemblyName(an);
if (string.Compare(name.Name, dllName, true) == 0)
{
FileInfo dllfi = new FileInfo(string.Format("{0}.dll", name.Name));
if (DateTime.Compare(dllfi.LastWriteTime, versionDT) >= 0)
versionDT = dllfi.LastWriteTime;
}
}
catch (Exception ex)
{
logger.FatalException("Unable to get version number: ", ex);
}
}
return versionDT;
}
From the problem description in your question I can see there are really 2 primary tasks that you are trying to accomplish:
1) Determine if a given assembly name can be loaded from the GAC.
2) Return the file modified date for the given assembly.
I believe these 2 points can be accomplished in a much simpler fashion and without having to work with the unmanaged fusion API. An easier way to go about this task might be as follows:
static void Main(string[] args)
{
// Run the method with a few test values
GetAssemblyDetail("System.Data"); // This should be in the GAC
GetAssemblyDetail("YourAssemblyName"); // This might be in the GAC
GetAssemblyDetail("ImaginaryAssembly"); // This just plain doesn't exist
}
private static DateTime? GetAssemblyDetail(string assemblyName)
{
Assembly a;
a = Assembly.LoadWithPartialName(assemblyName);
if (a != null)
{
Console.WriteLine("'{0}' is in GAC? {1}", assemblyName, a.GlobalAssemblyCache);
FileInfo fi = new FileInfo(a.Location);
Console.WriteLine("'{0}' Modified: {1}", assemblyName, fi.LastWriteTime);
return fi.LastWriteTime;
}
else
{
Console.WriteLine("Assembly '{0}' not found", assemblyName);
return null;
}
}
An example of the resulting output:
'System.Data' is in GAC? True
'System.Data' Modified: 10/1/2010 9:32:27 AM
'YourAssemblyName' is in GAC? False
'YourAssemblyName' Modified: 12/30/2010 4:25:08 AM
Assembly 'ImaginaryAssembly' not found
In a web application, I want to load all assemblies in the /bin directory.
Since this can be installed anywhere in the file system, I can't gaurantee a specific path where it is stored.
I want a List<> of Assembly assembly objects.
Well, you can hack this together yourself with the following methods, initially use something like:
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
to get the path to your current assembly. Next, iterate over all DLL's in the path using the Directory.GetFiles method with a suitable filter. Your final code should look like:
List<Assembly> allAssemblies = new List<Assembly>();
string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
foreach (string dll in Directory.GetFiles(path, "*.dll"))
allAssemblies.Add(Assembly.LoadFile(dll));
Please note that I haven't tested this so you may need to check that dll actually contains the full path (and concatenate path if it doesn't)
To get the bin directory, string path = Assembly.GetExecutingAssembly().Location; does NOT always work (especially when the executing assembly has been placed in an ASP.NET temporary directory).
Instead, you should use string binPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin");
Further, you should probably take the FileLoadException and BadImageFormatException into consideration.
Here is my working function:
public static void LoadAllBinDirectoryAssemblies()
{
string binPath = System.IO.Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "bin"); // note: don't use CurrentEntryAssembly or anything like that.
foreach (string dll in Directory.GetFiles(binPath, "*.dll", SearchOption.AllDirectories))
{
try
{
Assembly loadedAssembly = Assembly.LoadFile(dll);
}
catch (FileLoadException loadEx)
{ } // The Assembly has already been loaded.
catch (BadImageFormatException imgEx)
{ } // If a BadImageFormatException exception is thrown, the file is not an assembly.
} // foreach dll
}
You can do it like this, but you should probably not load everything into the current appdomain like this, since assemblies might contain harmful code.
public IEnumerable<Assembly> LoadAssemblies()
{
DirectoryInfo directory = new DirectoryInfo(#"c:\mybinfolder");
FileInfo[] files = directory.GetFiles("*.dll", SearchOption.TopDirectoryOnly);
foreach (FileInfo file in files)
{
// Load the file into the application domain.
AssemblyName assemblyName = AssemblyName.GetAssemblyName(file.FullName);
Assembly assembly = AppDomain.CurrentDomain.Load(assemblyName);
yield return assembly;
}
yield break;
}
EDIT: I have not tested the code (no access to Visual Studio at this computer), but I hope that you get the idea.
I know this is a old question but...
System.AppDomain.CurrentDomain.GetAssemblies()