Sie sind auf Seite 1von 91

Flexibel erweiterbare

Anwendungen mit MEF & MAF


Jörg Neumann
Joerg.Neumann@thinktecture.com

Sponsoren: Veranstalter:
Jörg Neumann
• Principal Consultant bei Acando
• Associate bei Thinktecture
• MVP im Bereich „Client App Dev“
• Beratung, Schulung, Coaching
• Buchautor, Speaker
• Mail
– Joerg.Neumann@Acando.de
– Joerg.Neumann@thinktecture.com
• Blog
– www.HeadWriteLine.BlogSpot.com
Agenda
• Warum Extensibility?
• Einflussfaktoren
• Das Managed Extensibility Framework
• Das Managed Add-In Framework
Warum Extensibility?
• Kapselung
– Fehler leichter finden
– Erweiterbarkeit der App gewährleisten
– Wiederverwendung von Erweiterungen
– Getrennte Entwicklung im Team
• Erweiterungen durch 3rd Parties
– Sicherheit & Stabilität sind wichtig
– Versionierung ist ein wichtiger Punkt
Begriffsdefinition
• Dependency Injection
– Contract First-Design
– Für das dynamische Nachladen von Modulen
– Einfachheit und Produktivität
– Sicherheit spielt eine untergeordnete Rolle
• Add-In
– Für die Erweiterung der Anwendung
– 3rd Party Model
– Sicherheit, Stabilität, Versionierung
Einflussfaktoren
• Discovery & Loading
• Metadaten
• Security
• Hosting
• Versionierung
• UI-Integration
Discovery & Loading
• Ermitteln der Erweiterungen
– Name, Version, Autor, Beschreibung
– Metadaten der Erweiterung
– Reflection kostet!
•Metadaten Caching
– Automatisches Discovery
•Dynamisches Nachladen, wenn Assemblies in
das Arbeitsverzeichnis kopiert werden
Security
• Erweiterung sollte nicht…
– Auf die App per Reflection zugreifen können
– Die App dynamisch nachladen können
• Simple Sandboxing API
– Beim Laden können dedizierte Rechte
zugewiesen werden
• Zugriff auf das Arbeitsverzeichnis
– Erweiterungen liegen ggf. im Datenverzeichnis
Hosting
• In Default AppDomain
– Kein Entladen möglich
– Sicherheit & Stabilität kritisch
• In separate AppDomain
– Entladen möglich
– Dedizierte Rechtevergabe möglich
• In separaten Prozess
– Maximale Security & Stabilität
– UI-Integration nicht möglich
Laden und Entladen

Remoting

Default Add-In
AppDomain AppDomain
Application
Assembly

Add-In
Assembly
AppDomain & Prozess-Isolation
• Kommunikation über AppDomain-Grenze
– Remoting nötig
– Serializable oder MarshalByRefObject
• Stabilität
– Unhandled Exceptions in Worker-Threads
bringen die Anwendung zum Absturz
•Nur bei AppDomain-Isolation
•Kann per Config-Switch abgestellt werden
Versionierung
• Ziel
– V1 der Erweiterung läuft auch mit V2 der App
– V2 der Erweiterung läuft auch mit V1 der App
• Konsequenz
– Keine statischen Referenzen auf den
Contract!
– Adapter-Assemblies übernehmen das
Routing
Kommunikation
• Erweiterung soll mit App kommunizieren
– Zugriff auf Services der App
• Erweiterung soll mit anderen
Erweiterungen kommunizieren
– Gemeinsame Schnittstellen
UI-Integration
• Erweiterung öffnet dedizierte Dialoge
• UI der Erweiterung wird eingebettet
• Herausforderungen
– Transport von UI-Elementen über die
AppDomain-Grenze
– Synchronisation zwischen App-UI und
Erweiterungs-UI
•Fokusbehandlung
•Tastatur, Accelerator Keys, …
MEF
• Managed Extensibility Framework
– Teil von .NET 4.0
•Namespace:
System.ComponentModel.Composition
– Beta-Version für .NET 3.5 auf CodePlex
– Silverlight-Version in Arbeit
• Generisches Dependency Injection
Framework
– Basiert auf Attributen
{ In-depth support and consulting for
software architects and developers }

