Click here to Skip to main content
15,880,967 members
Please Sign up or sign in to vote.
5.00/5 (2 votes)
Hello Everybody,

I have an interesting problem at hand.

I have a need to create a WPF Application which will host several UserControls. (The application does just that - serves as a container with docking abilities for UserControls)

The application will also host the data model for the UserControls as another hot Swappable component. (I plan to use internal queues for each Component so that Components can be swapped.)

These user controls will have to be pluggable (This is Easy), and reloadable (This seems to be difficult/ impossible) at run time.(AppDomains seem to be the answer here, But I don't know the limitations other than AppDomain limitations on MarshalByValue and marshalByRefObject Are there any GUI limitations? ) "Reloadable" at runtime as in Hot Swap the UI component while the app is UP.

I also want the Context menu on a UserControl to have some items from Host and some Items from the UserControl itself.

I also have logic to control Focus (Change Border Color etc. on Focus Events) between the host and UserControls seamlessly (Tabbing across Host and UserControls).

As an aside, I would also want the UserControl to be ActiveX enabled for integration with ActiveX containers like Excel.

This will give me flexibility in terms of running the UserControl by itself (Unit test), In a container (seamless Navigation and integration with container and other relevant UserControls), and as ActiveX control to integrate with ActiveX containers.

I would like to know if it is possible, and if there are frameworks(MAF?) that supports this. Any pointers to examples or documentation will help.
Thank you in advance,

Regards, Ven.
Posted
Comments
Sergey Alexandrovich Kryukov 13-Mar-11 14:50pm    
Very good, reasonable and interesting Question, my 5+++. Such questions are rare; one per hundreds with 99.9% of the rest being completely idiotic.
Respect,
--SA

This is not a problem. You can even host and plug-in whole WPF applications. I think you don't need this, so I'll explain a skeleton of plug-in design.

Let's assume you have host application which never changes (more exactly, its plug-in support sub-system is not changed or is changed incrementally, see below) and several plug-ins with possibility of changing plug-ins and developing new plug-ins. Pluggable at the very beginning of run-time is easy, reloadable is possible but way more difficult; the problem here is not loading, but unloading, because unloading of assembly is impossible; you can only unload Application Domain.

1) First, you should avoid matching of any type of member by name. This is quite possible. You need to develop a plug-in interface. Define interface type and keep in mind you should never change is from the moment you start developing plug-in implementations. More exactly, you can make interfaces upgradeable. For this purpose, you can add additional functionality in the derived interfaces. In other words, you need to keep interface changed incremental (never remove methods). The plug-in interface(s) will be your contract with plug-ins.

2) Make the definition(s) of your plug-in interfaces(s) accessible to both he host application and plug-in. It looks trivial, because you do it using a separate assembly referenced by both host and all plug-ins. You can do better! You can keep the interfaces and all helper types to be used by plug-ins in your application. It will help you to keep consistent versioning (no different assemblies — no different versions). Not everyone knows that you can reference your whole application (*.exe) in another assembly as any DLL. Essentially, .NET does not know difference between EXE and DLL (in contrast to native Windows).

3) Now, you need to recognize a plug-in in the plug-in assembly loaded from file. You can do it traversing all top-level types in assembly and checking is any of them support one of your plug-in interfaces. Trivial? And what if more than one class supports the plug-in interface? What if there are too many classes? You can do better! Create a custom attribute of assembly which helps to claim some type supporting a plug-in interface. Every plug-in should have something like this:
[assembly: ExposePlugin(
    typeof(TextProcessor),
    typeof(MyDataImporter),
    typeof(MyDataExporter))]

(Just in case this sample is not clear: the type ExposePluginInterface does not exist, I suggest you develop it.)
In this way, your host plug-in support does not traverse all assembly types but only those listed in the isntance of ExposePluginInterface.

4) A plug-in concept in interesting: each plug-in implements plug-in interface and passes implementation to the host; and host passes required host resources to plug-in. You implement the plug-in using those resources. (This is a note to item (1).) Now you can implement plug-ins and load them in the host. It leaves for the problem: how can you unload it?

5) Unloading of plug-ins is much more difficult. You should understand that Application Domains have totally isolated data. You cannot simply add control from one AppDomain to another. Application Domains can only communication through IPC. However, you can use highly simplified form of IPC, see System.AppDomain.SetData, System.AppDomain.GetData. There is a problem passing any data containing reference data, as the reference is only valid in the source AppDomain, so you can only assembly a deep clone on the other end. So, you hardly can pass UI controls between this boundary.
Here is one idea: you should not try to share control data between AppDomains. You can develop meta-data which only contains the instructions on how to assembly the controls on the host site. Make you plug-in interfaces more semantic. Of course, you can always share code between two ApplicationDomains and pass data (but only add data) through the method parameters.

