I've got some Workflow Foundation 4 ActivityDesigners that I'd like to have interact with the Visual Studio IDE (the fact that they are WF4 doesn't relate to the question).
These designers are defined in an assembly, lets call it herpaderp.dll. Eventually, this dll will be delivered to servers where its Activities and other code will live happily forever after.
But, before this time, I would like for my designers to be able to examine the current solution in order to provide a better design time experience for people using the Activities defined in herpaderp.dll. Something along the lines of "let's test this Activity configuration using sample data that is stored in the solution; here's the sample data I've found in a convenient combo box--please select one".
Now, this is pretty simple to do. Here's the naiive implementation:
var dteo = Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(DTE));
var dte = dteo as DTE;
if(dte == null) return; // not in Visual Studio or other wierdness, bail
var samples = dte.GetSampleDatum(); // super awesome extension method
Hey, that worked great! But there's a slight problem... herpaderp.dll no开发者_高级运维w must reference the following assemblies:
- Microsoft.VisualStudio.Shell.10.0.dll
- envdte.dll
These assemblies are part of the SDK. Having to package them up and deliver them to the server makes absolutely no sense to me. Its like adding another appendix to my large intestine.
How can I break these dependencies while retaining the ability to interact with Visual Studio from within my designers?
As I see it, I have three possible answers, none of which I'm particularly happy about.
- Use IoC to bind to an assembly at runtime which will perform the interaction for me. The assembly can reference the SDK assemblies while hiding behind a simple interface defined in herpaderp. Unfortunately, that adds a different dependency which only matters at design time and will be useless on the server.
- Load the dependencies at runtime using their assembly qualified names and hide behind
dynamic
. This offends my type-safe breeding. Plus, I'm not sure if I can even get away with it 100%. - Interract with a Visual Studio Package at runtime via some kind of service location. My solution delivers a Visual Studio extension, so I don't have to worry about writing it or forcing people to use it. But in order to interract with it, I'd have to use some kind of lame Service Locator BS pattern crap which I despise with a passion. Service locator. Feh. In addition, it will also require some kind of cross-process (or, at a minimum, cross-AppDomain in the same process) communication.
Option 3 is my best bet, I believe. Is there another solution I'm missing? Am I just wrong in hating one of my three answers?
The choice I've gone with is to use WCF to create a process-scoped (via a naming convention) named pipe to communicate back and forth.
This works pretty well. I can define the service and the client side within my internals in my Activity assembly and implement the service within the Package side. The only caveat is that, by default, the service implementation will be processing calls on the UI thread, so I cannot block the UI thread when making calls from the design surface/client side. With a little judicious extension method wizardry, I can offload the client calls onto the ThreadPool with little effort, marshalling results and exceptions back across via the current SynchronizationContext.
I was able to implement this without too much effort. Unfortunately, I have recently realized that the true issue is that I should have done was deliver my designers in a different assembly, which is delivered only via the designer installer. The Activities would then be delivered to the production server, thus breaking any dependency worries. This requires some inside knowledge of conventions and process which I had no clue about. This MSDN blog post details what I wish I had known before I asked this question.
精彩评论