Ganzer Bildschirm Voller Screen

Es ist ein leichtes eine Silverlight Anwendung in den FullScreen Modus zu schalten.

Application.Current.Host.Content.IsFullScreen = True

Es gibt allerdings Einschränkungen. So kann der Benutzer keine Texteingaben mehr durchführen und man muss diese Codezeile in  ein vom Benutzer initiiertes Event legen. Button Click ist so eines.

Den FullScreen kann der Benutzer mit ESC wieder verlassen. Ausnahme ist wenn man per ALT TAB (oder auch sonst wie) die aktive Anwendung wechseln möchte. Dann ist der Fullscreen  Modus ganz von alleine weg. Wenn man dies verändern möchte hilft folgendes Silverlight Beispiel Code

Application.Current.Host.Content.FullScreenOptions =
        System.Windows.Interop.FullScreenOptions.StaysFullScreenWhenUnfocused

Der Silverlight User erhält nun einen Dialog ob er dieses Verhalten erlauben möchte und kann seine Antwort auch dauerhaft abspeichern.

image

Wenn die Silverlight Anwendung OOB ( Out Off Browser) mit elevated Priviliges läuft, kommt der Dialog nicht und der Benutzer kann sogar Tastatureingaben durchführen.

Der Zoom Faktor des Browser wird übrigens im Vollbild Modus ignoriert. Wenn man das wissen und nutzen möchte, gibt es im Content.ZoomFaktor den Vergrößerungswert als Double zurück. Dies und noch viel mehr lernen Sie bei den Silverlight Schulungen der ppedv.

Anonyme Typen an ein Silverlight Datagrid binden

Was soll ich sagen, man lernt nie aus. In meiner aktuellen Silverlight Schulung saß ein Kurs Teilnehmer der es genau wissen wollte. Mein Silverlight Beispiel zeigt wie man mit minimalen Aufwand einen RSS Feed (hier n-tv) an ein Datagrid bindet. Dafür braucht man eine Klasse (hier rss) mit Eigenschaften (hier title) die Public sind. Struct geht schon mal nicht.

Der sehr einfache Silverlight Prototyp:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
        Dim wc As New WebClient
        AddHandler wc.DownloadStringCompleted, AddressOf fertig
        wc.DownloadStringAsync(New Uri("xmlfile1.xml", UriKind.Relative))
    End Sub

    Private Sub fertig(ByVal sender As Object, ByVal e As DownloadStringCompletedEventArgs)
        Dim xml = XDocument.Parse(e.Result)
        Dim query = From x In xml.Descendants("item")
                    Select New rss With {.titel = x.<title>.Value}
        DataGrid1.ItemsSource = query
    End Sub

End Class
Public Class rss
    Public Property titel As String
End Class

Die Ausgabe in der Silverlight Anwendung dann ungefähr so.

image

Der liebe Silverlight Kurs Teilnehmer war faul und wollte es mit anonymen Typen versuchen also ohne die RSS Klasse zu erstellen ala

 Dim query = From x In xml.Descendants("item")
                    Select New With {.titel = x.<title>.Value}
        DataGrid1.ItemsSource = query

Das Ergbebnis dann

image

Meine Antwort ist halt so. Anonym geht eben nicht da diese als internal deklariert sind und deshalb nicht sichtbar. Da mein Teilnehmer mit Testing ganz versiert war wusste er das man innen nach außen kehren kann mit InternalsVisibleTo. Dies muss man in der Datei AssemblyInfo.vb deklarieren. Üblicherweise befindet sich diese im Verzeichnis Properties der Silverlight Anwendung. Wenn nicht vorhanden einfach anlegen und folgendes reintippen.

<Assembly: System.Runtime.CompilerServices.InternalsVisibleTo("System.Windows")> 

Und schwupp gehts auch anonym.

Silverlight MCP 71-506

