Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Fragen, Diskussion, Kritik zu Projekt ".NET Applikationsserver"
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Hi!

Nach einigen Mühen habichs schließlich auch geschafft, das Teil zum Laufen zu bringen, ich hoffe richtig.

Die letzte Hürde war, dass in den Settings als DataSource immer "Amilo" eingetragen war, wo "SQLServer" hätte stehen müssen - hab ich da iwas übersehen, oder ist das ein Deploying-Problem?

Jedenfalls habich von der .sln eine Kopie gemacht, und starte beide Solutions mit F5, bei der einen ist "ApplicationSever" als StartProjekt eingestellt, bei der anderen (die als 2. zu starten ist) "WindowsClient". So kann ich auf beiden Seiten debuggen.

Also ich hätte gedacht, von wegen Trennung von Business und DAL, dass im Namespace BusinessLogic in der Klasse ProductManager kein SQL-code sein dürfte.



      /// <summary>
      /// Gibt ein bestimmtes Produkt anhand seines Produktschlüssels zurück.
      /// </summary>
      /// <param name="productID">Eindeutiger Produktschlüssel</param>
      /// <returns>Tabelle mit Produktdatensatz</returns>
      public ArticleMasterDataSet.ProductsDataTable GetProduct(Guid productID) {
         // Parameterliste erstellen
         IList<DbParameter> parameters = new List<DbParameter>();
         parameters.Add(DataAccess.CreateParameter(ProductManager._dbFactory, "@productID", DbType.Guid, ParameterDirection.Input, productID));

         // Tabelle für Ergebnisrückgabe erzeugen
         ArticleMasterDataSet.ProductsDataTable result = new ArticleMasterDataSet.ProductsDataTable();

         // SQL-Code
         string sql = "SELECT * FROM Products WHERE [email protected]";

         // Datenbank abfragen
         DataAccess.Query(ProductManager._dbFactory, result, ProductManager._connectionString, sql, parameters);

         // Abfrageergebnisse zurückgeben
         return result;
      }
Weil das SQL ist SQLServer-Dialekt, und bei einer OleDBProviderFactory müsste man den gleich wieder umschreiben, oder?

Ich hätte iwie DatenObjekte erwartet, aber im Namespace BusinessLogic gibts nur diesen ProductManager, der im wesentlichen die Dinge tut, die ich bisher mit typisierten DataAdaptern gemacht hab, und ich dachte auch, typisierte DataAdapter wären DAL.

Ganz penibel würdich sogar das Design fragwürdig finden, weil die Methode heißt GetProduct, aber es wird nicht das einzelne Product zurückgegeben, sondern eine ganze DataTable (wo dann nur im ErfolgsFall genau ein Product drinne ist).

Ich hätte diese eine DataRow als Business-Objekt angesehen, weil deren Properties das Produkt beschreiben.

Dann haben mich noch 2 Sachen im Gui irritiert:
Erstmal kann ich verschiedene Products anlegen, mit derselben ProductNumber - welchen Sinn hat eine Artikelnummer, wenn sie nicht eindeutig ist?

Und dann habich noch nicht rausgefunden, wie man den ist-Stand eines Warenbestandes eingibt.
Im Gui-Code wird iwo die entsprechende Textbox disabled, aber es gibt scheints keine Stelle, wo sie wieder enabled wird.

so weit erstmal, kommt bestimmt noch mehr.

Huppla, jetzt habich meine eingangsFrage wieder gelöscht: Also wirklich wichtig als Layer-Anfänger ist mir die Frage: Welches Projekt ist welchem Layer zugeordnet, und woran erkennt man das?

Ach, ich kann ja schon mal anfangen, dass man sieht, ich geb mir Mühe (bei Gott!):
AppServer: DAL
AppServer.API: auch DAL, aber gewissermaßen abstrahiert. Alle Projecte außer Contract-Projekte haben einen AppServerAPI - Verweis
Die Contract-Projekte enthalten entsprechende Datasets - tätich für Business-Objekte halten, also BL
Die "BusinessLogic"-Projekte irritieren mich halt, wie oben schon erwähnt. Von dem, was da gemacht wird tätich sagen: DAL, aber sie heißen ja ausdrücklich BusinessLogic
Die Services-Projekte sind wohl Konkretisierungen der Contracte -> BL - wobei ich als Contrakte eiglich abstracte Basisklassen bevorzuge, die dann konkret zu überschreiben sind. Da könnte man dann den sich wiederholenden Code (Rollenüberprüfung und sowas) zusammenfassen.
Oder geht das nicht, weil der AppServer nur mit Interfaces diese Proxy-Dinger erstellen kann?

Hmm. Also AppServer + AppServer.API sind schon eine Klasse für sich, also ich mein ein Layer.

das habich auch noch nicht ganz geschnallt - beim AppServer kann man iwie Schnittstellen bestellen, die er selbst nicht kennt, und kriegt die per TCP zugeschickt.
Und beim Client(!!) sind die Konkretisierungen dieser Interfaces, und diese Konkretisierungen können ganz konkret auffm Server herumfummeln - hier DB-Zugriffe ausführen?
Also nochmal kurz: Obwohl Interfaces und Konkretisierungen nur beim Client implementiert sind, kann man den Code auffm Server ausführen lassen - richtig?

Also da schlaf ich jetzt 'ne Nacht drüber
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Zitat
Also ich hätte gedacht, von wegen Trennung von Business und DAL, dass im Namespace BusinessLogic in der Klasse ProductManager kein SQL-code sein dürfte.

Hallo ErfinderDesRades,

diesen Punkt finde ich auch etwas unschön. Ich schreibe auch gerade meine eigene Client/Server Anwendung und orientiere mich hier stark an diesem Beispiel. In diesem Punkt allerdings weiche ich ab. Ich habe mir hier deshalb pro Komponente einen DAL geschrieben, der dann Methoden a la "GetProducts(diverse Flags und Filterkriterien)" bereitstellt. Somit abstrahiert mein DAL soweit von der zugrunde liegenden Technik (MySQL, MS SQL, XML, CSV), dass die Business Schicht nichts darüber wissen muss und in keiner Weise von Änderungen am DAL betroffen wäre.

Ich weiß aber auch, dass Rainbird die Ansicht vertritt, dass man sich in der Praxis doch für eine Technik entscheidet und eben nicht alle Stärken einer Technik voll ausspielen kann, wenn man versucht voll und ganz davon zu abstrahieren. Und im Endeffekt ist das bezogen auf das Gesamtbeispiel eher ein Nebenschauplatz. Natürlich kannst du komponentenorientierte DataAccessLayer schreiben, die von der konkreten Technik abstrahieren und den Anwendungsserver trotzdem sonst voll und ganz genauso benutzen, wie er ist.

Gruß

Christoph
private Nachricht | Beiträge des Benutzers
Rainbird
myCSharp.de - Experte

Avatar #avatar-2834.jpg


Dabei seit:
Beiträge: 3953
Herkunft: Mauer

DAL-Verständnis

beantworten | zitieren | melden

Zitat von ErfinderDesRades
Also ich hätte gedacht, von wegen Trennung von Business und DAL, dass im Namespace BusinessLogic in der Klasse ProductManager kein SQL-code sein dürfte.
Eine separate Datenzugriffsschicht, wie Du sie Dir vorstellst, macht nur dann Sinn, wenn Du unterschiedliche Datenbanksysteme unterstützen willst/musst. Wenn Du das nicht musst, ist es nur unnötiger Aufwand und erhöht die Komplexität des Codes, wo es nicht sein müsste. Es handelt sich hierbei auch um eine Beispielapplikation, die zeigen soll, wie man mit Remoting verteilte Anwendungen erstellen kann. Wie genau der Datenzugriff implementiert ist spielt dafür keine Rolle. Da Datenzugriff eh das Lieblingsthema für Glaubenskriege unter Entwicklern ist, gibt es dazu jede Menge Beispiele und Artikel im Netz, die sich nur dem Für und Wieder diverser Datenzugriffskonstrukte widmen.

Ob der SQL-Code direkt in der Geschäftslogik steht, oder in einer anderen Klasse ist sowieso fast egal, da in jedem Fall eine Neukompilierung und ein neues Deployment erforderlich ist, wenn sich am SQL-Code bzw. an der DB-Struktur irgendwas ändert. Den SQL-Code aus der Geschäftslogik rauszuhalten wird - meiner Meinung und Erfahrung nach - häufig überbewertet. Da im SQL-Code auch teilweise Berechnungen, Umformatierungen und Entscheidungen stattfinden, gehört er definitiv zur Geschäftslogik und es ist nicht falsch ihn da hinzuschreiben, wo ich ihn hingeschrieben habe.
Zitat von ErfinderDesRades
Weil das SQL ist SQLServer-Dialekt, und bei einer OleDBProviderFactory müsste man den gleich wieder umschreiben, oder?
Ich verwende Provider-Factories, deshalb könnte man den Provider ganz einfach wechseln, ohne die Komponenten anzufassen.
Zitat von ErfinderDesRades
Ich hätte iwie DatenObjekte erwartet, aber im Namespace BusinessLogic gibts nur diesen ProductManager, der im wesentlichen die Dinge tut, die ich bisher mit typisierten DataAdaptern gemacht hab, und ich dachte auch, typisierte DataAdapter wären DAL.
In Produkten gibts eben nicht viel Logik, da Produkte nicht gebucht werden, das sind nur Stammdaten. Schau Dir deshalb man bitte die Lager-Komponente an. Dort ist eine kleine Buchungslogik integriert. DatenObjekte (Typisierte DataSets) findest Du in den Contract-Assemblies. Da Datenobjekte nur "dumme" Strukturen darstellen und nicht die Geschäftslogik enthalten gehören sie auch nicht in die Geschäftslogik. Außerdem müssen sie in einer separaten Assembly stehen, da sie vom Server und vom Client gleichermaßen benötigt werden.
Zitat von ErfinderDesRades
Ganz penibel würdich sogar das Design fragwürdig finden, weil die Methode heißt GetProduct, aber es wird nicht das einzelne Product zurückgegeben, sondern eine ganze DataTable (wo dann nur im ErfolgsFall genau ein Product drinne ist).
Ich hätte diese eine DataRow als Business-Objekt angesehen, weil deren Properties das Produkt beschreiben.
Man kann keine einzelne DataRow serialisieren. Du musst daran denken, dass alle Parameter und Rückgabewerte serialisierbar sein müssen. Du kannst eine verteilte Anwendung nicht genauso entwerfen, wie Du das bei einer 2-Tier Anwendung machen würdest. Mit dem peniblen objektorientierten Ansatz, fällst Du bei verteilten Anwendungen auf die Nase - das ist ein Versprechen!
Zitat von ErfinderDesRades
Erstmal kann ich verschiedene Products anlegen, mit derselben ProductNumber - welchen Sinn hat eine Artikelnummer, wenn sie nicht eindeutig ist?
Ups! Hab ich vergessen Unique zu setzen. Ist vorher noch niemand aufgefallen. Danke für den Hinweis.
Zitat von ErfinderDesRades
Und dann habich noch nicht rausgefunden, wie man den ist-Stand eines Warenbestandes eingibt.Im Gui-Code wird iwo die entsprechende Textbox disabled, aber es gibt scheints keine Stelle, wo sie wieder enabled wird.
Du musst Lagerbuchungen machen, um den Bestand zu verändern. Das ist bei Lagerverwaltung so üblich.
Zitat von ErfinderDesRades
Also wirklich wichtig als Layer-Anfänger ist mir die Frage: Welches Projekt ist welchem Layer zugeordnet, und woran erkennt man das?
Da bin ich jetzt penibel. Es geht hier nicht um Layers, sondern um Tiers! Das ist ein grundlegender Unterschied. Bei Layers werden die Schichten über Klassen und Assemblies getrennt (also nur logische Trennung), bei Tiers werden sie durch Prozesse (EXE-Dateien) getrennt.
Die Deutsche Sprache unterscheidet leider nicht zwischen Layers und Tiers, da sind beides Schichten. Deshalb verwechseln viele Leute eine 3-Tier-Architektur mit einer 3-Layer-Architektur. Das sind zwei Paar Stiefel.
Das Beispiel hat die folgenden 3 Tiers:
  • Datenschicht: SQL-Server
  • Geschäftsschicht (Mittelschicht): Applikationsserver
  • Präsentationsschicht: Windows.Forms-Client

Jede Schicht kann auf einem anderen Computer laufen, deshalb auch "verteilte Anwendung".

Du denkst viel zu freingranular. Vergiss mal die Klassen, denk in Komponenten!
Zitat von ErfinderDesRades
Hmm. Also AppServer + AppServer.API sind schon eine Klasse für sich, also ich mein ein Layer.

das habich auch noch nicht ganz geschnallt - beim AppServer kann man iwie Schnittstellen bestellen, die er selbst nicht kennt, und kriegt die per TCP zugeschickt.
Nein, so ist das nicht. Der AppServer ist ein Stück Infrastruktur - vergleichbar mit ADO.NET, was ein Stück Infrastruktur für den Datenzugriff ist. Der AppServer implementiert die nötige Infrastruktur, um Geschäftskomponenten zu hosten. Zu dieser Infrastruktur gehört auch das Sicherheitssystem und der Dienst für die Sperren. Das ist keine "fachliche" Geschäftslogik.
Die Hauptaufgabe des Applikationsservers ist das bereitstellen von Geschäftskompoenten (Diensten), welche die eigentliche Geschäftslogik kapseln. Per App.config werden Assemblies, die solche Dienste enthalten beim AppServer registriert. Diese lädt er dann beim Start und veröffentlicht sie über TCP/IP laut Konfiguration.
Die Clients können über einen bestimmten URL (ähnlich wie bei einem Webserver) diese Dienste Ansprechen und sich ein Proxy-Objekte für den Zugriff auf die Dienste erzeugen lassen. Dazu muss ein Client nur die Schnittstelle des Dienstes kennen, den er konsumieren will. Die Proxy-Erzeugung erledigt die generische ServiceFactory. Über den Proxy lassen sich die Methoden eines Dienstes aufrufen, die in der öffentlichen Schnittstelle freigegeben sind. Der Aufruf wird serialisiert, übers Netz geschickt, dort deserialisiert und verarbeitet. Beim Rückgabewert geht das Ganze dann Rückwärts.
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Zitat von Rainbird
Zitat von ErfinderDesRades
Und dann habich noch nicht rausgefunden, wie man den ist-Stand eines Warenbestandes eingibt.Im Gui-Code wird iwo die entsprechende Textbox disabled, aber es gibt scheints keine Stelle, wo sie wieder enabled wird.
Du musst Lagerbuchungen machen, um den Bestand zu verändern. Das ist bei Lagerverwaltung so üblich.
Ich krieg da nix hin. Bei mir gibts das Haupt-Fenster, und da kannich ArtikelStamm oder Läger auswählen.
Bei ArtikelStamm kannich nach Artikeln suchen oder neue anlegen.
Ich kann auch einen Artikel "öffnen" - da krieg ich allerlei Detail-Infos, u.a. eine Tabelle der Läger, die diesen Artikel lagern.
Bei dieser Lager-Tabelle gibts 3 ToolstripButtons: "neuer Bestand", "Bestandsänderung", "Parameter bearbeiten".
Leider geht von diesen dreien nur der erste, und bei dem ist, wie gesagt der Ist-Bestand disabled.

[Edit: Habs jetzt hingekriegt. Glaub, das Problem war, dassich einen Artikel am Wickel hatte, für den kein Lagerbestand eingetragen war]

Über den "Läger"-Dialog (ich glaub übrigens, der Plural von Lager ist Lager) kommich zu garnix, was aussieht, als könne ich da eine LagerBuchung machen. Da kann ich verschiedene Lager-Standorte cruden.

Ansonsten habichsemal verteilt, deine verteilte Anwendung

Habich 3 Solutions von gemacht, damit ich das iwie besser durchblicke, das mit den Tiers. (War mir schon bekannt, dass du zw. Tiers und Layers unterscheidest. Ich hab Tiers einfach für eine Art verschärfte Layers gehalten. Ob und welche logischen Konsequenzen sich aus der Trennung in veschiedene Executeables ergeben, mussich beizeiten mal hirnen)

Hier die Aufteilung (in Klammern die Projekte):
  • AppServer-Solution: (AppServer, AppServerAPI)
  • Services-Solution: (AppServerAPI, ArticleMasterContracts, ArticleMasterServices, BL-Articlemaster, InventoryContracts, InventoryServices, BL-Inventory)
  • Client-Solution: (AppServerAPI, ArticleMasterContracts, ArticleMasterWinGui, InventoryContracts, InventoryWinGui, ClientApplication)
Die Zusammenhänge sind schon recht tricky:
Die Services-Solution muß nur kompiliert werden. Statt einer "Ausführung" greift der PostBuild-Befehl
copy /Y "$(TargetDir)*.*" "$(SolutionDir)AppServer\$(OutDir)"
, mit dem sich die ArticleMasterServices-DLL und die InventoryServices-DLL ins Ausführungsverzeichnis des AppServers kopieren.
Letzterer "kennt" die Services nur durch die App.Config, wo scheinbar

      <service>
        <!-- Geschäftsdienst "Artikelstammverwaltung" -->
        <wellknown mode="SingleCall" type="ArticleMasterServices.ArticleMasterService, ArticleMasterServices" objectUri="ArticleMasterContracts.IArticleMasterService" />
        <!-- Geschäftsdienst "Lagermverwaltung" -->
        <wellknown mode="SingleCall" type="InventoryServices.InventoryService, InventoryServices" objectUri="InventoryContracts.IInventoryService" />
      </service>
die Typ-Angabe einer Service-Klasse mit einer "object-uri" eines Service-Interfaces gemappt wird.
Wenn nun der Client beim AppServer ein IArticleMasterService bestellt, bekommt er ein ArticleMasterService-Objekt, (welches IArticleMasterService implementiert).
(äh, Rainbird - ich hab deine superlangen Projektnamen radikal verkürzt, hoffe aber, es bleibt erkennbar, was mit was gemeint ist)

