Wydajność danych i strategie związane z usterkami w Silverlight 3

Autor: John Papa

Kod jest dostępny do pobrania z witryny MSDN Code Gallery. Przejrzyj kod online

Ten artykuł jest oparty na wstępnych wersjach Silverlight 3.

Aplikacje Silverlight często opierają się na usługach WWW przy dostępie do danych. Wydajność pobierania danych i możliwość pobierania sensownych informacji dotyczących wyjątków, które mogą wystąpić w usługach WWW to dwa obszary krytyczne, które zostały poprawione w Silverlight 3.

Słaba wydajność może być zmorą aplikacji. Dobre strategie pobierania danych z usługi WWW mogą pomóc, ale czasami konieczne jest pobranie grafu obiektu, który może być ogromny i którego przekazanie z usługi WWW do klienta może zajmować dużo czasu. Silverlight 3 oferuje nową funkcję, która przekazuje dane z usługi WWW korzystając z kodowania binarnego, a to może znacząco poprawić wydajność przy przekazywaniu dużych grafów obiektów.

Wiele rzeczy może się nie powieść podczas przekazywania danych pomiędzy usługami, a aplikacjami Silverlight. Dlatego właśnie ważne jest posiadanie dobrej strategii obsługi wyjątków, które mogą wystąpić przy wywoływaniu usługi WWW. Silverlight 3 oferuje pewne ulepszenia sieciowe, które dają programistom więcej opcji przy przekazywaniu informacji o zarządzanych wyjątkach z usług WWW.

W niniejszym artykule przedstawię, jak działa kodowanie binarne, jaki ma ono wpływ na wydajność aplikacji i zademonstruję, jak się zachowuje w działaniu. Pokażę też kilka technik, które mogą być wykorzystane do przekazywania informacji o wyjątkach przy użyciu niezadeklarowanych i zadeklarowanych usterek z usług WWW pisanych w Windows Communication Foundation (WCF) do Silverlight. Zacznę od przedstawienia, co się dzieje, gdy wystąpi wyjątek i jak dodać pewne szybkie zmiany do konfiguracji, aby wyświetlać informacje podczas debugowania. Następnie pokażę, jak skonfigurować wzorzec usterek strategicznych do obsługi przekazywania informacji o wyjątkach przez usługi SOAP korzystając z zadeklarowanych usterek. Cały kod jest oparty na wersji beta Silverlight 3 i towarzyszy temu artykułowi w sieci.

Optymalizacja wydajności

Dane SOAP i XML przekazywane jako tekst znacznie zwiększają rozmiar komunikatów przesyłanych pomiędzy WCF a Silverlight. To może mieć negatywny wpływ na wydajność zarówno, jeśli chodzi o przetwarzanie danych, jak i czas wymagany do przekazywania danych przez HTTP. Silverlight 3 wprowadza możliwość korzystania z binarnego kodera komunikatów w usługach WCF komunikujących się z aplikacjami klienckimi Silverlight 3. Binarny koder komunikatów może poprawić wydajność usług WCF zwłaszcza przy przekazywaniu dużych grafów obiektów. Największe zyski wydajności przy korzystaniu z binarnego kodowania komunikatów można osiągnąć przy przekazywaniu tablic, liczb i grafów obiektów; mniejsze zyski mają miejsce przy bardzo małych komunikatach i łańcuchach tekstowych.

Nie jest to strategia kompresowania, więc nie ma ujemnego wpływu na wydajność spowodowanego zapakowywaniem i odpakowywaniem skompresowanych danych. Jednakże kodowanie binarne zwykle ogranicza rozmiar przekazywanych danych. Ograniczenie rozmiaru nie jest gwarantowane, ale staje się widoczne przy korzystaniu z dużych grafów obiektów i danych liczbowych. Kluczowym usprawnieniem uzyskanym z kodowania binarnego jest jego zoptymalizowanie na zwiększenie przepustowości serwera.

Konfigurowanie kodowania binarnego

