Frage

Dieser Beitrag setzt im Idealfall meinen anderen Beitrag zu MEF -Plugins fort, aber mein erster Beitrag war zu voll von Kommentaren und dieses Beispiel ist vollständiger. Hier fasse ich mein aktualisiertes Szenario zusammen, mit all meinen Erkenntnissen bis zu diesem Punkt. Ich hoffe, das kann für andere CM -Neulinge wie mich nützlich sein.

Sie können ein volles Repro-Sample-Szenario herunterladen: Es ist ein fast dummes dummes Skelett für eine CM + MEF-Plugins-basierte Anwendung:

VS2010 REPO -Lösung (aktualisiert)
Dies ist eine minimal abgespeckte Lösung, die meine Probleme mit CM+MEF darstellt. Es gibt 3 Projekte:

  • die Haupt Benutzeroberfläche (CMReprro).
  • Eine Kern -DLL, die unter allen Addins (Addincore) mit ein paar Schnittstellen und benutzerdefinierten Attributen für MEF -Metadaten verwendet wird.
  • Eine Probe -Addin -DLL (Alphaaddin) mit Ansicht und ViewModel, die die Schnittstellen implementiert.

Das Ader Enthält 2 Schnittstellen, die ein ViewModel und seine Ansicht darstellen, sowie 2 Attribute, die zum Dekorieren der ViewModels und der Aussicht verwendet werden sollen. Die ViewModel -Schnittstelle beschreibt eine Klasse, in der eine Grußnachricht aus einem Personnamen komponiert werden sollte. Daher enthüllt sie einige Eigenschaften und eine Methode dafür. Die View -Schnittstelle zeigt nur eine Eigenschaft, die ihren DataContext -Guss an die ViewModel -Schnittstelle zurückgibt.

Das Probe Addin hat eine Implementierung für ein ViewModel und eine Ansicht; Beide sind MEF-Exports, die durch das entsprechende Attribut verziert sind. In der realen Lösung werden mehrere Eigenschaften dieser Attribute zur Filterung verwendet. Hier habe ich nur einen Dummy Sprache Eigenschaft, die andere Plugins für verschiedene Sprachen ermöglichen sollte.

Das Haupt Benutzeroberfläche Hat einen MEF -Bootstrapper, der Code zum Abrufen von MEF -Exporten aus einem Addins -Ordner hinzufügt. Ich habe diesen Code so geändert, dass sie Exporte aus MEF -Verzeichnissen einbeziehen und einige MEF -Ausnahmen besser verstehen, aber ich kann trotzdem nicht herausfinden, wie man sie mit CM ordnungsgemäß "registrieren".

Das Haupt -ViewModel hat 2 Methoden: eines (EIN) Verwendet einen MEF -Katalog, um ein ViewModel und seine Ansicht abzurufen, sie zu binden und in ein Fenster zu zeigen. Andere (B) Verwendet denselben Katalog, um ein ViewModel zu erhalten, und dann einen CM -Fenstermanager zum Suchen, Erstellen, Binden und Zeigen der entsprechenden Ansicht gemäß CM -Namenskonventionen. Diese Methoden repräsentieren zwei alternative Möglichkeiten, mit denen ich in meinem realen Code umgehen sollte, dh einige entscheidende Objekte "alleine", nur mit MEF, aber dann lassen Die Arbeit beginnt von einem ViewModel.

Wie auch immer, in beiden Fällen fehlt mir etwas wie für die Registrierung bei CM. Ausgaben:

  • (EIN) Wie verweise ich VM+V für CM, damit die Konventionen für die Datenbank usw. angewendet werden? Zu diesem Zeitpunkt kann ich meine MEF -Teile zusammen bauen, aber CM ignoriert sie, da es nicht verwendet wurde, um keinen von ihnen zu instanziieren.Ich antworte mir hier:

    ViewModelBinder.Bind(viewmodel, (UserControl)view, null);

  • (B) Wie registriere ich die Exporte von MEF in CM, damit der CM -Fenstermanager die Ansicht finden kann? Derzeit gelingt es nicht, die Ansicht aus dem ViewModel zu finden.


