I have an application that can be customized by writing custom extensions. All of them are in proj\Extensions folder. At runtime my Core project loads every extension from the folder and executes code. The problem is when one of the extensions uses additional libraries, because the Core project cannot find reference to these additional libraries.
So for example in my Core project I have:
public void Preview(IFileDescription fileDescription)
{
var extension = Path.GetExtension(fileDescription.FilePath);
var reader = _readerFactory.Get(extension);
Data = reader.GetPreview(fileDescription);
}
In one of my extensions I have
public DataTable GetPreview(IFileDescription options)
{
var data = new DataTable();
using (var stream = new StreamReader(options.FilePath))
{
var reader = new CsvReader(stream); // <- This is from external library and because of this Core throws IO exception
}
/*
...
*/
return data;
}
Core only knows about the interface, so when one of readers uses for example CsvHelper.dll I get exception of FileNotFound, because Core cannot find CsvHelper.dll. Is there any way to tell the compiler to look for additional libraries in specific folder? I used Reference Paths, but it didn't solve the problem. It still threw the same exception.
Yes, it's possible. You can attach to the AppDomain.AssemblyResolve event and manually load the required DLLs from your add-in directory. Execute the following code before executing any add-in code:
var addinFolder = ...;
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) =>
{
var missing = new AssemblyName(e.Name);
var missingPath = Path.Combine(addinFolder, missing.Name + ".dll");
// If we find the DLL in the add-in folder, load and return it.
if (File.Exists(missingPath))
return Assembly.LoadFrom(missingPath);
// nothing found, let .NET search the common folders
return null;
};
Related
I need to develop a Shell Context Menu extension that references some other custom assemblies... I don't want to assign a Strong Name Key to those custom assemblies!
The guide I followed to do this uses the SharpShell project and illustrates how to sign (but does not expalins why) the assembly... and this is my problem: if I sign my final .dll then I have many errors during my project's building phase, because some assemblies my project references are not strongly named ("Referenced assembly does not have a strong name").
In general, googling about the C# Shell Extension implementation, all best tutorials I found sign the final assembly... is it mandatory?
Without signing the assembly ServerManager.exe returns this error: "The file 'XYZ.dll' is not a SharpShell Server".
Finally I've solved my troubles... the SharpShell.dll file obtained through NuGet was a different version of the ServerManager.exe ones.
Uninstalling the SharpShell NuGet package and directly referencing the SharpShell.dll you find inside the ServerManager folder was my solution!
Moreover, I was looking between the article comments... please read this question.
You don't need to use old DLL.
Please use this code directly, without using ServerManager.exe.
private static ServerEntry serverEntry = null;
public static ServerEntry SelectedServerEntry
{
get
{
if (serverEntry == null)
serverEntry = ServerManagerApi.LoadServer("xxx.dll");
return serverEntry;
}
}
public static ServerEntry LoadServer(string path)
{
try
{
// Create a server entry for the server.
var serverEntry = new ServerEntry();
// Set the data.
serverEntry.ServerName = Path.GetFileNameWithoutExtension(path);
serverEntry.ServerPath = path;
// Create an assembly catalog for the assembly and a container from it.
var catalog = new AssemblyCatalog(Path.GetFullPath(path));
var container = new CompositionContainer(catalog);
// Get the exported server.
var server = container.GetExport<ISharpShellServer>().Value;
serverEntry.ServerType = server.ServerType;
serverEntry.ClassId = server.GetType().GUID;
serverEntry.Server = server;
return serverEntry;
}
catch (Exception)
{
// It's almost certainly not a COM server.
MessageBox.Show("The file '" + Path.GetFileName(path) + "' is not a SharpShell Server.", "Warning");
return null;
}
}
Install code:
ServerRegistrationManager.InstallServer(SelectedServerEntry.Server, RegistrationType.OS64Bit, true);
Register code:
ServerRegistrationManager.RegisterServer(SelectedServerEntry.Server, RegistrationType.OS64Bit);
I have a Console Application in C# and a Class Library named AppManager.cs. The Method of this class is used in the console Application as given below.
try
{
AppManager mgr = new AppManager(); //Want to skip this line when dll is missing.
mgr.Method_Name(this, true); //Want to skip this line when dll is missing.
}
catch (Exception)
{
}
When I have published the Code and extracted only exe then application fails to run [I know as the exe try to find that dll and method present in dll won't available].
Now my Question is That Is there any way to skip the code which will produce error when it will not find the reference of dll.
I also tried this but it didn't worked:
String file = null;
String filePath = Path.GetDirectoryName(Application.ExecutablePath);
file = Directory.GetFiles(filePath, "myLibrary.dll", SearchOption.AllDirectories)
.FirstOrDefault();
if (file != null)
{
AppManager mgr = new AppManager();
mgr.Method_Name(this, true);
}
If you do that, it won't give the desired output too. Why don't you integrate the dll with your exe? You can do it by using this tool
Can you not simply check if the dll exists or not?
string filePath = #"SOME_PATH";
var exists = File.Exists(filePath);
if(exists)
{
AppManager mgr = new AppManager();
mgr.Method_Name(this, true);
}
In my application, a script task is created dynamically.
In SQL Server 2008's implementation of SSIS, the following method worked fine.
private void SetSourceCode(ScriptTask scriptTask, string code, string codeName)
{
string fileName = "ScriptMain.vb";
string language = "VisualBasic";
string proj = ".vbproj";
scriptTask.ScriptLanguage = VSTAScriptLanguages.GetDisplayName(language);
scriptTask.ScriptingEngine.InitNewScript(language,
scriptTask.ScriptProjectName, proj);
scriptTask.ScriptingEngine.ShowDesigner(false);
scriptTask.ScriptingEngine.AddCodeFile(fileName, code);
if (!scriptTask.ScriptingEngine.Build())
throw new Exception("Failed to build vb script code: " + codeName);
scriptTask.ScriptingEngine.SaveScriptToStorage();
if (!scriptTask.ScriptingEngine.CloseIDE(false))
{
throw new Exception("Unable to close Scripting engine.");
}
}
How do I migrate this code to SQL Server 2012, because following methods are removed from SQL Server 2012 dll-s (assemblies):
InitNewScript
AddProjectReference
AddCodeFile
SaveScriptToStorage
CloseIDE
Build
ShowDesigner
Generally, how do I dynamically set source code for script task in SQL Server 2012?
As you've noticed, the VSTA helper methods you could use in 2008 were moved/removed in 2012. It is still possible to do, but the code has changed.
The easiest thing to do is load an existing project using VstaHelper.LoadProjectFromFolder().
If you want to dynamically add script files, see the snippet below. There are two main things you need to keep in mind:
The ScriptingEngine and VstaHelper classes represent VSTA itself. This is where you’d create the project, and add new files. You cannot remove or replace an existing file directly here. When you call SaveProjecToStorage(), it's like closing the VSTA window … it saves the project and compiled binary to the ScriptTask.
ScriptTask.ScriptStorage allows you to directly manipulate the source file contents. From here, you can modify the content of a file.
The following code snippet should help you get started.
static void Main(string[] args)
{
// 1. Create new package, and add a script task
var pkg = new Package();
var exec = pkg.Executables.Add("STOCK:ScriptTask");
var th = (TaskHost)exec;
th.Name = "Script Task";
th.Description = "This is a Script Task";
var task = (ScriptTask)th.InnerObject;
// 2. Set the script language - "CSharp" or "VisualBasic"
task.ScriptLanguage = VSTAScriptLanguages.GetDisplayName("CSharp");
// 3. Set any variables used by the script
//task.ReadWriteVariables = "User::Var1, User::Var2";
// 4. Create a new project from the template located in the default path
task.ScriptingEngine.VstaHelper.LoadNewProject(task.ProjectTemplatePath, null, "MyScriptProject");
// 5. Initialize the designer project, add a new code file, and build
//task.ScriptingEngine.VstaHelper.Initalize("", true);
//task.ScriptingEngine.VstaHelper.AddFileToProject("XX.cs", "FileContents");
//task.ScriptingEngine.VstaHelper.Build("");
// 6. Persist the VSTA project + binary to the task
if (!task.ScriptingEngine.SaveProjectToStorage())
{
throw new Exception("Save failed");
}
// 7. Use the following code to replace the ScriptMain contents
var contents = File.ReadAllText("path to file");
var scriptFile =
task.ScriptStorage.ScriptFiles["ScriptMain.cs"] =
new VSTAScriptProjectStorage.VSTAScriptFile(VSTAScriptProjectStorage.Encoding.UTF8, contents);
// 8. Reload the script project, build and save
task.ScriptingEngine.LoadProjectFromStorage();
task.ScriptingEngine.VstaHelper.Build("");
// 9. Persist the VSTA project + binary to the task
if (!task.ScriptingEngine.SaveProjectToStorage())
{
throw new Exception("Save failed");
}
// 10. Cleanup
task.ScriptingEngine.DisposeVstaHelper();
// 11. Save
string xml;
pkg.SaveToXML(out xml, null);
File.WriteAllText(#"c:\temp\package.dtsx", xml);
}
In my C# application, I've a plugin mechanism that loads plugin DLLs from different pathes as specified in a configuration XML file. My application is localizable. The main assembly (the *.exe) has satellite assemblies for the localized languages next to the exe in the standard .NET way (e.g. .\en\en-US\main.resources.dll; .\de\de_DE\main.resources.dll; etc.).
I started localizing a plugin and had to discover that the satellite assembly has to be put in the folders next to the exe. When putting it next to the plugin DLL, the resource manager doesn't find it.
However, since my plugins are interchangable and potentially in different folders, I would highly prefer to put the localized resource assemblies next to the plugins and not to the exe.
Is this possible?!?!
An alternative I could live with would be to embed the localized resources into the DLLs. Is this possible??
Cheers,
Felix
I ran into this issue when working on a product for our company. I didn't find an answer anywhere, so I'm going to post my solution to it here in case someone else finds themselves in the same situation.
As of .NET 4.0 there is a solution to this issue, because satellite assemblies now get passed to the AssemblyResolve handler. If you already have a plugin system where assemblies can be loaded from remote directories, you'll probably already have an assembly resolve handler in place, you just need to extend it to use a different search behaviour for satellite resource assemblies. If you don't have one, the implementation is non-trivial since you basically take responsibility for all assembly search behaviour. I'll post the complete code for a working solution so either way you'd be covered. First of all, you need to hook your AssemblyResolve handler somewhere, like this:
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssemblyReference;
Then assuming you've got a couple of variables to hold path information for your main application and your plugin directories, like this:
string _processAssemblyDirectoryPath;
List<string> _assemblySearchPaths;
Then you need a little helper method that looks a little like this:
static Assembly LoadAssembly(string assemblyPath)
{
// If the target assembly is already loaded, return the existing assembly instance.
Assembly[] loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
Assembly targetAssembly = loadedAssemblies.FirstOrDefault((x) => !x.IsDynamic && String.Equals(x.Location, assemblyPath, StringComparison.OrdinalIgnoreCase));
if (targetAssembly != null)
{
return targetAssembly;
}
// Attempt to load the target assembly
return Assembly.LoadFile(assemblyPath);
}
And finally you need the all important AssemblyResolve event handler, which looks a little something like this:
Assembly ResolveAssemblyReference(object sender, ResolveEventArgs args)
{
// Obtain information about the requested assembly
AssemblyName targetAssemblyName = new AssemblyName(args.Name);
string targetAssemblyFileName = targetAssemblyName.Name + ".dll";
// Handle satellite assembly load requests. Note that prior to .NET 4.0, satellite assemblies didn't get
// passed to AssemblyResolve handlers. When this was changed, there is a specific guarantee that if null is
// returned, normal load procedures will be followed for the satellite assembly, IE, it will be located and
// loaded in the same manner as if this event handler wasn't registered. This isn't sufficient for us
// though, as the normal load behaviour doesn't correctly locate satellite assemblies where the owning
// assembly has been loaded using Assembly.LoadFile where the assembly is located in a different folder to
// the process assembly. We handle that here by performing the satellite assembly search process ourselves.
// Also note that satellite assemblies are formally documented as requiring the file name extension of
// ".resources.dll", so detecting satellite assembly load requests by comparing with this known string is a
// valid approach.
if (targetAssemblyFileName.EndsWith(".resources.dll"))
{
// Retrieve the owning assembly which is requesting the satellite assembly
string owningAssemblyName = targetAssemblyFileName.Replace(".resources.dll", ".dll");
Assembly owningAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => x.Location.EndsWith(owningAssemblyName));
if (owningAssembly == null)
{
return null;
}
// Retrieve the directory containing the owning assembly
string owningAssemblyDirectory = Path.GetDirectoryName(owningAssembly.Location);
// Search for the required satellite assembly in resource subdirectories, and load it if found.
CultureInfo searchCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
while (searchCulture != CultureInfo.InvariantCulture)
{
string resourceAssemblyPath = Path.Combine(owningAssemblyDirectory, searchCulture.Name, targetAssemblyFileName);
if (File.Exists(resourceAssemblyPath))
{
Assembly resourceAssembly = LoadAssembly(resourceAssemblyPath);
if (resourceAssembly != null)
{
return resourceAssembly;
}
}
searchCulture = searchCulture.Parent;
}
return null;
}
// If the target assembly exists in the same directory as the requesting assembly, attempt to load it now.
string requestingAssemblyPath = (args.RequestingAssembly != null) ? args.RequestingAssembly.Location : String.Empty;
if (!String.IsNullOrEmpty(requestingAssemblyPath))
{
string callingAssemblyDirectory = Path.GetDirectoryName(requestingAssemblyPath);
string targetAssemblyInCallingDirectoryPath = Path.Combine(callingAssemblyDirectory, targetAssemblyFileName);
if (File.Exists(targetAssemblyInCallingDirectoryPath))
{
try
{
return LoadAssembly(targetAssemblyInCallingDirectoryPath);
}
catch (Exception ex)
{
// Log an error
return null;
}
}
}
// If the target assembly exists in the same directory as the process executable, attempt to load it now.
string processDirectory = _processAssemblyDirectoryPath;
string targetAssemblyInProcessDirectoryPath = Path.Combine(processDirectory, targetAssemblyFileName);
if (File.Exists(targetAssemblyInProcessDirectoryPath))
{
try
{
return LoadAssembly(targetAssemblyInProcessDirectoryPath);
}
catch (Exception ex)
{
// Log an error
return null;
}
}
// Build a list of all assemblies with the requested name in the defined list of assembly search paths
Dictionary<string, AssemblyName> assemblyVersionInfo = new Dictionary<string, AssemblyName>();
foreach (string assemblyDir in _assemblySearchPaths)
{
// If the target assembly doesn't exist in this path, skip it.
string assemblyPath = Path.Combine(assemblyDir, targetAssemblyFileName);
if (!File.Exists(assemblyPath))
{
continue;
}
// Attempt to retrieve detailed information on the name and version of the target assembly
AssemblyName matchAssemblyName;
try
{
matchAssemblyName = AssemblyName.GetAssemblyName(assemblyPath);
}
catch (Exception)
{
continue;
}
// Add this assembly to the list of possible target assemblies
assemblyVersionInfo.Add(assemblyPath, matchAssemblyName);
}
// Look for an exact match of the target version
string matchAssemblyPath = assemblyVersionInfo.Where((x) => x.Value == targetAssemblyName).Select((x) => x.Key).FirstOrDefault();
if (matchAssemblyPath == null)
{
// If no exact target version match exists, look for the highest available version.
Dictionary<string, AssemblyName> assemblyVersionInfoOrdered = assemblyVersionInfo.OrderByDescending((x) => x.Value.Version).ToDictionary((x) => x.Key, (x) => x.Value);
matchAssemblyPath = assemblyVersionInfoOrdered.Select((x) => x.Key).FirstOrDefault();
}
// If no matching assembly was found, log an error, and abort any further processing.
if (matchAssemblyPath == null)
{
return null;
}
// If the target assembly is already loaded, return the existing assembly instance.
Assembly loadedAssembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((x) => String.Equals(x.Location, matchAssemblyPath, StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}
// Attempt to load the target assembly
try
{
return LoadAssembly(matchAssemblyPath);
}
catch (Exception ex)
{
// Log an error
}
return null;
}
The first part of that event handler deals with satellite resource assemblies, then the search behaviour I use for regular assemblies follows that. This should be enough to help anyone get a system like this working from scratch.
Ok If you want "detach" yoursefl from standart Localization resource binding, and want to have freedom to load an assembly from any location, one of the options is to
a) implement an interface to interact with translations within that assembly
b) use Assembly.Load function to load .NET assembly you want from location you want
I am building a program that uses a very simple plugin system. This is the code I'm using to load the possible plugins:
public interface IPlugin
{
string Name { get; }
string Description { get; }
bool Execute(System.Windows.Forms.IWin32Window parent);
}
private void loadPlugins()
{
int idx = 0;
string[] pluginFolders = getPluginFolders();
Array.ForEach(pluginFolders, folder =>
{
string[] pluginFiles = getPluginFiles(folder);
Array.ForEach(pluginFiles, file =>
{
try
{
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(file);
Array.ForEach(assembly.GetTypes(), type =>
{
if(type.GetInterface("PluginExecutor.IPlugin") != null)
{
IPlugin plugin = assembly.CreateInstance(type.ToString()) as IPlugin;
if(plugin != null)
lista.Add(new PluginItem(plugin.Name, plugin.Description, file, plugin));
}
});
}
catch(Exception) { }
});
});
}
When the user selects a particular plugin from the list, I launch the plugin's Execute method. So far, so good! As you can see the plugins are loaded from a folder, and within the folder are several dll's that are needed but the plugin. My problem is that I can't get the plugin to 'see' the dlls, it just searches the launching applications startup folder, but not the folder where the plugin was loaded from.
I have tried several methods:
1. Changing the Current Directory to the plugins folder.
2. Using an inter-op call to SetDllDirectory
3. Adding an entry in the registry to point to a folder where I want it to look (see code below)
None of these methods work. What am I missing? As I load the dll plugin dynamically, it does not seem to obey any of the above mentioned methods. What else can I try?
Regards,
MartinH.
//HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths
Microsoft.Win32.RegistryKey appPaths = Microsoft.Win32.Registry.LocalMachine.CreateSubKey(
string.Format(
#"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\{0}",
System.IO.Path.GetFileName(Application.ExecutablePath)),
Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree);
appPaths.SetValue(string.Empty, Application.ExecutablePath);
object path = appPaths.GetValue("Path");
if(path == null)
appPaths.SetValue("Path", System.IO.Path.GetDirectoryName(pluginItem.FileName));
else
{
string strPath = string.Format("{0};{1}", path, System.IO.Path.GetDirectoryName(pluginItem.FileName));
appPaths.SetValue("Path", strPath);
}
appPaths.Flush();
Use Assembly.LoadFrom not Assembly.LoadFile
Whenever I dynamically load plugins like this, I usually create an app domain and load the assembly in the new app domain. When creating an app domain, you can specify the base directory. Dependent assemblies will be loaded from this base directory.