Usługi WCF mogą komunikować się z aplikacjami Silverlight 2 korzystając z wiązania basicHttpBinding przesyłającego dane jako tekst poprzez HTTP. Przy korzystaniu z szablonu pliku usługi WCF z obsługą Silverlight (instalowany podczas instalowania narzędzi Silverlight dla Visual Studio) do tworzenia usługi WCF dla Silverlight 2 wiązanie było konfigurowane na korzystanie z basicHttpBinding. Ten szablon pliku został zmieniony w Silverlight 3 konfigurując usługę WCF na korzystanie z binarnego kodera komunikatów zamiast tekstowego.

Szablon pliku usługi WCF z obsługą Silverlight konfiguruje usługę WCF na wykorzystanie komunikatów kodowanych binarnie. Jeśli korzystamy z istniejącej usługi WCF, to może być ona skonfigurowana na stosowanie binarnego kodowania komunikatów przez utworzenie niestandardowego wiązania w sekcji bindings pliku Web.config. Następujący kod przykładowy pokazuje niestandardowe wiązanie o nazwie silverlightCustomBinding tak, jak się ono pojawia w sekcji pliku konfiguracyjnego. Wiązanie silverlightCustomBinding skonfigurowane na wykorzystanie binaryMessageEncoding jest następnie wywoływane przez swoją nazwę w konfiguracji punktów końcowych usługi.

<endpoint address="" binding="silverlightCustomBinding"
  contract="MyTestService" />
<bindings>
  <customBinding>
    <binding name="silverlightBinaryBinding">
      <binaryMessageEncoding />
      <httpTransport />
    </binding>
  </customBinding>
</bindings>

Ponieważ basicHttpBinding przesyła komunikaty jako tekst przez HTTP, łatwo jest debugować komunikat przy pomocy narzędzi takich jak Fiddler. Podczas gdy nadal można konfigurować basicHttpBinding, to zalety kodera binarnego mogą być na tyle duże, że jest to zalecane podejście. Łatwo jest przełączać się tam i z powrotem pomiędzy formatem binarnym i tekstowym po prostu zmieniając plik config. Jest to wygodne przy debugowaniu usługi WCF. Kodowanie binarne jest obsługiwane tylko przez usługi i klientów WCF. Jeśli aplikacja kliencka niewykorzystująca WCF musi korzystać z naszej usługi WCF, to najlepiej nie stosować kodowania binarnego.

Dane binarne kontra dane tekstowe

Pierwsza demonstracja pokaże związane zarówno z konfiguracją, jak i wydajnością różnice pomiędzy użyciem basicHttpBinding a binarnym kodowaniem komunikatów pomiędzy WCF a Silverlight 3. Aplikacja przykładowa dołączona do tego artykułu rozbija oba przykłady (tekstowy i binarny) na oddzielne usługi, które można wywoływać z tego samego klienta Silverlight 3.

Konfiguracja dla tych usług w pliku Web.config aplikacji przykładowej jest pokazana na Rysunku 1. Różnice pomiędzy konfiguracjami kodowania tekstowego i binarnego są wytłuszczone. Warto zauważyć, że usługa SpeedService0 wykorzystuje basicHttpBinding, natomiast SpeedService1 wykorzystuje customBinding z konfiguracją wiązania o nazwie silverlightBinaryBinding (pokazaną w poprzednim kodzie przykładowym).

Rysunek 1: Konfigurowanie kodowania tekstowego i binarnego.

<services>
  <service 
behaviorConfiguration="SilverlightFaultData.Web.SpeedServiceBehavior" 
    name="SilverlightFaultData.Web.SpeedService0">
    <endpoint address="" binding="basicHttpBinding" 
      contract="SilverlightFaultData.Web.SpeedService0" />
    <endpoint address="mex" binding="mexHttpBinding" 
      contract="IMetadataExchange" />
  </service>
  <service
behaviorConfiguration="SilverlightFaultData.Web.SpeedServiceBehavior" 
    name="SilverlightFaultData.Web.SpeedService1">
    <endpoint address="" binding="customBinding" 
      bindingConfiguration="silverlightBinaryBinding" 
      contract="SilverlightFaultData.Web.SpeedService1" />
    <endpoint address="mex" binding="mexHttpBinding" 
      contract="IMetadataExchange" />
  </service>
