简体   繁体   中英

Dependency injection for a visual studio add-in

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:

  1. 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.
  2. 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).
  3. 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.
  4. 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:

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM