einfaches WCF (asynchron mit MVVM) Tutorial

    • C#
    • .NET (FX) 4.0

    Es gibt 4 Antworten in diesem Thema. Der letzte Beitrag () ist von Thias.

      einfaches WCF (asynchron mit MVVM) Tutorial

      Hallo,

      ich habe vor einiger Zeit zwecks meiner Ausbildung ein Praktikum angefangen und arbeite deswegen in meinem zweiten Ausbildungsjahr komplett in einer Firma als Praktikant. Dort wurde mir die Aufgabe zugeteilt, die bisherige Schnittstelle, die mit DCOM arbeitet zu ersetzen. Hier setzt WCF (Windows Communication Foundation) an. Sie ist der "Nachfolger" von diversen Diensten und stellt ein recht mächtiges Framework zur Verfügung. Damit kann man recht "einfach" Server-Client-Applikationen realisieren...

      In diesem Tutorial möchte ich eine einfache Möglichkeit zeigen, wie man von einem Client einen Server anfunkt und dieser dann eine gewisse Aufgabe ausführt. Damit der Client während der Wartezeit nicht einfriert läuft das ganze asynchron. Zur Übersichtlichkeit wurde beim Client das MVVM-Pattern verwendet (näheres dazu findet sich hier)


      Ein WCF-Projekt ist nach dem ABC (Address, Binding, Contract) aufgebaut...ohne das läuft nichts. Wenn man weiß, was für Funktionen man hat/braucht kann man auch von hinten anfangen und nach CBA das ganze aufbauen

      C -> Contract

      nach Möglichkeit durch ein Interface (Schnittstelle) zu lösen, welche druch eine Klasse aufgerufen wird (siehe CdatenVertrag/Ivertrag/IvertragAsynchron)

      B -> Binding

      Angabe des Transportprotokolls (zb. TCP oder Http) in meinem Projekt habe ich TCP gewählt (NetTcpBinding). Bei Microsoft findet man auch noch mehr. Je nach Einsatzzweck wählt man hier das passende aus oder kreiert sich selbst eines...

      A -> Address

      Beinhaltet Angabe zum Protokoll, IpAdresse, Portnummer und Pfad zur Anwendung

      Binding//IpAdresse:Portnummer/PfadzurAnwendung -> in meinem Projekt: net.tcp://localhost:2121/IvertragAsynchron (anstatt localhost kann man auch eine Ipadresse eintragen)

      Damit es am Anfang keine Probleme gibt sollte man

      C#-Quellcode

      1. SecurityMode.None
      setzen.... (siehe im Code zb. Bei Server -> Program Zeile 28)

      Folgender Aufbau:

      Man startet mit einer leeren Projektmappe und fügt dieser folgende Projekte hinzu:

      Client -> WPF-Application
      HilfeTools -> Class Library ("Klassenbibliothek") (wird für das MVVM benötigt)
      Server -> Console-Application
      Vertrag -> Class Library

      Am besten fängt man mit dem Server an...

      Dem Serverprojekt fügt man zwei Klassen hinzu (bzw eine, da eine ja schon standardmäßig angelegt wird) "Cservice" und "Program" oder wie man sie auch immer nennen möchte...

      Die Klasse Cservice führt das aus, was der Server machen soll...zum Beispiel rechnet es Primzahlen aus oder gibt einfach nur nen String "Hallo Welt" zurück
      Die Klasse Program baut eine Verbindung zur "Außenwelt" auf...

      Program:
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.IO;
      3. using System.Linq;
      4. using System.Net;
      5. using System.Net.Sockets;
      6. using System.ServiceModel; //unabdingbar für WCF
      7. using System.Windows.Forms;
      8. using Vertrag;
      9. namespace Server
      10. {
      11. class Program
      12. {
      13. static void Main(string[] args)
      14. {
      15. using (ServiceHost sh = new ServiceHost(typeof(Cservice)))
      16. {
      17. string Host = System.Net.Dns.GetHostName();
      18. var IPAddress = Dns.GetHostEntry(Host).AddressList.First(adr => adr.AddressFamily != AddressFamily.InterNetworkV6); //gibt ipv4 zurück
      19. FileInfo fi = new FileInfo(Application.ExecutablePath); // Pfad von .txt-Datei im Debug-Verzeichnis vom Server
      20. string path;
      21. path= fi.DirectoryName + "\\Portnummer.txt";
      22. string portnummer;
      23. portnummer = File.ReadAllText(path); // liest Portnummer aus .txt heraus
      24. Console.WriteLine("Der Service läuft auf dem Port: " + portnummer);
      25. Console.WriteLine("");
      26. sh.AddServiceEndpoint(typeof(Ivertrag), new NetTcpBinding(SecurityMode.None), "net.tcp://" + IPAddress + ":" + portnummer + "/IvertragAsynchron");
      27. sh.Open();
      28. Console.WriteLine("Server läuft auf: " + "net.tcp://" + IPAddress + ":" + portnummer + "/IvertragAsynchron");
      29. Console.WriteLine("");
      30. Console.WriteLine("Zum Beenden bitte Taste drücken!");
      31. Console.Read();
      32. }
      33. }
      34. }
      35. }


      Hier ließt der Server die Ip-Adresse des Rechners aus, auf dem er gestartet wird und öffnet einen Port. Die Portnummer wird im Debugverzeichnis des Servers als .txt Datei hinterlegt. So kann man recht einfach die Portnummer anpassen/ändern. Nachdem der Server diese Portnummer ausgelesen hat, baut er aus der Ip-Adresse und dem Port sowie dem zugehörigen Vertrag (dazu später mehr) eine Verbindung auf... Dann wartet der Server auf eine Anfrage vom Client...


      Cservice:
      Spoiler anzeigen

      C#-Quellcode

      1. using Vertrag;
      2. namespace Server
      3. {
      4. class Cservice : Ivertrag
      5. {
      6. public bool Ping()
      7. {
      8. return true;
      9. }
      10. public CdatenVertrag DoIt() //public Cvertrag DoIt()
      11. {
      12. System.Threading.Thread.Sleep(1000);
      13. string ergebnis = null;
      14. int n = 0;
      15. n = 100000; // Obergrenze (bis dahin wird nach Primzahlen gesucht)
      16. bool[] prime = new bool[n];
      17. for (int i = 2; i <= n - 1; i++)
      18. {
      19. prime[i] = true;
      20. }
      21. if (true)
      22. {
      23. int i = 2;
      24. while (i * i < n)
      25. {
      26. if (prime[i])
      27. {
      28. ergebnis = ergebnis + " " + i.ToString();
      29. int j = i * i;
      30. while (j < n)
      31. {
      32. prime[j] = false;
      33. j = j + i;
      34. }
      35. }
      36. i += 1;
      37. }
      38. while (i < n)
      39. {
      40. if (prime[i])
      41. {
      42. ergebnis = ergebnis + " " + i.ToString();
      43. }
      44. i += 1;
      45. }
      46. }
      47. return new CdatenVertrag
      48. {
      49. Content = ergebnis//"Daten vom Server!"
      50. };
      51. }
      52. }
      53. }


      Hier werden einfach Primzahlen berechnet...(hier jetzt mal bis 100.000). Das Ergebnis wird in einen String geschrieben und an den Client dann zurück geschickt... return new CdatenVertrag -> Content = ergebnis


      Das war es auch schon zum Server...zu beachten sind die References (Verweise) und Usings

      Jetzt zum Client...

      Dem Client verpasst man eine nicht allzu aufwändige Oberfläche mit WPF/XAML...

      Textbox1 = Eingabefeld für die Ipadresse -> Name: hostNameFeld
      Textbox2 = Eingabefeld für die Portnummer -> Name: portnummerFeld
      Textbox3 = Ausgabefeld des Ergebnisses welches man vom Server erhält -> Name: Ausgabe

      Button = zum Verbindung aufbauen/zum Starten des Clients...
      Den Controlls gibt man halbwegs sinnvolle Namen, sodass man damit später auch was anfangen kann...

      Im XAML-Code sind noch 6 Zeilen wichtig:

      Spoiler anzeigen

      XML-Quellcode

      1. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      2. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      3. mc:Ignorable="d"
      4. d:DesignHeight="300" d:DesignWidth="300"
      5. xmlns:vm="clr-namespace:Client.Viewmodel"
      6. d:DataContext="{d:DesignInstance Type=vm:MainModel}" >


      genaueres ließt man hier

      Ebenfalls wichtig im XAML-Code sind die folgenden Dinge:

      in der Textbox für die IP-Adresse und die Portnummer

      XML-Quellcode

      1. Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}"


      bei der Textbox der Portnummer lautet der Code entsprechend

      XML-Quellcode

      1. Text="{Binding PortNumber, UpdateSourceTrigger=PropertyChanged}"


      beim Button

      XML-Quellcode

      1. Command="{Binding GetDataCommand}"


      beim Ausgabefeld

      XML-Quellcode

      1. Text="{Binding StringContent}"


      so wird der entsprechende Inhalt gebunden.

      Spoiler anzeigen

      XML-Quellcode

      1. <Window x:Class="Client.MainWindow"
      2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      4. Title="Client" Height="345" Width="786" WindowStartupLocation="CenterScreen" ResizeMode="NoResize"
      5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      6. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      7. mc:Ignorable="d"
      8. d:DesignHeight="300" d:DesignWidth="300"
      9. xmlns:vm="clr-namespace:Client.Viewmodel"
      10. d:DataContext="{d:DesignInstance Type=vm:MainModel}" >
      11. <!--die 4 letzten Zeilen ermöglichen Binding-Picking-->
      12. <!--https://www.vb-paradise.de/index.php/Thread/83442-MVVM-Binding-Picking-im-Xaml-Editor/-->
      13. <Window.Resources>
      14. <Style TargetType="{x:Type TextBlock}">
      15. <Setter Property="Margin" Value="5,0,4,0" />
      16. <Setter Property="VerticalAlignment" Value="Center" />
      17. </Style>
      18. <ImageBrush x:Key="blue" ImageSource="/Bilder/black1.jpg" />
      19. <ImageBrush x:Key="technik" ImageSource="/Bilder/mm1.png" />
      20. </Window.Resources>
      21. <Grid Height="328"
      22. VerticalAlignment="Top"
      23. Background="{StaticResource blue}" Margin="0,0,0,-28">
      24. <Grid.ColumnDefinitions>
      25. <ColumnDefinition/>
      26. <ColumnDefinition Width="0*"/>
      27. </Grid.ColumnDefinitions>
      28. <Grid.RowDefinitions >
      29. <RowDefinition Height="217.28"/>
      30. </Grid.RowDefinitions>
      31. <Menu Name="menu1" Grid.Row="0" VerticalAlignment="Top" Height="21">
      32. <MenuItem Header="Datei">
      33. <MenuItem.ToolTip>
      34. <ToolTip>
      35. <StackPanel>
      36. <TextBlock FontWeight="Bold">Speichern/Laden/Beenden</TextBlock>
      37. <TextBlock>Zum Speichern der Ip-Adresse und der Portnummer</TextBlock>
      38. <TextBlock>Zum Laden der Ip-Adresse und der Portnummer</TextBlock>
      39. <TextBlock>Zum Beenden des Programms</TextBlock>
      40. </StackPanel>
      41. </ToolTip>
      42. </MenuItem.ToolTip>
      43. <MenuItem Header="Speichern"
      44. Command="{Binding Speichern}"/>
      45. <MenuItem Header="Laden"
      46. Command="{Binding Laden}"/>
      47. <Separator/>
      48. <MenuItem Click="OnBeenden"
      49. Header="Beenden"/>
      50. </MenuItem>
      51. </Menu>
      52. <!--UpdateSourceTrigger aktualisiert den Inhalt der TextBox...so wird das erkannt, was hinein geladen wird-->
      53. <TextBox Margin="216,72,376,118"
      54. FontWeight="Medium"
      55. FontSize="16"
      56. Name="hostNameFeld"
      57. Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}"
      58. AcceptsReturn="False">
      59. <TextBox.ToolTip>
      60. <ToolTip>
      61. <StackPanel>
      62. <TextBlock FontWeight="Bold">Ip-Adresse/Host Name</TextBlock>
      63. <TextBlock>Tragen Sie hier die Server-Ip-Adresse oder den Server-Hostnamen ein</TextBlock>
      64. </StackPanel>
      65. </ToolTip>
      66. </TextBox.ToolTip>
      67. </TextBox>
      68. <!--UpdateSourceTrigger aktualisiert den Inhalt der TextBox...so wird das erkannt, was hinein geladen wird-->
      69. <TextBox Margin="424,72,206,118"
      70. FontWeight="Medium"
      71. FontSize="16"
      72. Name="portnummerFeld"
      73. Text="{Binding PortNumber, UpdateSourceTrigger=PropertyChanged}"
      74. AcceptsReturn="False">
      75. <TextBox.ToolTip>
      76. <ToolTip>
      77. <StackPanel>
      78. <TextBlock FontWeight="Bold">Server Portnummer</TextBlock>
      79. <TextBlock>Tragen Sie bitte die Portnummer des Servers ein, mit dem sich der Client verbinden soll</TextBlock>
      80. </StackPanel>
      81. </ToolTip>
      82. </TextBox.ToolTip>
      83. </TextBox>
      84. <TextBox Name="Ausgabe"
      85. Margin="30,138,30,-78"
      86. TextWrapping="Wrap"
      87. Background="{StaticResource blue}"
      88. Foreground="Red"
      89. VerticalScrollBarVisibility="Hidden"
      90. AcceptsReturn="False"
      91. Text="{Binding StringContent}">
      92. <TextBox.ToolTip>
      93. <ToolTip>
      94. <StackPanel>
      95. <TextBlock FontWeight="Bold">Ausgabe</TextBlock>
      96. <TextBlock>Gibt die Antwort des Servers zurück</TextBlock>
      97. </StackPanel>
      98. </ToolTip>
      99. </TextBox.ToolTip>
      100. </TextBox>
      101. <Label Foreground="Red"
      102. Margin="216,37,322,146">Server Ip-Adresse / Hostname
      103. </Label>
      104. <Label Foreground="Red"
      105. Margin="424,37,222,146">Server Portnummer
      106. </Label>
      107. <Label Foreground="Red"
      108. Margin="30,105,660,85">Ausgabe
      109. </Label>
      110. <Label Background="{StaticResource technik}"
      111. Margin="30,21,594,90"/>
      112. <Button Margin="594,72,30,118"
      113. Background="White"
      114. Command="{Binding GetDataCommand}"
      115. Focusable="True"
      116. IsDefault="True"
      117. >Client starten
      118. <Button.ToolTip>
      119. <ToolTip>
      120. <StackPanel>
      121. <TextBlock FontWeight="Bold">Client starten</TextBlock>
      122. <TextBlock>Startet den Client und baut eine Verbindung zum gewünschten Server auf</TextBlock>
      123. </StackPanel>
      124. </ToolTip>
      125. </Button.ToolTip>
      126. </Button>
      127. </Grid>
      128. </Window>


      Das ist der vollständige Code meines Fensters...Ich habe zwei Bilder als Hintergrund verwendet...Entweder ihr löscht den entsprechenden Code raus oder fügt selbst zwei beliebige Bilder hinzu...Einfach dem Client-Projekt einen neuen Ordner hinzufügen ("Bilder") und in diesen zwei Bilder hinzufügen

      dann in die Namen im Code anpassen

      XML-Quellcode

      1. <ImageBrush x:Key="blue" ImageSource="/Bilder/black1.jpg" />
      2. <ImageBrush x:Key="technik" ImageSource="/Bilder/mm1.png" />
      (black1 und mm1 durch eure entsprechenden Namen austauschen (die der Bilder im Ordner "Bilder"


      Jetzt geht man in die Codebehind-Klasse vom Client... -> MainWindows.xaml.cs

      Dort ist eigentlich nur eine Zeile Code von elementarer Bedeutung:

      C#-Quellcode

      1. this.DataContext = new Viewmodel.MainModel();
      Mit deren Hilfe wird das Binding weitergeleitet

      Spoiler anzeigen

      C#-Quellcode

      1. using System.Windows;
      2. //View (inkl. MainWindow.xaml)
      3. namespace Client
      4. {
      5. public partial class MainWindow : Window
      6. {
      7. public MainWindow()
      8. {
      9. InitializeComponent();
      10. this.DataContext = new Viewmodel.MainModel();
      11. // fürs Binding/MVVM hergestellte "Verknüpfung"
      12. }
      13. private void OnBeenden(object sender, RoutedEventArgs e)
      14. {
      15. this.Close();
      16. }
      17. }
      18. }


      jetzt kann man das Viewmodel dem Client hinzufügen...

      Man fügt dem Client eine neue Klasse hinzu und benennt diese jetzt Viewmodel oder wie auch immer (dann muss man die elementare Zeile Code aber wie folgt ändern: [...] = new WieAuchImmerName.MainModel();

      der Viewmodel-Klasse fügt man jetzt die Gegenstücke der im XAML-Code vorbereiteten Bindings hinzu

      C#-Quellcode

      1. public string _IpAddress = "";
      2. public string IpAddress
      3. {
      4. get { return _IpAddress; }
      5. set { _IpAddress = value; NotifyPropertyChanged("ipAddress"); }
      6. }


      Die in im Anführungszeichen stehenden Begriffe ("ipAddress") sind die im XAML genannten

      XML-Quellcode

      1. Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}"


      So weiß das Programm wohin damit...Der UpdateSourceTrigger ist später interessant (bei den HilfeTools)

      Wenn man dem Client eine Ip und eine PortNr. eingetragen hat, kann man diese auch Speichern (über die Menuzeile). So muss man nicht immer beides neu eintippen. Damit der Client das auch übernimmt, muss man die Textboxen "aktualisieren" dafür ist der UpdateSourceTrigger da...

      Wenn man jetzt auf den Button klickt baut der Client die Verbindung zum Server auf...wichtig hierbei ist, dass man die bei Ip und PortNr. die Werte eintippt, die der Server hat, da der Client ja eine Verbindung zum PC aufbaut, auf dem der Server läuft.

      Spoiler anzeigen

      C#-Quellcode

      1. using System.ServiceModel;
      2. using System.Windows.Input;
      3. using Vertrag;
      4. using HilfeTools;
      5. using System.Threading;
      6. using System.Net;
      7. using System.Windows;
      8. using System;
      9. using System.Diagnostics;
      10. using System.IO;
      11. //Viewmodel
      12. namespace Client.Viewmodel
      13. {
      14. public partial class MainModel : Chilfe
      15. {
      16. private IvertragAsynchron objProxy;
      17. public string _StringContent = "";
      18. public string StringContent
      19. {
      20. get { return _StringContent; }
      21. set { _StringContent = value; NotifyPropertyChanged("StringContent"); }
      22. }
      23. public string _IpAddress = "";
      24. public string IpAddress
      25. {
      26. get { return _IpAddress; }
      27. set { _IpAddress = value; NotifyPropertyChanged("ipAddress"); }
      28. }
      29. public string _PortNumber = "";
      30. public string PortNumber
      31. {
      32. get { return _PortNumber; }
      33. set { _PortNumber = value; NotifyPropertyChanged("portNumber"); }
      34. }
      35. private Cbefehl _GetDataCommand;
      36. public ICommand GetDataCommand
      37. {
      38. get { return (_GetDataCommand ?? (_GetDataCommand = new Cbefehl(OnStart))); }
      39. //Der Operator ?? wird NULL-Sammeloperator genannt.
      40. //Der linke Operand wird zurückgegeben, falls dieser NICHT NULL ist.
      41. //Andernfalls wird der rechte Operand zurückgegeben.
      42. }
      43. private Cbefehl _Speichern;
      44. public ICommand Speichern
      45. {
      46. get { return (_Speichern ?? (_Speichern = new Cbefehl(OnSpeichern))); }
      47. }
      48. private Cbefehl _Laden;
      49. public ICommand Laden
      50. {
      51. get { return (_Laden ?? (_Laden = new Cbefehl(OnLaden))); }
      52. }
      53. public void OnSpeichern()
      54. {
      55. Microsoft.Win32.SaveFileDialog dialog = new Microsoft.Win32.SaveFileDialog();
      56. dialog.Filter = "Txt-Files(*.txt)|*.txt";
      57. Nullable<bool> result = dialog.ShowDialog();
      58. if (result == true)
      59. {
      60. File.WriteAllText(dialog.FileName, IpAddress);
      61. }
      62. Nullable<bool> result2 = dialog.ShowDialog();
      63. if (result2 == true)
      64. {
      65. File.WriteAllText(dialog.FileName, PortNumber);
      66. }
      67. }
      68. public void OnLaden()
      69. {
      70. Microsoft.Win32.OpenFileDialog dialogo = new Microsoft.Win32.OpenFileDialog();
      71. dialogo.Filter = "Txt-Files(*.txt)|*.txt";
      72. Nullable<bool> result = dialogo.ShowDialog();
      73. if (result == true)
      74. {
      75. IpAddress = File.ReadAllText(dialogo.FileName);
      76. }
      77. Nullable<bool> result2 = dialogo.ShowDialog();
      78. if (result2 == true)
      79. {
      80. PortNumber = File.ReadAllText(dialogo.FileName);
      81. }
      82. }
      83. public void OnStart()
      84. {
      85. string rmAddres = null;
      86. rmAddres = "net.tcp://" + IpAddress + ":" + PortNumber + "/IvertragAsynchron";
      87. NetTcpBinding nettcpbinding = new NetTcpBinding(SecurityMode.None); // new NetTcpBinding(hier MUSS was stehen!! sonst tuts ned)
      88. ChannelFactory<IvertragAsynchron> chFactory = new ChannelFactory<IvertragAsynchron>(nettcpbinding);
      89. nettcpbinding.MaxReceivedMessageSize = 10485760; // 10485760 entspricht 10 MB
      90. TimeSpan mSpan = default(TimeSpan);
      91. mSpan = new TimeSpan(1200000000); // entspricht 2 Minuten (1 Millisekunde = 10.000 Ticks) -> 2min = 120.000ms = 1.200.000.000Ticks
      92. nettcpbinding.SendTimeout = mSpan;
      93. EndpointAddress epAddress = new EndpointAddress(rmAddres);
      94. objProxy = chFactory.CreateChannel(epAddress);
      95. this.StringContent = "Anfrage läuft...währenddessen kann das Fenster frei bewegt werden";
      96. if (objProxy.Ping())
      97. {
      98. objProxy.BeginDoIt(asyncResult =>
      99. {
      100. this.StringContent = objProxy.EndDoIt(asyncResult).Content; // hier wird die Fehlermeldung geworfen... -> CallbackException
      101. }, null);
      102. }
      103. }
      104. }
      105. }


      In der OnStart-Methode (beim Klicken auf den Button) ist noch was dabei, was verhindert, dass die Verbindung fehlschlägt, da es zu lange dauert oder die Nachricht zu groß ist. TimeSpan und MaxReceivedMessageSize ...Je nachdem wie groß der String ist, kann es sonst zu Problemen führen...ist aber nicht zwingend notwendig


      Damit die erwähnten Bindings/UpdateSourceTrigger funktionieren ist noch ein weiteres Projekt wichtig. Die HilfeTools

      hier braucht man zwei Klassen...nichts aufwändiges...

      Cbefehl:
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.Windows.Input;
      3. namespace HilfeTools
      4. {
      5. public class Cbefehl : ICommand
      6. {
      7. private Action _execute;
      8. public Cbefehl(Action execute)
      9. {
      10. _execute = execute;
      11. }
      12. public bool CanExecute(object parameter)
      13. {
      14. return true;
      15. }
      16. public event EventHandler CanExecuteChanged;
      17. public void Execute(object parameter)
      18. {
      19. _execute();
      20. }
      21. }
      22. }


      Hier ist wichtig, dass man von ICommand erbt -> siehe Viewmodel Speichern/Laden


      Chilfe
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.ComponentModel;
      4. using System.Diagnostics;
      5. using System.Linq;
      6. using System.Linq.Expressions;
      7. using System.Reflection;
      8. namespace HilfeTools
      9. {
      10. public abstract class Chilfe : INotifyPropertyChanged
      11. {
      12. public event PropertyChangedEventHandler PropertyChanged;
      13. public void NotifyPropertyChanged(string info)
      14. {
      15. if (PropertyChanged != null)
      16. {
      17. PropertyChanged(this, new PropertyChangedEventArgs(info));
      18. }
      19. }
      20. }
      21. }

      hier ist wichtig, dass man von INotifyPropertyChanged erbt -> siehe Viewmodel Properties für die Textboxen


      Wichtig ist auch, dass man das Projekt HilfeTools dem Client als Verweis hinzufügt...

      Jetzt kommt das Herzstück des Ganzen...

      Der Vertrag:

      Hier legt man 1 Klasse an -> CdatenVertrag
      und zwei Schnittstellen (Interface) -> Ivertrag und IvertragAsynchron

      der CdatenVertrag sieht wie folgt aus:
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Linq;
      4. using System.Runtime.Serialization;
      5. using System.ServiceModel;
      6. using System.Text;
      7. namespace Vertrag
      8. {
      9. [DataContract(Name = "DemoData")]
      10. public class CdatenVertrag
      11. {
      12. [DataMember(Name = "Content")]
      13. public string Content { get; set; }
      14. }
      15. }


      Bei den beiden Schnittstellen Ivertrag und IvertragAsynchron ist wichtig, dass diese den selben Namen bekommen...
      Ja ich war hier etwas unkreativ und mir ist nichts sinnvolles eingefallen

      Beide müssen in dieser Zeile den Namen identisch haben:

      C#-Quellcode

      1. [ServiceContract(Name = "Grapefruit")]


      Ivertrag:
      Spoiler anzeigen

      C#-Quellcode

      1. using System.ServiceModel;
      2. namespace Vertrag
      3. {
      4. [ServiceContract(Name = "Grapefruit")]
      5. public interface Ivertrag
      6. {
      7. [OperationContract()]
      8. CdatenVertrag DoIt();
      9. [OperationContract()]
      10. bool Ping();
      11. }
      12. }


      IvertragAsynchron:
      Spoiler anzeigen

      C#-Quellcode

      1. using System;
      2. using System.ServiceModel;
      3. namespace Vertrag
      4. {
      5. [ServiceContract(Name = "Grapefruit")]
      6. public interface IvertragAsynchron
      7. {
      8. [OperationContract(AsyncPattern= true)]
      9. IAsyncResult BeginDoIt(AsyncCallback callback, object asyncState);
      10. CdatenVertrag EndDoIt(IAsyncResult result);
      11. [OperationContract(AsyncPattern = false)]
      12. bool Ping();
      13. }
      14. }


      Das ist eigentlich auch schon alles... Der OperationContract mit bool Ping(); kann auch gelöscht werden...das war eigentlich nur zu Testzwecken...


      hier noch ein Paar nähere Infors zu einigen Sachen im Code

      Channelfactory (im Viewmodel)
      MVVM und MVVM2
      Vertrag
      WCF
      Binding

      Ich hoffe ich kann hiermit dem ein oder anderen helfen...Ich hab mir das hier alles selbst beigebracht und es hat einiges an Kraft gekostet durch zu halten, da ich hier nur das Forum hatte, wo ich um Hilfe fragen konnte, weil in meiner Firma keiner mit WCF vertraut ist...


      [edit]

      Auf Wunsch von @ErfinderDesRades habe ich auf meiner Platte noch zwei Beispiele gesucht (VB.Net und C#)...

      das VB-Beispiel ist ein wenig "verkrüpelt" und nicht auf dem aktuellen Stand...
      Das C# Beispiel sollte jetzt dem Tut entsprechen
      Dateien
      • VB-Beispiel.zip

        (2,35 MB, 284 mal heruntergeladen, zuletzt: )
      • WCFeinfachBSP.zip

        (1,64 MB, 442 mal heruntergeladen, zuletzt: )

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Runshak“ ()

      Vielen Dank für das Tut.

      Ich hab fleissig dran rum-studiert - es ist ja eine WCF-Variante ohne Konfigurations-Dateien. Hab auch bischen recherchiert, auch in Richtung Chat, und fand dieses hier: CodeProject: WcfChat
      Das ist aber konfigurations-basiert.
      Letztendlich habe ich dein Teil dann spasseshalber auch auf konfigurations-basiert umgefrickelt. Vorteil davon ist, dass man MetadataExchanging konfigurieren kann, und dem Client-Projekt dann eine "Service-Reference" zufügen, die einen kompletten Client bereitstellt, sodass man im Code ein Objekt verfügbar hat, bei dem man gar nicht mehr merkt, dass man einen Service addressiert.
      Bisher fand ich leider noch nicht, wie man MetadataExchange codeseitig konfiguriert, also ohne eigentümliche Sachen ins Server-Config-File einfügen zu müssen.

      Paar weitere Hinweise auch im Readme.
      Dateien
      vllt hilft dir das weiter:

      Spoiler anzeigen

      C#-Quellcode

      1. ​class ProxyFactory
      2. {
      3. private static ServiceEndpoint endpoint;
      4. public static FlugServiceClient CreateProxy(string mexUrl)
      5. {
      6. if (endpoint == null)
      7. {
      8. EndpointAddress mexAddress = new EndpointAddress(mexUrl);
      9. MetadataExchangeClient mexClient = new MetadataExchangeClient(mexAddress);
      10. MetadataSet metadataSet = mexClient.GetMetadata();
      11. WsdlImporter importer = new WsdlImporter(metadataSet);
      12. SerciceEndpintcollection endpoints = importer.ImportAllEndpoints();
      13. endpoint = endpoints[0];
      14. }
      15. FlugSercieClient client = new FlugSercieClient(endpoint.Binding, endpoint.Address);
      16. return client;
      17. }
      18. }


      das ist ein Beispiel aus dem Buch

      Steyer, Manfred/ Schwichtenberg, Holger/ Fischer, Matthias/ Krause, Jörg: Verteilte Systeme und Services mit .NET 4.5 - Konzepte und Lösungen für WCF 4.5 und ASP.NET Web-API, 2. Auflage, Carl Hanser Verlag, München, 2013
      Hier noch ein kleiner Nachtrag zur Sicherheit...

      In meinem Tut habe ich die Sicherheit auf

      C#-Quellcode

      1. new NetTcpBinding(SecurityMode.None)
      gestellt...

      So wird weder auf der Transportebene noch auf der Nachrichtenebene Sicherheit "bereitgestellt"...Aus diesem Grund sollte man das nur für Testzwecke verwenden oder in Ausnahmefällen (wobei ich keine Ausnahme wüsste ;) )


      Transport: Sicherheit wird auf der Transportebene durch das Transportprotokoll bereitgestellt

      Message: SOAP-Nachrichten werden gemäß der WS-Security Standards geschützt und sind für die Authentifizierung zuständig

      TransportWithMessageCredentials: Sicherheit wird mit der Transportsicherheit gewährleistet während die Client-Authentifizierung über Nachrichtensicherheit realisiert wird. Sicherheit ist nur von Endpunkt zu Endpunkt gegeben; es können keine SOAP-Vermittler verwendet werden

      TransportCredentialOnly: Die Client-Authentifizierung wird auf der Transportebene realisiert, es gibt aber keien Integrität und Vertraulichkeit der Nachrichten im Vergleich zu (TWMC). Der Modus ist nur für Umgebungen geeignet, in denen die Übertragung durch andere Mittel sichergestellt wird (bsp: IPSec)

      Both: Es wird sowohl MSMSQ-Transportsicherheit als auch SOAP-Nachrichtensicherheit angewendet für die maximale Sicherheit...allerdings wird hierfür sehr viel Leistung benötigt und es ist nur für das NetMsmqBinding vorhanden...

      Transport und Message kann einfach eingebaut werden wie das "None" (siehe CodeBeispiel ganz am Anfang) für TWMC und TCO werden Zertifikate benötigt, was die Implementierung etwas komplexer gestaltet...sollte ich noch Zeit finden dafür, werde ich das hier nachreichen...

      [edit] Auch bei Transport und Message werden Zertifikate benötigt...das merkt man aber erst wenn man das Projekt nicht mehr lokal ausführen möchte :-/ sorry dafür...bei den andern wird schon beim Kompilieren ein Fehler gezeigt...

      Sicherheitsmodus ->NoneTransportMessageTransportWith MessageCredentialTransport CredentialsOnlyBoth
      Binding v





      BasicHttpBindingStandardjajajajanein
      WSHttpBindingjajaStandardjaneinnein
      WSDualHttpBindingjaneinStandardneinneinnein
      WSFederationHttpBindingjaneinStandardjaneinnein
      NetTcpBindingjaStandardjajaneinnein
      NetPeerTcpBindingjaStandardjajaneinnein
      NetNamedPipeBindingjaStandardneinneinneinnein
      NetMsmqBindingjaStandardjaneinneinja
      MsmqIntegrationBindingjaStandardneinneinneinnein
      WebHttpBindingStandardjaneinneinjanein

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Runshak“ ()

      Hallo,

      erst einmal vielen Dank für dieses nützliche Beispiel!

      Auch wenn es nichts mit WCF zu tun hat, so doch ein Verbesserungsvorschlag zum Code für alle welche dieses Beispiel noch studieren werden.

      Das berechnen der Primzahlen in der Server-Subroutine "DoIt" dauert bei mir ca. 11 Sekunden.

      Dabei benötigt aber nicht die eigentliche Primzahlberechnung so lange, sondern die wiederholt aufgerufene Zeile:

      C#-Quellcode

      1. ergebnis = ergebnis + " " + i.ToString();


      Von daher ist es besser und dramatisch schneller eine String-Liste zu definieren und die Daten zuerst in diese zu speichern.
      Am Ende dann das Ergebnis mit String.Join ermitteln:

      C#-Quellcode

      1. string ergebnis = null;
      2. List<string> ergebnisLst = new List<string>();


      Dann wie folgt:

      C#-Quellcode

      1. // ergebnis = ergebnis + " " + i.ToString();
      2. ergebnisLst.Add((i.ToString()));


      Und zum Schluss der entscheidende String.Join:

      C#-Quellcode

      1. while (i < n)
      2. {
      3. if (prime[i])
      4. {
      5. // ergebnis = ergebnis + " " + i.ToString();
      6. ergebnisLst.Add((i.ToString()));
      7. }
      8. i += 1;
      9. }
      10. ergebnis = String.Join(" ", ergebnisLst);
      11. }


      Bzw. im Spoiler noch einmal der komplette Code

      Spoiler anzeigen


      C#-Quellcode

      1. using System;
      2. using System.Collections.Generic;
      3. using System.Linq;
      4. using System.Text;
      5. using Vertrag;
      6. namespace Server
      7. {
      8. public class Cservice : Ivertrag
      9. {
      10. public bool Ping()
      11. {
      12. return true;
      13. }
      14. public CdatenVertrag DoIt()
      15. {
      16. // System.Threading.Thread.Sleep(1000);
      17. string ergebnis = null;
      18. List<string> ergebnisLst = new List<string>();
      19. int n = 0;
      20. n = 10000000; // Obergrenze zur Berechnung von Primzahlen
      21. bool[] prime = new bool[n];
      22. for (int i = 2; i <= n - 1; i++)
      23. {
      24. prime[i] = true;
      25. }
      26. if (true)
      27. {
      28. int i = 2;
      29. while (i * i < n)
      30. {
      31. if (prime[i])
      32. {
      33. // ergebnis = ergebnis + " " + i.ToString();
      34. ergebnisLst.Add((i.ToString()));
      35. int j = i * i;
      36. while (j < n)
      37. {
      38. prime[j] = false;
      39. j = j + i;
      40. }
      41. }
      42. i += 1;
      43. }
      44. while (i < n)
      45. {
      46. if (prime[i])
      47. {
      48. // ergebnis = ergebnis + " " + i.ToString();
      49. ergebnisLst.Add((i.ToString()));
      50. }
      51. i += 1;
      52. }
      53. ergebnis = String.Join(" ", ergebnisLst);
      54. }
      55. return new CdatenVertrag
      56. {
      57. Content = ergebnis//"Daten vom Server!"
      58. };
      59. }
      60. }
      61. }