So heute habe ich nun die Beta Prüfung zum Silverlight MCP gemacht. Habe ich bestanden? Will ich mal hoffen. Als Beta Tester erfährt man das erst später. Leider darf ich nichts zum Inhalt sagen. Ist alles NDA. War jedenfalls wirklich schwierig. So einen kürzen Überblick will ich Euch dennoch geben. Durchaus einige WCF Fragen und gar nichts zu RIA Services. Auch gar nichts zu Blend.  Animationen per Hand (wer macht das schon). In meinem Fall habe ich die VB.NET Variante gewählt. Schwierig waren vor allem die langen Fragen. Wenn man über zwei Bildschirme scrollen muss ist es recht mühsam den Überblick zu behalten. Auch war ein paar Tricks eingebaut, wo man die genaue Fragestellung lesen musste. Oft steht aber in der Frage auch überflüssiges Blabla und man schaut am besten gleich auf die Antworten ob da offensichtlich unsinniges dabei ist. Überrascht hat mich auch der Umfang zu iCommand und eine Routing Fragen (aus dem Navigation Application Template). Die Databinding Sache empfand ich wieder als leicht. Wenn dann die Frage zu leicht ist suche ich immer den Haken. Keine Ahnung on da einer ist. Da ich die Prüfung in Englisch machen musste war manchmal nicht ganz klar was die eigentliche Bedeutung ist. Ein Lexikon hilft da auch nicht.

Inhalt ist aber ohnehin schon public. Allerdings nicht ganz komplett. Der Begriff CollectionViewSource fehlt auf der Seite Zwinkerndes Smiley.

Wer die Prüfung besteht hat nachgewiesen überdurchschnittliches Silverlight Know How. Kritik Punkt ist, das manche Fragen so exotisch sind (zb Assembly Caching Detail) das wenn ich das Feature mal alle 3 Monate brauche, ich einfach in der Doku nachlese.

Public Solls ab Anfang Januar 2011 sein.

MP3 download mit Silverlight

Nein dies ist kein Filesharing Anleitung um illegale MP3 Musik zu downloaden. Ich schreibe grad einen Vokabeltrainer für meinen Sohn. Dabei soll auch die Aussprache trainiert werden. Leo.org bietet neben Übersetzung auch den Service den Englischen Text vorzulesen. Für meine Lernanwendung möchte ich aber gelernte Vokabel speichern. Also das MP3 per Download im Isolatedstorage speichern. Die Download URL ist relativ simpel das gesprochene Wort mit der Endung mp3. Mittels dem Webclient wird ein asynchroner Download gestartet. Da es sich um binäre Daten handelt macht ein Stream mehr Sinn. Diesen erhält man per Openreadcompleted.

Dim url As String = "http://www.leo.org/dict/audio_en/" + txtenglisch.Text + ".mp3"
Dim wc As New WebClient
        AddHandler wc.OpenReadCompleted, AddressOf mp3fertig
wc.OpenReadAsync(New Uri(url, UriKind.Absolute)))

Wenn nun der Download der MP3 Datei fertig ist, muss man sich überlegen wie man die Datei speichert. Da gibt's im wesentlichen nur die Methode im IsolatedStorage. Für möglichst kurzen Code hole ich mir die Länge des Streams über einen kleinen Umweg StreamResourceInfo. Dann muss man Binär lesen und schließlich Binär schreiben.

Private Sub mp3fertig(ByVal sender As Object, ByVal e As OpenReadCompletedEventArgs)
  Dim srInfo = New StreamResourceInfo(e.Result, Nothing)
  Dim sr As BinaryReader = New BinaryReader(srInfo.Stream)
  Dim iso As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication
  Using iosr As IsolatedStorageFileStream = iso.OpenFile(txtenglisch.Text + ".mp3", FileMode.OpenOrCreate)
     Using bw As BinaryWriter = New BinaryWriter(iosr)
        bw.Write(sr.ReadBytes(srInfo.Stream.Length))
     End Using
  End Using
 sr.Close()
End Sub

Schließlich noch der Hinweis. Wenn der Platz  (1 MB) nicht reichen sollte, kann dieser vergrößert werden. Der Benutzer muss das aber noch bestätigen. IncreaseQuotaTo(1000000)