Die Client interessiert das alles nicht so rasend. Der bestellt per AppServerApi.ServiceFactory ein contractlich festgelegtes Service-Interface, und das kann halt Daten liefern.

So, ich hoffe, das alles richtig geschnackelt zu haben.

Und schichtentheoretisch betrachtet täte gelten:
DAT (Data Access Tier) ist der SQLServer erselbst.
BLT ist die Service-Solution
Gui is Gui
Und der AppServer selber ist Infrastruktur, und dassis nochmal was ganz anners.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von ErfinderDesRades am .
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Zitat
Habich 3 Solutions von gemacht, damit ich das iwie besser durchblicke, das mit den Tiers.

Für meinen Geschmack ist das der falsche Ansatz. Wenn du eine Anwendung komponentenorientiert aufbaust, solltest du in erster Linie in Komponenten und in zweiter Linie in Schichten denken. Wenn du das ganze also in einzelne Solutions aufteilen willst, dann würde ich eher die Komponenten (Lagerverwaltung, Artikelverwaltung) in Solutions ausgliedern. Wenn du dir dann eine Komponente wie die Lagerverwaltung genauer ansiehst, besteht die nun wiederum aus mehreren Schichten. Einer Präsentationschicht, einer Businessschicht und ggf. einer Datenschicht (Bei Rainbird gibt es die komponentenorientierte Datenschicht nun gerade nicht).
private Nachricht | Beiträge des Benutzers
jaensen
myCSharp.de - Experte

Avatar #avatar-2657.png


Dabei seit:
Beiträge: 2885
Herkunft: München

beantworten | zitieren | melden

Zitat von ErfinderDesRades
Über den "Läger"-Dialog (ich glaub übrigens, der Plural von Lager ist Lager)
;-) Guckst du Duden:
Zitat von Duden
La|ger, das; -s, - u. Läger [spätmhd. lager, unter Anlehnung an "Lage" für mhd. leger, ahd. ...
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Zitat von bvsn
Zitat
Habich 3 Solutions von gemacht, damit ich das iwie besser durchblicke, das mit den Tiers.

Für meinen Geschmack ist das der falsche Ansatz. Wenn du eine Anwendung komponentenorientiert aufbaust, solltest du in erster Linie in Komponenten und in zweiter Linie in Schichten denken. Wenn du das ganze also in einzelne Solutions aufteilen willst, dann würde ich eher die Komponenten (Lagerverwaltung, Artikelverwaltung) in Solutions ausgliedern. Wenn du dir dann eine Komponente wie die Lagerverwaltung genauer ansiehst, besteht die nun wiederum aus mehreren Schichten. Einer Präsentationschicht, einer Businessschicht und ggf. einer Datenschicht (Bei Rainbird gibt es die komponentenorientierte Datenschicht nun gerade nicht).
Versteh ich nicht, Artikelverwaltung und Lagerverwaltung sind hier mittnander im WniGuiClient verquickt.
Nämlich indem man einen Artikel wählt, kann man sich seine Läger ( ) anzeigen lassen, und auch den Lagerbestand in einem der Läger ändern.
Das sehe ich nicht, wie auseinanderpflücken.

Wassich auseinandergepflückt habe ist auch "as it is" - am Code habich nix geändert. Habe nur transparent gemacht, welches projekt welche anderen "kennen" muß.

Und da zeigt sich interessanterweise, daß die konkreten Service-Implementationen keiner kennen muß, es reicht, dass sie in AppServer.Config mit den Service-Contracts-Interfaces geappt werdn (und dass die Dlls in sein Arbeitsverzeicchnis kopiert wern).

Nun könnte mans noch weiter treiben, und die Services je in eine eigen Solution packen - die müssen sich untereinander schon mal gar nicht kennen. Aber das kannman auch lassen - ich jedenfalls .

Aber das WinGui aufteilen in Artikelverwaltung und Lagerverwaltung? - da müssteman 'ne Menge neu schreiben, und das wäre eine erhebliche Änderung der Struktur - der User hätte 2 Anwendungen statt einer.

Komponentologisch tätich denken, kann man evtl. noch eine WebClient-Komponente dranmachen, iwas mit ASP.Net.
Und dem stünde dann frei, ob er noch extra Services zufügen möchte, und/oder die bisherigen vorhandenen mit nutzt.
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von ErfinderDesRades am .
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Ich glaube da hast du mich falsch verstanden. Ich möchte auch nix am Code ändern :-)

Aber wenn du das ganze in einzelne Solutions packen möchtest, solltest du dich imho an den Projektmappenordnern orientieren, die Rainbird bereits erstellt hat.

Dann bekommst du:

- Eine Applikationserver Solution (Appserver, Appserver.API)
- Eine Artikelkomponenten Solution (theoretisch: BL, GUI, ggf. DAL -> in seinem Beispiel einfach alles unterhalb von Rainbird.Examples.NTier.ArtikelMaster)
- Eine Lagerkomponenten Solution (analog)
- Eine Client Solution

Natürlich kannst du auch anfangen und die Komponenten (Artikelverwaltung, Lagerverwaltung) noch weiter in einzelne Solutions zerlegen (den Schichten nach). Ich denke aber nicht, dass das unbedingt sein muss.
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Mir scheint ein Begriffs-Problem.
Eiglich wollte ich ja was über Layer lernen. und hab Layer und Tier nicht groß unterschieden.
Daraufhin erklärt mir Rainbird
Zitat von Rainbird
Da bin ich jetzt penibel. Es geht hier nicht um Layers, sondern um Tiers! Das ist ein grundlegender Unterschied. Bei Layers werden die Schichten über Klassen und Assemblies getrennt (also nur logische Trennung), bei Tiers werden sie durch Prozesse (EXE-Dateien) getrennt.
Die Deutsche Sprache unterscheidet leider nicht zwischen Layers und Tiers, da sind beides Schichten. Deshalb verwechseln viele Leute eine 3-Tier-Architektur mit einer 3-Layer-Architektur. Das sind zwei Paar Stiefel.
Das Beispiel hat die folgenden 3 Tiers:
  • Datenschicht: SQL-Server
  • Geschäftsschicht (Mittelschicht): Applikationsserver
  • Präsentationsschicht: Windows.Forms-Client

Jede Schicht kann auf einem anderen Computer laufen, deshalb auch "verteilte Anwendung".

Du denkst viel zu freingranular. Vergiss mal die Klassen, denk in Komponenten!

Also denkich, ein Tier ist eine Komponente ist ein Prozess, die können auf verschiedenen Rechnern laufen - verteilte Anwendung.
D.h. für mich: eine Solution, ich mach F5, dann läuft die.

2 Solutions braucht man ja zwangsweise, jedenfalls, wenn man sowohl im AppServer als auch inne ClientApp Haltepunkte setzen will.

Eine Solution Artikelkomponenten, wie du sie vorschlägst, bringt nix - die kann man nicht laufen lassen.

Verwendest du den Begriff "Komponente" iwie anners als Rainbird?
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Ich glaube da hast du etwas falsch verstanden. Meiner Definition nach geht der Begriff Tier einfach noch weiter als Layer und meint damit einen Layer, der aber theoretisch auch auf einer anderen Maschine laufen könnte.

Wenn du die Anwendung zu Übungszwecken nach Tiers aufteilen möchtest, kannst du das natürlich machen. Ich wollte eigentlich nur sagen, dass das in der Praxis nicht sinnvoll ist und man die Anwendung eher in Komponenten aufteilen sollte. Eine Komponente besteht dann wiederum aus Schichten. Und dieses Beispiel ist nach meinem Verständnis ganz klar komponentenorientiert aufgebaut.
Zitat
Eine Solution Artikelkomponenten, wie du sie vorschlägst, bringt nix - die kann man nicht laufen lassen.

Eine Komponente muss nicht zwangläufig für sich alleine lauffähig sein. Wird sie auch in den allermeisten Fällen nicht.
Zitat
Verwendest du den Begriff "Komponente" iwie anners als Rainbird?

Ich glaube, dass wir hier die gleiche Definition haben. Wobei Rainbird in dem ganzen natürlich deutlich sattelfester ist. Ich hoffe aber im Kern mit seiner Definition übereinzustimmen ;-)
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Christoph Burgdorf am .
private Nachricht | Beiträge des Benutzers
Rainbird
myCSharp.de - Experte

Avatar #avatar-2834.jpg


Dabei seit:
Beiträge: 3953
Herkunft: Mauer

Begriffs-Chaos

beantworten | zitieren | melden

Also um das Begriffs-Cahos zu entwirren:

Tiers =! Komponenten

man könnte eher sagen:

Dienste == Komponenten

Folgender BLOG-Eintrag macht das vielleicht klarer: http://yellow-rainbird.de/blogs/rainbird/archive/2008/11/11/dienste-einer-n-tier-anwendung-in-eine-klare-hierarchie-bringen.aspx