Addition (21. Juni)

Ich versuche es besser zu erklären, für wen kann ich nicht auf die Repro -Lösung zugreifen. Ich verwende einen "Standard" MEF Bootstrapper und ändere die Konfigurationsüberschreibung wie:

_container = new CompositionContainer(
  new AggregateCatalog(AssemblySource.Instance.Select(
    x => new AssemblyCatalog(x)).OfType()
  .Union(GetAddinDirectoryCatalogs())));

Dies erstellt einen MEF -Kompositionscontainer, der den Katalog aus AssemblySource mit CM -Typen wie Ereignisaggregator oder Fenstermanager zusammenfasst, mit einem Katalog aus mehreren Addin -Verzeichnissen, die Exporte sowohl für V als auch für VM enthalten.

In meinem Beispiel Main ViewModel erstelle ich ein neues VM aus einem Plugin, das unter anderem im Host -Anwendungsverzeichnis zu finden ist. Ich möchte, dass ein CM -Fenstermanager seine Ansicht in einem Dialogfeld, zB:

viewmodel = GetMyViewModelFromAddin();
windowmanager.ShowDialog(viewmodel);

CM trotzdem kann die Ansicht nicht mehr finden. Afaik, Namenskonventionen werden geehrt: Sowohl V als auch VM sind in derselben Addin -Versammlung, die als MEF -Exporte markiert ist, die wie etwas mithilfe von ViewModel / etwas bezeichnet werden. Wie auch immer, wie Leaf in seiner Klarstellung betonte, ist AssemblySource.instance eine statische IobservableCollection -Sammlung von Baugruppen, und ich habe meine Addins nicht hinzugefügt. Aber das ist richtig, der Punkt: Ich möchte nicht alle im Voraus hinzufügen, da dies bedeutet, alle Addins zu laden, ohne zu wissen, welche (falls vorhanden) jemals verwendet werden. Ein robustes Plugin -System ist schließlich der Grund für die Verwendung von MEF. Ich bin neu in CM und bin mir nicht sicher, ob es möglich ist (und wo), in diesem Szenario einen Erweiterungspunkt für CM zu finden. Der Fenstermanager ruft meine Bootstrapper -Implementierung überhaupt nicht auf, da nichts durch IOC instanziiert werden muss, da in der Quellinstanz der Montage keine Übereinstimmung gefunden wurde. Es scheint also, dass ich hier stecke, die einzige Lösung, die im Voraus alle Baugruppen in der Instanz geladen wird, aber dies scheint den ganzen Zweck der Verwendung von MEF zu besiegen.

Die Plugin-basierte Anwendung Ich entwickle ladet Tonnen von V+VM CM "Paaren", die die Widget der Benutzeroberfläche darstellen, die wiederum einen Fenstermanager verwenden, um andere V+VM-Paare als Dialoge zu popupieren. Ich kann die Instanziierung mit CM umgehen und mit MEF V+VM für jedes Widget abrufen, aber ich werde für jedes Widget, das einen Fenstermanager benötigt, dem gleichen Ansichtspositionsproblem vorhanden. Die andere Alternative (Problemumgehung), die ich sehen kann, besteht darin, die Verwendung von Fenstermanager zu vermeiden und meinen eigenen Mechanismus zu implementieren, um Dialoge von Widgets anzuzeigen, aber dies macht mich ein wenig falsch an CM ...). Normalerweise, wenn ich viel mehr Code schreibe als erwartet, neige ich dazu zu glauben, dass ich das Tool nicht richtig verwende. Irgendeine Idee?

War es hilfreich?

Lösung