Demo

Managed Extensibility Framework


MEF-Features
• Discovery
– Auto Discovery für Assembly oder Verzeichnis
• Metadaten
– Benutzerdefinierte Metadaten
• Produktivität
– Sehr leicht durch Attribute steuerbar
• UI-Integration
– Sehr einfache UI-Integration möglich
• Silverlight-Support
Vor- und Nachteile von MEF
• Vorteile
– Sehr leicht und produktiv
– Interaktion zwischen Erweiterungen möglich
– UI-Integration sehr einfach
• Nachteile
– Keine Isolation in AppDomains/Prozesse
– Keine dedizierte Zuweisung von
Berechtigungen möglich
– Kein Unterstützung für Versionierung
MAF
• Managed Add-In-Framework
– Teil von .NET 3.5
•Namespace: System.AddIn
{ In-depth support and consulting for
software architects and developers }

Demo

Managed Add-In Framework


MAF-Features
• Discovery
– Metadaten-Cache
• Isolation
– AppDomain, Prozess
• Security
– Kann dediziert eingestellt werden (Zonen)
• Versionierung
– Add-In-Pipeline mit Adaptermodell
• UI-Integration
– Support für WPF enthalten
Die Add-In Pipeline

Add-In Add-In Host Host


Add-In Contract Application
Views Adapters Adapters Views
Contract.dll
[AddInContract]
interface IAddInContract : IContract
{
string SayHello();
}

AddInSideAdapters.dll HostSideAdapters.dll
[AddInAdapterAttribute()] [HostAdapterAttribute()]
class ViewToContractAddInAdapter public class ContractToViewHostAdapter
: ContractBase, IAddInContract : HostViews.IAddIn
{ {
public ViewToContractAddInAdapter( public ContractToViewHostAdapter(
AddInViews.IAddIn view) Contracts.IAddInContract contract)
{ {
_view = view; _contract = contract;
} }
public virtual string SayHello() public string SayHello()
{ {
return _view.SayHello(); return _contract.SayHello();
} }
} }

AddInView.dll HostView.dll
[AddInBase]
interface IAddIn interface IAddIn
{ {
string SayHello(); string SayHello();
} }
Discovery

AddIns MyApplication.exe
AddInSideAdapters Host.Views.dll
AddInViews
Contracts
HostSideAdapters
AddIns MyApplication.exe

AddIn1 HostViews.dll
AddIn1.dll
PipelineSegments.store
AddIns.store

AddInSideAdapters

AddInSideAdapters.dll

AddInViews

AddInViews.dll

Contracts

Contracts.dll

HostSideAdapters
HostSideAdapters.dll
Vor- und Nachteile von MAF
• Vorteile
– Hosting
– Security
– Versionierung
• Nachteile
– Komplexität / Produktivität
– Interaktion von Add-Ins nicht möglich
– UI-Integration
– Keine benutzerdefinierten Metadaten
MEF

MAF
Discovery

Metadata
Cache

UI-Integration

Versionierung

Isolation
Entscheidungsmatrix

Security

Kommunikation
zwischen Parts
Silverlight
Support

Produktivität
Zusammenfassung
• Extensibility ist ein keine leichte Aufgabe
– Es gibt viel Faktoren zu bedenken
• Extensibility hat seinen Preis
– Performance
– Aufwand
• Frameworks
– Mit MEF & MAF existieren flexible
Frameworks die vieles erleichtern
MEF Ressourcen
• MEF auf Codeplex
http://tinyurl.com/MEFHome
• Programming Guide
http://tinyurl.com/MEFGuide
• Team Blogs
http://tinyurl.com/MEFBlogs
MAF Ressourcen
• System.AddIn Tools and Samples
http://www.codeplex.com/clraddins
• CLR Add-In Team Blog
http://blogs.msdn.com/clraddins
• Pipeline Builder
http://www.codeplex.com/clraddins
• Windows Forms Support für MAF
http://www.HeadWriteLine.BlogSpot.com
Sonstige Ressourcen
• Castle Windsor
http://www.castleproject.org/container/index.html
• Unity
http://www.codeplex.com/unity
• CLR Security (CAS Helper)
http://www.codeplex.com/clrsecurity
FRAGEN ?
{ In-depth support and consulting for
software architects and developers }
http://www.thinktecture.com/