Ich unterscheide z.B. zwischen GUI-Komponenten und Geschäftskomponenten. Eine Geschäftskomponente besteht aus Geschäftslogik-Klassen, Service-Klassen (um die Geschäftslogik von der Infrastruktur unabhängig zu halten) und Kontrakten (Schnittstellen und Datenklassen). Was zusammen eine komplette Komponente bildet, ist auf den ersten Blick nicht unbedingt klar, da der Code auf verschiedene Assemblies aufgeteilt ist. Dafür gibt es aber Namensräume, die Typen über Assemblies hinweg zusammenfassen.

GUI-Komponenten sind in den meisten Fällen Forms und UserControls. Idealerweise UserControls + Plug-In-Konfiguration.

Der Begriff Komponente ist naturgemäß ist etwas schwammig.

In wie viele Solutions Du das aufteilst, ist absolut egal. Was zählt ist die Packetierung der Komponenten in Assemblies.

Die GUI ist überigens aus verschidenen UserControls zusammen gebaut. Das Haupt-GUI-Projekt fügt die einzelnen Teile zu einem Ganzen zusammen.
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Zitat von Rainbird
Also um das Begriffs-Cahos zu entwirren:
Du meinst: das Chaos komplett machen?

Jedenfalls führst du noch (!!) einen Begriff ein - "Dienst", und Komponente scheint mir widersprüchlich verwendet - aber ich soll in Komponenten denken! (das überlegichmirnoch )
Zitat
Tiers =! Komponenten
Jo, was ist dann nun ein Tier?
Zitat
Dienste == Komponenten
Und was ein Dienst?
Zitat
Folgender BLOG-Eintrag macht das vielleicht klarer: http://yellow-rainbird.de/blogs/rainbird/archive/2008/11/11/dienste-einer-n-tier-anwendung-in-eine-klare-hierarchie-bringen.aspx
Sieht mir bischen aus wie ein gelockertes Layer-Modell: Man darf in eine Richtung durch die nachbarschicht hindurch-grabschen.
Gefällt mir glaubich, weil so Methoden, die einen Aufruf nur durchreichen, magichnichso.
Zitat
Ich unterscheide z.B. zwischen GUI-Komponenten und Geschäftskomponenten. Eine Geschäftskomponente besteht aus Geschäftslogik-Klassen, Service-Klassen (um die Geschäftslogik von der Infrastruktur unabhängig zu halten) und Kontrakten (Schnittstellen und Datenklassen).

Was zusammen eine komplette Komponente bildet, ist auf den ersten Blick nicht unbedingt klar, da der Code auf verschiedene Assemblies aufgeteilt ist. Dafür gibt es aber Namensräume, die Typen über Assemblies hinweg zusammenfassen.
Hmm. Im Application-Server ist ein Projekt genau ein Namensraum. Kein Namespace erstreckt über mehrere Projekte. Oder meinst du die Segmentierung der Namensraum-Namen durch '.'?
Dann wäre bei
Rainbird.AppServer
Rainbird.AppServer.API
Rainbird.Examples.NTier.ClientApplication
Rainbird.Examples.NTier.ArticleMaster.Contracts
Rainbird.Examples.NTier.ArticleMaster.BusinessLogic
Rainbird.Examples.NTier.ArticleMaster.Services
Rainbird.Examples.NTier.ArticleMaster.WindowsGUI
Rainbird.Examples.NTier.Inventory.Contracts
Rainbird.Examples.NTier.Inventory.BusinessLogic
Rainbird.Examples.NTier.Inventory.Services
Rainbird.Examples.NTier.Inventory.WindowsGUI
"Rainbird.Examples.NTier" eine Komponente?
Und "Rainbird.Examples.NTier.ArticleMaster" eine untergeordnete Komponente?
Zitat
GUI-Komponenten sind in den meisten Fällen Forms und UserControls. Idealerweise UserControls + Plug-In-Konfiguration.
Ein UserControl ist aber doch keine Assembly - aber trotzdem eine Komponente?
Zitat
Der Begriff Komponente ist naturgemäß ist etwas schwammig.
Du sagst es
Zitat
In wie viele Solutions Du das aufteilst, ist absolut egal. Was zählt ist die Packetierung der Komponenten in Assemblies.
Kannst du mich mal aufklären, was aus obigem Frame die Tiers sind, die Komponenten und die Dienste? Gilt das "Dienste == Komponenten" in dem Sinne, dass jeder Dienst ein Projekt ist?
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Rainbird
myCSharp.de - Experte

Avatar #avatar-2834.jpg


Dabei seit:
Beiträge: 3953
Herkunft: Mauer

beantworten | zitieren | melden

Zitat von ErfinderDesRades
Jedenfalls führst du noch (!!) einen Begriff ein - "Dienst", und Komponente scheint mir widersprüchlich verwendet - aber ich soll in Komponenten denken! (das überlegichmirnoch ;))
Der Begriff Dienst wird im Webservice-Umfeld benutzt, um Codeeinheiten eines bestimmten fachlichen Bereichs zu beschreiben, deren Methoden (auch Operationen genannt) über standardisierte Protokolle (z.B: SOAP) von Clients konsumiert werden können.
So ähnlich verhält es sich auch mit meinen Geschäftskomponenten. Ich hatte ja auch nur gesagt "man könnte eher sagen a==b" und nicht "a ist dasselbe wie b".
Zitat
Jo, was ist dann nun ein Tier?
Tiers sind die grobe Grundeinteilung (im Falle von 3-Tier-Architektur: Präsentationsschicht, Geschäftsschicht, Datenschicht). Wäre Komponente==Tier, dann würde es eine riesige Molloch-Komponente mit dem Namen BusinessLogic geben in der Artikel und Lagerverwaltung gleichermaßen implementiert sind. Das wäre ein monolithischer Ansatz. Ein komponentenorientierter Ansatz ist, wenn die Tiers aus einzelnen Komponenten zusammengesetzt sind, also z.B. 1 x Artikelverwaltrungs-Komponente + 1 x Lagerverwaltungs-Komponente. Genauso ist es im Presentation-Tier (GUI): 1 x Artikelverwaltungs-Froms-und-Controls + 1 x Lagerverwaltungs-Forms-und-Controls. Hinzu kommt die Infrastruktur. Das ist auf der Business-Tier (Geschäftsschicht) der AppServer mit seinen Diensten wie Sicherheit, Logging, etc. und auf der Persentation-Tier (GUI) die MDI-Shell + Glue-Forms (z.B. das Artikel-Formular im GUI-Hauptprojekt , welches nur den Sinn hat, die GUI-Komponenten der Artikel- und Lagerverwaltung miteinander zu integrieren).
Zitat
Und was ein Dienst?
Eine Klasse + eine Schnittstelle, deren Aufagbe es ist, die eigentliche Geschäftslogik von der Infrastruktur möglichst (ganz wird es sich selten vermeiden lassen) unabhängig zu halten und bestimmte Methoden der Geschäftslogik übers Netzwerk aufrufbar zu machen. Im Dienstcode wird z.B. die Kultur-Information des Aufrufers an den aktuellen Worker-Thread gehängt und es werden die Rechteprüfungen durchgeführt. Ein Dienst integriert ein Stück Geschäftslogik in die Infrastruktur (den AppServer).
Zitat
Sieht mir bischen aus wie ein gelockertes Layer-Modell: Man darf in eine Richtung durch die nachbarschicht hindurch-grabschen.
Gefällt mir glaubich, weil so Methoden, die einen Aufruf nur durchreichen, magichnichso.
Das hast Du falsch verstanden. Da wird nichts durchgereicht. Wenn Du z.B. die Geschäftslogik schreibst, um Lieferscheine zu verwalten, dann benötigst Du Daten aus dem Artikelstamm und Du musst Lagerbuchungen durchführen. Also muss die Kompnente "Lieferschein-Verwaltung" Methoden der Komponenten Artikelstammverwaltung und Lagerverwaltung aufrufen. Dazu darf sie aber nicht die eigentliche Implementierung (BusinessLogic-Assemblies) dieser beiden anderen Komponenten direkt aufrufen, da sonst Verweise nötig sind. Stattdessen muss die Lieferschein-Verwaltung nur Verweise auf die Kontrakte haben und kann (wie das auch z.B. ein GUI-Client tun würde) sich über die ServiceFactory Proxies für den Zugriff auf die beiden anderen Komponenten über deren Dienste (also der Teil der Geschäftskomponente, der die Geschäftslogik über Remoting konsumierbar macht) erzeugen lassen. Die Kommunikation läuft so entkoppelt ab und es spielt keine Rolle, wo die konsumierten Komponenten laufen. Vielleicht läuft die Artikelstammverwaltung auf einen ganz anderen Applikationsserver? Ein Tier muss nicht komplett auf einem Computer laufen. Man könnte das auch auf verschiedene Computer verteilen. Dazu benötigt man aber Komponenten und muss in Komponenten denken und nicht in Schichten (Tiers). Selbst wenn man die Komponenten nicht auf verschiedene Computer aufteilt, ist der Ansatz sinnvoll, da die Komponenten so sehr autonom sind. Die Architektur zwingt den Entwickler, autonome Komponenten zu schreiben und nicht über Verweise auf die Implementierung alles miteinander zu verwursteln.

