Laden...

Fragen, Diskussion, Kritik zu Projekt ".NET Applikationsserver"

Erstellt von wdb.lizardking vor 16 Jahren Letzter Beitrag vor 9 Jahren 142.221 Views
18 Beiträge seit 2009
vor 13 Jahren

Hey Rainbird.

Du darfst keinen TcpServerChannel verwenden sondern musst einen TcpChannel verwenden. Sonst kann der Server nicht selber wieder Client sein.

Believe it or not. Mir ist es tatsächlich heute auch selbst aufgefallen als ich mir überlegte, dass der Server ja selbst eine Remote-Verbindung zum Sicherheitsdienst aufbaut. Manchmal dauert es halt bissl länger 😁

P.S.: Super Buch! Allerdings teile ich z.B. die Auffassung nicht, daß Remoting bevorzugt im IIS gehostet werden sollte.

Ja, die Anschaffung lohnt sich. Der Teil der die Erweiterung von .NET Remoting, in Zusammenhang mit eigenen Kanalsenken behandelt, empfand ich als sehr hilfreich, da hierfür ja schon deutlich mehr Hintergrundwissen und eigene Implementierung benötigt wird.
Ich hoste meine Services üblicherweise auch nicht im IIS. Persönlich hat mich dabei aber lediglich immer der zusätzliche Deployment-Aufwand abgeschreckt.

Welche Gründe sprechen aus deiner Sicht gegen das Hosting im IIS?

3.728 Beiträge seit 2005
vor 13 Jahren
Iis

Hallo Wyatt,

Welche Gründe sprechen aus deiner Sicht gegen das Hosting im IIS? *Nur der vergleichsweise lahme HttpChannel nutzbar *Hoher Deploymentaufwand (man kann seine Anwenung nicht per einfachem Setup ausrollen, wenn beim Kunden noch kein IIS aufgesetzt und richtig konfiguriert ist) *Setzt teueres Server-Betriebssystem vorraus, was meiner "one size fits it all" Philosophie wiederspricht (ich will meine Software u.U. auch auf Windows XP Home installieren können) *Infrastruktur von IIS ist im Rahmen von .NET Remoting-Anwendungen nicht ohne weiteres erweiterbar

IIS ist deshalb kein schlechter Webserver, aber er ist nicht mein bevorzugter Host für Remoting. Ich verwende meistens Konsolenanwendungen, die sich entweder als solche direkt verwenden oder als Windows-Dienst installieren lassen. Letzteres ist in Produktiv-Umgebungen üblicher.

_
227 Beiträge seit 2006
vor 13 Jahren

Hallo,
ich habe eine Frage zur Service Abhängigkeit.
Mal angenommen wir haben nen Kunden- und nen ArtikelService. Nun kann jeder Artikel einem Kunden zugeordnet werden, wobei dem ArtikelService das sonst egal ist. Was spricht für/gegen die optionen
a) der ArtikelService gibt nur die ID des Kunden mit zurück, wenn der Client mehr anzeigen möchte soll er es sich beim KundenService holen (Artikel und KundenService sind ohne Referenzen unterinander)
b) der Artikel Service gibt ein komplettes Kunden Objekt mit zurück
c) ?

3.728 Beiträge seit 2005
vor 13 Jahren
Abhängigkeiten

Hallo daniel,

Für Variante a) spricht, dass die Dienste nicht Code technisch voneinander abhängig sind. Gegen Variante b) spricht, dass der Artikeldienst von der Geschäftspartnerverwaltung abhängig wäre. Ob das problematisch ist, oder nicht kann man pauschal nicht beantworten. Aber es ist eine Abhängigkeit.

Variante c) könnte so aussehen:

Die Verknüpfung von Artikeln zu Kunden in eine separate Tabelle auslagern und einen separaten Dienst für die Abfrage, Auflösung und Pflege dieser Verknüpfungen bereitstellen, der sowohl den Artikeldienst als auch den Geschäftspartnerdienst konsumiert.
Dann muss man sich allerdings noch fragen, wie das Ganze im Client abgebildet wird? Separates Formular für die Pflege von Kundenspezifischen Artikeln? Oder ein UserControl welches im Artikelstamm-Formular eingbettet wird?

Eine pauschale Antwort gibt es nicht. Es gibt für und gegen alle drei Varianten Argumente. Die Vor- und Nachteile zu kennen sollte aber helfen, die jeweils beste Variante für das konkrete Projekt auszuwählen.

_
227 Beiträge seit 2006
vor 13 Jahren

Hallo Rainbird,
danke. Es wird auf ein auf zwei Labels und einen Button zum Auswahlfenster öffnen im Artikelstamm-Formular rauslaufen.
Denke mit Variante a bin ich gut bedient.

B
62 Beiträge seit 2007
vor 13 Jahren

Hallo Rainbird,

Ich hätte da noch Fragen an dich - ich habe mir deine Beispiel n-Tier Anwendung heruntergeladen und angesehen !

Ich würde gerne, wenn möglich, meine Anwendung ähnlich aufbauen wie du es gemacht hast, ich halte da sehr viel von Strukturierter Qualitativer Programmierung und sehe da einen echten Mehrwert für die künftige änderungen.

Ich gehe davon aus, du meintest das mit deinem Vorschlag für mich genau so wie du in deiner n-Tier Applikation vorgeführt hast? (PN)

Wo kann ich diese begrifflichkeiten wie "Contract" Klasse und alles eigentlich nachlesen , also wo hast du dir das Wissen hergeholt für die Architektur ?

Ich verstehe deine Art Umsetzung leider noch nicht ganz, da ich wie erwähnt ja versuche das ganze irgendwie für mich umzusetzen, sehe ich noch nicht so ganz was da am nähesten für mein Beispiel ist.
Mein AppServer fürs Remoting und die Call Assemblys sind ja nur der kleine Teil der an eine größere CTI Anwendung dran flanscht.

Deine Module sind ja nicht ganz eigenständige Programme, da sie in dem Client geladen werden und aber auch auf die Server API zugreifen.