joerg.neumann@thinktecture.com
www.HeadWriteLine.BlogSpot.com

33
Hat Ihnen mein Vortrag gefallen?

Ich freue mich auf Ihr Feedback!


Wir sehen uns wieder:

 In 5 Tagen zum SharePoint Profi!


02.-06. November 2009 in Frankfurt
www.SharePointCamp.de

 Schneller zum .NET 3.5 Developer


09.-13. November 2009 in Wien
www.DevTrain.de/Camp

 24.-25. Februar 2010 in München


www.VSone.de
VIELEN DANK
Dein Name
!
{ In-depth support and consulting for
software architects and developers }

MEF
im Detail

Jörg Neumann
Begriffsdefinition
• Export
– Exportierte Typen (Plug-Ins)
• Imports
– Verweis auf exportierte Typen
• Parts
– Imports und Exports
Imports und Exports
Export definieren:
[Export]
public class MyExtension
{
public string SayHello()
{
return "Hello!";
}
}

Import definieren:
public class Program
{
[Import]
public MyExtension MyExtension { get; set; }
}
Exports
• Typen
– Klassen
– Properties
– Methoden
• Können public oder non-public sein
– Non-public nicht unter Partial Trust
Imports
• Typen
– Properties
– Construktor-Parameter
– Fields
– Collections
• Benachrichtigung beim Importieren
– IPartImportsSatisfiedNotification
Interface-basierte Exports
Interface definieren:
public interface IExtension
{
string SayHello();
}

Export definieren:
[Export(typeof(IExtension))]
public class MyExtension : IExtension
{
public string SayHello() { return "Hello!"; }
}

Import definieren:
public class Program
{
[Import]
public IExtension MyExtension { get; set; }
}
Der Composition Container
• Fügt Imports und Exports zusammen
• Erstellt Instanzen der Exports
• Welche Parts hierbei eine Rolle spielen
wird über Kataloge definiert
Alle Parts der ausführenden Assembly verbinden:
AssemblyCatalog catalog =
new AssemblyCatalog(Assembly.GetExecutingAssembly());
var container = new CompositionContainer(catalog);
container.ComposeParts(this);
Kataloge
• Definiert, wie Parts geladen werden
• Kataloge für verschiedene Quellen
– Assembly, Dateisystem, …
• Namespace
– System.ComponentModel.Composition.Hosting
Katalogtypen
• AssemblyCatalog
– Lädt Parts aus einer Assembly
• DirectoryCatalog
– Lädt Parts aus den Assemblies eines
Verzeichnisses
• TypeCatalog
– Explizit Angabe von Typen
• AggregatingCatalog
– Kombiniert unterschiedliche Kataloge.
AssemblyCatalog
• Ermittelt alle Parts einer Assembly
Parts in der ausführenden Assembly ermitteln:
var catalog = new AssemblyCatalog(
System.Reflection.Assembly.GetExecutingAssembly());
DirectoryCatalog
• Führt den Scan einmalig durch
• Veränderungen müssen selbst überwacht
werden (z.B. mit FileSystemWatcher)
• Refresh() aktualisiert den Katalog
Parts aller Assemblies eines Verzeichnis ermitteln:
var catalog = new DirectoryCatalog("Extensions");
TypeCatalog
• Zur expliziten Aufnahme von Parts in
einen Katalog
Type1 und Type2 in Katalog aufnehmen:
var catalog =
new TypeCatalog(typeof(Type1), typeof(Type2), ...);
AggregateCatalog
• Aggregiert unterschiedliche Kataloge in
einen einzelnen Katalog
Assembly & DirectoryCatalog aggregieren:
var catalog = new AggregateCatalog(
new ComposablePartCatalog[]
{
new AssemblyCatalog(Assembly.GetExecutingAssembly()),
new DirectoryCatalog("Extensions")
});
Catalog mit Container verbinden
• Catalog wird im Construktor übergeben
Catalog mit CompositionContainer verbinden:
var container = new CompositionContainer(catalog);
Batches
• CompositionBatch-Klasse
• Ermöglicht das dynamische Laden und
Entladen von Parts zur Laufzeit
Parts hinzufügen/entfernen:
var batch = new CompositionBatch();
batch.AddPart(partInstance1);
batch.AddPart(partInstance2);
batch.RemovePart(part3);

