Dependency injection for a visual studio add-in - c#

I'm working on a visual studio add-in that takes SQL queries in your project, plays the request and generates a C# wrapper class for the results. I want to do a simplest possible dependency injection, where projects using my add-in supply a class that can provide the project's db connection string, among other things.
This interface is defined in my add-in...
[Serializable]
public interface IDesignTimeQueryProcessing
{
public string ConnectionString { get; }
...
}
And the question : How do I define and instantiate the concrete implementation, then use it from the add-in?
Progress?
The interface above is defined in the add-in. I've created a reference in the target project to the add-in, written the concrete implementation, and put the name of this class in the target project web.config. Now I need to load the target project from the add-in to use my concrete class.
If I use Assembly.Load()...
var userAssembly = Assembly.LoadFrom(GetAssemblyPath(userProject));
IQueryFirst_TargetProject iqftp = (IQueryFirst_TargetProject)Activator.CreateInstance(userAssembly.GetType(typeName.Value));
I can successfully load my class, but I lock the target assembly and can no longer compile the target project.
If I create a temporary app domain...
AppDomain ad = AppDomain.CreateDomain("tmpDomain", null, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(targetAssembly) });
byte[] assemblyBytes = File.ReadAllBytes(targetAssembly);
var userAssembly = ad.Load(assemblyBytes);
I get a file not found exception on the call ad.Load(), even though the bytes of my dll are in memory.
If I use CreateInstanceFromAndUnwrap()...
AppDomain ad = AppDomain.CreateDomain("tmpDomain", null, new AppDomainSetup { ApplicationBase = Path.GetDirectoryName(targetAssembly) });
IQueryFirst_TargetProject iqftp = (IQueryFirst_TargetProject)ad.CreateInstanceFromAndUnwrap(targetAssembly, typeName.Value);
I get an
InvalidCastException. "Unable to cast transparent proxy to type QueryFirst.IQueryFirst_TargetProject"
This makes me think I'm very close? Why would an explicit cast work fine with Assembly.Load(), but fail when the same assembly is loaded in a newly created AppDomain?

I'm assuming that your add-in is going to be triggered in someway in order to start working with SQL Queries.
I'd recommend that you bundle a separate .exe file with your add-in and do the processing in there.
Here's why:
Personally, I've had a lot of issues with AppDomains similar to what you're running into with file locking and the head ache of Temp Domains. The other issue you'd likely run into is once you load an Assembly into an AppDomain, you can't unload. By using a separate process (that dies when it's finished) you don't have to worry about the problem.
Depending on the type of Projects you want to support, those Projects will have dependencies. Managing references to dependent dlls will be a lot easier if you can just point your standalone .exe at a directory (ie the bin directory).
If you hook into Visual Studio's Build Events (DTE.Events.BuildEvents.OnBuildBegin) you can kill your process and release the locks on the dll files. Or you could have your process first make copies.
Testing/debugging is a lot easier with a stand alone file. You don't need to worry about trying to debug by attaching to Visual Studio (How to debug a Vsix project).
You can use the following methods to start/kill processes:
Process.Start
Process.Kill
I think you can reference the output of a Console Project directly from your VSIX Add In Project via the References Anatomy of a VSIX Package. Otherewise, you might need to do some custom MSBuild to get the .exe included within the VSIX file.
Once it's included, you can find the .exe because it should be in the same path as your executing VSIX (Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) and I would pass it the Path to the loaded Project's bin directory.
As an aside, this isn't Dependency Injection. If you want to use DI inside a VS Extension, you can use whatever framework you'd like, but I think MEF is natively supported. Personally, I prefer Ninject. Define your Kernel inside the Package class and the use it to load your top level class.

Related

How to call tool DLLs in C# when the DLL-path is different on the target PC?

I might be a bit stupid, but I want to create a tool in Visual Studio in C# and want to call third party tools via their API-DLLs. The only topics I found here are dealing with one of the two methods that I already know:
Compilation time: add a reference to "C:\FooTool\foo.dll" in my project + "using fooToolNamespace.fooToolClass" in my code (compilation time) --> I can "naturally" use the classes of the DLL and will even get full IntelliSense support if a suiting XML-file is available with the DLL. Also compilation time checks will be done for my usage of the dll.
Dynamic (run time): calling e.g. Assembly.LoadFile(#"C:\FooTool\foo.dll") and then using reflection on it to find functions, fields and so on --> no IntelliSense, no compilation time checks
So I actually have the DLL at hand and thus option 1) would be nice during development. But if my tool is used on a different PC, the third-party DLL might be in a different path there, e.g. "C:\foo\foo.dll" and "C:\bar\foo.dll".
In my understanding using a copy of "foo.dll" will not work, because "foo.dll" might have dependencies, e.g. requiring other files of the FooTool-directory. Thus in my understanding I have to call the DLL which is "installed" to the target PC and not a local copy of it.
So can I somehow change the path where my tool accesses the "foo.dll" at runtime and still use method 1) during development?
Or is there another way of doing things?
Or am I just dumb and there is a simple solution for all this?
Thanks a lot for the help and have a great day
Janis
To be able to use option 1 (a referenced DLL), you need to put the DLL somewhere "where your EXE (or, more precisely, the Assembly Resolver) can find it" on the customer's PC.
So where does the assembly resolver look for your DLL?
In the directory where the EXE resides (for desktop/console applications) or the bin subdirectory (for web applications). Since you mention that your DLL requires other dependencies as well, you'd need to copy them to that location as well.
The Global Assembly Cache (GAC). If your dependency supports this, installing it to the GAC ensures that it can be found by your application.
These two are the "supported" scenarios. There is also the possibility to tweak the assembly resolver to look into other directories as well, but that should be reserved for special cases where the other two options failed. (We had such a case and solved it with a custom AssemblyResolve handler on the application domain.)