Caliburn.Micro durchläuft 3 Stufen, um eine Ansicht aus einer ViewModel -Instanz zu finden.

  1. Verwandelt sich der Text, um den Namen vom Typ Ansicht zu transformieren, um den Namen des Typs zu sehen. Es gibt einen Satz von Standard Konventionen Für diese Transformationen zB z. SomeNamespace.ViewModels.CustomerViewModel könnte zuordnen SomeNamespace.Views.CustomerView.

  2. Caliburn.micro verwendet mit den Namen der Ansichtstyp (n), dann verwendet Caliburn.micro dann AssemblySource.Instance (eine statische IObservableCollection<Assembly> Sammlung von Baugruppen), um die erste Übereinstimmung zu finden Type.

  3. Caliburn.Micro versucht, eine Instanz davon zu erstellen Type über einen der IoC.GetInstance() Methoden (die an Ihren Bootstrapper und damit an MEF delegieren).

Ich vermute (Ihre Dateifreigabe -Site ist hier blockiert), dass das Problem bei der Lösung von Ansichten von ViewModels auf den zweiten Schritt und der Schritt zurückzuführen ist AssemblySource.Instance Sammlung enthält nicht Ihre dynamische Baugruppe.

Eine Lösung könnte darin bestehen, jede dynamisch geladene Addin -Baugruppe zu hinzufügen AssemblySource.Instance Wenn sie geladen sind oder wenn Sie alle Baugruppen beim Start kennen, können Sie die Methode zur Sälungsemblies -Methode überschreiben, um die Liste der Baugruppen zurückzugeben, die Sie mit Ansichten und ViewModels erwarten.


Update, um zu zeigen, wie Sie Baugruppen von MEF -geladenen Teilen ziehen können

Wenn Sie verwenden DirectoryCatalog Um Ihre Teile aus anderen Baugruppen zu laden, können Sie die verwendeten Baugruppen finden:

var directoryCatalog = new DirectoryCatalog(@"./");            
AssemblySource.Instance.AddRange(
    directoryCatalog.Parts
        .Select(part => ReflectionModelServices.GetPartType(part).Value.Assembly)
        .Where(assembly => !AssemblySource.Instance.Contains(assembly)));

Wenn sich Ihr Addins -Ordner während der Laufzeit der Anwendung ändert DirectoryCatalog.Refresh() Der Katalog und führen Sie den Code aus, um neue Baugruppen hinzuzufügen AssemblySource.Instance

Andere Tipps

Ich fand eine Problemumgehung. Es ist nicht so hübsch, aber es ermöglicht es CM und seinem Fenstermanager, zu arbeiten. Ich fasse hier meine Erkenntnisse zusammen, hoffe, dass dies anderen hilft oder mich auf eine bessere Lösung verweist.

Angesichts der Tatsache, dass (a) ich nicht alle Baugruppen, einschließlich ihrer Abhängigkeiten in meinem Plugins -Ordner, laden möchte, um zu vermeiden, dass meine App -Domäne mit nicht verwendeten Sachen verschmutzt wird. und (b) der einzige verfügbare CM -Erweiterungspunkt hierfür ausgewählte SELTASSEMBLIES, mein Ziel ist es, meine Baugruppen dort hinzuzufügen, aber nur die Plugin -Baugruppen, die bei CM registriert werden müssen.

Ich suchte also nach einer Möglichkeit, alle DLLs in eine temporäre App -Domäne zu laden, sie nach einigen Aspekten zu scannen, die sie als Plugins markieren, und dann in die aktuelle App -Domäne nur die Plugin -Domäne laden und sie an SelectAssemblies übergeben. Dies ist weit davon entfernt, die optimale Lösung zu sein, da ich keinen allgemeinen Scanning -Weg wie MEF verwenden kann, und ich habe das Gefühl, dass ich die Anstrengung dupliziere, aber zumindest ist es eine funktionierende Lösung.