Wäre eine Umsetzung wie du sie mir vorgeschlagen hast dann wie folgt:

AppServer
-> Server programm GUI oder Konsole
-> CallManager Klasse (MarshalbyRefObj - beinhaltet die genaue Geschäftslogik?)

AppServer API
-> ICallManager (kein MarshalbyRefObj)
-> Call Klasse [Serializeable]


Jetzt kommt für mich das schwierige ... du hast hier ne Schicht mit den Contracts und Services...

Wie würde ich das für mich umsetzen ?!


Windows Client (CTI Anwendung)
-> Windows GUI
-> Referenziert die AppServer API

++Ich habe mich bei deinem Tipp und meiner Umsetzung sehr an deine "LockingService" und "ILockingService", sowie die Klasse "LockInfo" gehalten, da diese meiner Sache am nächsten war. ++

Ich krieg das "zusammen"bauen noch nicht ganz in den Kopf, ich glaube ich denk da noch nicht abstrakt genug ?!

Ich müsste CallManager.cs (in meinem fall) also als Singletone Objekt initialisieren ?
Sollte die CallManager Klasse in ein eigenes Projekt zur Assembly gebuilded werden, so dass ich diese im AppServer refenziere oder liegt die CallManager.cs im AppServer schon richtig dort?

Viele Grüße

Sebastian

3.728 Beiträge seit 2005
vor 13 Jahren
Verteilte Telefonie-Anwendung mit Remoting

Ich gehe davon aus, du meintest das mit deinem Vorschlag für mich genau so wie du in deiner n-Tier Applikation vorgeführt hast? (PN)

So könntest Du das machen. Du kannst es aber auch noch vereinfachen. Services und BusinessComponents kannst Du z.B. auch jeweils in einer Klasse zusammenfassen.

Wo kann ich diese begrifflichkeiten wie "Contract" Klasse und alles eigentlich nachlesen , also wo hast du dir das Wissen hergeholt für die Architektur ? Bücher, Web, Foren, Gespräche mit anderen Entwicklen. Das Übliche.

Fast alles über .NET Remoting findest Du z.B. hier: .NET Framework Developer's Guide:
.NET Remoting

Das Wort Contract (Vertrag) stammt aus der SOA-Ecke.

Ich verstehe deine Art Umsetzung leider noch nicht ganz, da ich wie erwähnt ja versuche das ganze irgendwie für mich umzusetzen, sehe ich noch nicht so ganz was da am nähesten für mein Beispiel ist.

Du solltest Deinen eigenen Remoting Server aufbauen. Dann weist Du auch, wie alles unter der Haube funktioniert. Dein Server hat dann auch nur die Funktionen, die Du brauchst. Mein Beispiel kannst Du als Orientierung nehmen. Ein Einstieg ist nicht schwer. Ein Remoting-Server braucht in der Minimalversion nur zwei Zeilen Code.

Deine Module sind ja nicht ganz eigenständige Programme, da sie in dem Client geladen werden und aber auch auf die Server API zugreifen.

Ich baue meine Anwendungen stets mit Komponenten.

Wäre eine Umsetzung wie du sie mir vorgeschlagen hast dann wie folgt:

AppServer
-> Server programm GUI oder Konsole
-> CallManager Klasse (MarshalbyRefObj - beinhaltet die genaue Geschäftslogik?)

AppServer API -> ICallManager (kein MarshalbyRefObj) -> Call Klasse [Serializeable]

Jetzt kommt für mich das schwierige ... du hast hier ne Schicht mit den Contracts und Services... Wie würde ich das für mich umsetzen ?!

Windows Client (CTI Anwendung)
-> Windows GUI
-> Referenziert die AppServer API

Der AppServer sollte ein Windows-Dienst oder eine Konsolenanwendung sein.

Ich würde folgende Projekte machen:

Rainbird.AppServer

Rainbird.AppServer.Api

