Daten mittes WebClient-Klasse übertragen

Der einfachste Weg Daten zu übertragen geht mit der WebClient-Klasse. Im Gegensatz zur WebClient-Klasse des großen .NET Frameworks umfasst sie nur asynchrone Aufrufe, damit der Benutzer nicht unnötig durch langlaufende Aufrufe warten muss.

Die einfachste Methode ist die DownloadStringAsync-Methode zum Laden von beliebigen Text-Dateien. Aber Uploads und Streams lassen sich damit behandeln.

Der folgende Code zeigt wie man Daten einer Url (z.B. http://blogs.msdn.com/olivers/rss.xml) laden kann. Falls einen die Crossdomain-Policy keinen Strich durch die Rechnung macht, siehe unten.

   1: string url = _address.Text;
   2: Uri uri = new Uri(url);
   3:  
   4: WebClient webclient = new WebClient();
   5: webclient.DownloadStringCompleted += new DownloadStringCompletedEventHandler(webclient_DownloadStringCompleted);
   6: webclient.DownloadStringAsync(uri);

Wichtig, damit man mitbekommt, wenn die Daten zurückkommen ist das Event DownloadStringCompleted.

   1: void webclient_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
   2: {
   3:     if (e.Error != null)
   4:     {
   5:         // Error
   6:         _resultText.Text = e.Error.Message;
   7:         return;
   8:     }
   9:  
  10:     string txt = e.Result;
  11:     _resultText.Text = txt;
  12: }

Das Ergebnis, in diesem Fall ein String, befindet sich im Argument e, welches die Methode als Parameter mit übergeben bekommt.

Daten mittels WebService übertragen

Um Daten mit einem WebService auszutauschen, sind keine neuen Techniken notwendig. Es geht wie bisher sehr einfach über Visual Studio.

Zu erst benötigt man einen WebService, den man in seinem Web in dem die Silverlight-Anwendung später gehostet wird, erstellt. Es muss tatsächlich die Webanwendung sein, in der die Silverlight-Anwendung liegt. Ist dies nicht der Fall kommt man mit der Crossdomain-Policy in Berührung. Dazu später mehr.

Der eigentliche WebService ist sehr einfach:

   1: [WebService(Namespace = "http://tempuri.org/")]
   2: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3: [System.ComponentModel.ToolboxItem(false)]
   4: // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
   5: // [System.Web.Script.Services.ScriptService]
   6: public class DemoWebService : System.Web.Services.WebService
   7: {
   8:  
   9:     [WebMethod]
  10:     public string HelloWorld()
  11:     {
  12:         return "Hallo Welt";
  13:     }
  14:  
  15:     [WebMethod]
  16:     public DateTime GetServerTime()
  17:     {
  18:         DateTime serverTime = DateTime.Now;
  19:         return serverTime;
  20:     }
  21:  
  22:     [WebMethod]
  23:     public int Add(int a, int b)
  24:     {
  25:         return a + b;
  26:     }
  27: }

Diesen WebService muss man nun von seiner Silverlight-Anwendung referenzieren. Dies geht am leichtesten mit der rechten Maustaste auf dem Silverlight-Projekt:

image

Anschließend sucht man sich den WebService über den folgenden Dialog aus.

image

Visual Studio erzeugt nun im angegebenen Namespace (also mit using den entsprechenden Namespace einbinden), in meinem Fall “DemoServiceReference” alle benötigten Proxyklassen und Webservice aufrufe, die dann einfach im Code ansprechen kann.

   1: DemoWebServiceSoapClient soapClient =
   2:    new DemoWebServiceSoapClient();

Prinzipiell lässt sich der Webservice-Proxy mit der obenstehenden Zeile erzeugen. Leider steht hart verdrahtet die Url auf meinen lokalen Webservice in der web.config. Das kann man beim produktiv setzen relativ einfach umstellen, ist aber beim Testen sehr mühsam.

Ich verwende daher immer die folgende Methode zum Erstellen des Webservice:

   1: private DemoWebServiceSoapClient GetWebService()
   2: {
   3:     string url = HtmlPage.Document.DocumentUri.AbsoluteUri;
   4:     url = url.Substring(0, url.LastIndexOf("/") + 1);
   5:     url += "DemoWebService.asmx";
   6:  
   7:     BasicHttpBinding binding = new BasicHttpBinding();
   8:     EndpointAddress address = new EndpointAddress(url);
   9:  
  10:     DemoWebServiceSoapClient soapClient =
  11:         new DemoWebServiceSoapClient(binding, address);
  12:  
  13:     return soapClient;
  14: }

In den Zeilen 3-5 erzeuge ich mir zur Laufzeit die Url zu meinem Webservice, den ich dann in Zeile 10 instanziere. Dazu muss ich dem Service mitteilen, welches Binding und welche Zieladresse mein Service hat.

Diese Methode funktioniert natürlich auch nur dann, wenn der Service wirklich im Root-Verzeichnis der Anwendung steht und “DemoWebService.asmx” heißt ;)