container.Compose(batch);
Listen importieren
• ImportMany-Attribut
Deklaration:
[ImportMany(AllowRecomposition=true)]
public IEnumerable<IExtension> Extensions { get; set; }

Verwendung:
foreach (IExtension ext in this.Extensions)
{
Console.WriteLine(ext.SayHello());
}
Lazy Instantiation
• Instanz wird erst erzeugt, wenn das erste
mal auf sie zugegriffen wird
Deklaration:
[Import]
public Lazy<IExtension> Extension { get; set; }

[ImportMany]
public IEnumerable<Lazy<IExtension>> Extensions { get; set; }

Verwendung:
this.Extension.Value.SayHello();
Dynamic Instantiation
• Instanz wird immer dynamisch neu
erstellt, wenn zugegriffen wird
• Separater Instantiation Export Provider
– Microsoft.ComponentModel.Composition.DynamicInstantiation
– Muss dem Container hinzugefügt werden
• PartCreator<T>
– Gibt PartLifetimeContext<T> zurück, über
das eine Instanz erzeugt werden kann
– CreatePart() erzeugt die Instanz
Dynamic Instantiation
Dynamic Instantiation Provider hinzufügen:
var dynProvider =
new DynamicInstantiationExportProvider();
CompositionContainer container =
new CompositionContainer(catalog, dynProvider);
dynProvider.SourceProvider = container;

Deklaration:
[Import]
public PartCreator<IExtension> Extension { get; set; }

Verwendung:
this.Extension.CreatePart().ExportedValue.SayHello();
Metadaten
• Dienen zur Steuerung
– Filtern von Attributen
– Übertragen von Informationen
• Implementierung
– ExportMetadata-Attribut
•Schlüssel/Wert-Par
– Benutzerdefiniertes Attribut
•Leitet von ExportMetadata ab
Metadaten
Deklaration (Export):
[Export(typeof(IExtensions2))]
[ExportMetadata("Language", "German")]
public class MyExtension : IExtension
{ ... }

Deklaration (Import):
[ImportMany]
Lazy<IExtension, IDictionary<string, object>>[] Extensions
{ get; set; }

Verwendung:
foreach (var ext in this.Extensions)
{
if (ext.Metadata.ContainsKey("Language") &&
ext.Metadata["Language"].ToString() == "German")
{ …}
}
Benutzerdefinierte Attribute
Implementierung:
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class)]
public class LanguageAttribute : ExportAttribute
{
public Language Language { get; set; }
}
public enum Language : int { German, English }

Deklaration:
[Export(typeof(IExtensions2))]
[LanguageAttribute(Language = Language.German)]
public class MyExtension2German : IExtensions2
{ ... }
Benutzerdefinierte Attribute
Verwendung:
foreach (var ext in this.Extensions)
{
if (ext.Metadata.Language == Language.English)
{
Console.WriteLine(ext.Value.SayHello());
}
}
Typisierte Metadaten
• Typisierte Metadaten
– Implementierung eines Interface
– Default-Attribut
•Standardwert, falls Attribut nicht zugewiesen
Typisierte Metadaten
Implementierung:
public interface IMetadata
{
[DefaultValue(Language.German)]
Language Language { get; }
}

Deklaration:
[ImportMany]
public Lazy<IExtension, IMetadata>[] Extensions { get; set; }

Verwendung:
foreach (var ext in this.Extensions)
{
if (ext.Metadata.Language == Language.English)
Console.WriteLine(ext.Value.SayHello());
}
Part Lifetime
• PartCreationPolicy-Attribut
– Wird auf Part-Ebene festgelegt
– Signalisiert, ob die diese ein oder mehrfach
instanziiert werden kann
– Werte
•NonShared: Bei jeder Anfrage eine neue Instanz
•Shared: Immer nur eine Instanz pro Container
•Any: Es ist beides erlaubt (abhängig vom Import)
Part Lifetime
• Policy kann auch beim Import angegeben
werden
[ImportMany(typeof(IExtension),
RequiredCreationPolicy = CreationPolicy.Shared)]

Part.Any Part.Shared Part.NonShared

Import.Any Shared Shared Non Shared

Import.Shared Shared Shared No Match

Import.NonShared Non Shared No Match Non Shared


Part Lifetime
• Der Container bestimmt die Lebenszeit
der Parts
• Wenn der Container entladen wird, endet
auch die Lebenszeit der Parts
Part Lifetime
• Der Container hält keine Referenzen auf
Parts, mit Ausnahme von:
– Part ist als „shared“ markiert
– Part implementiert IDisposable
– Ein oder mehrere Parts sind mit der Option
„AllowRecomposition=true“ gekennzeichnet
• Ansonsten:
– Garbage Collection
{ In-depth support and consulting for
software architects and developers }
http://www.thinktecture.com/

joerg.neumann@thinktecture.com
www.HeadWriteLine.BlogSpot.com

66
{ In-depth support and consulting for
software architects and developers }

MAF
im Detail

Jörg Neumann
IST-Zustand
• Monolithische Anwendungen
• Schlechte Kapselung
• Fehleranfälligkeit durch Abhängigkeiten
• Zeitraubende Fehlersuche
• Aufwändige Erweiterung
Add-In-Modelle
• Fachliche und technische Kapselung
• Weniger Abhängigkeiten
• Mehr Transparenz
• Effizientere Problembehebung
• Dynamische Erweiterung einer
Anwendung
– Auch durch Dritthersteller
• Einfaches Deployment
Varianten
• Funktionserweiterungen
– Add-Ins erweitern die Anwendung durch neue
Funktionen
• UI-Integration
– Add-Ins stellen Dialoge und UI-Elemente zu
Verfügung
• Anwendungen auf Basis eines Kernels
– Alle Funktionen werden durch Add-Ins bereit
gestellt
Herausforderungen
• Auffinden von Add-Ins (Discovery)
• Laden und Entladen von Assemblies
• Absichern der Host-Anwendung
– Fehler in Add-Ins
– Absichern von Daten und Prozessen
• Versionierung
Laden und Entladen von Add-Ins

Remoting

Default Add-In
AppDomain AppDomain
Application
Assembly

Add-In
Assembly
Versionierung

Contract V2

Application Add-In
V2 V2

Application Add-In
V1 V1

Contract V1
System.AddIn
• Managed Add-In Framework (MAF)
• Teil von .NET 3.5
• Features
– Add-In Discovery
– Sicheres Laden/Entladen von Add-Ins
– Versionierung
– Add-In Store
Die Add-In Pipeline

Inherits
Add-In Abstract Implements Inherits Inherits Abstract
View Base Class Contract IContract Host View Base Class

Add-In Add-In Host Host


Add-In Contract Application
Views Adapters Adapters Views

Add-In Add-In V2 to V2 Contract V2 to V2 Host Application


V2 Views V2 Adapter V2 Adapter Views V2 V2
V2 to V1 V2 to V1
Adapter Adapter
Versionierung

Add-In Add-In Host Host Application


Add-In V1 Contract V1
Views V1 Adapters V1 Adapters V1 Views V1 V1

Add-In
Adapters
V2ToV1
Add-In Host Host Application
Add-In V2 V2ToV2 Contract V2
Views V2 Adapters V2 Views V2 V2
Contract.dll
[AddInContract]
interface IAddInContract : IContract
{
string SayHello();
}

AddInSideAdapters.dll HostSideAdapters.dll
[AddInAdapterAttribute()] [HostAdapterAttribute()]
class ViewToContractAddInAdapter public class ContractToViewHostAdapter
: ContractBase, IAddInContract : HostViews.IAddIn
{ {
public ViewToContractAddInAdapter( public ContractToViewHostAdapter(
AddInViews.IAddIn view) Contracts.IAddInContract contract)
{ {
_view = view; _contract = contract;
} }
public virtual string SayHello() public string SayHello()
{ {
return _view.SayHello(); return _contract.SayHello();
} }
} }

AddInView.dll HostView.dll
[AddInBase]
interface IAddIn interface IAddIn
{ {
string SayHello(); string SayHello();
} }
Discovery

AddIns MyApplication.exe
AddInSideAdapters Host.Views.dll
AddInViews
Contracts
HostSideAdapters
AddIns MyApplication.exe

AddIn1 HostViews.dll
AddIn1.dll
PipelineSegments.store
AddIns.store

AddInSideAdapters

AddInSideAdapters.dll

AddInViews

AddInViews.dll

Contracts

Contracts.dll

HostSideAdapters
HostSideAdapters.dll
Hosting
• AppDomain- oder Prozess-Isolation
• Kommunikation über .NET-Remoting
Application Process Add-In Process
Default Add-In Add-In Add-In
AppDomain AppDomain AppDomain 1 AppDomain 2

Application Add-In 1 Add-In 3 Add-In 4

Add-In 2 Add-In 5
Nutzung in Client-Anwendungen
• Visuelle Add-Ins
– Fenster
– Eingebettete Elemente
• Problemstellungen
– UI-Elemente sind nicht serialisierbar
– Übertragung per Referenz nicht möglich
Client-Support
• Support für WPF
– Basiert auf WPF-Interop
– Visual-Objekte (und Ableitungen) können
verwendet werden
– Window Handle wird übertragen
– Kein Process-Hosting möglich
WPF-Support
• Contract
– INativeHandleContract
– Namespace: System.AddIn.Contract
– Assembly: System.Addin.Contract.dll
• Adapter
– FrameworkElementAdapters
– Namespace:
System.AddIn.Pipeline.FrameworkElementAdapters
– Assembly: System.Windows.Presentation.dll
UI-Elemente übertragen

INativeHandleContract
IntPtr GetHandle();

FrameworkElementAdapters
FrameworkElement ContractToViewAdapter(INativeHandleContract hwnd);
INativeHandleContract ViewToContractAdapter(FrameworkElement root);
UI-Elemente übertragen

INativeHandleContract GetUI() FrameworkElement GetUI()


FrameworkElementAdapters. FrameworkElementAdapters.
ViewToContractAdapter() ContractToViewAdapter()

FrameworkElement INativeHandleContract FrameworkElement


GetUI() GetUI() GetUI()
Windows Forms Support
• Kein Support für Windows Forms von MS
• Eigene Implementierung
– Übertragung von WinForms-Elementen
– Add-In-Verwaltungsdialoge
– Command-Modell für Menüs und Toolbars
– Download:
http://headwriteline.blogspot.com
Windows Forms Add-In Proxy
Übertragen von Objekten
• Klassen müssen Remote-fähig sein
– Mit Serializable-Attribut dekoriert sein oder
– ISerializable implementieren oder
– Von MarshalByRefObject ableiten
– Für das Übertragen von Listen stellt MAF
IListContract und Adapter-Klassen zu
Verfügung
• Für komplexe Typen sind separate
Interfaces, Adapter und Views erforderlich
– Der PipelineBuilder ist hierbei behilflich
Fehlerbehandlung
• Add-In löst eine Exception aus
– Host kann diese mit try/catch fangen
• Add-In löst in einem Worker Thread eine
Exception aus
– Der Host wird heruntergefahren
– Der Host kann dies jedoch protokollieren
(siehe Beispielprojekt)
Zusammenfassung
• MAF ist ein mächtiges System
• Solides und sicheres Hosting
• Flexible Versionierung
• WPF Support
• Die Lernkurve ist am Anfang recht steil
• Pipeline Builder hilft bei der Entwicklung

93
{ In-depth support and consulting for
software architects and developers }
http://www.thinktecture.com/

joerg.neumann@thinktecture.com
www.HeadWriteLine.BlogSpot.com

94