Laden...

Forenbeiträge von TTopsecret Ingesamt 34 Beiträge

17.04.2015 - 13:59 Uhr

INotifyPropertyChanged ist auch in jedem ViewModel richtig implementiert und aktualisiert auch richtig das UI, das ist nicht das Problem 😉
Das Template wird mehrmals erstellt weils mehrfach gleichzeitig gebraucht wird^^

Um das Template nicht jedesmal neu erstellen zu müssen(was auch immer relativ lange dauert) wäre interessant, ob ein Template einfach als Vorlage erstellt und danach nur geklont und mit einem neuen Model im ViewModel ausgerüstet werden kann und ob dies wirklich eine Zeitersparnis ermöglicht.

16.04.2015 - 16:44 Uhr

Das wäre auch, wie es so schön genannt wird, mit Kanonen auf Spatzen geschossen. So wie ich das sehe wird das nur ein kleines Projekt werden, ausserdem setzt eine DB schon ein gewisses Wissen voraus.

Ich schliesse mich den anderen an, ein Objekt für jede Frage wäre extrem nützlich.

So als Tipp, die Fragen kannst du dann auch ganz leicht als XML speichern und laden.

        public void Save(QuestionContainer obj, string path)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(QuestionContainer));

            using(FileStream stream = new FileStream(path, FileMode.Create))
                serializer.Serialize(stream, obj);
        }

        public QuestionContainer Load(string path)
        {
            QuestionContainer data = null;
            XmlSerializer serializer = new XmlSerializer(typeof(QuestionContainer));

            using(XmlReader reader = XmlReader.Create(path))
                data = serializer.Deserialize(reader) as QuestionContainer ;

            return data;
        }
16.04.2015 - 12:49 Uhr

Danke dir, x:Shared war das, was fehlte 👍 War mir noch Unbekannt

Die schonende Ressourcenverwaltung war mir schon bekannt. Jedoch war mir nicht bewusst, dass dies auch für x:Static übernommen wird, weil sich der Wert dort ja immer ändern kann, meiner Meinung nach.

Das Problem war, dass es ohne die Style-Templates die Bindung beim Drucken der Seite nicht korrekt oder sogar gar nicht durchführte. Zudem wurden für alle Templates als Grundlage das Element Control genommen, dadurch habe ich ja nicht ein 'bestehendes' UI-Element abgeändert, sondern ein 'neues' erstellt. Wie würdest du es umsetzen?

16.04.2015 - 07:57 Uhr

Ich versuchs nochmal =) Der Ausdruck wird nicht in der UI angezeigt, sondern generiert und ausgedruckt (beispielsweise XPS, um es direkt auf dem Computer anzeigen zu lassen). Mit generieren meine ich, die Templates werden neu instanziert und der Seite(FixedPage) hinzugefügt:

var element = new PageProjectInfo(viewModel);

    public class PageProjectInfo : Control
    {
        public PageProjectInfo()
        {
            
        }

        public PageProjectInfo(IProjectInfoViewModel viewModel) : this()
        {
            this.DataContext = viewModel;
        }
    }

PageProjectInfo wäre oben die Template-Klasse im TargetType. Wenn ich das Template so erstelle, sollte es doch auf die x:Static im neuen Ausdruck den neuen Wert übernehmen, oder hat hier x:Static versteckte Feature?

Die Sprachressourcen der UI und des Ausdrucks sind separiert und können einzeln umgeschalten werden.

15.04.2015 - 15:25 Uhr

Ausgangslage: Eine WPF-Applikation, die von mir entwickelt wird, erstellt Druckausgaben mit FixedDocument, dessen Elemente mit Xaml-Templates dargestellt werden. Die Druckausgabe kann dabei in mehreren Sprachen erstellt werden, dessen Zeichenfolgen von einem resx abgerufen werden.

Das funktioniert soweit. Jedoch ist nun das Problem, dass nach einem Sprachwechsel beim erneuten Drucken der Ausgabe nicht die neuen Übersetzungen übernommen werden, sobald es bereits einmal gedruckt(generiert) wurde.

Die Templates werden bei jeder Druckausgabe neu generiert. Dabei werden auch die Übersetzungen mit der neuen Kultur abgerufen, das habe ich bereits mit dem Debugger überprüft. Leider wird in der Ausgabe die neu abgerufenen Texte nicht übernommen.

Im Code sieht das in verkürzter Version folgendermassen aus:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:lang="clr-namespace:{Applikation Name}.Views.Documents.Languages">

    <Style TargetType="{Template Klasse}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{Template Klasse}">
                    <Border>
                        <TextBlock  Text="{x:Static lang:Resources.ProjectNumber}" />
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    
</ResourceDictionary>

Das betreffende Feld wäre hier {x:Static lang:Resources.ProjectNumber}

Und aus dem resx-generierten Code:

        public static string ProjectNumber {
            get {
                return ResourceManager.GetString("ProjectNumber", resourceCulture);
            }
        }

Es wird der richtige Wert zurückgegeben.
Falls ihr noch mehr Code/Wissen über den Ablauf braucht, einfach schreiben 😉

Danke und Gruess
TTopsecret

20.02.2015 - 16:27 Uhr

Möglich wäre es auch, aber jenachdem auch zeitaufwendig einen eigenen Context und die dazugehörigen Repository für jedes Format zu erstellen.