Um den Webservice-Aufruf anschließend durchzuführen ist der folgende Code notwendig:

   1: private void LoadServerTime()
   2: {
   3:     DemoWebServiceSoapClient soapClient =
   4:         GetWebService();
   5:  
   6:     soapClient.GetServerTimeCompleted 
   7:         += new EventHandler<GetServerTimeCompletedEventArgs>(soapClient_GetServerTimeCompleted);
   8:     soapClient.GetServerTimeAsync();
   9: }
  10:  
  11: void soapClient_GetServerTimeCompleted(object sender, GetServerTimeCompletedEventArgs e)
  12: {
  13:     if (e.Error == null)
  14:     {
  15:         DateTime serverTime = e.Result;
  16:         _serverTimeText.Text = serverTime.ToLongTimeString();
  17:     }
  18:     else
  19:     {
  20:         _serverTimeText.Text = e.Error.Message;
  21:     }
  22: }

Dieses Codebeispiel lädt die Uhrzeit des Servers in den Client und zeigt diese dort an.

   1: private void AddValues()
   2: {
   3:     int value1 = int.Parse(_valueA.Text);
   4:     int value2 = int.Parse(_valueB.Text);
   5:  
   6:     DemoWebServiceSoapClient soapClient = GetWebService();
   7:     soapClient.AddCompleted += new EventHandler<AddCompletedEventArgs>(soapClient_AddCompleted);
   8:     soapClient.AddAsync(value1, value2);
   9: }
  10:  
  11: void soapClient_AddCompleted(object sender, AddCompletedEventArgs e)
  12: {
  13:     if (e.Error == null)
  14:     {
  15:         int result = e.Result;
  16:         _resultText.Text = result.ToString();
  17:     }
  18:     else
  19:     {
  20:         _resultText.Text = e.Error.Message;
  21:     }
  22: }

Dieser Code ruft einen Webservice mit zwei Parametern auf und zeigt das Ergebnis ebenfalls an.

Crossdomain und die Sicherheit

Was sie vielleicht feststellen werden, ist das man über WebClient oder das Einbinden eines Webservices in Silverlight-Anwendungen (oder auch Flash-Anwendungen) nicht jeden beliebigen Inhalt von fremden Webseiten nachladen können. Solche sogenannten Cross-Domain-Calls werden von Silveright (und Flash) schlichtweg vorher geprüft und gegebenenfalls unterbunden. Um sorgenfrei eine externe Datei oder einen Webservice von einem anderen Server laden zu können müssten folgende Gegebenheit auf dem Fremdserver vorhanden sein:

1. Eine Datei mit dem Namen crossdomain.xml

2. In dieser Datei muss explizit jede (“*”) Domäne freigegeben sein, oder explizit die des Aufrufers.