Rainbird.Cti.Services (Geschäftslogik
--> CallManager

Rainbird.Cti.Contracts (Schnittstellen/Verträge)
--> ICallManager
--> Call

Rainbird.Cti.Gu (Benutzeroberfläche)
diese meiner Sache am nächsten war. [/u]

Ich müsste CallManager.cs (in meinem fall) also als Singletone Objekt initialisieren ?

Wenn er Variablen über längere Zeit halten muss, dann Singleton, ansonsten SingleCall.

Sollte die CallManager Klasse in ein eigenes Projekt zur Assembly gebuilded werden, so dass ich diese im AppServer refenziere oder liegt die CallManager.cs im AppServer schon richtig dort?

Eigene Assembly! + Eigene Contract-Assembly. Wie oben erklärt.
AppServer benötigt keinen Verweis! CallManager nur als Remoting-Dienst in der App.config eintragen.

B
62 Beiträge seit 2007
vor 13 Jahren

Hallo Rainbird,

ich habe versucht meine Solution soweit zu ordnen und entsprechend deinen Vorschlägen zu modelieren - habe mich aber auch durch deinen MSDN Link gelesen.

Hab jetzt ein, zwei Zusammenhänge mehr in deiner Applikation verstanden 😃

Ich verstehe aber noch nicht ganz den Nutzen einer Interface Klasse?
Soweit ich das verstanden habe, wird diese als Proxy Stellvertreter Objekt der Remoting Instanz genutzt. Aber wozu dient das ganze? Wieso nicht direkt die Manager Klasse, statt dem Interface ?

Mein Appserver sieht jetzt folgendermaßen aus:


    public partial class Appserver : Form
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.Run(new Appserver());
        }

        public Appserver()
        {
            InitializeComponent();
            this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + "Initialisiere ...");
        }

        private void ListWellKnownServiceTypes()
        {
            try
            {
                foreach (WellKnownServiceTypeEntry entry in RemotingConfiguration.GetRegisteredWellKnownServiceTypes())
                {
                    this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + entry.TypeName + " is available at " + entry.ObjectUri);
                }
            }
            catch (Exception ex)
            {
                string txt = "Eventuell liegt die Konfigurationsdatei in einem falschen Format vor." + Environment.NewLine +
                     "Genaue Fehler Informationen:" + Environment.NewLine + Environment.NewLine +
                "  Nachricht: " + ex.Message + Environment.NewLine +
                "  Quelle: " + ex.Source + Environment.NewLine + Environment.NewLine +
                "  Stack Trace:" + Environment.NewLine +
                ex.StackTrace;
                MessageBox.Show(txt, "Generic Exception", MessageBoxButtons.OK, MessageBoxIcon.Stop);
            }
        }

        private void Appserver_Load(object sender, EventArgs e)
        {
            // Daten über die ausführende Assembly abrufen
            Assembly currentAssembly = Assembly.GetExecutingAssembly();


            try
            {
                this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + "Try to load Remoting Configuration File ...");

                // Pfad zur Konfigurationsdatei erzeugen
                string configFile = currentAssembly.Location + ".config";
                if (File.Exists(configFile))
                {
                    // Remoting-Infrastruktur konfigurieren
                    RemotingConfiguration.Configure(configFile, true);
                    this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + "Remoting Configuration loading complete.");
                }



                this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + "Try to load Business Services ...");
                ListWellKnownServiceTypes();
            }
            catch (Exception ex)
            {
                // FileNotFound ?

                // Will catch any generic exception
                string txt;
                txt = "Konfiguration der App.config Datei fehlgeschlagen, oder es liegt ein falsches Format vor." + Environment.NewLine +
                "Stellen Sie sicher das die App.config Datei im selben Verzeichnis wie die Anwendung liegt " +
                " und die Datei im richtigen Format vorliegt." + Environment.NewLine +
                "Nutzen Sie das Hilfe Menü, About um herauszufinden wo die exe Datei liegt." + Environment.NewLine + Environment.NewLine +
                "Genaue Fehler Informationen:" + Environment.NewLine + Environment.NewLine +
                "  Nachricht: " + ex.Message + Environment.NewLine +
                "  Quelle: " + ex.Source + Environment.NewLine + Environment.NewLine +
                "  Stack Trace:" + Environment.NewLine +
                ex.StackTrace;
                MessageBox.Show(txt, "Generic Exception", MessageBoxButtons.OK, MessageBoxIcon.Stop);

                this.list_Hist.Items.Add(DateTime.Now.ToShortTimeString() + " " + "Remoting Configuration File loading failed.");
            }
        }

    }

Die App.config sieht folgendermaßen aus:


<configuration>
<system.runtime.remoting>
	<application>
		<service>
			<wellknown
				mode="Singleton"
				type="CTI_Integration.Extension.Services.CallManager, CTI_Integration.Extension.Services"
				objectUri="CTI_Integration.Extension.Contracts.ICallManager"
		/>
		</service>
		<channels>
			<channel ref="tcp" port="9090">
				<serverProviders>
					<formatter ref="binary"/>
				</serverProviders>
			</channel>
		</channels>
	</application>
</system.runtime.remoting>
</configuration>

Ich habe zusätzlich noch eine AppServer.API (wie du) - welche allerdings nur das Call.cs Klassen Objekt beherbergt (was ich momentan etwas überflüssig finde) - du verwendest die API um Server Funktionen zu integrieren und anzusprechen, allerdings soll mein AppServer lediglich die Call Klassen Objekte verwalten.

Macht eine AppServer API bei mir sinn?

Meine Call Klasse sieht wie folgt aus:


using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Xml.Serialization;

namespace CTI_Integration.AppServer.API
{
    /// <summary>
    /// Beschreibt ein Call Object auf dem Application Server
    /// </summary>
    [Serializable]
    [XmlRoot(ElementName="Call")]
    public class Call
    {
        #region Deklarationen

        // Universal Call ID 1
        [XmlElement(ElementName = "UCID_1", IsNullable = false)]
        string _ucid_1 = string.Empty;
        
        bla bla bla ... 
   
        #endregion

        #region Konstruktor

        /// <summary>
        /// Konstruktor
        /// </summary>
        public Call()
        {
            // Default Konstruktor
        }
      
        Bla bla bla

        #endregion

        #region Eigenschaften

        /// <summary>
        /// Gibt die Universal Call ID zurück oder setzt sie  
        /// </summary>
        public string UCID_1
        {
            get { return _ucid_1; }
            set { _ucid_1 = value; }
        }

        Bla bla bla ...

        #endregion

    }
}



Meine Solution sieht wie folgt aus:

AppServer (Referenziert Appserver.api)
-> Appserver.cs // WinForms
-> App.config // Remoting Config File

AppServer.API (Referenziert nix)
-> Call.cs [Serializable]


EXTENSION

Contracts (Referenziert die Appserver.api)
-> ICallManager (Kein MBR Objekt o.ä.)
IcallManager hat als Rückgabewerte u.a. CALL Objekte , diese zeigen auf Appserver.api.Call

Services (Referenziert Appserver.api / Contracts )
-> CallManager MBR Objekt
CallManager arbeitet eben auch mit Call Objekten, welche auf Appserver.api.Call zeigen

WindowsGUI (Refenziert Appserver.api / Contracts)
-> Habe hier ein Usercontrol liegen, über welches z.b. ein Call Objekt geupdated werden muss


Windows CLIENT

Client (Referenziert die Appserver.api / Contract )
-> Programm selbst ... (sehr groß, besteht auch aus mehreren Projekten)
-> App.config

Mir ist nicht klar, wie ich auf Client Seite das richtige (CallManager) Objekt als Singleton aktivieren soll oder muss ?!

Vorher habe ich es so gemacht:


        try
        {
            object[] args = null;
            Call = (CTI_Integration.Call)(Activator.CreateInstance(typeof(CTI_Integration.Call), args));
            //Call.newClient();
            objNotCreated = (Call == null);

        }
        catch (RemotingException exp)
        { .......}