Noch autonomer geht nur, wenn man die Komponenten orchestriert, also jede Komponente auch für alles externe eine eigene Schnittstelle hat und die Kommunikation zwischen den Komponenten über Mappings läuft. Das wird dann im Volksmund häufig als SOA bezeichnet. Ist innerhalb einer Anwendung aber meistens zu Aufwändig und der Nutzen ist Fragwürdig (es gibt ja auch noch nicht wirklich Langzeit-Erfahrungen mit SOA, da der Begriff sehr schwammig ist und es ein relativ neuer Hype ist). Außerdem müsste man auch jede Komponente mit der Infrastruktur orchestrieren, was noch aufwändiger und abstrakter ist, da man dann z.B. nicht mehr innerhalb einer Komponente davon ausgehen kann, dass eine Windows-Authentifizierung verwendet wird. Es müsste sogar so sein, dass jede Komponente ihre eigene Datenbank hat, da man sonst nicht beliebige Komponenten kombinieren kann.
Deshalb vergessen wird mal SOA, das klappt so nicht.

Wenn Komponenten nun aber nicht nur von der Presentation-Tier aus konsumiert werden, sondern sich auch gegenseitig konsumieren, muss diese Kommunikation geregelt ablaufen. Es muss festgelegt werden, welche Komponenten welche anderen Komponenten überhaupt aufrufen dürfen. Die Komponente Artikelstammverwaltung darf z.B. niemals die Komponente Lieferschein-Verwaltung aufrufen, aber umgekehrt. Um den Überblick zu behalten, organisiert man die Komponenten in Ebenenen (Achtung! Das sind weder Tiers noch Layers). Diesen Ebenen kann man auch Namen geben, wenn's passt, wie z.B. Stammdatenebene oder Workflowebene. Wie sowas konkret aussehen kann, zeigt das Diagramm im erwähnten Blog-Eintrag.
Zitat
Hmm. Im Application-Server ist ein Projekt genau ein Namensraum. Kein Namespace erstreckt über mehrere Projekte. Oder meinst du die Segmentierung der Namensraum-Namen durch '.'?
Dann wäre bei
Rainbird.AppServer
Rainbird.AppServer.API
Rainbird.Examples.NTier.ClientApplication
Rainbird.Examples.NTier.ArticleMaster.Contracts
Rainbird.Examples.NTier.ArticleMaster.BusinessLogic
Rainbird.Examples.NTier.ArticleMaster.Services
Rainbird.Examples.NTier.ArticleMaster.WindowsGUI
Rainbird.Examples.NTier.Inventory.Contracts
Rainbird.Examples.NTier.Inventory.BusinessLogic
Rainbird.Examples.NTier.Inventory.Services
Rainbird.Examples.NTier.Inventory.WindowsGUI
"Rainbird.Examples.NTier" eine Komponente?
Und "Rainbird.Examples.NTier.ArticleMaster" eine untergeordnete Komponente?
Rainbird.Examples.NTier ist der Namensraum der kompletten Lösung. Es gibt zwei Module, die aus jeweils einer Geschäftskomponente und einer GUI-Komponente bestehen:

Rainbird.Examples.NTier.ArticleMaster
Rainbird.Examples.NTier.Inventory
Zitat
Ein UserControl ist aber doch keine Assembly - aber trotzdem eine Komponente?
Ja, da man aus UserControls ähnlich einem Baukastensystem komplette Oberflächen aufbauen kann.
Zitat
Kannst du mich mal aufklären, was aus obigem Frame die Tiers sind, die Komponenten und die Dienste? Gilt das "Dienste == Komponenten" in dem Sinne, dass jeder Dienst ein Projekt ist?
Tiers kann man nicht direkt auf Assemblies oder Namensräume oder Klassen abbilden. Tiers sind "nur" eine architektonische Planungseinheit.
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Vielen Dank.

Son Satz Definitionen brauchich gelegentlich, sonst habich das Gefühl, mein Denken verkommt zum Mutmaßen.

Aber ich hab anneres Problem:
Habichjetzt auch einen Service dranpatchen wollen, die ServiceFactory macht da sogar mit, nur der Service kann nichts tun.
  • Die Datenbank um eine Table erweitert
  • dazu passendes Dataset gebastelt
  • einen Service geschrieben, inkl. Interface
  • inne AppServer.app.config einen Eintrag zugefügt
    
             <!-- Geschäftsdienst "Eckard Spezial" -->
             <wellknown mode="SingleCall" type="EService2.EService, EService" objectUri="EService2.IEService" />
    


   public partial class EInfoForm : Form {

      private IEService _eService;

      public EInfoForm() {
         InitializeComponent();
         _eService = ServiceFactory<IEService>.CreateProxy();
         _eService.FillTable(eDataset1.ETable);    // <-hier der Fehler
      }
   }

Huch - im Detail hat die Fehlermeldung diese "LOG"-Zeilen - das kennichnochgarnicht:
System.BadImageFormatException was unhandled
  Message=Die Datei oder Assembly "EService" oder eine Abhängigkeit davon wurde nicht gefunden. Die Assembly wird von einer Laufzeit erstellt, die aktueller als die derzeit geladene Laufzeit ist, und kann nicht geladen werden.
  Source=mscorlib
  FileName=EService
  FusionLog==== Zustandsinformationen vor Bindung ===
LOG: Benutzer = Admin-PC\Admin
LOG: DisplayName = EService
 (Partial)
LOG: Appbase = file:///C:/Programming/CodeDownloads/CsDownloads/myCSharp/ApplicationServer/AppServer/bin/Debug/
LOG: Ursprünglicher PrivatePath = NULL
Aufruf von Assembly : (Unknown).
===
LOG: Diese Bindung startet im default-Load-Kontext.
LOG: Die Anwendungskonfigurationsdatei wird verwendet: C:\Programming\CodeDownloads\CsDownloads\myCSharp\ApplicationServer\AppServer\bin\Debug\AppServer.vshost.exe.Config
LOG: Die Computerkonfigurationsdatei von C:\Windows\Microsoft.NET\Framework\v2.0.50727\config\machine.config wird verwendet.
LOG: Die Richtlinie wird derzeit nicht auf den Verweis angewendet (private, benutzerdefinierte, teilweise oder pfadbasierte Assemblybindung)
LOG: Download von neuem URL file:///C:/Programming/CodeDownloads/CsDownloads/myCSharp/ApplicationServer/AppServer/bin/Debug/EService.DLL.
ERR: Das Setup der Assembly konnte nicht abgeschlossen werden (hr = 0x8013101b). Die Suche wurde beendet.

Jo, das mit den verschieden aktuellen Laufzeiten versteh ich ühaupt nicht, auch finde ich keinen prinzipiellen Unterschied zw. meinem Service und den deinigen.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von ErfinderDesRades am .
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Hallo ErfinderDesRades,

deine DLL heißt EService.dll und liegt im Verzeichnis des Anwendungsservers?

Gruß

Christoph
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

nein, die dll heißt EService2, und die Service-Klasse dadrin heißt EService.
Aber liegt im AppServer-ausführungsverzeichnis, auchso mittm PostBuild gemacht:
copy /Y "$(TargetDir)*.*" "$(SolutionDir)AppServer\$(OutDir)"
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
FZelle
myCSharp.de - Experte



Dabei seit:
Beiträge: 10074

beantworten | zitieren | melden

Du solltest evtl mal schauen, was bei type="" (Vollqualifizierter Assemblyname) wirklich hinkommt.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Hallo ErfinderDesRades,

tröste dich, ich habe den Fehler auch gemacht! Da gehört der tatsächliche Name der DLL hin ;-)

Gruß

Christoph

Edit: Hallo vergessen :-)
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Christoph Burgdorf am .
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

wassichda hingeschrieben habe, habichja gepostet. Vermutlich meinst du, ich solle schauen, was da hinkommen sollte?

Das weißich natürlich nicht, da habichnur sinngemäß von Rainbirds Einträgen abgekupfert.
Aber nicht ganz korrekt abgekupfert - und siehe - jetzt gehtes! (dafür geht was anneres nicht, aber ich probier erstmal selbst)

Aber verstehen tu ich davon kaum was

      <!-- Dienste -->
      <service>
        <!-- Geschäftsdienst "Artikelstammverwaltung" -->
        <wellknown mode="SingleCall" type="ArticleMasterServices.ArticleMasterService, ArticleMasterServices" objectUri="ArticleMasterContracts.IArticleMasterService" />
         <!-- Geschäftsdienst "Lagermverwaltung" -->
         <wellknown mode="SingleCall" type="InventoryServices.InventoryService, InventoryServices" objectUri="InventoryContracts.IInventoryService" />
         <!-- Geschäftsdienst "Eckard Spezial" -->
         <wellknown mode="SingleCall" type="EService2.EService, EService2" objectUri="EService2.IEService" />
      </service>
Wo findet man dokumentiert, welche Tags und welche Attribute welche Auswirkung haben?
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Christoph Burgdorf
myCSharp.de - Member

Avatar #avatar-2915.jpg


Dabei seit:
Beiträge: 365
Herkunft: Hannover