Silverlight Socket Client

In einem früheren Post habe ich beschrieben, wie man einen einfachen Socket Server baut. Dieses mal will ich den Service konsumieren. Eine Socket Service Kommunikation ist extrem effizient. Allerdings müssen die verwendeten Ports in allen beteiligten Firewalls auch frei geschalten sein. Silverlight auferlegt sich zudem eine künstliche Beschränkung auf den Bereich 4502-4534. Noch viel schwerwiegender ist, das man bei der TCP Socket Kommunikation sehr schnell in Cross Domain Szenarien landet. Dann erhält man Security Exceptions, außer die Anwendung läuft OOB full trusted. Für allen jene denen diese Beschreibung noch nicht verwirrend genug ist: Wenn die Anwendung im Browser läuft muss eine ClientAccessPolicy.XML Datei angelegt werden, die dann von Silverlight ungefragt angefordert wird. Format und Inhalt ungefähr so.

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*" />
      </allow-from>
      <grant-to>
        <socket-resource port="4502-4534" protocol="tcp" />
      </grant-to>
    </policy>
  </cross-domain-access>
</access-policy> 

Der vorläufige Höhepunkt ist, das Silverlight ab Version 4 versucht diese Datei auf Port 943 anzufordern. Dafür müsste man nun noch einen extra Socket Server schreiben der auf diesem Port lauscht und die Datei überträgt. Ist mühsam und deswegen gibt es wohl nun die Zweitmöglichkeit die Datei in das root des Webservers zu legen und per http auszuliefern. Allerdings muss man Silverlight das im Code noch mitteilen (SocketClientAccessPolicyProtocol.Http) wie wir gleich im folgenden Code sehen werden. Erlaubt sei noch der Hinweis das dies mit dem WebDev Server aus Visual Studio nicht geht, weil dieser auf einen zufälligen Port lauscht und nicht auf 80.

Weiter gehts mit der Initialisierung der Socket Verbindung.   Die Sache mit der IP Adresse ist ein wenig mühsam und muss über den DNS Endpoint gelöst werden um rauszukriegen auf welcher Website die Anwendung läuft. Am Ende wird versucht die Verbindung herzustellen.

Dim sock As Socket
Private Sub Button1_Click(ByVal sender As System.Object, 
ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click Dim ep As DnsEndPoint = New DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4506) sock = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) Dim args As SocketAsyncEventArgs = New SocketAsyncEventArgs() args.RemoteEndPoint = ep args.SocketClientAccessPolicyProtocol =
SocketClientAccessPolicyProtocol.Http 'Clientaccesspolicy AddHandler args.Completed, AddressOf onSocketConnect sock.ConnectAsync(args) End Sub

Da das ganze asynchron läuft, wie bei Silverlight üblich, benötigt man die Funktion onSocketConnect, die aufgerufen wird sobald die Verbindung besteht. Jetzt wirds ein wenig Tricky. Um die eingehenden Daten zu erhalten, brauchen wir noch ein zweites Event OnSocketEmpfang. Da dies auch am Completed Event der SocketArgs hängt, habe ich die Events durch deregistrieren und registrieren ausgetauscht. Ob dies der beste Weg ist, ist mir nicht bekannt. Zumindest habe ich auf an anderer Stelle ähnliche Ansätze gefunden. Nun gehts ans Empfangen mit ReceiveAsync.

Private Sub onSocketConnect(ByVal sender As Object, ByVal e As SocketAsyncEventArgs)
  If sock.Connected Then
     RemoveHandler e.Completed, AddressOf onSocketConnect
     Dim resp(1024) As Byte
     e.SetBuffer(resp, 0, resp.Length)
     AddHandler e.Completed, AddressOf onSocketEmpfang
     sock.ReceiveAsync(e)
  End If
End Sub