Das hat ja soweit geklappt, allerdings hatte ich auch die Verwaltung und das zu verwaltende Objekt in einer Klasse gebastelt, sodass das Objekt sich selbst in einer Liste verwaltet hatte , was sehr doof war 😛

Wenn ich jetzt aus dem Windows CLIENT den CallManager aufrufen möchte (liegt ja dann bei CTI_Integration.Extension.Services.CallManager) heisst es, es kann nicht aufgerufen werden, da ich keinen Zugriff darauf bekomme.


Der AppServer lädt also die CallManager Klasse als Singleton Objekt als WellknownType aus einer Konfigurationsdatei - das klappt soweit. (CallManager liegt NICHT beim Server, sondern in der Service Assembly, ich verweise nur in der App.Config auf die Manager Klasse).

Nur erschließt sich mir noch nicht wie ich Clientseitig das richtige Klassenobjekt jetzt WIE instanziere und dann ein entsprechendes Call Objekt rüberschiebe. (Call.cs Klasse , welche EIN Call Objekt beschreibt liegt momentan in der Appserver.API )

Habe wie gesagt den Developers Guide gelesen, aber da wir ja schon weiter sind als es in dem Guide beschrieben oder demonstriert wird , sehe ich vllt. gerade die Zusammenhänge nicht ganz die ich mit der Struktur erreicht haben soll.

Kurz, ich bin verwirrt - würde mich da über Hilfe freuen.

Vielen Dank und Gruß

Sebastian

3.728 Beiträge seit 2005
vor 13 Jahren
Details

Ich verstehe aber noch nicht ganz den Nutzen einer Interface Klasse?

Soweit ich das verstanden habe, wird diese als Proxy Stellvertreter Objekt der Remoting Instanz genutzt. Aber wozu dient das ganze? Wieso nicht direkt die Manager Klasse, statt dem Interface ?

Das ist wichtig fürs Deployment (also das Ausrollen der Anwendung). Die Dienst-Implementierungs-Assembly (also die Assembly in der die Klasse CallManager definiert ist) sollte nicht auf den Client verteilt werden. Der Client darf die Implementierung gar nicht kennen. Damit er aber trotzdem die Methodensignaturen (und damit auch Intellisense beim entwicklen) hat, wird an den Client nur die Schnittstelle ICallManager verteilt. Der Client arbeitet immer mit ICallManager. Niemals mit CallManager.

Wenn Du das so machst, kannst Du auf dem Server Updates fahren, ohne die Clients neu ausrollen zu müssen. Solange die Schnittstelle kompatibel ist!

Ich habe zusätzlich noch eine AppServer.API (wie du) - welche allerdings nur das Call.cs Klassen Objekt beherbergt (was ich momentan etwas überflüssig finde) - du verwendest die API um Server Funktionen zu integrieren und anzusprechen, allerdings soll mein AppServer lediglich die Call Klassen Objekte verwalten.

Macht eine AppServer API bei mir sinn?

Ich würde zunächst sagen nein. Die Call-Klasse ist einen Contract (kein Dienstvertrag wie ICallManager, sondern einen Datenvertrag) und gehört deshalb zusammen mit ICallManager in die Contract-Assembly. Der Client braucht dann nur diese eine Assembly und findet darin alles, was er für die Kommunikation mit dem Server braucht.

Mir ist nicht klar, wie ich auf Client Seite das richtige (CallManager) Objekt als Singleton aktivieren soll oder muss ?!

Singleton-Aktivierung bedeutet, dass es nur ein einziges CallManager-Objekt auf dem Server gibt! Dieses behandelt alle Client-Anfragen. Jede Client-Anfrage geht in einem eigenen Thread ein. Du musst deshalb den CallManager threadsicher programmieren. Bei SingleCall hast Du das Problem nicht, dafür kannst Du keinen Status halten, weil SingleCall aktivierte Objekte nur einen Methodenaufruf lang leben.

Bei Singleton-Objekten nicht vergessen InizializeLifetimeService zu überschreiben und die Lease zu konfigurieren. Wenn ein Objekt unendlich lange leben soll, dann einafch null zurückgeben.


// CallManager-Proxy erzeugen
ICallManager proxy=(ICallManager)Activator.GetObject(typeof(ICallManager),"tcp://localhost:9090/CTI_Integration.Extension.Contracts.ICallManager");

// Call vom Server abrufen (Ich kenne Deine ICallManager-Schnittstelle nicht, aber so könnte es aussehen)
Call call=proxy.GetCall(4711);

K
142 Beiträge seit 2006
vor 13 Jahren
Benutzerkonzept

Hallo,

erstmal ein großes Lob an Rainbird - irgendwie ist vieles was man (oder zumindest ich) an Doku und Infos bezüglich Software-Architektur findet sehr abstrakt, da ist dieses Beispiel sehr hilfreich um auch einen praktischen Einstieg in die Thematik zu bekommen.

In den Bereichen Ablaufverfolgung, Transaktionen und Sperrungsverwaltung habe ich schon viel dazu gelernt.

Aktuell möchte ich eine Art Prozessdatenbank umsetzten, also z.B. gibt es in einer Fertigungslinie mehrere Rechner, die alle auf eine Datenbank zugreifen (Artikel-Stammdaten abholen, Messwerte schreiben,...) . Diese liegt entweder auf einem extra Linien-Server oder auf einem der Prozess-Rechner. Mir ist noch nicht ganz klar, wie ich am Besten den Benutzerzugriff regeln kann. Windows-Authentifizierung scheidet wohl aus, da die Anlage auch bei einem Benutzerwechsel nicht anhalten soll. Ein Ansatz wäre, den Prozess selbst als Benutzer anzumelden, und über einen parallelen Rollenbasierten Ansatz den Zugriff auf die einzelnen UI-Elemente zu steuern (mögliche Umsetzung über IPrincipal und IIdentity).