Zu 3: Das Problem ist, Expression(System.Linq.Expressions.Expression) erlaubt keinen Kontruktor mit Parameter und keinen Mehrzeiler im Select. Also ohne ToList gäbe es mit dem Code unten eine Exception. Meine Hoffnung war, eine Expression so aufzubauen, dass es dasselbe macht.

20.02.2015 - 13:44 Uhr

Also eine kurze Rückmeldung und zwei Fragen zum Thema.

Repository Pattern ist natürlich nützlich für eine feste Datenquelle wie eine Datenbank. Oben war jedoch mehr ein Speicherstand gemeint, der auch in derselben Anwendung auf anderen Computern geladen werden können. Dazu habe ich das Strategy Pattern gefunden, ist nach meiner Meinung deutlich besser dafür geeignet.

Aber nun doch noch zwei Fragen zum Repository Pattern. Dazu habe ich nun eine sqlite Datenbank genommen, jedoch dauert der erste Aufruf auch bei wenig Datensätzen relativ lange, da die Daten in den den local Bereich des DbContext geladen werden. Nach dem ersten Aufruf ist die Performance jedoch kein Problem mehr. Gibt es eine Möglichkeit, die Zeit des ersten Aufrufs zu verkürzen?

Eine zweite Frage habe ich auch noch. Ich habe noch nicht den vollständigen Überblick über Expression. Wie kann ich im Select-Bereich ein ViewModel mit Konstruktor erstellen ohne ToList() zu verwenden?

        SensorTypes = new ObservableCollection<EditableSensorTypeViewModel>(
                unitOfWork.SensorTypeRepository.GetAll().ToList()
                .Select<ISensorType, EditableSensorTypeViewModel>(x => 
                new EditableSensorTypeViewModel(x)));
17.02.2015 - 07:50 Uhr

Ich danke euch für die Stichworte. Dann werde ich das aktuelle Projekt mit Repository und UnitOfWork umsetzen, scheint sich bewährt zu haben.

16.02.2015 - 11:52 Uhr

Hallo Community,

ich arbeite seit einiger Zeit mit WPF und MVVM und habe das Entwurfsmuster zu schätzen gelernt. Jedoch stellt mir immer noch die Frage, wie ich den Zugriff auf eine Datenquelle optimal in das Muster einbinde. Beispielsweise eine Speicher- und Ladefunktion mit verschiedene mögliche Zielformaten. Gibt es hier einen bereits bewährten Weg/e?

Ein Beispiel, wie ich es in einem Projekt umgesetzt habe. Die Methoden befinden sich direkt in einem Objekt, wo sich die aktuellen Daten befinden. Jedoch stellt mir hier der Gedanke, dies etwas anders zu strukturieren um flexibler zu sein.


        public void Save(FileInfo path)
        {
            if(path == null)
                throw new ArgumentNullException("path");

            // Quellzugriffsobjekt abrufen
            ISourceAccess source = GetSourceAccess(path.Extension);

            // Daten speichern
            source.Save(this, path.FullName);
        }

        protected static ISourceAccess GetSourceAccess(string extension)
        {
            // Ruft je nach Endung das dazugehörige Objekt ab, um Daten zu speichern und zu laden
            switch(extension)
            {
                case ".xml":
                    return new XML();
                case ".meineEndung":
                    return new Binary();
                default:
                    throw new ArgumentException("Speicherformat steht nicht zur Verfügung.");
            }
        }

Mit lieben Grüssen
TTopsecret

03.02.2015 - 16:09 Uhr

Habe es dann gesehen. Damit funktioniert es, danke 👍

Das ist des Teufels Job bei den Programmierern 😁

03.02.2015 - 13:44 Uhr

Deine Vorlage scheint auch noch Fehler zu enthalten. Wenn beim Treeview im gleichen Ast Elemente nach oben verschoben werden, wird das falsche Element gelöscht. Dasselbe Problem habe ich nun leider dadurch auch gerade, bin grad am Suchen wo der Index nicht richtig zählt.

03.02.2015 - 13:10 Uhr

Stimmt, zu spät bedacht. Habe nun eine IViewModel ObservableCollection statt der UserControl Collection erstellt.

Danke dir. Ich habe nun dein Helpers-Projekt in meine Projektmappe eingebunden, hoffe das ist erlaubt. Habe schon eine Weile nicht mehr mit vb.net gearbeitet, brauchte einen Moment um den Example-Code in C# zu übertragen und auf mein Projekt anzupassen. Muss jetzt nur noch erreichen, dass das Element sich an der richtigen Stelle der Liste einfügt, danach sollte es funktionieren!

03.02.2015 - 08:36 Uhr

Dann macht es mich neugierig, wie solche Effekte am Besten in ein MVVM Projekt integriert werden sollen. Etwa doch als Codebehind?

Dabei handelt es sich bei ObservableCollection um eine Liste von UserControls, die mit ItemsControl in einen StackPanel geholt werden. Die Events müssen durch das Bild, das während dem Verschieben bei der Mausposition angezeigt wird, quasi ständig mit dem XAML kommunizieren.

EDIT auf edit von ErfinderDesRades: Also das Ziel soll sein, die Effekte trotzdem zu versuchen sie möglichst in den VM zu holen.

03.02.2015 - 08:14 Uhr

Habe auch noch eine kurze Frage zu diesem Thema.