Wenn dann ab und zu mal Sekunden in der Form 40 oder so eintrudeln, landen wir im OnSocketEmpfang. Die Daten stehen zwar freundlicherweise im e.Buffer, aber leider befinden wir uns im Background Thread. Entsprechend muss per BeginInvoke ein Delegate definiert werden die eine Methode aufruft die im UI Thread läuft.  Dabei werden die Argumente e als Parameter übergeben. Schlussendlich rufe ich wieder den ReceiveAsync Befehl auf, weil die Daten regelmäßig bis zum Sankt Nimmerleinstag kommen.

Private Sub onSocketEmpfang(ByVal sender As Object, 
ByVal e As SocketAsyncEventArgs) Dispatcher.BeginInvoke
(New Action(Of SocketAsyncEventArgs)(AddressOf updateUI), e) sock.ReceiveAsync(e) End Sub

Der Vollständigkeit halber noch der Teil der letztendlich eine TextBox im Frontend aktualisiert.

Private Sub updateUI(ByVal e As SocketAsyncEventArgs)
   TextBlock1.Text = 
Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred) +
Environment.NewLine End Sub

Wer keine Erfahrung mit dem synchronsieren von Threads hat und das für Teufelszeug hält, soll mal in die app.xaml.vb oder app.xaml.cs blicken. Dort wird das globale Fehlerhandling genau so realisiert. Mit Hilfe einer Lambda Expression noch etwas eleganter codieren, obs lesbarer ist mag jeder selbst entscheiden.

Dispatcher.BeginInvoke(
 New Action(Of SocketAsyncEventArgs)(
Sub() TextBlock1.Text +=
Encoding.UTF8.GetString(e.Buffer, 0, e.BytesTransferred) + Environment.NewLine End Sub), e)

Socket Server mit VB.NET

Um ein größeres Ziel zu verfolgen musste ich einen Socket Server schreiben. Tendenziell wird man bei Server an einen Lösungsansatz mit Service denken. Ich nehme aber eine einfach Winforms Anwendung, weil mein Server nur Sinn macht wenn ein Benutzer angemeldet ist. Im weitesten Sinn überwacht mein Socket Server ein Stück Hardware und meldet Daten in einer Art Event an eine Silverlight Anwendung. Die Implementierung erfolgt auf Alpha Prototyp Level. Also sicher noch erhebliches Refactoring Potential. Ich war wirklich faul und habe mich auch auf die vorhandenen Steuerelemente gestürzt und eben ein Timer Control (Timer1) auf das Formular gezogen.

Der Socket Server lauscht auf Port 5406, weil dies im Bereich der von Silverlight nutzbaren Ports liegt.

tcpL = New TcpListener(IPAddress.Parse("127.0.0.1"), 4506)
tcpL.Start()
tcpL.BeginAcceptSocket(AddressOf onConnect, Nothing)

Der Listener und auch der Client (es reicht eine Verbindung) wird global in der Forms Klasse definiert.

 Dim tcpL As TcpListener
 Dim tcpClient As TcpClient

Weil es später zur Laufzeit recht wenig Transparenz gibt, was das Programm gerade tut, schreibe ich in das Output Fenster von Visual Studio. Mit AcceptClient wird der Kanal geöffnet und gewartet bis sich ein Client meldet. Die eigentliche Kommunikation findet dann asynchron statt. Da ich die Daten die vom Silverlight Client gesendet werden nicht brauche, ist die Funktion der “ClientSpricht” leer.

Dim iasy As IAsyncResult
Private Sub onConnect(ByVal ia As IAsyncResult)
 Try
  tcpClient = tcpL.EndAcceptTcpClient(ia)
  iasy = tcpL.BeginAcceptTcpClient(AddressOf clientSpricht, Nothing)
  Trace.WriteLine("connected...")
 Catch ex As Exception
  Trace.WriteLine(ex.ToString())
 End Try
End Sub

Viel wichtiger ist, was der Server zum Server sagt. Dazu wird regelmäßig ein Timer Event gefeuert, das folgenden Code ausführt und die zur Demonstration Sekunden sendet.

If IsNothing(tcpClient) = False AndAlso tcpClient.Connected Then
  Dim networkStream As NetworkStream = tcpClient.GetStream
  Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytesDate.Now.Second.ToString)
  networkStream.Write(sendBytes, 0, sendBytes.Length)
