Ja mam taki sposób, który na pewno działa i dla apki desktopowej i dla WWW.
Po pierwsze, tworzysz interfejs, który nazywa się na przykład "IPlugin" zawierający metody wywoływane z aplikacji macierzystej, które pozwolą porozumieć się z pluginem, powinien to być wyczerpujący zestaw metod, dostosowany do potrzeb kazdego przewidywanego pluginu.
Następnie, jak plugin jest wczytywany z pliku, to w miejscu wczytania pluginu z pliku jest taki kod:
Assembly As = Assembly.LoadFrom(@"D:\Progs\JakisPlik.dll"); // Nazwa pliku zawierający żądany plugin
IPlugin = (IPlugin)As.CreateInstance("KlasaPluginu"); // Klasa pluginu dziedzicząca po interfejsie IPlugin
Natomiast sam plugin tworzysz poprzez utworzenie zwykłego Class Library, do referencji dodajesz aplikację macierzystą, jest to plik EXE w przypadku desktopowej lub plik DLL w przypadku webowej.
Wtedy w ramach projektu wtyczki powinien być widoczny wspomniany interfejs IPlugin, natomiast klasa, która będzie klasą główną pluginu musi dziedziczyć po interfejscie IPlugin i zawierać metody zdefiniowane w interfejsie IPlugin. Jeżeli każdy plugin będzie w osobnym pliku, to można przyjąć jedną nazwę dla klasy głównej każdego pluginu i tą nazwę na sztywno wpisać w kodzie aplikacji macierzystej.
Przy tak wczytanym pluginie można wywoływać metody tak, jakby to była część aplikacji macierzystej. Oczywiście można w ten sposób wywoływać tylko metody zdefiniowane w interfejsie.
IPlugin.MetodaPluginu();
W zależności od potrzeb, zamiast interfejsu możesz utworzyć dodatkową klasę wspólną dla pluginów i każdy plugin będzie mógł dziedziczyć po tej klasie.