</services>

Usługi SpeedService0 i SpeedService1 obie pobierają wszystkie produkty oraz kategorię, dostawcę i szczegóły zamówień dla każdego produktu. Kwerenda (pokazana na Rysunku 2) wykorzystuje Entity Framework do pobierania grafu obiektu.

Rysunek 2: Pobieranie grafu obiektu.

[OperationContract]
public IList<Products> DoWork()
{
    var ctx = new NorthwindEFEntities();
    var query = from p in ctx.Products
                .Include("Categories")
                .Include("Suppliers")
                .Include("OrderDetails")
                select p;
    var productList = query.ToList<Products>();
    return productList;
}

Jednym z najlepszych aspektów binarnego kodowania komunikatów jest to, że jedyne zmiany dotyczą pliku konfiguracyjnego. Nie trzeba dokonywać żadnych zmian w kodzie.

Gdy aplikacja przykładowa zostanie uruchomiona i wybrana będzie opcja kodowania tekstowego (jak pokazano na Rysunku 3), to wykonana zostanie usługa wykorzystująca basicHttpBinding. Graf obiektu zostanie zwrócony, a korzystając z narzędzia monitorowania HTTP takiego jak Fiddler lub FireBug można zobaczyć, że graf obiektu w formie tekstowej ma rozmiar 4MB, a czas jego pobierania wynosi 850ms. W przypadku wybrania opcji kodowania binarnego zwrócony graf obiektu ma 3MB, a jego pobranie zajmuje 600ms. Choć jest to niewielki przykład wykorzystujący umiarkowanej wielkości graf obiektu z bazy danych Northwind, to wyniki są zgodne z pomiarami zespołu Silverlight Web Service. W tym przykładzie wykorzystującym kodowanie binarne graf obiektu zawiera w sumie około 2300 obiektów i ma rozmiar mniejszy o 25% oraz pobierany jest o 30% szybciej niż przy kodowaniu tekstowym.

Rysunek 3: Pobieranie danych poprzez BasicHttpBinding.

Komunikaty o błędach są danymi

Gdy zarządzane wyjątki .NET są wzbudzane w usłudze WWW, nie mogą być one konwertowane na komunikat SOAP i przekazywane z powrotem do aplikacji klienckiej Silverlight 2. Silverlight 2 nie może też odczytywać usterek SOAP. Te dwa problemy utrudniają debugowanie usług WWW wykorzystywanych z poziomu Silverlight 2. Ponieważ usterki SOAP nie mogą być wykorzystywane z poziomu Silverlight 2, częstym komunikatem błędu, na który w końcu natknie się większość programistów Silverlight 2 podczas korzystania z dostępu do usługi WWW jest słynny komunikat „The remote server returned an error: NotFound (Zdalny serwer zwrócił błąd: Nieznaleziony)”, który nie zawiera praktycznych informacji. Pierwotny wyjątek i jego szczegóły nie są przekazywane do klienta Silverlight 2, co sprawia, że debugowanie usług WWW jest trudne. Komunikaty błędów zawierają dane, które są często istotne przy określaniu, jak aplikacja kliencka powinna zareagować. Na przykład Rysunek 4 pokazuje wyniki wywołania usługi WWW, gdzie wyjątek zostaje wzbudzony, ponieważ nie można znaleźć bazy danych.

Rysunek 4: Słynny błąd NotFound (Nieznaleziony).

Gdy wyjątek zostanie wzbudzony, kod stanu HTTP 500 zostanie zwrócony do Silverlight. Stos sieciowy przeglądarki uniemożliwia Silverlight odczyt odpowiedzi z kodem stanu 500, więc wszelkie informacje o usterkach SOAP w nich zawarte są niedostępne dla aplikacji klienckich Silverlight. Nawet jeśli komunikat mógłby zostać zwrócony, to Silverlight 2 nie jest w stanie zamienić usterki z powrotem na zarządzany wyjątek. Oba te problemy zostały rozwiązane w Silverlight 3.

Opanowanie problemów