beantworten | zitieren | melden

Du findest dazu sicherlich in der MSDN Hilfe, wenngleich ich auch auf die Schnelle nix passenden gesehen habe. Ansonsten ist dieser Link ganz hilfreich:
http://www.csharphelp.com/archives2/archive460.html

Außerdem habe ich bereits ähnliche Fragen in diesem Thread gestellt. Rainbird hat darauf bereits sehr detailiert geantwortet. Geh mal auf Seite 3 in diesem Thread und schau nach:
Zitat
Das ist viel einfacher, als Du denkst. RemotingConfiguration.Configure parst und verarbeitet die angegebene Konfigurationsdatei (also die App.config). Das bedutetm dass nach Aufruf von Configure der Server bereits voll Einsatzbereit ist und alle in der App.config eingetragenen Dienste automatisch schon als WellKnowServiceTypes registriert sind. Im einfachsten Fall sieht ein Remoting-Host so aus[...]
private Nachricht | Beiträge des Benutzers
Rainbird
myCSharp.de - Experte

Avatar #avatar-2834.jpg


Dabei seit:
Beiträge: 3953
Herkunft: Mauer

Doku

beantworten | zitieren | melden

Zitat von ErfinderDesRades
Wo findet man dokumentiert, welche Tags und welche Attribute welche Auswirkung haben?
Schaust Du hier: http://msdn.microsoft.com/de-de/library/z415cf9a(VS.80).aspxhttp://msdn.microsoft.com/de-de/library/kwdt6w2k(VS.80).aspx
private Nachricht | Beiträge des Benutzers
ErfinderDesRades
myCSharp.de - Experte

Avatar #avatar-3151.jpg


Dabei seit:
Beiträge: 5409

beantworten | zitieren | melden

Hupsalalala!

Jo, da binnich beim Lesen der Seiten wohl statt auf den "nächsten"-Button auf den "letzten"-Button gekommen, und dachte, ich hätte den ganzen Thread schon intus.
Sorry, das eine oder andere hättich dann wohl nicht gefragt, hmm.

@rainbird: Danke für die Doku

So, jetzt habichaber doch noch ein paar dumme Fragen, zu den Geheimnissen der Remoterei.

Erstmal versuchichs mirselbst zu erklären:
Ein Service (Damit meine ich jetzt, was man braucht, um einen neuen Service dranzupatchen) besteht zum einen aus einer Service-Contract-Dll, in der die DatenObjekte implementiert sind.
Die Methoden, die der Service ausführen können soll, sind dort als Interface definiert ("IService" nennichdas mal).
In einer anderen Dll ist die Konkretisierung des Interfaces implementiert (bei Rainbird die "Business-Logic").
Der Client hat die Contract-Dll eingebunden.
Beim Server liegen Contract-Dll, Konkretisierungs-Dll und Abhängigkeiten herum.
Per App.Config werden Contract und Konkretisierung dem AppServer bekannt gemacht (nichts wird eingebunden!).

Wenn der Client zur Laufzeit einen IService bei der ServiceFactory bestellt (mit Activator.GetObject()), bekommt er ein IDingsbums (Proxy), dessen Methoden im Client aufrufbar sind, die aber im Server in der Konkretisierungs-Dll ausgeführt werden - klarer Fall von Hexerei!
Parameter und Rückgabewerte der Methoden dieses Proxies werden beim Methoden-Aufruf serialisiert und wandern durch den Channel (tcp oder ipc, weitere?).

Zu beachten:
  • Die konkreten ServiceObjekte müssen von MarshalByRefObject erben.
  • Die Datentypen der Methoden -Parameter und -Rückgabewerte müssen serialisierbar sein.
  • Parameter und Rückgabewerte sollten leichtgewichtig sein, weil wandern pro Aufruf durch den Channel.
  • Achtung: Parameter zeigen ein geändertes Verhalten - sind nicht mehr als Referenz manipulierbar!!
    ZB die Methode Service.Fill(DataTable) bringt nix, weil sie die DataTable beim Server befüllt, aber nicht zurückschickt.

Soweit.

Könnte man nun theoretisch sonem AppServer eine neue Konkretisierungs-Dll unterschieben, ohne ihn abzuschalten?
Neue Services hinzupatchen ebenso?

Allerdings nicht mit diesem AppServer, weil der nur zum Start die config auswertet, und das wars dann, odr?
Aber geht immerhin ohne neu-compilieren - aber ist das nun so ein großer Gewinn?

Mühe macht mir auch, einen neuen Service zu entwickeln. Weil die Konkretisierungs-Dll nirgends wirklich eingebunden ist, kann man keine Haltepunkte setzen.
Immerhin erfreulich eine andere Hexerei: Im FehlerFall stoppt der Code in der Fehlerzeile, ob die nun zur AppServerSolution gehört, oder nur drangepatcht ist.
Und statt Haltepunkte kannmanja Debugger.Break() eincoden.
Der frühe Apfel fängt den Wurm.
private Nachricht | Beiträge des Benutzers
Rainbird
myCSharp.de - Experte

Avatar #avatar-2834.jpg


Dabei seit:
Beiträge: 3953
Herkunft: Mauer

Hexerei

beantworten | zitieren | melden