NuGet dependencies in MEF loaded assemblies

I am trying to code an application in C#.NET Core that can be extended using MEF. Currently, I am able to do that without any issues with libraries, that have no dependencies or have the same dependencies as the host app (so dependencies are already loaded). But, if I want to use a library with a NuGet reference, that is not used by the main app, the loading of this library fails on that reference.
How can I force the main app to load the missing NuGet dependency, if it tries to load an assembly with such reference? It seems to me as a pretty common use case, but I am lost here and cannot find a way out. Thanks.
For reference, I am posting the portion of the code.
[ImportMany]
private IEnumerable<Lazy<IService, IServiceMetadata>> _asrServices;
...
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new DirectoryCatalog(Path.Combine(Directory.GetCurrentDirectory(), "Services")));
CompositionContainer _container = new CompositionContainer(catalog);
...
foreach (Lazy<IService, IServiceMetadata> _service in _asrServices)
{
var _serviceInstance = _service.Value // here the loading fails
}
Jiri
.NET currently has two build "systems". One is the original project files, that import Microsoft.Common.props and Microsoft.CSharp.targets (assuming it's a c# project) and lots of XML in between, that has been around ever since .NET was first released, apparently in 2002. Since .NET Core was made generally available in 2016 there has been a new project system generally called SDK projects because the way a *proj file references the build system is though an Sdk element or attribute in the msbuild xml. Although it's off-topic, because there's a common bad assumption, I want to point out that although SDK projects were created for .NET Core, you can target the .NET Framework from SDK projects.
With the original project files, when you build, all the projects references get copied to the output directory. However, with SDK projects, only the project's assembly is copied to output (I'm not sure, but I think even content set to copy to output doesn't actually get copied on build). In order to get everything in a single directory, you should use the dotnet cli's publish command.
So, if you have a build script that builds your project and copies all the plugins somewhere, you should add a dotnet publish step to the script for each plugin using the SDK style project file.

Visual Studio Prevent Changes to Linked File

In Visual Studio you can add a link to a source file in another project. Is there a way to enforce preventing any changes from being performed on the linked source file (ie: link them into a project as 'read only', so as to prevent accidental modifications by folks who don't realize they are linked, and not local to the project)?
I have two projects, one of which is a DLL, the other is an EXE. The DLL contains a Windows ServiceInstaller and ServiceBase classes. I link these classes into my EXE (there are multiple flavors of the EXE) from the DLL in order for the EXE to be installable as a service and for me to not have to replicate the ServiceBase and ServiceInstaller in all of the EXEs. I do not however want to inadvertently be able to make changes to the linked classes from within the context of the EXE project.
Not via some Visual Studio-supported mechanism, no.
IMHO, as a general rule you should not be using a linked file like that. Yes, the feature exists in VS, but for the very reason you mention as well as others, it's a great way to create code maintenance headaches.
Note that your own scenario could be just as easily solved by exposing shared types in an assembly and referencing that assembly from those that need them. I.e. just reference the DLL from your EXE and use the types as compiled into the DLL rather than having the EXE define new versions of the types using the same source code.
Visual Studio does not have a way to set a "read-only" attribute for a linked file, but in general, linked files cause more problems than they solve.
Generally speaking, the preferred method for code reuse is to put the classes into a DLL and then reference those classes from the EXE - but in your case, the ServiceInstaller and ServiceBase classes have to be present in the EXE in order for the Windows service mechanism to pick them up.
Instead of linking the files, you could create base classes that inherit from ServiceInstaller and ServiceBase and put those in the DLL. Then add new classes into the EXE that inherit from your custom base class (which contains most of the logic). This way, all of the shared code gets pulled in from the shared DLL, but the EXE(s) still contain the classes necessary for the Windows service to start.

Lazy load a DLL that was placed on the filesystem after the program has been started

I am building an automation harness using C# and am trying to do the following:
Bootstrap the harness
Install the executable
Use a couple of DLLs that the executable lays down to establish a connection to the infrastructure that the exe connects to (large, complex software system)
The DLLs that I need to use are a few static classes that perform their own 'bootstrap' process to get connected to the rest of the servers in the test environment. What I have tried is creating a connection class that looks like this:
using CompanyName.Framework;
namespace TestNamespace{
public class ProductConnectorClass{
// Initialize a connection to the product and do stuff
public ProductConnectorClass(){
var connection = CompanyName.Framework.Initialize(...);
// Do stuff with the connection
connection.RunStuff();
}
}
}
The DLL that contains the CompanyName.Framework namespace is not accessible when the test framework is first started. It is copied via code from another class that looks lomething like this:
namespace TestNamespace{
public class TestRunnerClass{
public TestRunnerClass(){
// pseudo code here, so you get the idea:
CopyMsiToHost(msiRemotePath, msiLocalPath);
InstallMsi(msiLocalPath);
CopyDllsToTestHarnessDir();
ProductConnectorClass pcc = new ProductConnectorClass();
}
}
}
It is when the code hits the ProductConnectorClass pcc = new ProductConnectorClass(); line that I get an exception:
FileNotFoundException was unhandled
Could not load file or assembly 'CompanyName.Framework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=XXXXXXXXXXXXXXXX' or one of its dependencies. The system cannot find the file specified.
This puzzles me since I have set Specific Version: False on the DLL in my test project and I am operating under the assumption that .NET Lazy-loads DLLs (in my mind that means search for and load them at the time they are needed). At this point I am looking for a simple way to get a running .NET program to find a needed DLL that has been placed after the process started running.
Note 1: If I restart the process after the DLL is already in place, it loads fine. It looks like it is only scanning for DLLs on startup, which still puzzles me.
Note 2: This SO answer gives me some information about how DLLs are loaded, but doesn't fully answer my question. The synopsis is that if .NET has tried to load the DLL and it fails that it won't try to load it again. In my case I don't expect that a load attempt has been made on the DLL since the class where the DLL is referenced has not been instantiated yet.
From experiments in test projects that I have performed, it appears that I need to perform modularization of my code in order to get this to work. In no case am I able to reference a dll that does not currently exist, start my program then drop down the DLL.
Here are my steps to a solution:
Refactor my common classes and interfaces to their own DLL project in my Automation solution
Create another DLL project ('TestLogic') in my solution that uses the DLL I lay down after the program has started
When I need to use the DLL that is laid down after the program starts, I use reflection to load the TestLogic dll and perform the tests
For reference I did a google search for 'C# late load dll' and found this msdn social post with some helpful advice. Here is a copy/paste/modify of the most helpful reply from that thread:
Move all the code that references the DLLs that are installed after the test framework starts to a completely separate assembly. We'll call this "TestLogicAssembly".
Create a new assembly and define an interface. We'll call this "TestInterfaceAssembly".
Reference TestInterfaceAssembly in both your main DLL and your TestLogicAssembly.
Create a class in TestLogicAssembly that implements the interface created in TestInterfaceAssembly.
Don't reference TestLogicAssembly from your main application.
At runtime, check to see if the DLLs that are laid down as part of the install step of the test framework are actually installed. If they are, use Assembly.Load to load the TestLogicAssembly, then find the type that implements the interface defined in number 2. Use Activator.CreateInstance to create an instance of this type, and cast it to the interface you created.

Launching C# WPF from Java causes FileNotFoundExceptions

I have a existing Java Project which needs functionality from a SDK written in C#. It should open a WPF Window and send the information back to Java on close.
For a basic connection of those two worlds i created a Java Project ("DotNetCaller") calling native functions. These are implemented in a C++/CLI Project ("DotNetBridge") which calls the C# Project ("DotNetApplication").
I already can set Strings from Java in C# and callback from C# to Java.
But as soon as i add a WPF Window and try to launch it with:
Application app = new Application();
app.Run(new DotNetWindow());
in a STA Thread it crashes.
The DotNetApplication doesnt find mscorlib.resources, after i provide the DLL, PresentationFramework.resources is missing and if i provide that, the DotNetApplication.resource is missing (which i cant provide).
If i call the DotNetApplication alone or from the DotNetBridge the Window displays as expected.
Can anyone tell ma what i'm really missing here?
Thanks
Edit:
I looked at this example once more and tried to adapt it to my needs.
I have set the dll directory of the ResolveEventHandler to the .NET dir in "Referenced Assemblies"
C:\Program Files (x86)\Reference
Assemblies\Microsoft\Framework.NETFramework\v4.0
and added a Window in C#.
It failed aswell but with a new exception in the C++ part rather than C#.
The ResolveHandler gets called with an empty argument causing an uncatchable exception in mscorelib.
I added a check if the String is empty and this basic approach works fine now.
I'm still unsure if i have the correct approach for this, so feel free to contribute.
Your AppDomain::AssemblyResolve handler probably needs to be overhauled and based on your own understanding of what you want to do. There is some guidance here. The basic rule is that you return nullptr for requests that you can't handle.
But first you have to plan the locations in which you want to deploy (and/or debug) your assemblies. A simple layout would be to put all of the assemblies that your JNI DLL depends on in the same folder as the JNI DLL (with the exception of any that will be installed in the GAC). You can then use its location to satisfy resolution requests. But remember to return nullptr if no file containing a manifest for an assembly with the requested name is present there. (This is likely the case with your ".resources" requests. If there isn't one it's okay unless you know otherwise.)
I'd be a little surprised if an assembly in a Reference Assemblies folder wasn't also in the GAC—but it'd be up to the assembly provider. Reference Assemblies is for design and build tools (e.g. Visual Studio). (The old way was for each folder that had assemblies in it to be registered for each version of Visual Studio so the assemblies could be used for design and build.) If a dependency is not in the GAC, you can use the "Copy Local" property on the reference to make it available for debugging.
You might find the Assembly Binding Log Viewer useful while designing and troubleshooting. With it you can see all the folders and extensions that are tried before giving over to calling the AppDomain::AssemblyResolve handler chain. (Disable logging when you are done.)

Categories

Resources