Ich hoffe, ich habe mich einigermaßen verständlich ausgedrückt, aber das Szenario "Rechner muss trotz Benutzer-Wechsel ständig mit DB verbunden sein" sollte doch häufiger vorkommen. Gibt es hierfür evt. auch ein Design-Pattern oder so was in der Art?

Grüße, Koller.

3.728 Beiträge seit 2005
vor 13 Jahren
Verständnisproblem

Hallo koller,

sorry, aber ich das Dein Problem nicht wirklich verstanden. Kannst Du das noch etwas näher beschreiben?

K
142 Beiträge seit 2006
vor 13 Jahren

Hallo Rainbird,

am besten kann ich das wohl an einem Beispiel erklären:

In einer Fertigungslinie gibt es mehrere Windows-Rechner, die verschiedene Aufgaben erfüllen: z.B. Visualisierung von Prozessdaten, Steuerung einer Bildverarbeitung, Soft-SPS usw. Alle diese Rechner sollen Daten protokollieren - evt. über einen Applikationsserver und einer Datenbank wie in deinem Beispiel.

Die komplette Fertigungslinie wird über einen Hauptschalter eingeschaltet, die PCs booten dann auch automatisch (Power-Loss-Einstellung im BIOS) und Windows wird mit einem Auto-Logon-Benutzer gestartet. Über einen Eintrag in der Autostart-Gruppe wird auf den einzelnen PCs die entsprechende Software (mit den letzten Einstellungen) gestartet.

Wenn alle Systeme in der Fertigungslinie ein Bereit-Signal senden (i.A. an eine übergeordnete SPS), kann die Fertigungslinie im Automatikbetrieb gestartet werden. Ab dem Moment müssen die PCs die Möglichkeit haben, Daten in der Datenbank abzulegen.

Alternativen:

  1. Der Bediener, der gerade für die Linie verantwortlich ist, muss sich an jedem PC anmelden (zu umständlich?).

  2. Jeder PC (also eigentlich der anonyme Auto-Logon-Benutzer) hat automatisch die Rolle "Darf Prozessdaten wegschreiben". Sollen Software-Parameter geändert werden, muss sich ein Benutzer mit den entsprechenden Berechtigungen anmelden (fehlende Sicherheit: es kann nicht nachvollzogen werden, wer die Linie gestartet hat).

  3. Es gibt einen Master-PC, an dem sich der Bediener einloggt, alle anderen PCs in der Linie arbeiten dann mit diesem Benutzer. Ein Benutzer mit höheren Rechten kann sich aber zwischendurch an einem einzelnen Rechner anmelden, um z.B. Parameter zu ändern.

Option 3 schient mir eigentlich am sinnvollsten, mir ist hierbei nur nicht klar, wie die Anmeldedaten des Benutzers am Master-PC an die anderen Rechner übertragen werden können.

Ich hoffe, mein Problem ist hiermit etwas deutlicher geworden.

Grüße, Koller.

3.728 Beiträge seit 2005
vor 13 Jahren
Bediener auf alle PCs replizieren

Option 3 schient mir eigentlich am sinnvollsten, mir ist hierbei nur nicht klar, wie die Anmeldedaten des Benutzers am Master-PC an die anderen Rechner übertragen werden können.

Das ginge z.B. per Remoting-Callback. Bei Starten abonnieren die Clients beim Applikationsserver Benachrichtigungen bei Bedienerwechsel. Christoph Burgdorf hat eine schöne Erweiterung geschrieben, mit welcher sich genau das Abbilden lässt und die auch technisch zu meinem Beispiel passt: NotificationService als Erweiterung zu Rainbirds ApplikationServer Beispiel

So kommen die Anmeldedaten (sprich Benutzername, Domäne und Passwort) vom Master-PC über den Applikationsserver zu den Clients. Die Clients müssen sich nun im Hintergrund als neuer Bediener mit den erhaltenen Anmeldedaten anmelden. Das geht mittels LogonUser (Eine genaue Beschreibung dafür findest Du hier: MSDN: Windows.Sicherheit integrieren). LogonUser gibt einen Zeiger (IntPtr) auf ein Sicherheitstoken (das ist der Nachweis, dass der Benutzer korrekt authentifiziert wurde). Mit Hilfe dieses Sicherheitstokens kann man nun innerhalb der laufenden .NET-Applikation impersonieren (MSDN: WindowsIdentity.Impersonate-Methode). Nun läuft die Client-Anwendung im Kontext des neuen Bediners. Die Impersonierung kann auch jederzeit wieder aufgehoben werden (Undo-Methode).

So könnte eine Lösung mit Windows-Sicherheit aussehen. Negativ daran ist, dass mit Windows-Passwörtern hantiert werden muss, aber das lässt sich nicht anders machen.

Plan B wäre eine ganz eigene Benutzerverwaltung aufzusetzen. Das hat dann aber den Nachteil, dass u.U. in den Prozess eingebundene Fremdprogramme damit nichts anfangen können.

K
142 Beiträge seit 2006
vor 13 Jahren

Hallo Rainbird,

vielen Dank für die Infos. Dann werde ich mich durch die Links mal durcharbeiten.

Grüße, Koller.

3.728 Beiträge seit 2005
vor 13 Jahren
Weiterentwicklung

Hallo zusammen,

ich habe kürzlich ein freies Kommunikations Framework auf Codeplex veröffentlicht, welches für die selbe Art von Anwendungen geeigent ist, wie der kleine Remoting Applikationsserver hier in diesem Thread.

Nähere Infos und den Download-Link gibt es unter Zyan - Framework für verteilte EBCs (unterstützt auch klassische Komponenten)

Zyan ist die Weiterentwicklung der ursprünglichen Applikationsserver-Idee.

Vielleicht ist das Projekt ja für den einen oder anderen von euch interessant.

177 Beiträge seit 2009
vor 11 Jahren

Hi,

das Thema ist zwar schon ne Weile her, und gibt sicherlich schon viele andere Möglichkeiten sowas umzusetzen, dennoch habe ich hier ein Beispiel gefunden wo ich halbwegs durchblicke 😃