Wo kommen die GUI-"Effekte" hin, die nicht als reines XAML möglich sind? Beispielsweise die Reihenfolge der Elemente einer ObservableCollection, die in einem Stackpanel angezeigt werden, mit Drag&Drop tauschen und während dem Schieben ein Teil des Elementes(Controls) bei der Maus als Bild angezeigt werden soll?

Nach meinem bisherigen Verständnis passen die Effekte auch nicht ganz in den ViewModel.

11.11.2014 - 15:59 Uhr

Mein Fehler, da hast du natürlich recht. Aber wäre trotzdem interessant zu wissen ob dies nur ein Bug ist. Bei den Links glauben sie ja auch das Gleiche. Vielleicht wäre euch ja etwas bekannt gewesen =)

11.11.2014 - 14:57 Uhr

Wusste nicht sonst wohin, also stelle ich hier die Kuriosität des Frameworks hinein:

Im Moment schaue ich immer mal wieder im Code des .NET Frameworks was hinter gewissen Klassen, Methoden und Eigenschaften steht. Ist meistens recht interessant, manchmal bin ich aber auch verwirrt über die Umsetzung. Beispielsweise kommt es vor, dass die gleiche Variable mehrmals auf den gleichen Wert überprüft wird, obwohl er nicht geändert werden konnte.

Bei einer Stelle bin ich jetzt besonders überrascht und halte es für einen Fehler. Wenn ich die String.Join Methode mit den Parameter (String, object[]) benutze, gibt es, wenn das erste Element null ist, automatisch den Wert String.Empty zurück:

object[] objects = new object[] { null, "text1", "text2" };
String a = String.Join(";", objects); // a wird String.Empty

Der Code im Framework sieht so aus:


        [ComVisible(false)]
        public static String Join(String separator, params Object[] values) {
            if (values==null)
                throw new ArgumentNullException("values");
            Contract.EndContractBlock();
 
            if (values.Length == 0 || values[0] == null)
                return String.Empty;
 
            if (separator == null)
                separator = String.Empty;
 
            StringBuilder result = StringBuilderCache.Acquire();
 
            String value = values[0].ToString();           
            if (value != null)
                result.Append(value);
 
            for (int i = 1; i < values.Length; i++) {
                result.Append(separator);
                if (values[i] != null) {
                    // handle the case where their ToString() override is broken
                    value = values[i].ToString();
                    if (value != null)
                        result.Append(value);
                }
            }
            return StringBuilderCache.GetStringAndRelease(result);
        }

Ausserdem wird das erste Element sogar zweimal auf null überprüft, was die zweite Kuriosität wäre. In MSDN steht:

Hinweise zu Aufrufern

Wenn das erste Element von valuesnull ist, verkettet die Join(String, Object[])-Methode die Elemente in values nicht, sondern gibt stattdessen String.Empty zurück. Eine Reihe von Problemumgehungen für dieses Problem ist verfügbar. Es ist am einfachsten, dem ersten Element des Arrays den Wert String.Empty zuzuweisen, wie das folgende Beispiel zeigt.

Die Begründung fehlt auch hier. Und dies passiert nur bei 'public static String Join(String separator, params Object[] values)'. Bei allen anderen Überladungen spielt es keine Rolle. Vielleicht seid ihr ja Intelligenter als ich und seht gerade wieso das bei genau DIESER Überladung nur für das ERSTE Element gemacht wurde?

11.11.2014 - 08:36 Uhr

Mir ist nicht ganz klar, wie das bei GNU GPL gehandhabt wird: Werden die Bedingungen nur gültig, wenn der Quellcode eines unter GPL stehendes Projekt verändert oder erweitert wird oder reicht dafür auch schon den Quellcode von aussen als Bibliothek zu nutzen, damit die Software auch unter GPL stehen muss?

04.11.2014 - 13:10 Uhr

Soweit ich weiss besitzt jede ClickOnce-Anwendung eine einmalige ID(PublicKeyToken), die bei der erstmaligen Veröffentlichung erstellt wird.

Nur eine Vermutung, kein Gewähr: Wenn du den bereits vorhandene ClickOnce-Order auf dem Server mit verschiebst, sollte es eigentlich auch weiterhin funktionieren. Wenn sich der Pfad ändert, muss dieser in der {Anwendungsname}.appref-ms Datei auf jedem Client auch angepasst werden, in dieser Datei ist ebenfalls der PublicKeyToken sichtbar. Vielleicht reicht es auch schon in {Anwendungsname}.application im ClickOnce-Order den alten PublicKeyToken einzutragen. Der Pfad muss natürlich derselbe sein wie früher.

23.10.2014 - 13:33 Uhr

Übergeben reicht nicht, gelesen sollte der NetworkStream auch noch werden. RecData wird zur Zeit nie einen Wert zugewiesen, obwohl der eigentlich die Daten aus dem Stream erhalten sollte. Zudem kommt das Problem von Th69 noch dazu

broadcastStream.Read(RecData, 0, RecBytes);

Gruss

23.10.2014 - 12:58 Uhr

Wo liest du überhaupt den broadcastStream auf der Serverseite? 😉

Gruss

23.10.2014 - 08:16 Uhr

(Können den hier keine Spoiler gesetzt werden?)