Zitat von ErfinderDesRades
Parameter und Rückgabewerte der Methoden dieses Proxies werden beim Methoden-Aufruf serialisiert und wandern durch den Channel (tcp oder ipc, weitere?).
Es gibt noch einen HTTP-Channel, der nützlich ist, wenn man durch Firewalls muss. Remoting ist aber auch erweiterbar. Es gibt z.B. eine Erweiterung für XML-RPC-Kommunikation: http://www.xml-rpc.net/
Zitat von ErfinderDesRades
  • Parameter und Rückgabewerte sollten leichtgewichtig sein, weil wandern pro Aufruf durch den Channel.[/quote]
    Es ist natürlich gut, wenn sie leichtgewichtig sind, aber trotzdem gilt die Faustregel "Klotzen statt kleckern!". Jeder Methodenaufruf hat Kommunikations-Overhead, deshalb ist es besser, viele Daten an einem Stück zu übertragen, statt bröckchenweise. Der absolute Horror sind statuswahrende Objekte, auf deren Properties remote zugegriffen wird. Gut sind dagegen statuslose Objekte, die ihre Daten am Stück (z.B. als ganze Objekte, DataTables oder DataSets) übertragen.
    Zitat von ErfinderDesRades
    Könnte man nun theoretisch sonem AppServer eine neue Konkretisierungs-Dll unterschieben, ohne ihn abzuschalten?
    Neue Services hinzupatchen ebenso?
    Neue Services hinzupatchen sollte relativ einfach machbar sein. Du kannst ja Services auch zur Laufzeit mit Code veröffentlichen.
    Im laufenden Betrieb Services updaten ist schon schwieriger. Du müsstest eine zweite AppDomain hochziehen und einen Remoting-Nachrichtendispatcher schreiben, der während des Updates alle Nachrichten auf die zweite AppDomain umleitet. Generell dürftest Du die Dienste nicht in der Standard-AppDomain laufen lassen, da man in der Standard-AppDomain keine Assemblies mehr entladen kann.
    Ein weiteres Problem wäre die Kompatiblität. Es ist nicht garantiert, dass der upgedatete Service zum vorherigen kompatibel ist.
    Zitat von ErfinderDesRades
    Allerdings nicht mit diesem AppServer, weil der nur zum Start die config auswertet, und das wars dann, odr?
    Aber geht immerhin ohne neu-compilieren - aber ist das nun so ein großer Gewinn?
    Ja, momentan wird die App.config nur beim Start ausgewertet.
    Ein großer Gewinn ist es schon, ohne Neukompilierung neue Dienste zu deployen. Du musst nur per XCopy die neuen Assemblies auf den Produktiv-Server kopieren, einen Entrag in der App.config machen und den Applikationsserver-Windowsdienst (in Produktivumgebungen ist es selten eine Konsolenanwendung) neu starten.
    Zitat von ErfinderDesRades
    Mühe macht mir auch, einen neuen Service zu entwickeln. Weil die Konkretisierungs-Dll nirgends wirklich eingebunden ist, kann man keine Haltepunkte setzen.
    Natürlich kann man Haltepunkte setzen. Entweder legt man mehrere Startprojekte für die Mappe fest, wenn man alles lokal hat, oder man Klickt auf Menü Debuggen und wählt dann "An Prozess anhängen ...". So kann man den Debugger an jeden laufenden Windows-Prozess anhängen. Man kann beliebig viele Prozesse gleichzeitig Debuggen in Visual Studio. Selbst wenn der Applikationsserver auf einem anderen Computer läuft, auf dem kein Visual Studio installiert ist, kannst Du trotzdem Haltepunkte verwenden. Du musst auf dem entfernten Server nur den Visual Studio-Remotedebugger starten (das geht auch über eine Netzwerkfreigabe auf dem Entwickler-PC; Einfach die EXE des Remotedebuggers starten). Über den "Prozess anhängen" Dialog kannst Du den lokalen Debugger dann mit der Remotedebugging-Sitzung auf dem entfernten Server verbinden. Du must also auf nichts verzichten.
    Den Remotedebugger gibts allerdings erst ab Visual Studio Professional. Aber im .NET Framework SDK ist der kostenlose SDK Debugger (sieht aus wie Visual Studio, kann aber nur Debuggen) enthalten. Damit kannst Du überall dort kostenlos debuggen, wo kein Visual Studio installiert ist.http://support.microsoft.com/?scid=kb%3Ben-us%3B910448&x=9&y=14

    Leider beschäftigen sich viele Entwickler viel zu wenig mit ihrer IDE.
  • private Nachricht | Beiträge des Benutzers
    herbivore
    myCSharp.de - Experte

    Avatar #avatar-2627.gif


    Dabei seit:
    Beiträge: 52329
    Herkunft: Berlin

    beantworten | zitieren | melden

    Hallo zusammen,

    in Anbetracht des engen Zusammenhangs zum Applikationsserver-Projekt, sei hier noch auf NotificationService als Erweiterung zu Rainbirds ApplikationServer Beispiel verwiesen.

    herbivore
    private Nachricht | Beiträge des Benutzers
    Wyatt
    myCSharp.de - Member

    Avatar #avatar-3090.png


    Dabei seit:
    Beiträge: 18
    Herkunft: BW

    Programmatische Veröffentlichung des SecurityService

    beantworten | zitieren | melden

    Hallo Rainbird.
    Ich habe zur Zeit deinen ApplicationServer testweise im Einsatz.
    Zunächst einmal: fett respect dafür! Echt saubere Arbeit.

    Ich hätte folgende Fragen, die ich auch nach längeren Überöungen und Experimentieren nicht selbst lösen konnte:

    1.
    Mich würde interessieren, wie man es schafft den SecurityService programmatisch zu veröffentlichen/bereitszustellen?

    Der folgende Versuch (und Varianten davon) sind gescheitert:

    
    
    			// Channel-Konfiguration
    			System.Collections.IDictionary properties = new System.Collections.Hashtable();
    			properties["name"] = "AppServerChannel";
    			properties["port"] = 9999;
    			properties["secure"] = true;
    
    			// Eigenen BinaryServerFormatterSinkProvider erstellen
    			BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
    			provider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;	
    
    			// Channel mit vordefinierten Properties erstellen 
    			TcpServerChannel tcpChannel = new TcpServerChannel(properties, provider);
    
    			// Channel registrieren
    			ChannelServices.RegisterChannel(tcpChannel, true);
    
    			// Sicherheitsdienst veröffentlichen	
    			 RemotingConfiguration.RegisterWellKnownServiceType(typeof(SecurityService),
    				 "Rainbird.AppServer.API.ISecurityService", WellKnownObjectMode.Singleton);
    
    			 RemotingServices.Marshal(SecurityService.Instance);
    
    

    Der Client erhält beim Logon den Fehler mit der Meldung "Der angeforderte Dienst wurde nicht gefunden".
    Verwende ich wieder die Konfigurationsdatei funktioniert alles einwandfrei.

    2.
    Wie registrierst du den SecurityService denn überhaupt?
    In der Config-Datei konnte ich keine Einträge diesbezüglich finden.

    3.
    Mir ist aufgefallen, dass trotz der Einstellung "Singleton" für meinen Geschäfts-Dienst, beim ersten Methodenaufruf der Standard-Konstruktor des Service aufgerufen wird, obwohl ich ihn bereits im Server zuvor instanziert habe.
    Wenn ich den Dienst ohne Default-Konstruktor implementiere, erhält der Client beim Methodenaufruf eine Exception mit der Meldung, dass kein parameterloser Konstruktor definiert wurde.
    Hierfür finde ich keinerlei Erklärung. Die Instanz des Dienstes veröffentliche folgendermaßen:

    
    
    		RemotingConfiguration.RegisterWellKnownServiceType(typeof(cBusinessService),
    				"ServiceContracts.IBusinessService", WellKnownObjectMode.Singleton);
    
    			cBusinessService service = new cBusinessService();
    			RemotingServices.Marshal(service);
    
    

    Für deine Hilfe wäre ich echt dankbar, sofern deine Zeit es zulässt.

    Viele Grüße
    Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Wyatt am .
    private Nachricht | Beiträge des Benutzers
    Rainbird
    myCSharp.de - Experte

    Avatar #avatar-2834.jpg


    Dabei seit:
    Beiträge: 3953
    Herkunft: Mauer

    Direct Remoting

    beantworten | zitieren | melden

    Hallo Wyatt,

    das ist ganz einfach:

    Den Kanal konfiguriere ich in der App.config (da sich Port etc. ja ohne Neukompilierung ändern lassen sollen) und den Sicherheitsdienst manuell über Direct Remoting. Du findest Die Codezeile in der Core.cs:

    
    // Sicherheitsdienst veröffentlichen
    RemotingServices.Marshal(SecurityService.Instance, "Rainbird.AppServer.API.ISecurityService");
    
    Mehr ist nicht nötig. Marshal veröffentlicht die bestehende Singleton-Instanz unter dem angegebenen URI. Wenn Du keinen URI bei Marshal angibst, erzeugt Remoting einen zufälligen (den Du dann aber nicht weisst). Also entweder RegisterWellknownServiceType ODER Marshal, aber nicht beides zusammen!

    Der Unterschied ist, dass Marshal eine bereits erzeugte Instanz veröffentlicht und RegisterWellkownServiceType nur einen Typ (Remoting kümmert sich dann um die Erzeugung einer Instanz). Ich wollte in diesem Fall die Erzeugung der Sicherheitsdienst-Instanz komplett selbst in der Hand haben, darum habe ich diesen Weg gewählt.

    Eine normale Singleton-Veröffentlichung hätte es auch getan. Das war einfach nur Kontrollzwang.

    Remoting hat für die meisten Probleme ganz einfache und gute Lösungen (im Gegensatz zu WCF).
    private Nachricht | Beiträge des Benutzers
    Wyatt
    myCSharp.de - Member

    Avatar #avatar-3090.png


    Dabei seit:
    Beiträge: 18
    Herkunft: BW

    beantworten | zitieren | melden

    Hi Rainbird!

    Danke für deine stets hilf- und lehrreichen Antworten!
    Zitat
    Marshal veröffentlicht die bestehende Singleton-Instanz unter dem angegebenen URI. Wenn Du keinen URI bei Marshal angibst, erzeugt Remoting einen zufälligen (den Du dann aber nicht weisst). Also entweder RegisterWellknownServiceType ODER Marshal, aber nicht beides zusammen!

    Diesen Unterschied kannte ich nicht.
    Damit ist meine Frage (3) auch gelöst. Das Problem war somit, dass ich den Typ (per RegisterWellKnownServiceType) UND die Instanz (per Marshal) veröffentlicht hatte. Somit erhielt der Client eine neue Instanz des veröffentlichten Typs. Meine gemarshallte Instanz wurde nicht an den Client geliefert.
    Das "Problem" zu meiner Frage (1) konnte ich nun auch lösen:
    In der Config-Datei kann man ja den Namen der Applikation angeben. Diesen muss man ja im URI der Application-Server Methode mitgeben, also

    
    ApplicationServer.Logon("tcp://localhost:9999/<ApplicationName>/");
    
    Wenn ich aber Remoting programmatisch konfiguriere, kann ich ja keinen Applikationsnamen angeben. Somit musste ich einfach den Namen aus dem URI entfernen. Sounds easy, but hard to find ;(


    1.
    Gibt es eine Möglichkeit den Applikationsnamen auch bei der programmatischen Konfiguration einzustellen?
    [Edit 19:43 Uhr] Selbstohrfeig

    
    RemotingConfiguration.ApplicationName = "MyAppServer";         // Server
    ApplicationServer.Logon("tcp://localhost:9999/"MyAppServer/"); // Client
    

    2.
    Es funktioniert jetzt mit der programmatischen Konfiguration alles top, bis auf:
    Wenn ich aber auf Server-Seite "ApplicationServer.IsInRole(role)" verwende, kehrt der Aufruf nicht zurück, es wird aber auch keine Exception geworfen.
    Verwende ich wieder die Config-Datei, bleibt die Methode "IsInRole()" nicht stehen.

    Ich habe es debuggt und festgestellt, dass hier:

    
            public static bool IsInRole(string roleName)
            { 
                // Wenn momentan eine Sitzung besteht ...
                if (HasSession)
                {
                    // Verbindung zum Sicherheitsdienst des Anmeldungs-Applikationsservers herstellen
                    ISecurityService securityServiceProxy = (ISecurityService)Activator.GetObject(typeof(ISecurityService), ServerURL + typeof(ISecurityService).FullName);
    				
                    // Berechtigungsprüfung durchführen
                   return securityServiceProxy.IsInRole(roleName);
                }
                // Falsch zurückgeben
                return false;
            }
    
    bei "securityServiceProxy.IsInRole" der Aufruf nicht zurückkehrt. Der Aufruf komm im Debug-Modus garnicht erst im Security Service an. Finde den Grund nur leider nicht. Ist das nicht seltsam, zumahl es von Client-Seite aus funktioniert? Es funktioniert ja seltsamerweise auch auf Server-Seite, sobald ich wieder die Config-Datei verwende.
    Haste du vielleicht spontan ne Idee dazu?

    Besten Dank im Voraus!
    Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Wyatt am .
    private Nachricht | Beiträge des Benutzers
    Rainbird
    myCSharp.de - Experte

    Avatar #avatar-2834.jpg


    Dabei seit:
    Beiträge: 3953
    Herkunft: Mauer

    beantworten | zitieren | melden

    Zitat von Wyatt
    bei "securityServiceProxy.IsInRole" der Aufruf nicht zurückkehrt. Der Aufruf komm im Debug-Modus garnicht erst im Security Service an. Finde den Grund nur leider nicht. Ist das nicht seltsam, zumahl es von Client-Seite aus funktioniert? Es funktioniert ja seltsamerweise auch auf Server-Seite, sobald ich wieder die Config-Datei verwende.
    Haste du vielleicht spontan ne Idee dazu?
    Schau Dir mal den Code der Eigenschaft ApplicationServer.SeverURL an. Der URL wird aus dem Aufrufkontext bezogen. Der URL des Applikationsservers an dem sich ein Benutzer anmeldet ist Teil der Sitzungsinformationen, die im Aufrufkontext (Siehe MSDN: CallContext-Klasse) abgelegt werden (das passiert in ApplicationServer.Logon). Die Daten im Aufrufkontext werden automatisch und stillschweigend bei JEDEM Remoting-Aufruf übertragen und dort in den Datenslots des Arbeitsthreads abgelegt (Man kann an einen Thread einfach irgendwelche Daten hängen. Siehe MSDN: Lokaler Threadspeicher).

    Warum der ganze Zirkus?

    Wenn ein Client einen Service A aufruft und dieser einen anderen Service B aufruft, passiert der Aufruf von Service B nicht im Benutzerkontext des Aufrufers von Service A (also dem Client-Benutzer), sondern mit dem Benutzerkonto unter dem der Thread von Service A läuft. Und das ist das Dienstkonto des Applikationsservers. Service B würde z.B. statt 'DOMÄNE1\MaxMustermann' plötzlich 'DOMÄNE1\AppServerKonto' als aufrufender Benutzer gemeldet bekommen. Das ist aber doof, da die Rollenprüfung eigentlich auf den ursprünglichen Aurufer der Aufrufkette erfolgen soll.
    Der Aufrufkontext löst dieses Problem, da die Daten im Threadspeicher abgelegt udn dort auch wieder herausgenommen werden. Daten im CallContext werden also über die KOMPLETTE AUFRUFKETTE durchgereicht, egal wie tief die Kette verschachtelt ist oder über wie viele Server kommuniziert wird.
    Statt die Identität des Aufrufers direkt zu prüfen, wird die Identität der Sitzung geprüft, deren Sitzungsschlüssel im Aufrufkontext abgelegt ist.

    Wenn der Client keine Windows.Forms-Anwendung sondern eine ASP.NET-Webanwendung ist, muss man den Aufrufkontext zusätzlich noch mit dem ASP.NET-Sitzungsstatus synchronisieren (ApplicationServer.LoadContextDataFromWebSessionState und ApplicationServer.SaveContextDataInWebSessionState). Bei einer Windows.Forms-Anwendung braucht man das nicht, da der GUI-Thread die ganze Zeit am Leben bleibt und die Daten in seinem Threadspeicher nicht vergisst.

    Wenn Du einen Dienstaufruf aus einem manuell erzeugten Thread machst, wirst Du standardmäßig eine Ausnahme bekommen, in der sich der Server beschwert, dass Du nicht angemeldet bist. Neue Thread heißt auch neuer - und damit leerer -Threadspeicher. Also Logon aufrufen oder Daten im Aufrufkontext in den Threadspeicher der zusätzlichen Threads replizieren.
    Bisher hat das scheinbar noch keiner gemacht. Da wir das Thema aber gerade sowieso haben, wollte ich es gleich erwähnt haben.

    Ursache Deines Problems im Speziellen: Du hast versucht eine Rollenprüfung durchzuführen, ohne den Benutzer vorher mit Logon anzumelden.

    Dieses Verhalten ist so gewollt. Ohne Login geht gar nix. Das ist ein Sicherheitsaspekt. 8)
    Ich habe sehr auf Sicherheit geachtet. Mir ist es bisher nicht gelungen, meinen eigenen Sicherheitsdienst auszutricksen.

    Es macht ja auch keinen Sinn, die Rolle des Applikationsserver-Dienstkontos zu prüfen. Es soll ja geprüft werden, ob ein Client-Benutzer eine bestimmte Dienstmethode aufrufen darf oder nicht.
    private Nachricht | Beiträge des Benutzers
    Wyatt
    myCSharp.de - Member

    Avatar #avatar-3090.png


    Dabei seit:
    Beiträge: 18
    Herkunft: BW

    beantworten | zitieren | melden

    Hey Rainbird.

    Wow, jede Menge brauchbarer Info und das auch noch um die Uhrzeit. Ein einfaches DANKE, reicht da schon fast nicht mehr aus: DICKES DANKE =)

    Die Sache mit dem CallContext hatte ich schon soweit verstanden, wenn auch nicht in diesem Detailgrad. Advanced .NET Remoting (Ingo Rammer) verwendet den CallContext ebenfalls, um bspw. Logging-Einstellungen zu übertragen.
    Zitat
    Ursache Deines Problems im Speziellen: Du hast versucht eine Rollenprüfung durchzuführen, ohne den Benutzer vorher mit Logon anzumelden.

    Nein, es muss wohl an etwas anderem liegen. Mein Client ruft Logon auf:

    
    				cApplicationServerManager.LogonFail += new EventHandler<SessionEventArgs>(cApplicationServerManager_LogonFail);
    
    				// Log on to the Remote Application Server
    				cApplicationServerManager.Logon("tcp://localhost:9999/MyAppServer/");
    
    				// If we have a valid SessionId to the Remote Business Server
    				if (cApplicationServerManager.HasSession)
    				{
    					// Connect to the Business Service
    					IBusinessService businessService = ServiceFactory<IBusinessService>.CreateProxy();
    
                         IArticle[] articles = businessService.GetArticles();
                         // ...
    				}
    

    Der Aufruf landet auch wunderbar im Service. Hier rufe ich dann ApplicationServer.IsInRole() auf. Darin wird die Property "ServerUrl" aufgerufen, die auch noch erfolgreich den URL aus dem CallContext extrahiert. Dann wird (alles auf Serverseite) eine Remote-Verbindung zum SecurityService aufgebaut und SecurityService.IsInRole() aufgerufen. Dieser Aufruf bleibt ohne Exception einfach stehen.
    Verwende ich allerdings die Config-Datei zur Konfiguration des Server-Channels, funktioniert es einwandfrei.

    Ich finde aber den entscheidenden Unterschied nicht:

    Programmatische Konfiguraion meines Server-Channels:

    
    			RemotingConfiguration.ApplicationName = "MyAppServer";
    			RemotingConfiguration.CustomErrorsMode = CustomErrorsModes.Off;
    
    			// Eigenen BinaryServerFormatterSinkProvider erstellen
    			BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
    			provider.TypeFilterLevel = System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;			
    
    			// Channel mit vordefinierten Properties erstellen 
    			TcpServerChannel tcpChannel = new TcpServerChannel("AppServerChannel", 9999, provider);
    			tcpChannel.IsSecured = true;
    
    			// Channel registrieren
    			ChannelServices.RegisterChannel(tcpChannel, true);
    

    Wenn ich die Config-Datei verwende geht aber alles. Was fehlt bei der programmatischen Konfiguration? Mir fällt nichts auf.
    Gruß
    Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Wyatt am .
    private Nachricht | Beiträge des Benutzers
    Rainbird
    myCSharp.de - Experte

    Avatar #avatar-2834.jpg


    Dabei seit:
    Beiträge: 3953
    Herkunft: Mauer

    TcpChannel

    beantworten | zitieren | melden

    Hallo Wyatt,

    Du darfst keinen TcpServerChannel verwenden sondern musst einen TcpChannel verwenden. Sonst kann der Server nicht selber wieder Client sein. Clientseitig solltest Du auch den TcpChannel verwenden (Falls der Client in einem Szenario mal kurz Server spielen muss).

    Es kann auch sinnvoll sein serverintern über einen zusätzlichen IpcChannel zu kommunizieren. Ist schneller und muss nicht über den TCP-Stack.

    P.S.: Super Buch! Allerdings teile ich z.B. die Auffassung nicht, daß Remoting bevorzugt im IIS gehostet werden sollte.
    private Nachricht | Beiträge des Benutzers
    Made with ♥ and ASP.NET Core. Rendered in 73ms. Running Version 0.1.301+4a40c4fb81 on Azure App Service (debian.10-x64 with .NET 5.0.6)
    Copyright 2003-2021 myCSharp.de - Alle Rechte vorbehalten. Benutzerinhalte unterliegen cc-by-sa 4.0