Ich hab deine Anwendung mal geladen, installiert, da ich das mit den DBFactorys nicht so gecheckt habe, habe ich mein schon vorhandenen DAL eingebaut damit ich meine schon vorhandenen Storage Procedures verwenden kann.

Funktioniert alles soweit Super, der Server startet, mein Client meldet sich an, Datenaustausch funktioniert Super. Danke für das Tolle Beispiel, ich finde es sehr übersichtlich.

Nun zu mein Problem:
Ich habe Win7 mit 3 Benutzer "Peter","RainBirdServer","rdu" , alle User gehören der Gruppe "Benutzer","Administratoren" und "PetersGruppe" an. PetersGruppe ist die Gruppe die ich bei deinem Setup.exe eingetragen habe.

Nun habe ich eine VM aufgesetzt wo ich den Client starte, der Benutzer der VM ist "rdu", in der VM existiert auch die Gruppe "PetersGruppe" wo "rdu" Mitglied ist.

Mit dem Benutzer "Peter" starte ich den Server , dann starte ich in der VM den Client, dieser wird auch korrekt angemeldet , sobald ich aber eine Abfrage starte knallt es bei der Rollenüberprüfung


            // Wenn der Aufrufer in der Rolle "Product Reader" ist ...
            if (ApplicationServer.IsInRole("Product Reader"))
            {
                // Aufruf an Geschäftslogik delegieren
                return new CountryManager().GetCountry(countryID);
            }
            else
                // Ausnahme werfen
                throw new SecurityException(ApplicationServer.LocalizedAccessDeniedMessage);

Bei der Überüfung von "is ApplicationServer.CurrentUser.Name trustworthy"... versucht er zu prüfen ob der der Benutzer "Peter" (der der den Server gestartet hat) Trustworthy ist.

Habe daraufhin den Benutzer Rainbirdserver angelegt, und mich mit ihm angemeldet und den Server gestartet, es passiert das gleiche, bei der Anmeldung bekomme ich ein OK zurück, alles Super, und beim Abruf von Daten knallt es , ich melde mich mit "rdu" an ok, beim Prüfen ob der Benutzer in der Rolle ist, kommt nun "is rainbirdserver" Trustworthy , das gleiche wenn ich mich mit dem Benutzer Peter anmelde, Login am Server ok, Session wird gegeben, beim Abruf von Daten ... Fehler.

Er schmeißt ein System.Security.SecurityException: Zugriff verweigert beim überprüfen von IsInRole. Wo könnte der Fehler liegen? Um auszuschließen das mein Client irgendwie murks macht habe ich mit deinem angehängten Client auch probiert, das gleiche, Login Ok, IsInRole nicht ok.

Danke!
Gruß
Peter

3.728 Beiträge seit 2005
vor 11 Jahren
Windows-Authentifizierung

Hallo elTorito,

ich vermute dass es daran liegt, dass die drei Windows 7 PCs nicht Teil der selben Active Directory-Domäne sind. Das n-Tier-Architekturbeispiel verwendet Windows-Authentifizierung über Kerberos oder alternativ auch das NTLM-Protokoll. Das funktioniert nur in einem Domänennetzwerk sinnvoll. Ohne einen zentralen Domänencontroller kann die Authentifizierung nicht richtig funktionieren, da jeder alleinstehende Windows 7-PC (also einer der nicht Mitglied einer Domäne ist) nur seine eigenen Benutzer und Gruppen kennt und auch nur diese authentifizieren kann.

Angenommen der Clientcomputer heißt PC1 und der Benutzer Peter, dann würde er sich mit PC1\Peter versuchen beim Server anzumelden. Wenn der Server PC2 heißt und auch einen Benutzer Peter hat, klappt zwar die Authentifiziere, wenn auf beiden PCs für den Benutzer Peter das selbe Passwort gesetzt ist, aber PC2 kann nicht prüfen, in welchen Gruppen PC1\Peter Mitglied ist. Für die Prüfung der Gruppenmitgliedschaft, müsste PC2 auf die SAM-Datenbank von PC1 zugreifen. Das geht aber in einem Peer-To-Peer Netzwerk nicht. Bei einem Domänennetzwerk sieht die Sache anders aus. Da gibt es eine zentrale SAM-Datenbank auf dem Domänencontroller. Alle Gruppenmitgliedschaftsprüfungen laufen dabei über den Domänencontroller.

Mein Beispiel wurde für den Einsatz in einem Domänennetzwerk geschrieben. Ohne Domänennetzwerk wird es nicht sinnvoll funktionieren. Es ist eben nur ein Beispiel.

Zyan bietet aber eine Lösung für das Problem an. Mit dem BasicWindowsAuthProvider Authentifizierungsanbieter kannst Du Windows-Authentifizierung haben, die nur serverseitig abläuft. Dabei spielt es keine Rolle, ob Client und Server in einer Domäne sind, oder nicht.
Auf folgende Doku-Seiten erfährst Du mehr über Zyan und welche Konfigurationsmöglichkeiten es gibt:
Architektur einer Zyan-Anwendung
ProtocolSetups

Wenn Dir Zyan nicht gefällt, kannst Du auch im Standard .NET Remoting eine eigene Authentifizierung implementieren. Dazu musst Du eigene Kanalsenken schreiben. Schau mal hier: MSDN: Sinks and Sink Chains
Das schreiben von Remoting-Kanalsenken an sich ist nicht schwer. Du brauchst aber tiefgreifende Kenntnisse über die .NET Remoting Infrastruktur.

Gruß
Rainbird

177 Beiträge seit 2009
vor 11 Jahren

Hallo Rainbird,

danke für deine ausführliche Antwort, habe nun verstanden warum es nicht geht.

Zyan bietet aber eine Lösung für das Problem an.

Ja, da bin ich schon drüber gestolpert , hatte das Projekt auch schonmal geladen, aber es wollte einfach nicht laufen.