Obsługa wyjątków w WCF i Silverlight 3 wymaga opanowania obu tych problemów. Po pierwsze, aby wyjątek został zwrócony do klienta Silverlight bez blokowania przez stos sieciowy przeglądarki odczytania go z poziomu Silverlight, kod stanu trzeba zmienić z 500 na coś, co pozwoli Silverlight odczytać odpowiedź. Można to osiągnąć dziedzicząc po klasie BehaviorExtensionElement i implementując klasę IEndpointBehavior, nakazując jej zmienić kod stanu z 500 na 200 przy wystąpieniu usterki i ustawiając usługi na korzystanie z tego zachowania w pliku konfiguracyjnym. Dokumentacja MSDN zawiera artykuł WCF endpoint behavior, który można wykorzystać do osiągnięcia tego celu i pozwolenia klientom Silverlight na dostęp do zawartości usterki. Następujący przykład kodu pokazuje określony kod w klasie SilverlightFaultBehavior, który konwertuje kod stanu:

public void BeforeSendReply(ref Message reply, object correlationState)
{
    if (reply.IsFault)
    {
        HttpResponseMessageProperty property = 
          new HttpResponseMessageProperty();
        // Tutaj kod odpowiedzi jest zmieniany na 200.
        property.StatusCode = System.Net.HttpStatusCode.OK;
        reply.Properties[HttpResponseMessageProperty.Name] = property;
    }
}

Do klasy SilverlightFaultBehavior można odwoływać się w pliku Web.config jako do rozszerzenia zachowania, jak pokazano w następującym fragmencie kodu:

<extensions>
  <behaviorExtensions>
    <add name="silverlightFaults"
      type="SilverlightFaultBehavior.SilverlightFaultBehavior,
        SilverlightFaultBehavior, Version=1.0.0.0,
        Culture=neutral, PublicKeyToken=null" />
  </behaviorExtensions>
</extensions>

Mając SilverlightFaultBehavior na swoim miejscu drugim elementem jest sprawienie, aby Silverlight był w stanie konwertować usterkę na zarządzany wyjątek, żeby można go było odczytywać. Choć nie jest to możliwe w Silverlight 2, to Silverlight 3 ma teraz możliwość przetwarzania usterek. Pozwala to Silverlight 3 odczytywać usterkę i przedstawiać odpowiednie informacje użytkownikowi, gdy wyjątek zostanie wzbudzony w usłudze WWW.

Cały proces odczytywania informacji o wyjątku w Silverlight 3 przebiega mniej więcej następująco:

1) Wyjątek jest wzbudzany w usłudze WWW.

2) Usługa wykorzystuje SilverlightFaultBehavior do przekonwertowania kodu stanu HTTP z 500 na 200.

3) Wyjątek jest konwertowany na usterkę SOAP i przekazywany do klienta Silverlight 3.

4) Przeglądarka pozwala Silverlight odczytać ten komunikat, ponieważ ma kod stanu 200.

5) Kod w aplikacji Silverlight 3 sprawdza typ błędu, aby zobaczyć, czy jest to FaultException lub FaultException.

Niezadeklarowane usterki

Usługi WCF oparte na SOAP zgłaszają błędy przy użyciu komunikatów usterek SOAP lub zarządzanych wyjątków .NET. Dlatego zarządzane wyjątki .NET są konwertowane na usterki SOAP, przekazywane do klienta, a następnie tłumaczone z powrotem na zarządzane wyjątki .NET.

Usterki mogą być niezadeklarowane lub zadeklarowane i wszystkie mają silnie określone typy. Niezadeklarowane usterki nie są określane w kontrakcie operacyjnym i powinny być używane jedynie do debugowania. Niezadeklarowane usterki zwracają komunikat wyjątku do klienta dokładnie tak, jak został on wzbudzony w usłudze WWW. Aby pozwalać na niezadeklarowaną usterkę, atrybut includeExceptionDetailInFaults elementu konfiguracyjnego serviceDebug musi być ustawiony na true, jak pokazano poniżej:

<serviceDebug> 
<behavior name="SilverlightFaultData.Web.Service1Behavior">
  <serviceMetadata httpGetEnabled="true" />
  <serviceDebug includeExceptionDetailInFaults="true" />
</behavior>