[EDIT]
6) See this on how to run the code in the context of the newly created AppDomain:
AppDomain refuses to load an assembly[^]
The idea is using System.AppDomain.DoCallBack or (less likely as it required the plugin-in with the entry point (EXE file) System.AppDomain.ExecuteAssembly.
[END EDIT]

You don't have to develop whole plug-in architecture is this way. Alternatively, you can take the other way around. In place of plug-in you can develop separate applications, each representing semantics of what you now consider a plug-in. The plug-ins will be your UI presentation libraries. Imagine you create several Presentation Foundations on your own, but simple and specialized. In particular, you should know how to run a WPF application in a library. If you're interested, ask another Question, but the answer might be a whole separate article. I actually successfully used both ways; which one to take depends on your ultimate goals.

Good luck,
—SA
P.S.: Please see another Answer for follow-up discussion.
 
Share this answer
 
v5
Comments
Espen Harlinn 13-Mar-11 15:42pm    
I like your answer, my 5!
Sergey Alexandrovich Kryukov 13-Mar-11 15:57pm    
Thank you very much; my purpose was to bring your attention that good Questions still appear...
By the way, did you see my (the only one) Question on [Completely Useless Challenge]? (I put forward the idea in discussion with John.)
--SA
Sergey Alexandrovich Kryukov 13-Mar-11 21:28pm    
Thank you, where?
--SA
Espen Harlinn 14-Mar-11 3:52am    
Glitch, lost in space, or maybe > dev/null, anyway - I've fixed it :)
Sergey Alexandrovich Kryukov 14-Mar-11 3:53am    
Thank you. It's very nice of you to come back.
Answering follow-up Questions:

Question 1


Consider the following sample:
C#
//plugin-code of the implemented plug-in interface:

Control[] IControlPlugin.AddControls(DockPanel parent) {
    Button leftRect = new Button();
    leftRect.Content = "_Left";
    DockPanel.SetDock(leftRect, Dock.Left);
    parent.Children.Add(leftRect);
    Button rightRect = new Button();
    rightRect.Content = "_Right";
    DockPanel.SetDock(rightRect, Dock.Right);
    parent.Children.Add(rightRect);
    return new Control[] { leftRect, rightRect, };
} //AddControls

//host code:
Controls[] pluginControls = MyPlugin.AddControls(MyHostDockPanel);

Plug-in implements operations on some UIElement set and adds some new controls. What happens if this method is called from a different AppDomain? It will work, because no data in the plug-in AppDomain are involved. All data are taken from the host AppDomain and all returned data is used only in the host AppDomain. The fact that the code is from the different (plug-in) domain does not matter, the data is passes only on stack, which belongs to the calling thread, and all heap involved belongs to the host AppDomain. This usage does not try to violate domain boundary. Note that "this" is never used, it would be a problem. By the way, instantiation of the classes by plug-in interface also can be done in the host AppDomain. In this way, you can even use "this" and instance method. The idea is to use the plug-in only as a source of classes and methods. Instances of the classes (and all other data) should always be in the host AppDomain side.

This example also illustrates that threads do not feel domain boundaries. When you try to do Dispatcher.Invoke or Dispatcher.BeginInvoke, this is different story. This will not work across Application Domain. Why? Because Invoke is not a call, not in any sense of this word. This is pure data exchange. A delegate instance and all parameters used for call (including instance of the calling class) are put into the queue to be used in the UI thread. But this is all data, only sensible in one AppDomain, not in any other. Anyway, you can only use IPC if you want run thread with data in one AppDomain and want to handle UI running in other AppDomain, but this is not a boundary between threads in different Application Domain (such boundary does not exist, and even the notion of such boundary makes no sense). This is still the save very boundary between data.

Question 2


The TAB navigation and all other aspects will work seamlessly by definition by the reasons explained in the answer to Q1. Once controls are created by plug-in but belong to data in the host AppDomain, they will work exactly in the same way as they were created in the same assembly and the same AppDomain.

—SA
 
Share this answer
 
Comments
Sandeep Mewara 14-Mar-11 2:51am    
5ed... and bookmarked!

:)
Sergey Alexandrovich Kryukov 14-Mar-11 2:55am    
Thank you. Want to use some of the ideas? :-)
--SA
Sandeep Mewara 14-Mar-11 3:14am    
Sure yes. In a month, would be learning a lot on WPF... so...yeah... looking forward to learn from you too. :)
Sergey Alexandrovich Kryukov 14-Mar-11 3:31am    
From me? Well, give it a try :-)
--SA
Sandeep Mewara 14-Mar-11 3:32am    
Yeah. You too. :)

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900