Um mindestens eine Möglichkeit zum Laden nur der Plugins bereitzustellen, dekoriere ich meine Plugin -Baugruppen, die eine CM -Registrierung mit einem benutzerdefinierten Attribut als Plugins erfordern, und zählen Sie ihre Typen weiter auf, die nach denen suchen, die später von MEF verwendet werden sollten.

Das Attribut ist so einfach wie:

[AttributeUsage(AttributeTargets.Assembly)]
public class AssemblyRegisteredWithCMAttribute : Attribute {}

Ich fand dann diesen sehr guten Code für das Scannen von Baugruppen in eine andere temporäre Appdomain:

http://sachabarber.net/?p=560

Ich musste es ein wenig ändern, da es beim Laden von Baugruppen mit Abhängigkeiten fehlschlug und die Baugruppentypen scannte, während ich in meinem Fall nur nach dem Attribut prüfen muss.

Hier ist mein Code:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Policy;
using System.Reflection;
using System.Diagnostics.CodeAnalysis;

Namespace cmreprro {/// /// separate AppDomain -Assembly -Loader. /// /// modifiziert von http://sachabarber.net/?p=560 . Public Class SeparateAppdomainAsMblyloader {/// /// lädt eine Montage in eine neue AppDomain, die die Namen der Dateien zurückgibt /// mit dem Assembly -Attributnamen markierten Anpassungen /// dem angegebenen Namen. Die neue AppDomain wird dann entladen. /// /// Liste der Dateien zum Laden /// Assemblies -Verzeichnis /// Matching Attribut Name /// Liste der gefundenen Namespaces /// Null -Dateien, Assemblerverzeichnis oder Matching Attribut Public List LoadAsSemblies (String [] Afiles, String, String SassemblyDirectory, String smatchingAttribute) {if (afiles == null) werfen neue argumentNulLexception ("afiles"); if (sassemblyDirectory == null) werfen neue argumentNulLexception ("sasemblyDirectory"); if (smatchingAttribute == null) werfen neue argumentNulLexception ("smatchingAttribute");

        List<String> namespaces = new List<String>();

        AppDomain childDomain = BuildChildDomain(AppDomain.CurrentDomain);

        try
        {
            Type loaderType = typeof(AssemblyLoader);
            if (loaderType.Assembly != null)
            {
                AssemblyLoader loader = (AssemblyLoader)childDomain.
                                                            CreateInstanceFrom(
                                                                loaderType.Assembly.Location,
                                                                loaderType.FullName).Unwrap();

                namespaces = loader.LoadAssemblies(aFiles, sAssemblyDirectory, sMatchingAttribute);
            } //eif
            return namespaces;
        }
        finally
        {
            AppDomain.Unload(childDomain);
        }
    }

    /// <summary>
    /// Creates a new AppDomain based on the parent AppDomains 
    /// Evidence and AppDomainSetup.
    /// </summary>
    /// <param name="parentDomain">The parent AppDomain</param>
    /// <returns>A newly created AppDomain</returns>
    private AppDomain BuildChildDomain(AppDomain parentDomain)
    {
        Evidence evidence = new Evidence(parentDomain.Evidence);
        AppDomainSetup setup = parentDomain.SetupInformation;
        return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);
    }

    /// <summary>
    /// Remotable AssemblyLoader, this class inherits from <c>MarshalByRefObject</c> 
    /// to allow the CLR to marshall this object by reference across AppDomain boundaries.
    /// </summary>
    private class AssemblyLoader : MarshalByRefObject
    {
        private string _sRootAsmDir;

        /// <summary>
        /// ReflectionOnlyLoad of single Assembly based on the assemblyPath parameter.
        /// </summary>
        /// <param name="aFiles">files names</param>
        /// <param name="sAssemblyDirectory">assemblies directory</param>
        /// <param name="sMatchingAttribute">matching attribute name</param>
        [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        internal List<string> LoadAssemblies(string[] aFiles, string sAssemblyDirectory, string sMatchingAttribute)
        {
            AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += OnReflectionOnlyAssemblyResolve;
            _sRootAsmDir = sAssemblyDirectory;

            List<string> aAssemblies = new List<String>();

            try
            {
                sMatchingAttribute = "." + sMatchingAttribute;

                foreach (string sFile in aFiles)
                    Assembly.ReflectionOnlyLoadFrom(sFile);

                aAssemblies.AddRange(from asm in AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies()
                                     let attrs = CustomAttributeData.GetCustomAttributes(asm)
                                     where attrs.Any(a => a.ToString().Contains(sMatchingAttribute))
                                     select asm.FullName);
                return aAssemblies;
            }
            catch (FileNotFoundException)
            {
                /* Continue loading assemblies even if an assembly
                 * can not be loaded in the new AppDomain. */
                return aAssemblies;
            }
        }

        private Assembly OnReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs e)
        {
            // http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx

            System.Diagnostics.Debug.WriteLine(e.Name);

            AssemblyName name = new AssemblyName(e.Name);
            string sAsmToCheck = Path.GetDirectoryName(_sRootAsmDir) + "\\" + name.Name + ".dll";

            return File.Exists(sAsmToCheck)
                    ? Assembly.ReflectionOnlyLoadFrom(sAsmToCheck)
                    : Assembly.ReflectionOnlyLoad(e.Name);
        }
    }
}

}