Usługa Service1 w aplikacji przykładowej wykorzystuje to zachowanie, co pozwala, aby wyjątek był automatycznie konwertowany na FaultException. Klient Silverlight 3 może następnie sprawdzić argument e.Error w swojej procedurze obsługi końca zdarzenia asynchronicznego i podjąć odpowiednie działanie, jak pokazano w kodzie poniżej i na Rysunku 5:

 

if (e.Error != null) {
    ErrorPanel.DataContext = e.Error;
    if (e.Error is FaultException<ExceptionDetail>) {
        var fault = e.Error as FaultException<ExceptionDetail>;
        ErrorPanel.DataContext = fault;
    }
}

Rysunek 5:Niezadeklarowany wyjątek FaultException.

Niezadeklarowane usterki pokazują wyjątek w stanie surowym ze wszystkimi brzydkimi informacjami o błędzie, czego pokazywanie użytkownikowi oczywiście nie jest dobrym pomysłem. Dlatego nie zaleca się korzystania z niezadeklarowanych usterek w aplikacji produkcyjnej. Wyjątki zarządzane mogą także zawierać wewnętrzne informacje dotyczące aplikacji (czasami informacje poufne). Ustawienie includeExceptionDetailInFaults na true powinno mieć miejsce tylko podczas tymczasowego debugowania błędu aplikacji, a nie w środowiskach produkcyjnych. Usilnie zalecam, aby includeExceptionDetailInFaults ustawiać na false w aplikacjach produkcyjnych.

Zadeklarowane usterki

Zadeklarowana usterka jest tworzona, gdy operacja w usłudze zostanie oznaczona atrybutem FaultContractAttribute (lub typem dziedziczącym po FaultContractAttribute). W przeciwieństwie do usterek niezadeklarowanych, usterki zadeklarowane nadają się do środowisk produkcyjnych, gdyż tłumaczą informacje o wyjątku w kodzie na typ usterki. W usłudze WWW programista może utworzyć typ usterki w kodzie i ustawić w jego właściwościach informacje, które należy przesłać do Silverlight. Usterka powinna być wypełniana tylko takimi informacjami, które muszą być znane klientowi. Wszelkie poufne informacje (takie jak poświadczenia) nie powinny być przesyłane do klienta w usterce. W aplikacji klienckiej Silverlight programista może napisać kod wyszukujący ten typ danych i wyświetlający użytkownikowi odpowiednie informacje.

Rysunek 6 pokazuje działanie usługi oznaczone atrybutem FaultContract typu DataFault. Można by użyć ogólnego typu FaultContract, chociaż zalecam korzystanie z określonego, niestandardowego typu usterki dla danej operacji. W tym przypadku operacja wykorzystuje typ DataFault, który utworzyłem w tej aplikacji przykładowej. Ta operacja usługi się nie powiedzie, ponieważ bazy danych nie można znaleźć. Wzbudzony zostanie wyjątek, a następnie przechwycony przez blok try/catch, gdzie wyjątek jest odczytywany, a kluczowe informacje są umieszczane w DataFault zanim zostanie wzbudzony. W tym momencie DataFault jest konwertowany na usterkę SOAP i przesyłany z powrotem do klienta Silverlight z kodem stanu 200.

Rysunek 6: Tworzenie zadeklarowanej usterki.

[OperationContract]
[FaultContract(typeof(DataFault))]
public IList<Products> DoWork()
{
    try
    {
        var ctx = new NorthwindEFEntities();
        var query = from p in ctx.Products
                    select p;
        return query.ToList<Products>();
    }
    catch (Exception ex)
    {
        DataFault fault = new DataFault { Operation = Operation.Other, Description = ex.Message };
        throw new FaultException<DataFault>(fault, "because");
    }
}

