Laden...

RainBirds Zyan: Server Event am Client abrufen (oder Alternativen?)

Erstellt von caldicot vor 13 Jahren Letzter Beitrag vor 13 Jahren 7.389 Views
C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren
RainBirds Zyan: Server Event am Client abrufen (oder Alternativen?)

Hi,

Ich habe eine Klasse, die 2 Listen beinhaltet. Darin habe ich 2 Events implementiert (wie in [Lösung] Problem mit EventHandler beschrieben)
Für jede Liste gibt es eine Add-Methode().
Beim Aufruf der Methode wird das Event gefired.
Lokal funktioniert das wunderbar.

Die oben erwähnte Klasse publishe ich mit Rainbirds Zyan.
Die Datenübertragung funktioniert auch.
Ich kann auf die Listen zugreifen, Funktion Remote ausführen etc.
Ich hätte jetzt gerne, dass der Server dem Client mittels Event mitteilt, wenn ein neues Element in die Liste eingefügt wurde.

Leider funktioniert das nicht mehr.
Ich kann am Client dem Event kein EventHandler zuweisen.

Client Code:


HttpCustomClientProtocolSetup protocolSetup = new HttpCustomClientProtocolSetup(true);
ZyanConnection connection = new ZyanConnection("http://localhost:10000/Test", protocolSetup);

ITest proxy = connection.CreateProxy<ITest>();

//Diese Zeile funktioniert nicht mehr:
proxy.MyEvent += new EventHandler<EventArgs>(proxy_MyEvent);

Die Ausnahme TargetInvocationException wird geworfen.
Kann mir bitte jemand helfen?

Ich habe auch schon gelesen, dass Events via Remoting nicht geeignet sein sollen.
Gibt es hier eine bessere Möglichkeit dem Client aufmerksam zu machen, dass ein neues Element hinzugefügt wurde?

Ich hoffe ich habe mich klar und verständlich ausgedrückt 😃

Vielen Dank
caldi

849 Beiträge seit 2006
vor 13 Jahren

Hallo,

was steht den sonst noch in der Exception? Meinst gibts da noch aussagekräftigere Meldungen drin.. und auf InnerExceptions achten.

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hi,

hier die ganze Exception.

Würde das denn grundsätzlich mit dem Event funktionieren oder ist das sowieso konzeptionell zu vermeiden?

"System.Reflection.TargetInvocationException" wurde aufgefangen.
Message=Ein Aufrufziel hat einen Ausnahmefehler verursacht.
Source=mscorlib
StackTrace:
Server stack trace:
bei System.RuntimeMethodHandle._SerializationInvoke(IRuntimeMethodInfo method, Object target, SignatureStruct& declaringTypeSig, SerializationInfo info, StreamingContext context)
bei System.Runtime.Serialization.ObjectManager.CompleteISerializableObject(Object obj, SerializationInfo info, StreamingContext context)
bei System.Runtime.Serialization.ObjectManager.FixupSpecialObject(ObjectHolder holder)
bei System.Runtime.Serialization.ObjectManager.DoFixups()
bei System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
bei System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
bei System.Runtime.Remoting.Channels.CoreChannel.DeserializeBinaryRequestMessage(String objectUri, Stream inputStream, Boolean bStrictBinding, TypeFilterLevel securityLevel)
bei System.Runtime.Remoting.Channels.BinaryServerFormatterSink.ProcessMessage(IServerChannelSinkStack sinkStack, IMessage requestMsg, ITransportHeaders requestHeaders, Stream requestStream, IMessage& responseMsg, ITransportHeaders& responseHeaders, Stream& responseStream)
Exception rethrown at [0]:
bei System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
bei System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
bei Zyan.Communication.IComponentInvoker.Invoke(String interfaceName, ArrayList outputPinCorrelationSet, String methodName, Object[] args)
bei Zyan.Communication.ZyanProxy.InvokeRemoteMethod(IMethodCallMessage methodCallMessage)
Exception rethrown at [1]:
bei System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
bei System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
bei Shared.Komponenten.ITest.add_MyEvent(EventHandler`1 value)
bei Client.Form1.Form1_Load(Object sender, EventArgs e) in C:\Users...\Client\Form1.cs:Zeile 31.
InnerException: System.IO.FileNotFoundException
Message=Die Datei oder Assembly "Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" oder eine Abhängigkeit davon wurde nicht gefunden. Das System kann die angegebene Datei nicht finden.
Source=mscorlib
FileName=Client, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
FusionLog=WRN: Protokollierung der Assemblybindung ist AUS.
Sie können die Protokollierung der Assemblybindungsfehler aktivieren, indem Sie den Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) auf 1 festlegen.
Hinweis: Die Protokollierung der Assemblybindungsfehler führt zu einer gewissen Leistungseinbuße.
Sie können dieses Feature deaktivieren, indem Sie den Registrierungswert [HKLM\Software\Microsoft\Fusion!EnableLog] entfernen.

   StackTrace:  
        bei System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark&amp; stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)  
        bei System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark&amp; stackMark, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)  
        bei System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, StackCrawlMark&amp; stackMark, Boolean forIntrospection, Boolean suppressSecurityChecks)  
        bei System.Reflection.RuntimeAssembly.InternalLoad(String assemblyString, Evidence assemblySecurity, StackCrawlMark&amp; stackMark, Boolean forIntrospection)  
        bei System.Reflection.Assembly.Load(String assemblyString)  
        bei System.Runtime.Serialization.FormatterServices.LoadAssemblyFromString(String assemblyName)  
        bei System.Reflection.MemberInfoSerializationHolder..ctor(SerializationInfo info, StreamingContext context)  
   InnerException: 
3.728 Beiträge seit 2005
vor 13 Jahren
Verteilte Events

Hallo caldicot,

das funktioniert so nicht. Sender und Empfänger leben in verschiedenen Prozessen. Der Server-Prozess kann die Ereignisprozedur am Client nicht aufrufen, weil er dazu den Typ (also die Klasse in der proxy_MyEvent definiert ist) kennen müsste. Dem ist natürlich nicht so und das ist auch gut so. Die Client-Implementierungs-Assembly hat auf dem Server nix verloren.

Events funktionieren aber trotzdem. Du brauchst nur ein Zwischenstück, welches Client und Server gleichermaßen bekannt ist (also in der Shared-Assembly liegt).

Der Client erzeugt von diesem EventReceiver-Zwischenstück eine Instanz und registriert sie bei der Server-Komponente. Die Serverkomponente ruft am EventReceiver-Proxy eine Methode (z.B. FireEventOnClient), welche den EventReceiver auf dem Client veranlast das tatsächliche Event zu feuern.

Ich habe aber vor, eine Standardlösung für Benachrichtigungen in Zyan einzubauen. Bin nur noch nicht dazu gekommen.

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hallo Rainbird,

Danke für deine schnelle Antwort.

Wäre das ein Beispiel für das von dir beschriebene Zwischenstück?
.NET Remoting - Events, Events? Events!

oder das?

Events and Delegates in Remoting

Habe leider keine echte Idee, wie ich sowas implementieren kann. 😦

Schön zu hören, dass dieses Problem zukünftig mit Zyan leicht zu lösen sein wird 😃

Viele Grüße
caldi

Hinweis von gfoidl vor 13 Jahren

Bitte beachte auch [Hinweis] Wie poste ich richtig? Punkt 3.1

BTW: Du hast 2x den gleichen Link angegeben - Absicht?!

3.728 Beiträge seit 2005
vor 13 Jahren
Beispiel für Events

Hallo caldicot,

hier findest Du ein sehr gutes Beispiel, wie man Events über Remoting machen kann: NotificationService als Erweiterung zu Rainbirds ApplikationServer Beispiel

Das Beispiel wurde zwar nicht für Zyan entworfen, sollte sich aber relativ einfach für den Einsatz mit Zyan anpassen lassen.

Ich versuche, ein Standard Zyan Benachrichtigungs-Feature noch bis Ende der Woche fertig zu bekommen. Wenn es fertig ist, sage ich Bescheid.

Ich würde ja ein kurzes Beispiel-Snippet hinschreiben, aber ganz so trivial ist die Event-Geschichte eben nicht. Dann kann ich es auch gleich richtig machen und einbauen.

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hallo Rainbird,

danke für deine Hilfe.
Ich werde versuchen das anhand des Beispiels hinzukriegen. 😃

Ich versuche, ein Standard Zyan Benachrichtigungs-Feature noch bis Ende der Woche fertig zu bekommen. Wenn es fertig ist, sage ich Bescheid.

Das klingt ja super 😉

Vielen Dank für deine Mühen.
caldi

3.728 Beiträge seit 2005
vor 13 Jahren
Benachrichtigungen mit Zyan

Hallo caldicot,

Zyan kann jetzt standardmäßig Clients benachrichtigen. Um das neue Feature nutzen zu können, musst Du Dir die neue Version 0.9.2 runterladen: http://zyan.codeplex.com/releases/view/55535

Die nötigen Typn liegen im Namensraum Zyan.Communication.Notification. Als EventArgs müssen zwingend NotificationEventArgs (aus selbigem Namensraum) verwendet werden. Über diese EventArgs kannst Du ein beliebiges Objekt mitgeben.

Das Event-System (Notification Service) muss manuell am Host gestartet werden (da es ja nur optional ist und nicht von jedem benötigt wird). Das geht so:


// Benachrichtigungsdienst am Zyan-Komponentenhost einschalten
host.StartNotificationService();

Bevor ein Client ein Event einer Serverkomponente abonnieren kann, muss das Event erst für den entfernten Zugriff veröffentlicht werden. Dazu wird das gewünschte Ereignis der Serverkomponente mit dem Host, unter angabe eines logischen Ereignisnamens, verdrahtet. Das wird durch folgende Zeile Code erledigt:


// Ereignis MyEvent der Komponente am Host veröffentlichen
component.MyEvent += host.PublishEvent("MyEvent");

Das war´s schon auf der Serverseite. So sieht der komplette Servercode aus:


...

using (ZyanComponentHost host = new ZyanComponentHost("EventTest",protocolSetup))
{
    // Benachrichtigungsdienst am Zyan-Komponentenhost einschalten
    host.StartNotificationService();

    // Serverkomponente erzeugen
    Test component = new Test();

    // Ereignis MyEvent der Komponente am Host veröffentlichen
    component.MyEvent += host.PublishEvent("MyEvent");

    // Komponente für entfernten Zugriff veröffentlichen
    host.RegisterComponent<ITest ,Test>(component);

    // Server laufen lassen, bis Enter gedrückt wird
    Console.ReadLine();
}

Auf dem Client kann das Ereignis jetzt über die Zyan Connection abonniert werden. Das fuktioniert so:


HttpCustomClientProtocolSetup protocolSetup = new HttpCustomClientProtocolSetup(true);
ZyanConnection connection = new ZyanConnection("http://localhost:10000/Test", protocolSetup);

// Proxy erzeugen
ITest proxy = connection.CreateProxy<ITest>();

// Ereignis abonnieren
connection.SubscribeEvent("MyEvent", proxy_MyEvent);

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hallo Rainbird,

HERZLICHEN DANK!
Probiere ich gleich morgen aus!

Wahnsinn wie schnell das bei Dir geht 😃

Nochmals Danke!
Viele Grüße
caldi

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hallo Rainbird,

so ganz krieg ichs noch nicht hin.

Meine Klasse, die das Event beinhaltet sieht so aus:


 private static List<myObject> m_objectHistory = new List<myObject>();

        public event EventHandler<NotificationEventArgs> ObjectAddedEvent;

        protected virtual void OnObjectAddedEvent(NotificationEventArgs e)
        {
            if (ObjectAddedEvent != null)
            {
                ObjectAddedEvent(this, e);
            }
        }

        public bool addObject(string kennung, string status)
        {
            try
            {
                m_objectHistory.Add(new myObject(kennung, status));
                
                OnObjectAddedEvent(new NotificationEventArgs("")); 
// Hier kann ich nicht NotificationEventArgs.Empty mitgeben, weil der Getter den Typ 
// EventArgs zurück gibt. Und das darf ich ja nicht?

                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

Auf dem Server schaut meine Event Veröffentlichung so aus:


 HttpCustomServerProtocolSetup protocolSetup = new HttpCustomServerProtocolSetup(Properties.Settings.Default.PORT, userMng, true);
            ZyanComponentHost host = new ZyanComponentHost("Test", protocolSetup);
            host.StartNotificationService();

            history.ObjectAddedEvent += host.PublishEvent("ObjectAddedEvent");

Auf dem Client habe ich das Event abonniert:



           HttpCustomClientProtocolSetup protocolSetup = new HttpCustomClientProtocolSetup(true);
            ZyanConnection connection = new ZyanConnection("http://localhost:10000/Test", protocolSetup, credentials, true); //credentials sind weiter oben definiert, das funktioniert bereits
            history = connection.CreateProxy<IHistory>();
            connection.SubscribeEvent("ObjectAddedEvent", proxy_ObjectAddedEvent);

Die Event-Methode soll in ein DataGridView das gerade hinzugefügte Element schreiben:
Kann ich hier mittels sender auch auf das Remote Objekt History zugreifen und direkt die lastEntry.. holen?
Im Moment ist history so global definiert, dass die Methode Zugriff auf den Proxy hat.


void proxy_ObjectAddedEvent(object sender, NotificationEventArgs e)
        {
            DataTable dt = (DataTable)this.dataGridView1.DataSource;
            DataRow dr = dt.NewRow();

            myObject item = history.getLatestEntry;

            dr["Zeit"] = item.zeit;
            dr["Kennung"] = item.kennung;
            dr["Name"] = item.name;
            dt.Rows.Add(dr);

            if (item.isMine)
            {
                this.dataGridView1["Kennung", 0].Style.BackColor = Color.Aqua;
                this.dataGridView1["Name", 0].Style.BackColor = Color.Aqua;
            }
        }

Wenn ich die Event-Methode im Debug durchlaufe, dann wird keine Exception geworfen, aber am Schluss, kurz bevor die Methode beendet werden würde, kommt von VS der Hinweis, dass eine Quelldatei fehlt:


Aufruflistenort:
Zyan.COmmunication.dll!Zyan.Communication.Notification.NotificationReceiver.FireNotifyEvent(object sender, Zyan.Communication.Notification.NotificationEventArgs e) Zeile 52

Quelldateiinformationen:
Quelle für "C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs" wird gesucht. Checksum: MD5 {1c 38 a5 41 bd dd 7c 84 92 85 dd 50 20 e2 ba 2a}
Die Datei "C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs" ist nicht vorhanden.
"C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs" wird in Skriptdokumenten gesucht...
"C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs" wird in Projekten gesucht.
Die Datei wurde nicht in einem Projekt gefunden.
In Verzeichnis "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\crt\src\" wird gesucht...
In Verzeichnis "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\atlmfc\src\mfc\" wird gesucht...
In Verzeichnis "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\atlmfc\src\atl\" wird gesucht...
In Verzeichnis "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\atlmfc\include\" wird gesucht...
In den Einstellungen zum Debuggen von Quelldateien für die aktive Lösung ist angegeben, dass der Benutzer nicht zum Suchen der Datei aufgefordert wird: C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs.
Die Quelldatei "C:\Code\Zyan\source\Zyan.Communication\Notification\NotificationReceiver.cs" wurde nicht gefunden.

Edit:
Die Fehlermeldung krieg ich auch, bei der Authenticate() Methode.
Allerdings tritt der Fehler nur dann auf, wenn die Zyan.Communication.pdb Datei im Verzeichnis liegt.
Ist diese nicht vorhanden, funktioniert die Authenticate() Methode.
Bei dem Event auf dem Client bleibt das Programm jedoch auch ohne PDB Datei stehen, nur eben ohne Fehler Hinweis.

Habe ich was falsch gemacht?

Danke
caldi

3.728 Beiträge seit 2005
vor 13 Jahren
Multithreading

Hallo caldicot,

das Problem wird vermutlich auch nicht durch einen Zyan-Bug verursacht, sondern liegt daran, dass der Callback-Aufruf vom Server, welcher am Ende die Ereignisprozedur proxy_ObjectAddedEvent aufruft, in einem anderen Thread ankommt, als der GUI-Thread (also der Thread in dem die Meldungsschleife des Windows-Formulars läuft). Der Zugriff auf Steuerelemente darf per Design nur aus dem GUI-Thread erfolgen. Also musst Du die Ereignisprozedur auf den GUI-Thread umleiten.
Wie das genau gemacht wird, findest Du hier: [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)

Das ist ein allgemeines Problem von Multithreading in Verbindung mit Windows.Forms und hat nichts im Speziellen mit Zyan zu tun.

Den Zyan-Quellcode kannst Du Dir übrigens hier runterladen: http://zyan.codeplex.com/SourceControl/list/changesets#

Dann kannst Du richtig debuggen.

Habe ich was falsch gemacht?

Kann ich aus der Ferne schwer beurteilen. Passen die PDB-Dateien zu den DLLs? Also Debug-Version PDB und Debug-Version DLL auf Client und Server?

OnObjectAddedEvent(new NotificationEventArgs(""));  
// Hier kann ich nicht NotificationEventArgs.Empty mitgeben, weil der Getter den Typ   
// EventArgs zurück gibt. Und das darf ich ja nicht?  
  

Sorry, da verstehe ich das Problem leider nicht.

C
caldicot Themenstarter:in
51 Beiträge seit 2010
vor 13 Jahren

Hallo Rainbird,

offensichtlich habe ich doch etwas falsch gemacht.
Aufgrund Deines Tipps habe ich Dein Zyan Projekt in meine Projektmappe eingebunden und die Verweise aktualisiert.

Der Fehler tritt nicht mehr auf.
In das DataTable (das ist die DataSource des DataGridViews) wird beim Event anstandslos der Eintrag hinzugefügt. Damit habe ich auch kein Problem mit dem Zugriff auf die GUI und dem Event auslösenden Thread.

Auch wenn's nicht direkt hier her gehört:
Beim Start des Clients werden alte Einträge vom Server geschickt und in den DataTable eingetragen. Der DataGridView zeichnet das auch sofort.

Wenn ich über das Event in die DataSource den neusten Eintrag hinzufüge, dann wird das DataGridView nicht neu gezeichnet. Erst beim Scrollen wird neu gezeichnet. Der Inhalt wird aber gesetzt, weil das neue Element nachdem Neuzeichnen vorhanden ist.
Meine Frage ist jetzt, warum das so ist, bzw. wie man das löst?
Mit .Update() habe ich es versucht, aber ohne Erfolg.

Sorry, da verstehe ich das Problem leider nicht.

Ich hätte hier zuerst probiert das so zuschreiben:


OnObjectAddedEvent(NotificationEventArgs.Empty);

Allerdings meckert der Compiler, weil NotificationEventArgs.Empty den Typ EventArgs zurück gibt, aber die Methode OnObjectAddedEvent den Typ NotificationEventArgs erwartet.
Mache ich da was falsch?
Oder ist die Lösung von unten genauso sauber?

Danke
caldi

3.728 Beiträge seit 2005
vor 13 Jahren

Wenn ich über das Event in die DataSource den neusten Eintrag hinzufüge, dann wird das DataGridView nicht neu gezeichnet. Erst beim Scrollen wird neu gezeichnet. Der Inhalt wird aber gesetzt, weil das neue Element nachdem Neuzeichnen vorhanden ist.

Wie fügst Du den neuen Eintrag zu?
Wie das das DataGridView an die DataTable gebunden (direkt oder über eine BindingSource)?

Meine Frage ist jetzt, warum das so ist, bzw. wie man das löst?

Es gibt verschiedene Möglichkeiten, wie Daten den Weg in ein Datengitter finden.
Empfehlenswert is es, die Bindung über eine sog. BindingSource zu machen. Du findest diese Komponente in der Toolbox im Forms-Designer im Register Daten. Über diese BindungSource kannst Du die Datenbindung bequem steuern (z.B. blättern oder die gebunden Steuerelkemente anweisen, sich sofort zu Aktualisieren).

Die (meistens) ideale Vorgehensweise für datengebundene Formulare sieht so aus:*DataSet aufs Form ziehen (Toolbox -> Daten) *Im aufpoppenden Dialog entweder die gewünschte typisierte DataSet-Klasse auswählen oder untypisiertes DataSet wählen *Im Falle eines untypisierten DataSets müssen Tabelle(n) und deren Spalten im Designer (Eigenschaften der DataSet-Instanz -> Tables) festgelegt werden *BindingSource aufs Form ziehen (Toolbox -> Daten) *DataSet-Instanz als DataSource der BindingSource-Instanz festlegen *DataMember der BindingSource-Instanz auf die gewünschte Tabelle des DataSets einstellen, an die gebunden werden soll *Steuerelemente an die BindingSource-Instanz binden *Bei Bedarf einen BindingNavigator (zum Blättern) aus Form ziehen (Toolbox -> Daten) und die BindingSource-Instanz als BindingSource des Navigators festlegen

Dann hast Du alles zur Entwurfszeit festgelegt, hast maximale Designer-Unterstützung.

Hier findest Du weitere Infos zur BindingSource: MSDN: BindingSource-Komponente

Allerdings meckert der Compiler, weil NotificationEventArgs.Empty den Typ EventArgs zurück gibt, aber die Methode OnObjectAddedEvent den Typ NotificationEventArgs erwartet.
Mache ich da was falsch?
Oder ist die Lösung von unten genauso sauber?

NotificationEventArgs implementiert kein Empty. Das kommt von der Basisklasse. Wenn Du keine EventArgs benötigst, dann übergib doch einfach null.
Das ist völlig in Ordnung.