Im Endeffekt, ja. Im weitergehenden Ereignis Add-/RemoveMachine in der newClient Methode gab es in einigen Fällen eine Exception zurück, die dann es erst dort behandelt wurde. Dadurch wurde das Hinzufügen abgebrochen, wenn der Client eine neue Verbindung aufbauen wollte. Ich hatte leider nicht mehr dran gedacht, die Verbindung im catch wieder richtig zu schliessen.

Der Dienst überprüft am Abend, ob noch ein Client angemeldet ist, wenn nein wird der Strom im Gebäude abgeschaltet, wenn ja wird eine Shutdown-Meldung an den Client gesendet, die er dann abbrechen kann, wenn er weiterarbeiten möchte. Deshalb den Projekttitel ShutdownService.

Programmierhans meinte auf Anfrage, es mache nichts aus wenn ich hier den Netzwerk-Code poste. Ich bin offen für Kritik, Verbesserungsvorschläge usw, nur drauf los. Bisher gab es leider Niemand, der mir mitteilen konnte, auf was ich noch alles achten sollte.

using ShutdownService.Helper;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace ShutdownService.Server
{
    /// <summary>
    /// Verwaltet die Verbindungen zu allen Clients.
    /// </summary>
    public class TcpServer
    {
        #region Variable
        /// <summary>
        /// Gibt die dazugehörende Connection zur IP zurück.
        /// </summary>
        /// <param name="machineIp">Die IP-Adresse zu der Verbindung.</param>
        /// <returns>Die Connection zur IP.</returns>
        public TcpConnection this[IPAddress machineIp]
        {
            get
            {
                return connectionList.Find(x => x.GetIpAddress().ToString() == machineIp.ToString());
            }
        }

        /// <summary>
        /// IP Adresse des Servers.
        /// </summary>
        public IPAddress ServerIP { get; private set; }

        /// <summary>
        /// Port des Servers.
        /// </summary>
        public int ServerPort { get; set; }

        /// <summary>
        /// True/False ob der Server läuft.
        /// </summary>
        public bool ServerActive { get; private set; }

        /// <summary>
        /// Die Liste speichert alle aktuelle Verbindungen.
        /// </summary>
        private List<TcpConnection> connectionList;

        /// <summary>
        /// Der Hörer als Thread.
        /// </summary>
        private Thread thrListener;

        /// <summary>
        /// Der Listener nimmt alle neue eingehende Verbindungen ab.
        /// </summary>
        private TcpListener tcpListener;

        /// <summary>
        /// Event: Wenn eine neue Verbindung hergestellt wurde und kann hier weitergeleitet werden.
        /// </summary>
        public event EventHandler<ObjectEventArgs<TcpConnection>> AddedMachine;

        /// <summary>
        ///Event: Wird ausgelöst, wenn eine Verbindung getrennt wurde und kann hier weitergeleitet werden.
        /// </summary>
        public event EventHandler<ObjectEventArgs<TcpConnection>> RemovedMachine;

        /// <summary>
        /// Event: Wird ausgelöst, wenn sich ein neuer Computer beim Server meldet.
        /// </summary>
        private event EventHandler<ObjectEventArgs<TcpClient>> OnClientConnected;

        /// <summary>
        /// Event: Nachricht eines Clients weitergeben.
        /// </summary>
        public event EventHandler<ObjectEventArgs<string[]>> ReceivedMessage;

        #endregion

        #region Basic

        /// <summary>
        /// Konstruktor: Erstellt einen neuen TcpServer.
        /// </summary>
        /// <param name="port">Der Port, auf dem der Server laufen soll.</param>
        public TcpServer(int port)
        {
            ServerPort = port;
            ServerIP = IPAddress.Any;
            connectionList = new List<TcpConnection>();
            OnClientConnected += newClient;
            ServerActive = false;
            thrListener = new Thread(listen);
        }

        /// <summary>
        /// Startet den Server.
        /// </summary>
        public void Start()
        {
            if(!ServerActive)
            {
                ServerActive = true;
                tcpListener = new TcpListener(IPAddress.Any, ServerPort);
                tcpListener.Start();
                thrListener.Start();
                Logger.LogMessage("TcpServer gestartet");
            }
        }

        /// <summary>
        /// Alle Verbindungen des Servers trennen.
        /// </summary>
        public void Stop()
        {
            ServerActive = false;
            tcpListener.Stop();
            foreach(TcpConnection con in GetAllConnection())
            {
                RemoveMachine(con);
            }
            Logger.LogMessage("TcpServer gestoppt");
        }

        /// <summary>
        /// Beendet den Server, falls er läuft und startet ihn neu. Falls dies fehlschlägt, wird der Port auf Standard gesetzt (53456)
        /// </summary>
        public void Restart()
        {
            try
            {
                if(ServerActive)
                    Stop();
                Start();
            }
            catch(Exception e)
            {
                ServerPort = 53456;
                Logger.LogMessage(e);
                Logger.LogMessage("Server konnte nicht mit den Einstellungen neu gestartet werden. Port wird auf " + ServerPort + " gesetzt.");
                Start();
            }
        }

        #endregion

        #region ConnectionsManage

        /// <summary>
        /// Fügt einen neuen Computer in der Liste dazu.
        /// </summary>
        /// <param name="con">Die Verbindungsdaten der Machine.</param>
        private void AddMachine( TcpConnection con )
        {
            if(con == null)
                return;
            connectionList.Add(con);
            if(AddedMachine != null)
                AddedMachine(this, new ObjectEventArgs<TcpConnection>(con));
            Logger.LogMessage(con.GetIpAddress() + " hinzugefügt");
        }

        /// <summary>
        /// Löscht ein Computer aus der Liste, wenn entweder die Verbindung verloren geht oder der Computer abgeschalten wird.
        /// </summary>
        /// <param name="con">Die Verbindungsdaten der Machine.</param>
        private void RemoveMachine( TcpConnection con )
        {
            if(con == null)
                return;
            try
            {
                connectionList.RemoveAll(x => x.GetIpAddress().ToString() == con.GetIpAddress().ToString());
                if(RemovedMachine != null)
                    RemovedMachine(this, new ObjectEventArgs<TcpConnection>(con));
                Logger.LogMessage(con.GetIpAddress() + " entfernt");
                con.Close();
                con = null;
            }
            catch(Exception e)
            {
                Logger.LogMessage(e);
            }
        }

        /// <summary>
        /// Sendet eine Nachricht an einen bestimmten Computer.
        /// </summary>
        /// <param name="machineName">Die Maschine, an die die Nachricht gehen soll.</param>
        /// <param name="message">Die Nachricht, welche übertragen werden soll.</param>
        public void SendMessageToMachine( IPAddress machineIp, string message )
        {
            if(String.IsNullOrEmpty(message) || machineIp == null)
                return;
            TcpConnection con = this[machineIp];
            if(con != null)
                con.SendMessage(message);
            Logger.LogMessage(message + " wurde an " + con.GetIpAddress() + "gesendet");
        }

        /// <summary>
        /// Sendet eine Nachricht an alle Computer.
        /// </summary>
        /// <param name="message">Die Nachricht, welche übertragen werden soll.</param>
        public void SendMessageToAllMachine( string message )
        {
            if(String.IsNullOrEmpty(message))
                return;
            foreach(TcpConnection con in GetAllConnection())
                SendMessageToMachine(con.GetIpAddress(), message);
        }

        /// <summary>
        /// Nachricht vom Client auswerten.
        /// </summary>
        private void evaluationMessage( object sender, ObjectEventArgs<string> e )
        {
            string message = e.Obj;
            TcpConnection con = (TcpConnection)sender;
            if(String.IsNullOrEmpty(message) || con == null)
                return;
            string[] temp = message.Split(new Char [] { '|' });
            // Hostname wird gespeichert
            if(temp[0] == "HOSTNAME")
            {
                TcpConnection tempCon = this[con.GetIpAddress()];
                tempCon.HostName = temp[1];
                int index = connectionList.FindLastIndex(x => x.GetIpAddress().ToString() == tempCon.GetIpAddress().ToString());
                if(index != -1)
                    connectionList[index] = tempCon;
                if(AddedMachine != null && RemovedMachine != null)
                {
                    RemovedMachine(this, new ObjectEventArgs<TcpConnection>(con));
                    AddedMachine(this, new ObjectEventArgs<TcpConnection>(tempCon));
                }
            } else if(ReceivedMessage != null)
                ReceivedMessage(sender, new ObjectEventArgs<string[]>(temp));
        }

        /// <summary>
        /// Wartet auf eingehende Verbindungen.
        /// </summary>
        private async void listen()
        {
            while(ServerActive)
            {
                try
                {
                    TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync();
                    if(OnClientConnected != null)
                        OnClientConnected(this, new ObjectEventArgs<TcpClient>(tcpClient));
                } 
                catch
                {
                    if(ServerActive)
                        Restart();
                }
            }
        }

        /// <summary>
        /// Verwaltet die neu eingegangene Verbindung.
        /// </summary>
        private void newClient( object sender, ObjectEventArgs<TcpClient> e )
        {
            try
            {
                if(this[((IPEndPoint)e.Obj.Client.RemoteEndPoint).Address] != null)
                    RemoveMachine(this[((IPEndPoint)e.Obj.Client.RemoteEndPoint).Address]);
                TcpConnection newConnection = new TcpConnection(e.Obj);
                newConnection.ReceiveMessage += evaluationMessage;
                newConnection.ConnectionLost += clientConnectionLost;
                AddMachine(newConnection);
            }
            catch(Exception b)
            {
                e.Obj.Close();
                Logger.LogMessage(b);
            }
        }

        /// <summary>
        /// Die Verbindung zum Client ist abgebrochen.
        /// </summary>
        private void clientConnectionLost(object sender, EventArgs e)
        {
            RemoveMachine((TcpConnection)sender);
        }

        #endregion
    }
}