Trace.WriteLine("sending..." + Date.Now.Second.ToString)
End If

Wie kann man nun feststellen das dieser Server auch funktioniert? Ganz einfach mit Telnet4358. Zuerst wird das Kommando telnet localhost 4506 eingegeben und dann erscheinen die Zahlen wie im Bild ersichtlich regelmäßig.

image

Jscript Zauberei mit Silverlight

Wenn man nachfolgende Webentwicklern um den Verstand bringen will, arbeitet man mit dynamisch generierten JScript Code. Da gibt es nachher nichts im Browser zu sehen mit View Source. Um das ganze auf die Spitze zu treiben, erzeuge ich aus  einer Silverlight Anwendung den Code im Browser. Es gibt dazu auch durchaus Probleme, die zu dieser Lösung passen könnten. In früheren Silverlight Weblog Eintrag habe ich beschrieben wie man statt WebClient den Browser Stack direkt ansprechen kann. Basierend auf diesem Ansatz nun eine Jscript Lösung. Ist kein schöner Code, sondern eher nur konzeptionell. Basis ist eine XMLRPC Abfrage der Gültigkeit einer USTID beim Finanzamt.

Zunächst wird eine Callback Funktion mit Rufmichan im Browser erzeugt. Dann wird die Methode erzeugt die dann eine Instanz des XMLHTTP Objektes erzeugt und den Webservice aufruft. Dann muss diese Funktion (callws) aufgerufen werden. Dies passiert per Invoke.