Jetzt überschreibe ich in meinem Bootstrapper die Methode SelectAssemblies wie folgt:

...
protected override IEnumerable SelectAssemblies()
{
    string sAddinPath = GetAbsolutePath(ADDIN_PATH);
    FileCheckList list = new FileCheckList(sAddinPath);

// check only DLL files which were added or changed since last check
SeparateAppDomainAssemblyLoader loader = new SeparateAppDomainAssemblyLoader();
List<string> aAssembliesToRegister = loader.LoadAssemblies(list.GetFiles(null),
                                                           sAddinPath, "AssemblyRegisteredWithCM");

string[] aFilesToRegister = (from s in aAssembliesToRegister
                             select Path.Combine(sAddinPath, s.Substring(0, s.IndexOf(',')) + ".dll")).ToArray();

// update checklist
foreach (string sFile in aFilesToRegister) list.SetCheck(sFile, true);
list.UncheckAllNull();
list.Save();

// register required files
return (new[]
            {
                Assembly.GetExecutingAssembly(),
            }).Union((from s in list.GetFiles(true)
                      select Assembly.LoadFrom(s))).ToArray();

} ...

Wie Sie sehen können, rufe ich den Loader nicht für alle DLLs in meinem Addins -Pfad auf, sondern nur für diejenigen, die eine Checkliste für zwischengespeicherte Dateien in diesem Ordner seit dem letzten vollständigen Scan hinzugefügt oder geändert wurden. Dies sollte die Dinge ein wenig beschleunigen und nicht vorhanden ist, dass die Checkliste -Datei vorhanden ist: Wenn nicht festgestellt wird, dass sie beim Start durch Scannen aller Dateien nachgebildet wird, werden nur dann hinzugefügt oder geändert. Änderungen). Daher erhalte ich den Addins -Ordner, erstelle eine Dateien -Checkliste für diesen Ordner, nehme eine Liste neuer oder geänderter Dateien und übergeben Sie sie an den Assemblies -Loader. Dies gibt nur die Namen der DLL -Dateien zurück, die registriert werden sollten (dh die mit meinem Attribut gekennzeichneten Baugruppen). Ich aktualisiere dann die Checkliste für das nächste Start und registriere nur die erforderlichen Dateien. Auf diese Weise kann ich meine Fenstermanager meines Addin VM verwenden lassen und die Ansicht für jedes erforderliche ViewModel korrekt suchen. Etwas hässlich, aber arbeiten. Nochmals vielen Dank an Leaf, der mir die Arbeit von CM erklärte.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top