Klasa DataFault (pokazana na Rysunku 7) definiuje właściwość Operation i właściwość Description. Właściwości, które zawiera usterka, są sprawą programisty. Te właściwości powinny reprezentować kluczowe informacje dla usterki tak, aby mogły być badane przez klienta Silverlight. Operacja jest ustawiana na niestandardowe wyliczenie typu Operation (również pokazane na Rysunku 7) określające typ operacji SQL, która była wykonywana, gdy wystąpił wyjątek. Description należy ustawić na komunikat niestandardowy, a nie na komunikat wyjątku, aby uniknąć przesyłania jakichś informacji poufnych (aplikacja przykładowa wykorzystuje ex.Message tylko dla celów demonstracyjnych; nie zalecam przesyłania właściwości Message wyjątku bezpośrednio z powrotem do klienta Silverlight). FaultException również przyjmuje parametr, który reprezentuje powód wystąpienia wyjątku. W tym przykładzie powód ten jest ustawiony na „because” (ponieważ). Parametr ten można wykorzystać, aby pomóc klientowi zaklasyfikować przyczynę wyjątku.

Rysunek 7: Klasa DataFault.

public class DataFault
{
    public Operation Operation { get; set; }
    public string Description { get; set; }
}

public enum Operation
{
    Select,
    Insert,
    Update,
    Delete,
    Other
}

Usługa Service3 z przykładu ma konfigurację, w której punkt końcowy określa, że behaviorConfiguration powinno korzystać z klasy SilverlightFaultBehavior (tłumaczy kod stanu z 500 na 200). Ta konfiguracja jest pokazana poniżej:

<service
  behaviorConfiguration="SilverlightFaultData.Web.Service3Behavior"
  name="SilverlightFaultData.Web.Service3">
  <endpoint address=""
    behaviorConfiguration="SilverlightFaultBehavior"
    binding="customBinding" bindingConfiguration="silverlightBinaryBinding"
    contract="SilverlightFaultData.Web.Service3" />
  <endpoint address="mex" binding="mexHttpBinding"
    contract="IMetadataExchange" />
</service>

Gdy usługa, która wykorzystuje zadeklarowaną usterkę, jest wykonywana, klient Silverlight otrzymuje i może odczytać usterkę. Następujący kod jest wykonywany, gdy kończona jest asynchroniczna operacja usługi WWW:

if (e.Error is FaultException<ServiceReference3.DataFault>)
{
    var fault = e.Error as FaultException<ServiceReference3.DataFault>;
    ErrorPanel.DataContext = fault;
}

Właściwość Error jest sprawdzana w celu określenia, czy jest wyjątkiem FaultException typu DataFault. Jeśli tak, to można zbadać jej indywidualne właściwości Operation i Description. Rysunek 8 pokazuje niestandardowe informacje DataFault wyświetlane bezpośrednio użytkownikowi.

Rysunek 8: Badanie zadeklarowanej usterki.

W aplikacjach produkcyjnych strategia usterek zadeklarowanych powinna służyć do mapowania niektórych wyjątków na usterki SOAP. Kluczem tutaj jest określenie okoliczności, w których wyjątki powinny być mapowane na usterki. Zależy to od tego, czy aplikacja kliencka powinna być informowana o określonych informacjach dotyczących błędów na serwerze.

Podsumowanie

Niniejszy artykuł wyjaśnił, w jaki sposób aplikacje Silverlight 3 mogą odnieść korzyści zarówno z funkcji kodowania binarnego, jak i funkcji zarządzania wyjątkami. Kodowanie komunikatów binarnych stanowi solidną alternatywę dla basicHttpBinding podczas korzystania z klientów WCF takich jak Silverlight. Wyjątki często zawierają informacje krytyczne, które mogą pomagać w debugowaniu aplikacji. Ten artykuł pokazał, jak przekazywać informacje o wyjątkach w środowiskach projektowych i produkcyjnych przy użyciu rozszerzeń Silverlight 3.

Swoje pytania i uwagi do Johna można przesyłać na adres mmdata@microsoft.com.

John Papa (johnpapa.net) jest starszym konsultantem i fanem baseballu, który spędza letnie wieczory ze swoją rodziną dopingując drużynę Yankees. John, mając tytuły Silverlight MVP, Silverlight Insider, oraz prelegenta INETA, jest autorem kilku książek, w tym ostatniej, zatytułowanejData-Driven Services with Silverlight 2(O'Reilly, 2009). Często występuje na konferencjach takich jak VSLive!, DevConnections i MIX.