Private Sub Button1_Click(ByVal sender As System.Object, 
ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click createRufmichan() createJSCall("http://evatr.bff-online.de/evatrRPC?UstId_1=
DE123456789&UstId_2=AB1234567890&Firmenname=
&Ort=&PLZ=&Strasse=&Druck="
) Dim r = HtmlPage.Window.Invoke("callWS") End Sub

Nun sehen wir uns den Code an der im Browser erzeugt werden soll und wie das geschieht. Um der nachfolgenden Rückruffunktion eine Möglichkeit zu geben auf dei INstnaz zuzugreifen wird w ausserhalb der Funktion callWS definiert. Das HtmlPage Objekt von Silverlight kann per Createlement auch JScript Elemente erzeugen und dann per AppendChild ans DOM des Browserwindows anhängen.

Public Sub createJSCall(ByVal url As String)
  Dim s As String = "w = new XMLHttpRequest();
w.onreadystatechange=rufmichan;
w.open('GET', url; w.send(null);"
Dim js = HtmlPage.Document.CreateElement("script") js.SetAttribute("type", "text/javascript") js.SetAttribute("text", "var w;function callWS() {" + s + " }") HtmlPage.Document.DocumentElement.AppendChild(js) End Sub

Wenn dann der Callback zurück kommt wird folgende Funktion benötigt.

Public Sub createRufmichan()
  dim js = HtmlPage.Document.CreateElement("script")
  js.SetAttribute("type", "text/javascript")
  js.SetAttribute("text", "function rufmichan() 
{if (w.readyState == 4) {alert(w.responseText);}}"
) HtmlPage.Document.DocumentElement.AppendChild(js)

Das wars schon. Wie kann ich aber feststellen das alles passt? Natürlich wenn die Messagebox mit dem Ergebnis kommt. Auch sehr hilfreich sind seit IE8 die Entwiclertools zu starten mit F12 Taste.

image

Cross Domain und die drei tapferen Network Stacks

Folgende Zeichnung zeige ich immer in meinen Silverlight Kurs. (Tablet PC sei Dank)

image

Damit soll der Silverlight Schulung Teilnehmer lernen, das es per Definition verboten ist, auf einen zweiten Webserver zuzugreifen (Stichwort crossdomain.xml und clientaccesspolicy.xml). Diese Grundlagen werde ich vielleicht wann anders beschreiben. Wer es nachschlagen möchte “Crossdomain”.

Das folgende Szenario ist nicht ganz trivial. Läuft die Anwendung OOB ( Out of Browser) mit elevated Priviliges werden crossdomain limitierungen ignoriert. Wenn die Anwendung im Browser läuft wird erstens per default ein anderer Network Stack verwendet und es gibt eine Fehlermeldung. Außer der Web Server hat im Root Directory eine der beiden vorher erwähnten XML Dateien liegen.

Das bedeutet das eine Silverlight Anwendung ,abhängig wo sie läuft, unterschiedlichen Code ausführt. Also folgenden Zeilen Code benutzen effektiv zwei unterschiedliche Network Stacks.

Dim wc As New WebClient
AddHandler wc.DownloadStringCompleted, AddressOf fertig
wc.DownloadStringAsync(...

Der eine Stack wird Network Stack genannt, weil er echte Netzwerk Funktion implementiert. Der andere Stack heißt Browser Stack weil er das XMLHTTP Objekt des Browser kapselt.

Nun gibt es die Anforderung das ein solcher Cross Domain Zugriff (zb auf die Twitter API) durchgeführt werden soll

  • aus dem Browser
  • unter Ignoranz der crossdomain policys

Das geht mit dem “dritten Netzwerk” Stack. Dem XMLHttp Objekt direkt z.B. aus JScript. Silverlight bietet dafür eine HTMLPage Helper Klasse mit der man ziemlich einfach direkt im Browser arbeiten kann. So lässt sich per eval beliebig dynamisch generierter JScript Code ausführen oder mit Invoke eine Jscript Funktion aufrufen. Noch besser ist aber das Silverlight ScriptObject. Damit lassen sich unter Semi Intellisense Unterstützung JScript Objekte instanzieren. Mit Invoke werden dann die Methoden aufgerufen.

Ausgehend von folgendem Jscript Beispiel

var w = new XMLHttpRequest();
w.onreadystatechange = function () 
{
if (w.readyState == 4) {
    alert(w.responseText);
 }}
w.open("GET", "http://...", true);
w.send(null);

Lässt sich so mit managed Code in VB.NET die Silverlight Anwendung ausprogrammieren.

Dim url As String = "http://..."
Dim req As ScriptObject
req = HtmlPage.Window.CreateInstance("XMLHttpRequest")
req.Invoke("open", "GET", url, False)
req.Invoke("send")
TextBlock1.Text = req.GetProperty("responseText")

Ein erster Test mit Chrome und Firefox funktioniert ohne Probleme. Mit Internet Explorer 9 kommt eine Sicherheitsabfrage die der Benutzer bestätigen muss.

image

Abgesehen von dieser kleinen Hürde kann so die Crossdomain Limitierung von Silverlight mit wenigen Code Zeilen umgangen werden. Darüber hinaus bietet der XMLHTTP JScript Network Stack auch mehr Funktionen, da der Silverlight Wrapper nur das nötigste implementiert hat.

aneinandergereiht: POCO nach XML oder JSON in Silverlight

Aktuell bin ich wieder mal in einer Silverlight Schulung. Obwohl ich das Thema wirklich von A-Z kenne, entdecke ich immer wieder Neues. Dieses mal im ausgezeichneten Silverlight 4 Buch von Thomas Claudius Huber (erschienen bei Galileo). Thomas macht auf Seite 833 etwas, was ich so nicht tun würde. Er nimmt einen DataContractJsonSerializer um einen JSon Rückgabe eines REST Services nach POCO zu wandeln. Interessante Idee, aber nicht im Sinne des Erfinders. Dafür waren wohl eher die Klassen JSoneArray oder JSonObject gedacht. Nicht desto trotz – einen Blick wert. Aber ich möchte erst mal die Basics aufgreifen. Ein Objekt, hier z.B. Person muss serialsiert werden um z.B. im Isolated Storage dauerhaft gespeichert zu werden.

Zunächst einmal ein Prototyp der einfach das Objekt serialisiert und im UI ausgibt. Es müssen drei Namensräume eingebunden werden

System.IO
System.Runtime.Serialization
System.Text

Dann wird der klassische DataContractSerializer angeworfen der XML erzeugen wird. Am Ende wird das erzeugte Byte Array in UTF Encodiert um die Anzeige zu realisieren.

Dim p As New person
Dim ms As New MemoryStream()
Dim ser As New DataContractSerializer(GetType(person))
ser.WriteObject(ms, p)
Dim array() As Byte = ms.ToArray()
ms.Close()
TextBlock1.Text = Encoding.UTF8.GetString(array, 0, array.Length)
image

Alternativ gibt es auch noch ein Klasse XMLSerializer mit den Methoden Serialize und Deserialize auf die ich hier aktuell nicht eingehen möchte. Will man Json erzeugen muss man einen anderen Serialisierer nehmen. Der wiederum findet sich im Namensraum System.Runtime.Serialization.Json. Dann muss nur eine Zeile getauscht werden.

Dim ser As New DataContractJsonSerializer(GetType(person))
image

Deutlich zu erkennen ist, das die Datenmenge bei Json im Vergleich zu XML deutlich geringer ist. Aus diesem Grund sehe ich auch SOAP als noch viel schwer gewichtigeres XML Format sehr kritisch und sehe die Zukunft eher in REST basierten Ansätzen wie das moderne ODATA.

Wie kommen nun die Daten wieder zurück? Der sehr gut verkürzte Code.

Dim p As New person
Dim ser As New DataContractJsonSerializer(GetType(person))
Dim ms As New MemoryStream(Encoding.UTF8.GetBytes(TextBlock1.Text))
p = ser.ReadObject(ms)
ms.Close()
    
Im Grund nichts Neues. Das gibts in .NET schon eine ganze Weile (seit 3.5).

Asynchrone Validierung von Benutzereingaben mit INotifydataErrorInfo

Manchmal ist die Wahl wirklich die Qual. In Silverlight 4 gibt es mindestens vier mir bekannte Möglichkeiten in Dialogen Eingaben des Benutzers zu prüfen.

  • per Exception (von Anbegin)
  • per Eigenschaftsattribut (mit RIA Services eingeführt)
  • IDataErrorInfo
  • INotifyDataErrorInfo (neu in SL4)

Mit dem letzteren werden wir uns hier nun beschäftigen. Wenn eine Datenklasse das Interface INotifyDataerrorInfo implementiert kann von andere Stelle im Code ein Fehler Event aufgerufen werden. Im Unterschied zu IDataErrorInfo kann dann ein Property auch mehrere Fehler besitzen (ob es sich darüber freuen wird?) was den Code nicht einfacher macht.

Darüber hinaus kann man direkt das ErrorsChanged Event aufrufen und so auch asynchron validieren.

Mein folgendes Silverlight Beispiel ist Teil einer kleinen APP die die UST ID auf Gültigkeit beim zuständigen Finanzamt prüft. Das heißt es werden Daten an einen Webservice geschickt und dieser antwortet für jedes Feld ob dies gültig oder ungültig ist. Der Benutzer muss dann die Eingabe korrigieren oder seinem Geschäftspartner mitteilen das eine UST freie Lieferung nicht möglich ist.

Dazu erstellte ich mir einen Datenklasse die später dann auch an das UI per Binding gebunden wird. Die Attribute dienen ebenfalls der Eingabe validierung bzw dem Layout des Dialoges. Dort kommen Label Steuerelemente zum Einsatz die dann z.B. Display Name verwenden.

Public Class ustidFirma
    Implements INotifyDataErrorInfo
    <Required()>
    <Display(Name:="Ustid")>
    Public Property ustid As String
    <Required()>
    <Display(Name:="Firma")>
    Public Property firma As String

Wesentlicher ist aber die Logik. Eine exemplarische Implementierung nachdem das Interface InotifyDataErrorInfo implementiert wird. Es werden zwei Methoden benötigt. Die Eigenschaft HasErrors definiert ob grundlegend Fehler vorhanden sind. Die Funktion GetErrors gibt eine Liste der Fehler zurück die für einen bestimmte Eigenschaft, z.B. ustid, vorliegen. Das ErrorsChanged Event dient dazu die UI über Änderungen zu informieren.

Imports System.ComponentModel
Public Class test1
    Implements INotifyDataErrorInfo
    Public Event ErrorsChanged(ByVal sender As Object, 
ByVal e As System.ComponentModel.DataErrorsChangedEventArgs)
Implements System.ComponentModel.INotifyDataErrorInfo.ErrorsChanged Public Function GetErrors(ByVal propertyName As String)
As System.Collections.IEnumerable
Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors End Function Public ReadOnly Property HasErrors As Boolean
Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors Get End Get End Property End Class

Zurück zu meinem Beispiel UST Prüfung. Um die Fehler zu verwalten erstelle ich mir eine Liste für die Attribute und deren Fehler.

 Private errors As New Dictionary(Of String, List(Of String))

HasErrors liefert dann zurück ob in der Liste was drin steht.

Public ReadOnly Property HasErrors As Boolean Implements System.ComponentModel.INotifyDataErrorInfo.HasErrors
 Get
     Return errors.Count > 0
 End Get
End Property

Die Funktion GetErrors, liefert die Fehlerliste für das ausgewählte Property.

Public Function GetErrors(ByVal propertyName As String) 
As System.Collections.IEnumerable
Implements System.ComponentModel.INotifyDataErrorInfo.GetErrors If errors.ContainsKey(propertyName) Then Return errors(propertyName) Else Return Nothing End If End Function

 

Eine manuell von mir erstellte Hilfsfunktion RaisErrorsChanged füllt die Fehlerliste und wirft dann das Event um im  Userinterface die Bindung zu aktualisieren.

Public Sub RaiseErrorsChanged(ByVal propertyName As String, ByVal Fehler As String)
 If Not errors.ContainsKey(propertyName) Then errors(propertyName) = New List(Of String)()
   errors(propertyName).Add(Fehler)
   RaiseEvent ErrorsChanged(Me,
   New DataErrorsChangedEventArgs(propertyName))
End Sub

Diese Sub wird von mir sozusagen per Hand aufgerufen. Die dazu gehörige Logik blende ich hier aus. Stellen Sie sich einfach einen Button vor der folgenden Code enthält.

 Dim ui As ustidFirma = LayoutRoot.DataContext
 ui.RaiseErrorsChanged("ustid", "USTID falsch")

Da es sich um eine gebundenes Objekt handelt kann ich jederzeit dieses Objekt zurückholen und darauf dann die Methode RaiseErrorsChanged aufrufen. Im XAML Code sind dafür mehrere Dinge nötig. Der Namensraum (hier willkürlich local genannt) um auf die Projektklassen zugreifen zu können. Die deklarative Instanz mit dem frei gewählten Namen uf1.  Das Grid mit dem Namen Layoutroot ( Silverligth Default) bekommt dann dieses Objekt uf1 per Binding in seinem DataContext zugewiesen. Beachten Sie das dies notwendig ist, um im vorigen Codeblock damit agieren zu können und das Label und die Textbox zu Binden. Dabei ist noch wesentlich das in der Bindung der Text Eigenschaft der Textbox das Binding Attribut ValidatesOnNotifyDataErrors gesetzt wird. Der folgende XAML Code ist stark gekürzt.

xmlns:local="clr-namespace:KoelnSL"
             >
    <UserControl.Resources>
        <local:ustidFirma x:Key="uf1"/>
..... <Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource uf1}}"> ..... <sdk:Label Target="{Binding ElementName=txtmyID}"/> <sdk:Label Target="{Binding ElementName=txtID}"/> ..
<TextBox x:Name="txtmyID" /> <TextBox x:Name="txtID"
Text="{Binding ustid,Mode=TwoWay,ValidatesOnExceptions=true,
NotifyOnValidationError=true,ValidatesOnNotifyDataErrors=True}"
/> .. </Grid>

Der Vollständigkeit halber noch der fertige Dialog.

image

Training, Schulung, Sharepoint

Month List