ObjectEventArgs ist ein von mir erstelltes EventArgs, der einfach eine einzige beliebige typsichere Variable mit übergeben kann


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using ShutdownService.Helper;
using System.Threading.Tasks;

namespace ShutdownService.Server
{
    /// <summary>
    /// Alle Verbindungsdaten und Streams zu einem bestimmten Client.
    /// </summary>
    public class TcpConnection
    {
        /// <summary>
        /// Der Client der Verbindung.
        /// </summary>
        private TcpClient tcpClient;

        /// <summary>
        /// Über diesen Kanal kommen die eingehenden Nachrichten.
        /// </summary>
        private BinaryReader strReader;

        /// <summary>
        /// Über diesen Kanal wird am Benutzer eine Nachricht gesendet.
        /// </summary>
        private BinaryWriter strWriter;

        /// <summary>
        /// Stream, über den die Verbindung läuft.
        /// </summary>
        private NetworkStream netStream;

        /// <summary>
        /// Lese-Thread.
        /// </summary>
        private Thread thrReader;

        /// <summary>
        /// Event: Die eingehenden Nachrichten werden weitergegeben.
        /// </summary>
        public event EventHandler<ObjectEventArgs<string>> ReceiveMessage;

        /// <summary>
        /// Event: Wird ausgelöst, wenn der Client keine Verbindung mehr zum Service besitzt.
        /// </summary>
        public event EventHandler ConnectionLost;

        /// <summary>
        /// Der Computername der Verbindung.
        /// </summary>
        public string HostName = "Unbekannt";

        /// <summary>
        /// Konstruktor: Erstellt die Verbindung.
        /// </summary>
        /// <param client="client">Der neue Client.</param>
        public TcpConnection(TcpClient client)
        {
            tcpClient = client;
            netStream = tcpClient.GetStream();
            strReader = new BinaryReader(netStream);
            strWriter = new BinaryWriter(netStream);
            thrReader = new Thread(clientListening);
            thrReader.Start();
        }

        /// <summary>
        /// Gibt die IP-Adresse zurück.
        /// </summary>
        public IPAddress GetIpAddress()
        {
            return ((IPEndPoint)tcpClient.Client.RemoteEndPoint).Address;
        }

        /// <summary>
        /// Schliesst die Verbindung.
        /// </summary>
        public void Close()
        {
            try
            {
                if(netStream != null)
                    netStream.Close();
            }
            finally
            {
                netStream = null;
            }
            tcpClient.Close();
        }

        /// <summary>
        /// Sendet am Benutzer eine Nachricht.
        /// </summary>
        /// <param name="message">Die Nachricht, welche an den Client gesendet werden soll.</param>
        public void SendMessage(string message)
        {
            try
            {
                if(!String.IsNullOrEmpty(message))
                {
                    // $ wird als Trennzeichen zwischen den Nachrichten genutzt, damit auch wenn die Nachrichten dann zusammen gesendet werden, am Ziel noch getrennt werden können.
                    message = message.Replace("$", "") + "$";
                    strWriter.Write(message);
                    strWriter.Flush();
                }
            }
            catch
            {
                if(ConnectionLost != null)
                    ConnectionLost(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// Der Stream wird auf eingehende Nachrichten abgehört und die Daten dann weitergegeben.
        /// </summary>
        private void clientListening()
        {
            try
            {
                while(true)
                {
                    string returnData = strReader.ReadString();
                    if(returnData == null)
                        throw new Exception("Verbindung verloren");
                    string[] testData = returnData.Split(new Char[] { '$' });
                    if(testData[0] == "1")
                    {
                        SendMessage("3");
                        continue;
                    }
                    foreach(string i in testData)
                        if(!string.IsNullOrEmpty(i))
                            if(ReceiveMessage != null)
                                ReceiveMessage(this, new ObjectEventArgs<string>(i));
                }
            }
            catch
            {
                if(ConnectionLost != null)
                    ConnectionLost(this, EventArgs.Empty);
            }
        }
    }
}

22.10.2014 - 08:58 Uhr

Habe das Problem durch den Logger gefunden, es lag nicht am Socket oder der Netzwerkklasse sondern hat sich woanders in der Anwendung eingeschlichen. Hatte jedoch dann Einfluss auf den Ablauf der Netzwerkklassen. Trotzdem danke für die Hilfe 👍

21.10.2014 - 14:43 Uhr

Entschuldige. Falls tatsächlich der Lesethread abstürzen sollte, wird der Stream und Socket des betreffenden Clients geschlossen und wartet bis der Client erneut versucht eine neue Verbindung zum Dienst aufzubauen(der Client bemerkt dabei jedoch nicht sofort vom Unterbruch und wartet bis die drei Ping-Versuche fehlschlagen). Ich habe nun eine Log-Datei erstellt und beobachte, wo sich der entscheidende Fehler ereignen könnte, falls er sich im Dienst befindet.

21.10.2014 - 11:34 Uhr

Lesen und schreiben wird auf beiden Seiten in eigenen Threads ausgeführt, weil beim BinaryWriter/-Reader, die benutzt werden, die Asynchronmöglichkeit nicht zur Verfügung steht. Auch wenn Fehler auftreten sollten ist die Anwendung aber Intelligent genug nicht einfach abzustürzen, sondern es wird im Ping Intervall erneut versucht eine Verbindung aufzubauen.

Welchen Writer/Reader würdet ihr empfehlen? StreamReader?

21.10.2014 - 10:22 Uhr

Danke für die Antworten. Mit WireShark habe ich noch nicht nachgeprüft, aber gute Idee, werde ich einmal tun wenn mir wieder der Datenverlust auffällt. Der Ping habe ich zur Zeit auf 10 Sekunden definiert.

Ev. tritt bei der Antwort ein Fehler auf, dann werde ich ein Log erstellen müssen, der alle Netzwerkfehler(oder Programmfehler..) protokolliert. Aber sollte, wenn der Dienst die Verbindung beendet, nicht auch sofort ein Fehler beim Client auftreten?
Merkwürdig ist dabei einerseits der Client, der glaubt den Ping erfolgreich versendet zu haben und keinen Netzwerkfehler auslöst(und erst nach dreimaligen Versuch ohne Antwort dann auch die Verbindung beendet), anderseit auch, dass es immer Phasen gibt. Es kann mehrere Stunden ordentlich laufen und dann plötzlich mehrfach nacheinander den Fehler auftreten.

Edit: Könnte es noch an einem nicht beendeten Stream liegen? Muss ich einmal überprüfen

21.10.2014 - 08:04 Uhr

Wie setzt ihr eine solche Verbindung um, wenn eine gebraucht wird? Also konzeptionell.

Erstellt ihr für jede Nachricht eine Antwort(wenn der Server diese nicht erhält dann noch mehrfach versucht wird die gleiche Nachricht zu senden)? Ist die Wahrscheinlichkeit grösser eine Nachricht zu empfangen, wenn die Nachricht länger ist? Wird die Priorität des Thread mit der Zeit so tief gesetzt, dass er über eine halbe Minute wartet, bis diese einmal ausgeführt wird und eine Antwort erstellt?

Im Moment braucht es drei fehlgeschlagnene Testnachrichten bis der Client die Verbindung abbricht(kommt immer wieder einmal vor) und erneut versucht eine Verbindung aufzubauen, was dann auch ohne Problem erfolgreich funktioniert. Der Dienst ist im Moment noch in einer Testphase und ist mit nicht mehr als max vier Clients verbunden.

20.10.2014 - 17:05 Uhr

Der Service läuft auf einem echten PC, der sich im lokalen Netzwerk und im gleichen Gebäude befindet

20.10.2014 - 13:25 Uhr

Hallo Community

Ich besitze einen Service auf einem Server und mehrere Clients in einem lokalen Netzwerk, die über TCP/IP eine dauerhafte Verbindung zum Server besitzen. Dies funktioniert auch weitgehend(wieso nicht vollständig siehe unten) und wenn der Client zum Beispiel ausgeschaltet wird, merkt das der Service sofort.

Regelmässig wird auch vom Client mit einer kurzen Nachricht überprüft, ob der Client diese noch empfangen kann. Leider wird sie aber regelmässig verschluckt(kommen nie an), trotz aktiver Verbindung oder der Service glaubt plötzlich, er habe keine Verbindung mehr, der Client jedoch schon und werden dann erst getrennt, wenn die Testnachricht nicht beantwortet wird um kurz danach wieder erfolgreich eine Verbindung aufzubauen. Verbindungsunterbrüche im Lan-Netzwerk kann weitgehend ausgeschlossen werden.

Gibt es Best Practices um eine zuverlässige und dauerhafte Verbindung über TCP/IP aufzubauen? Tipps und Tricks damit keine Nachrichten verloren gehen? Leider habe ich im Internet kaum eine brauchbare Lösungen gefunden, die das verhindern konnten.

Danke & Gruss

24.09.2014 - 11:07 Uhr

Sorry, war nur als Beispiel gedacht, natürlich kein Gutes^^ Die Liste habe ich nur hier hinzugefügt und ist nicht im Projekt vorhanden. Ausserdem sind die Aufgaben im Projekt deutlich besser gekapselt und habe es hier nur der Vereinfachung so geschrieben(auch wenn es bei mir noch lange nicht 'perfekt' ist). Einen FileSystemWatcher habe ich zwar auch, der gibt aber nur das Event über ein weiteres weiter(so ähnlich wie auch im Beispiel oben), sollte also Threadsicher sein. Bei mir überprüft er ob sich etwas in der Konfigurationdatei geändert hat und lädt dementsprechend die Konfigurationen neu. Gibt es dazu eine bessere Lösung?

Das Problem war, auf die beim ServiceHost mitgegebene Klasse ein Event zu setzen. Habe nun weiter im Internet gesucht und eine Lösung gefunden:

if(test != null)
            {
                serviceHost = new ServiceHost(test, svcUri);
                var behaviour = serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
                behaviour.InstanceContextMode = InstanceContextMode.Single;
            }
            serviceHost = serviceHost ?? new ServiceHost(typeof(ServiceCommunication), svcUri);

Damit sollte ich dann die Events setzen können.

24.09.2014 - 09:32 Uhr

Vielleicht ist es etwas schwer zu verstehen, was ich damit meine. Beim Beispiel im ersten Beitrag kommt nun noch ein Thread dazu:

[STAThread]
        static void Main( string[] args )
        {
            ServiceHost host = new ServiceHost(typeof(MyService), new Uri("http://localhost:61210/myService"));
            host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "Service");
            host.Open();
            ConfigFileWatcher watcher = new ConfigFileWatcher();
            Thread thread = new Thread(new ThreadStart(watcher.Start));
            thread.Start();
            Console.WriteLine("Taste drücken um zu beenden.");
            Console.ReadKey();
            host.Close();
        }

Der einen FileSystemWatcher aufruft um einen Ordner zu überprüfen und der Pfad der geänderte Datei in einer Liste speichert.

public class ConfigFileWatcher
    {
        private FileSystemWatcher fsw;