Leider ist dies in den seltensten Fällen der Fall. Es lohnt sich mal im Internet bei populären Tageszeitungen die Crossdomain.xml anzusehen. Kleiner Tipp: www.bild.de/crossdomain.xml und www.spiegel.de/crossdomain.xml sind zwei tolle Beispiele dafür.

Mehr Informationen gibt es zur Crossdomain.xml hier.

Um nun diesen Problemen aus dem Web zu gehen gibt es einen ganz einfachen Weg. Und zwar verwenden eigentlich (!!!) nur Client-Technologien im Browser diesen Mechanismus, den übrigens unser Wegbegleiter standardisiert hat, und dem wir mit Silverlight folgen.

Dies alles trifft nicht zu, wenn man von einem Server oder einer Desktop-Anwendung auf diese Inhalt zugreift. Daher bauen wir uns eine kleine Brücke, und zwar einen ProxyService an unserem Server, der für uns die gewünschten Daten lädt und dann an unsere Silverlight-Seite durchreicht.

Der Service könnte wie folgt aussehen:

   1: [WebService(Namespace = "http://tempuri.org/")]
   2: [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
   3: [System.ComponentModel.ToolboxItem(false)]
   4: // To allow this Web Service to be called from script, using ASP.NET AJAX, uncomment the following line. 
   5: // [System.Web.Script.Services.ScriptService]
   6: public class ProxyWebService : System.Web.Services.WebService
   7: {
   8:  
   9:     [WebMethod]
  10:     public string GetTextFromUrl(string url)
  11:     {
  12:         WebClient webclient = new WebClient();
  13:         string text = webclient.DownloadString(url);
  14:         return text;
  15:     }
  16: }

Dieser Code fragt nun bei jeder Anfrage die bestimmte URL an und gibt die Textdatei an den Aufrufer zurück. Das ganze ist etwa ineffektiv wenn man die Anfrage sehr oft macht. Es würde sich hier definitiv lohnen einen Cache einzubauen, wenn man bestimmte Adressen häufiger aufruft.

Dieser WebService wird nun ebenfalls in unserer Silverlight-Anwendung referenziert. Damit können wir nun Text-Daten von jeder beliebigen Seite laden.

   1: public ProxyCrossdomainSample()
   2: {
   3:     InitializeComponent();
   4:  
   5:     _address.Text = "http://blogs.msdn.com/olivers/rss.xml";
   6:     _loadButton.Click += new RoutedEventHandler(_loadButton_Click);
   7: }
   8:  
   9: void _loadButton_Click(object sender, RoutedEventArgs e)
  10: {
  11:     LoadDataFromProxyWebService();
  12: }
  13:  
  14: private void LoadDataFromProxyWebService()
  15: {
  16:     string url = HtmlPage.Document.DocumentUri.AbsoluteUri;
  17:     url = url.Substring(0, url.LastIndexOf("/") + 1);
  18:     url += "ProxyWebService.asmx";
  19:  
  20:     BasicHttpBinding binding = new BasicHttpBinding();
  21:     EndpointAddress address = new EndpointAddress(url);
  22:  
  23:     ProxyWebServiceSoapClient soapclient 
  24:         = new ProxyWebServiceSoapClient(binding, address);
  25:  
  26:     soapclient.GetTextFromUrlCompleted += new EventHandler<GetTextFromUrlCompletedEventArgs>(soapclient_GetTextFromUrlCompleted);
  27:     soapclient.GetTextFromUrlAsync(_address.Text);
  28: }
  29:  
  30: void soapclient_GetTextFromUrlCompleted(object sender, GetTextFromUrlCompletedEventArgs e)
  31: {
  32:     if (e.Error != null)
  33:     {
  34:         _resultText.Text = e.Error.Message;
  35:         return;
  36:     }
  37:  
  38:     string result = e.Result;
  39:     _resultText.Text = result;
  40: }

Die vollständige Beispielanwendung gibt es hier: