31 sierpnia 2010
Odsłon: 107
W poprzednich artykułach opisałem dość szczegółowo implementację odtwarzacza relacji meczowych, zrealizowanego
jako aplikacja Silverlight umieszczona na stronie ASP.NET MVC. Skomplikowaną kwestią okazało się przekazywanie
danych wejściowych do tej aplikacji. W większości przypadków w takich sytuacjach potrzebujemy przekazać tylko
proste informacje, jak nazwa użytkownika czy ID, po którym - poprzez usługi WCF - możemy dociągnąć resztę danych.
Tutaj jednak wolałem unikać opierania się o WCF (potencjalne problemy hostingowe), a zakres danych do przekazania
jest dość obszerny.
Byłem więc zmuszony do skorzystania z podstawowego mechanizmu, czyli parametrów kontrolki Silverlight.
Normalnie wygląda to tak, że embedując taką kontrolkę na stronie w tagach <param> możemy przekazać pewne
wartości, w tym m.in. initParams, do których mamy potem wygodny dostęp z poziomu Silverlight.
Poniżej prosty przykład jak mogłoby to wyglądać.
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="1000px" height="700px">
<param name="source" value="ClientBin/GameViewer.xap" />
[...]
<param name="initParams" value="String1=abc,String2=xyz" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50401.0" style="text-decoration: none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style: none" />
</a>
</object>
</div>
Tak podane parametry możemy teraz wykorzystać w kodzie aplikacji Silverlight - w moim przypadku zapamiętuję całą kolekcję
dla potrzeb dalszego przetwarzania.
private void Application_Startup(object sender, StartupEventArgs e)
{
this.RootVisual = new MainPage();
initParams = e.InitParams;
}
Jak widać, wartość initParams jest napisem o dość sztywnym formacie, w którym nie bardzo można przekazać
wprost jakiekolwiek bardziej skomplikowane dane. Jak więc przekazać tam złożony graf obiektów reprezentujących zdarzenia
meczowe? Ja posłużyłem się w tym celu serializacją do JSON, wspartą dodatkowo kodowaniem. Poniżej ogólny schemat
całej komunikacji.
Przekazywanie rezultatów meczu przez poszczególne warstwy.
Logika symulatora meczów produkuje wynik w postaci grafu obiektów. Jego transformacja na DTO (Data Transfer Objects)
w serwisie jest tutaj mało ciekawa - zwykłe mapowanie obiektów, więc to pominiemy. Najważniejsze jest to, co dzieje się
w kontrolerze, w metodzie obsługującej żądanie - poniżej jej kluczowy fragment.
GameData gameData = simulatorService.Play();
using (MemoryStream stream = new MemoryStream())
{
var serializer = new DataContractJsonSerializer(typeof(GameData));
serializer.WriteObject(stream, gameData);
ViewData["GameData"] = Convert.ToBase64String(stream.ToArray());
}
Obiekty DTO są więc najpierw serializowane do formatu JSON, a wynik tej operacji jest dodatkowo enkodowany przy
pomocy metody ToBase64String klasy Convert. Tak przetworzony string może być już bezpiecznie wstawiony
do kolekcji initParams, co zostało zrealizowane na stronie aspx w następujący - nie najpiękniejszy, przyznaję - sposób.
<% Response.Write("<param name=\\"initParams\\" value=\\"" + "GameData=" + ViewData["GameData"] + "\\" />"); %>
I to wszystko. Pozostaje jeszcze tylko odtworzenie grafu po stronie aplikacji Silverlight. Realizuje to view model w jednej
z metod inicjujących odtwarzanie.
byte[] data = Convert.FromBase64String(gameData);
using (MemoryStream stream = new MemoryStream(data))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GameData));
result = (GameData)serializer.ReadObject(stream);
}
Trzeba tutaj zwrócić uwagę na jeszcze jeden problem. Klasy, do których deserializujemy, muszą mieć taką samą strukturę,
co klasy źródłowe (czyli DTO zdefiniowane na poziomie serwisów). Normalnie rozwiązalibyśmy to w taki sposób, że umieścilibyśmy te
klasy we współdzielonej assembly, w tym przypadku jednak nie możemy tego zrobić, gdyż projekt Silverlight targetuje inny framework.
Można to obejść na parę sposobów, np. linkując te same pliki w dwóch projektach, ale ja zdecydowałem się na razie po prostu zduplikować
wszystkie te klasy (pamiętajmy, że to tylko dane - nie ma tam żadnych zachowań, więc nie jest to aż takim problemem).
Dane przekazywane na stronie w takiej zakodowanej postaci mają sporą objętość (w tej chwili jest to ok. 1 MB na pięcominutowy mecz), dlatego rozważam też wprowadzenie kompresji w dalszym etapie. Ponieważ jednak Silverlight nie uwzględnia części .NET Framework z tą funkcjonalnością, trzeba byłoby posiłkować się zewnętrzną biblioteką, dlatego na razie odkładam to na później.
Jak zawsze, pełny kod źródłowy można przeanalizować na stronie projektu na CodePlex:
http://euromanager.codeplex.com