        public event FileSystemEventHandler ConfigChanged;

        public List<String> stringList = new List<string>();

        public ConfigFileWatcher( string path )
        {
            fsw = new FileSystemWatcher(path);
            fsw.EnableRaisingEvents = true;
            fsw.Changed += DataChanged;
        }

        public void Start()
        {
            try
            {
                while(true)
                {
                    fsw.WaitForChanged(WatcherChangeTypes.All);
                }
            }
            catch
            {
            }
            fsw.EnableRaisingEvents = false;
        }

        public void DataChanged( object sender, FileSystemEventArgs e )
        {
            stringList.Add(e.FullPath.ToLower()); 
            ConfigChanged(sender, e);
        }
    }

Wie kann ich nun von der Methode "Hello" im ersten Beitrag den ersten Eintrag in der Liste zurückgeben? Und das ohne static zu benutzen?

23.09.2014 - 13:56 Uhr

Ahso danke, also es kommt einfach immer darauf an, von wem es genutzt werden soll 🙂

Was Du mit anderen Objekte meinst weiß ich nicht. Ich kenn Dein Gedankengank nicht 😉
Singletons in Web-Umgebungen sollte einen jedenfalls immer stutzig machen. Und von static lässt man - wenn es nur irgendwie geht - die Finger weg.

Ich entwickle zum Beispiel einen Windowsservice. Dort verwaltet er bereits die Daten, mit dem WCF-Service soll eine GUI-Anwendung auf diese zugreifen können. Bisher habe ich einfach einen Thread gestartet, der für die Daten sorgt, aber ich kann mir grad nicht vorstellen, wie ich die vom ServiceHost verweisten Klasse ohne Verweis(wie eben static) auf diese zugreifen soll.
Im Konstruktor von ServiceHost gibt es die Möglichkeit einen Singleton anzugeben, deshalb bin ich auf die Idee gekommen ^^

23.09.2014 - 13:26 Uhr

Danke für die schnelle Antwort. Im ursprünglichen Projekt war IMetadataExchange im App.config und hatte dort auch so den Fehler.

Habe es nun entsprechend deines Links abgeändert und mit "net.tcp://localhost:61211/myService" funktioniert es jetzt sogar, mit http jedoch weiterhin nicht. Heisst das, mit http ist es gar nicht möglich und sollte nur net.tcp verwenden?

static void Main( string[] args )
        {
            ServiceHost host = new ServiceHost(typeof(MyService), new Uri[] { new Uri("http://localhost:61210/myService"), new Uri("net.tcp://localhost:61211/myService") });
            ServiceMetadataBehavior behavior = new ServiceMetadataBehavior();
            host.Description.Behaviors.Add(behavior);
            host.AddServiceEndpoint(typeof(IMetadataExchange), MetadataExchangeBindings.CreateMexTcpBinding(), "mex");
            host.AddServiceEndpoint(typeof(IMyService), new NetTcpBinding(SecurityMode.Message), "net.tcp://localhost:61211/myService");
            host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "http://localhost:61210/myService/Service");
            host.Open();
            Console.WriteLine("Taste drücken um zu beenden.");
            Console.ReadKey();
            host.Close();
        }

Habe auch noch eine zweite Frage. Wenn ich nun alles so erstelle, wie bekomme ich dann von dort Zugriff auf die anderen Objekte? Muss ich via static/Singleton arbeiten?

23.09.2014 - 12:01 Uhr

Hallo zusammen,

Ich habe mich in den letzten Tagen mit WCF befasst, stehe aber nun auf der Stelle, auch nach längerem Suchen.

Ich möchte einen Dienstverweis auf einen WCF-Server hinzufügen. Das Ziel findet es auch, nur kann es nicht auflösen.

Fehlermeldung:
Fehler beim Herunterladen von "http://localhost:61210/myService/Service/$metadata".
Anforderung nicht erfolgreich mit HTTP-Status 400: Bad Request.
Metadaten enthalten einen Verweis, der nicht aufgelöst werden kann: "http://localhost:61210/myService/Service".
Der Inhaltstyp "application/soap+xml; charset=utf-8" wurde von Dienst "http://localhost:61210/myService/Service" nicht unterstützt. Möglicherweise stimmen Client- und Dienstbindungen nicht überein.
Der Remoteserver hat einen Fehler zurückgegeben: (415) Cannot process the message because the content type 'application/soap+xml; charset=utf-8' was not the expected type 'text/xml; charset=utf-8'..
Wenn der Dienst in der aktuellen Projektmappe definiert ist, sollten Sie die Projektmappe erstellen und den Dienstverweis erneut hinzufügen.

Nachdem ich beim richtigen Projekt dieses Problem gehabt habe, habe ich es nun eines nur mit dem grundlegensten Codes erstellt, jedoch dasselbe Problem. Im Internet steht, dass es an SOAP 1.1 und 1.2 liegen könnte und ich einen BasicHttpBinding hinzufügen soll. Auch das hat mich nicht weitergebracht.

class Program
    {
        /// <summary>
        /// Der Haupteinstiegspunkt für die Anwendung.
        /// </summary>
        [STAThread]
        static void Main( string[] args )
        {
            ServiceHost host = new ServiceHost(typeof(MyService), new Uri("http://localhost:61210/myService"));
            host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "Service");
            host.Open();
            Console.WriteLine("Taste drücken um zu beenden.");
            Console.ReadKey();
            host.Close();
        }
    }

    public class MyService : IMyService
    {
        public string Hello( string name )
        {
            return "Hello " + name;
        }
    }

    [ServiceContract]
    public interface IMyService
    {
        [OperationContract]
        string Hello( string name );
    }

Und rufe es beim anderen Projekt mit "http://localhost:61210/myService/Service" auf.

Danke für die baldige Hilfe
TTopsecret