Wenn Dir Zyan nicht gefällt, kannst Du auch im Standard .NET Remoting eine eigene Authentifizierung implementieren. Dazu musst Du eigene Kanalsenken schreiben. Schau mal hier: MSDN: Sinks and Sink Chains

Habe ich auch schonmal probiert , aber schnell sein lassen da reichen meine Kenntnisse noch nicht für.

Habe mir jetzt nochmal das Zyan Framework geladen, wollte schon wieder nicht, nach ein wenig "Murkserei" dann doch ans laufen bekommen, einen Server erstellt, meine DAL, und BAL eingebunden, meine GUI vorgeschaltet und läuft, sowohl in der VM als auch auf den PC. Sehr schön. Dann werde ich nun mein Projekt auf den Zyan aufbauen, mal schauen wie weit ich komme. Den Server aufsetzen und die Schnittstellen implementieren sowie die GUI dran koppeln (habe mich am LINQ Beispiel orientiert) hat auf jedenfall Super schnell und problemlos geklappt.

Werde nunmal schauen wie das mit der Berechtigung geht.

Vielen Dank nochmal für deine Antwort und für die Arbeit die du /Ihr da in das Projekt gesteckt habt.

PS: Nach runterladen der Solution, wollte es mit dem kompilieren aus folgenden Grund nicht klappen:
In der Befehlszeile für Präbuildereignisse steht folgendes drin:
$(SolutionDir)tools\SnTools\sngen.bat $(SolutionDir)

Da hat der beim kompilieren ein fehler geschmissen beim ausführen der sngen.bat, und zwar wurde das Leerzeichen von ""E:\Eigene Dokumente...." nicht berücksichtigt , habe hier den Pfad hart reingeschrieben und dann hat es geklappt.

Gruß
Peter

3.728 Beiträge seit 2005
vor 11 Jahren
Eigenen Authentifizierungsanbieter schreiben

Hallo elTorito,

Werde nunmal schauen wie das mit der Berechtigung geht.

Du kannst Dir auch einen eigenen Authentifizierungsanbieter schreiben. In folgendem Diskussionsbeitrag findest Du ein einfaches Beispiel, wie das geht:
http://zyan.codeplex.com/discussions/267636

177 Beiträge seit 2009
vor 11 Jahren

Hi Rainbird,

danke für die Info, werd ich mir mal durchlesen, aktuell habe ich einen "eigenen" Authentifizierungsanbieter , und zwar habe ich meinen vorhandenen einfach eingebaut:


    public class UserAuthProvider : IAuthenticationProvider
    {
		public AuthResponseMessage Authenticate(AuthRequestMessage authRequest)
		{

               // Aufruf an Geschäftslogik delegieren
               User user = UserManager.LogOn(userName, password);
            }
....
     }

Weiß aber noch nicht wie effizient das ist, ist ja quasi doppelt gemoppelt... Hintergrund war/ist folgender, ich habe eine User / Rollen Matrix Tabelle , der User meldet sich an, ich bekomme die Rollen des Users .

Auf Client Seite habe ich dann für jede Rolle eine eigene Klasse, ein Benutzer mit der Rolle Mitarbeiter loggt sich z.B. wie folgt ein:


Zyan.Business.Employees.LogOn(dlg.UserName, dlg.Password);

In der Employees Klasse erfolgt dann der Aufbau zum Zyan Server:


        private IRoles RolesServiceProxy
        {
            get
            {
                // Wenn noch kein Stellvertreterobjekt erzeugt wurde ...
                if (_rolesProxy == null)
                {
                    // Verbindung herstellen
                    TcpBinaryClientProtocolSetup protocol = new TcpBinaryClientProtocolSetup();
                    ZyanConnection conn = new ZyanConnection("tcp://meinServer:6666/elToritoSample", protocol, credentials, false, true);

                    // add global error handler
                    conn.Error += ConnectionErrorHandler;

                    // create proxy for the default queryable service
                    _rolesProxy = conn.CreateProxy<IRoles>();
                }

                // Stellvertreterobjekt zurückgeben
                return _rolesProxy;
            }
        }

und meine LogOn Methode:


        public void LogOn(string userName, string password)
        {
                // Melde den aktuellen Kunden ab.
                zyanBusiness.Customers.LogOff();

                // Melde den aktuellen Mitarbeiter ab.
                LogOff();

                // Melde den Mitarbeiter an.
                this.employee = RolesServiceProxy.LogOn(userName, password);

                foreach (Role role in employee.Roles)
                {
                    if (String.Compare(role.Name, ZyanBusiness.EmployeeRoleName, StringComparison.InvariantCultureIgnoreCase) == 0)
                    {
                        // Current user is a member of employee role.
                        return;
                    }
                }
}

Im weiteren Verlauf des Clients prüfe ich dann mit :


            // Prüfe, ob ein Mitarbeiter angemeldet ist.
            if (!zyanBusiness.Employees.IsLoggedOn)

Ob ein Mitarbeiter angemeldet ist, oder ein Customer oder eine andere Rolle, abhängig davon werden dann Menüpunkte angezeigt oder nicht, bzw. der Zugriff auf diverse Methoden erlaubt.

wobei IsLoggedOn mir nur ein Objekt User zurück gibt oder nicht:


        /// <summary>
        /// Prüft, ob aktuell ein Mitarbeiter angemeldet ist.
        /// </summary>
        public bool IsLoggedOn
        {
            get
            {
                return this.employee != null;
            }
        }

Wie gesagt, weiß noch nicht wie effizient das ist , und obs nicht eine doppelt gemoppelte Geschichte ist 🙂

Ursprünglich hatte ich mir eine Klasse von IPrincipal abgeleitet und mit :


        [PrincipalPermission(SecurityAction.Demand,Role = MeinPrincipal.EmployeeRoleName)]
        public static void DeleteCountry(Guid countryId)
        {
            CountryDal.DeleteCountry(countryId);
        }

in der BusinessLogic noch eine zusätzliche Sicherheitsprüfung eingebaut, da wollte ich nochmal schauen ob ich das mit dem Zyan auch wieder so hinbekomme,... ich hab halt das was ich schon hatte versucht zu verwenden, nun muss ich noch rausfinden was "doppelt" ist und was nicht...

Nun aber erstmal Urlaub, danach gehts mit dem Projekt weiter 😁

gruß
Peter

177 Beiträge seit 2009
vor 9 Jahren

Nun aber erstmal Urlaub, danach gehts mit dem Projekt weiter 😁
gruß
Peter

2012 ... Das war (k)ein langer Urlaub ... 😁 In dem Urlaub Antrag gemacht und eins führte zum anderen... Geheiratet, Hund und Häusschen angeschafft, andauernde Kernsanierung...

Aber habe das Projekt nun mal wieder aus der Schublade gekramt 8)
Meine "Authentifizierungs" Probleme sind gelöst. Meine GUI steht soweit. So langsam muss die Anwendung ein paar "nützliche" Funktionen bekommen.

in meiner Anwendung möchte ich bestimmten Datensätzen Dateien zuordnen, diese sollen auf dem Server in ein bestimmtes Verzeichnis abgelegt werden...

Wie implementiere ich das mit dem Zyan? Hab da was gelesen von NegotiateStream ist das der richtige Ansatz?

Danke

3.728 Beiträge seit 2005
vor 9 Jahren

Hallo elTorito,

Wie implementiere ich das mit dem Zyan?

Es gibt folgende Möglichkeiten:*Gepufferte Übertragung von Dateien komplett als byte[] *Gepufferte Übetragung von Dateien in Segmenten als byte[] *Streaming mit herkömmlichen .NET Streams

Für große Dateien (mehrere Megabyte) sind Streams zu empfehlen. Für kleine Dateien gepufferte Übertragung als byte[].
Die Stream-Lösung hat allerdings den Nachteil, dass der Server statuswahrend implementiert sein muss. Außerdem steuert der Client dabei eine serverseitige Stream-Instanz fern, was dem Server ein Stück weit die Kontrolle entzieht. Wenn das nicht gewünscht ist, ist die gepufferte segmentierte Variante eine Alternative. Aber auch die verlangt statuswahrende Implementierung, da die einzelnen Segmente über mehrere Methodenaufrufe hinweg übertragen werden.

Schau Dir folgenden Testcode am, um zu verstehen, wie Streams mittels Zyan übertragen werden können: Streaming mit Zyan

Die Varianten mit byte[] werden so implementiert:*Datei mittels Stream in ein byte[] einlesen *byte[] ganz normal als Methodenparameter oder Property an den Server senden *Dateiname ebenfalls als Methodenparameter mitgeben *Serverseitig das byte[] in einen FileStream auf die Serverplatte schreiben, oder in eine DB, oder sonst wohin.

Bei der segmentierten Variante wird einfach nicht die komplette Datei auf einen Rutsch übertragen, sondern kleinere Batzen (auch Chunks genannt). So kann der Server bereits bei Erhalt des ersten Bytehaufenbatzen mit dem schreiben auf Platte beginnen (ganz ähnlich wie bei der Strwam-Lösung), was bei großen Dateien, wie z.B. Videos, ein großer Zeitvorteill ist.

Hab da was gelesen von
>
ist das der richtige Ansatz?

Nein, NegotiateStream würdest Du verwenden, wenn Du Dich auf Socketebene bewegst.

Gruß
Rainbird

177 Beiträge seit 2009
vor 9 Jahren

Hi Rainbird,

über den Streaming Test Code bin ich die Tage schon gestolpert und am rumexperimentieren, aber es gelingt mir nicht:

FileServiceProxy.Listfiles gibt mir eine Liste von Dateien auf dem Server zurück, ich wähle eine aus und klicke einen Button Download:

Im Client:


var input = FileServiceProxy.GetFile(path);
var output = File.Create(dlg.FileName);

FileServiceProxy.CopyData(input, output);
input.Close();
output.Close();

Es knallt in der MessageSinkWrapper.cs in:


public IMessage SyncProcessMessage(IMessage msg)
		{
			NormalizeMessageUrl(msg);
			return InnerSink.SyncProcessMessage(msg);
		}

Fehlermeldung:
Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.

Das gleiche wenn ich vom Client:


// Get File from Server 
var input = FileServiceProxy.GetFile(path);
var output = File.Create(dlg.FileName);
input.CopyTo(output);

wenn ich folgendes Probiere (neue connection aufbauen):


 ZyanConnection conn1 = new ZyanConnection(url, protocol, credentials, false, true);
IFileService ifrsProxy1 = conn1.CreateProxy<IFileService>();
Stream input = ifrsProxy1.GetFile(path);
var output = File.Create(dlg.FileName);
input.CopyTo(output);
// close streams
input.Close();
output.Close();

Fehlermeldung:
SocketException:Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte

(Firewall testweise ausgeschaltet)

Muss ich ein bestimmtes Protocol verwenden für die Dateiübertragung? Momentan verwende ich dass TcpCustomServerProtocolSetup mit eigenen AuthProvider.

Danke

177 Beiträge seit 2009
vor 9 Jahren

Hi,

mal wieder übereilt nachgefragt...
Ich hatte da byte[] und Stream bisschen durcheinander gebracht.... Die Kombination machts

Bei der segmentierten Variante wird einfach nicht die komplette Datei auf einen Rutsch übertragen, sondern kleinere Batzen (auch Chunks genannt). So kann der Server bereits bei Erhalt des ersten Bytehaufenbatzen mit dem schreiben auf Platte beginnen

So habe ich das nun auch umgesetzt.

Upload: Stream wird in byte[] Häppchenweise übertragen, auf Serverseite wird byte[] in ein FileStream geschrieben, nach und nach je nach "Chunk Größe"

Download: Filestream vom Server wird als byte[] häppchenweise zurückgegeben bis Dateogröße (hole ich vorher) der Download Größe entspricht.

Danke !