Ošetřování chyb ve WCF a Silverlight

Tomáš Holan       12.11.2012       Silverlight, WCF/WS       11138 zobrazení

V každé Silverlight aplikaci musíme zpravidla nějakým způsobem řešit ošetřování chyb vznikajících na serverové straně při volání operací WCF služby. V tomto článku se nebudu rozepisovat o tom jak ošetřování chyb ve WCF funguje a jaké jsou obecně možnosti a způsoby řešení, jen dodávám, že se celkově nejedná o triviální problém (v Silverlightu jsme navíc omezeni například tím, že výjimky nelze serializovat apod.) Místo toho zde předložím jeden způsob, který v poslední době docela úspěšně používám.

Začnu tím, že rovnou uvedu jak se toto konkrétní řešení bude chovat, jaké bude mít výhody a nevýhody a co budeme muset splnit pro každý typ výjimky, aby bylo možné výjimku daného typu na klientu zachytit:

  • Tento způsob bude použitelný pro systémové i vlastní typy výjimek.
  • Na straně serveru do našeho projektu přidáme implementaci připraveného WCF error handleru a naší WCF službu označíme speciálním atributem.
  • Pro každý typ výjimky (tj. typ odvozený ze třídy Exception) budeme muset na serverové straně definovat samostatný Fault Contract tj. data kontrakt pro přenos informací o vzniklé chybě. Tento kontrakt bude mít konstruktor s jedním parametrem typu odpovídající výjimky.
  • Každou operaci WCF služby, ve které daná výjimka vzniká, budeme muset explicitně označit atributem FaultContract.
  • Výjimka bude moci být v operacích vyhozena standardně (pomoci throw) tj. v implementaci operací nebude vyžadován žádný speciální kód.
  • Na straně klienta nebude potřeba doplňovat téměř žádnou podporu (s výjimkou jedné třídy).
  • Na klientu bude daná výjimka zachytitelná standardně jako System.ServiceModel.FaultException<typ definovaný pro fault>. Tento způsob je bohužel obecně trochu nevýhodný v tom, že takto nelze zachytit odvozené výjimky, ale vždy pouze výjimku daného jednoho typu – tj. odvozené typy je třeba zachytávat samostatně.
  • Každý zachycený fault bude kromě našich vlastních dat vždy automaticky obsahovat informace o vzniklé výjimce včetně hierarchie inner exception (stejně jako ExceptionDetail u obecné neošetřené výjimky).

Řešení je založené na tom, že na serverové straně se ve WCF error handleru budou výjimky automaticky konvertovat na námi definované Fault kontrakty. Strana klientu zůstane prakticky beze změny.


Před uvedením samotného řešení ještě upozorním na to, že musíme mít rozchozené korektní předávání fault kontraktů do Silverlight klienta pomoci SilverlightFaultBehavior. Protože se jedná o docela známou věc (podrobnosti najdete zde, nebo například zde a zde), jen stručně uvedu, že stačí do projektu přidat soubor s třídou SilverlightFaultBehavior (ke stažení zde) a provést příslušnou změnu konfigurace ve web.config:

  <system.serviceModel>
    <extensions>
      <behaviorExtensions>
        <add name="silverlightFaults" type="IMP.Shared.SilverlightFaultBehavior, MyApplication.Web" />
      </behaviorExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">
          <serviceMetadata httpGetEnabled="true" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="">
          <silverlightFaults />
        </behavior>
      </endpointBehaviors>
    </behaviors>
    ...
  </system.serviceModel>

A teď již k samotnému postupu jak řešení pro ošetření chyb nasadit a použít:

  1. Na serverové straně do našeho projektu přidáme třídu ErrorHandler (ke stažení zde).
  2. V definici naší WCF službu označíme  atributem ErrorHandlingBehavior.
#region public types declarations
[Serializable]
public class AuthenticationException : Exception
{
    #region constructors and destructors
    public AuthenticationException() { }

    public AuthenticationException(string message) : base(message) { }

    public AuthenticationException(string message, Exception innerException) : base(message, innerException) { }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    protected AuthenticationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
    #endregion
}

[DataContract]
internal abstract class ServiceFault : System.ServiceModel.ExceptionDetail
{
    #region constructors and destructors
    protected ServiceFault(Exception ex) : base(ex) { }
    #endregion
}

[DataContract]
internal class AuthenticationFault : ServiceFault
{
    #region constructors and destructors
    public AuthenticationFault(AuthenticationException ex) : base(ex) { }
    #endregion
}
#endregion

[ServiceContract]
[ErrorHandlingBehavior]
public class AuthenticationService
{
    [OperationContract]
    [FaultContract(typeof(AuthenticationFault))]
    public void Authenticate(string userName, string password)
    {
        //Test vyhození výjimky		
        throw new AuthenticationException("Invalid user name or password.");
    }
}
  1. Nadefinujeme abstraktní základní třidu pro všechny naše fault kontrakty ServiceFault, která bude poděděna ze třídy System.ServiceModel.ExceptionDetail (viz implementace výše).
  2. Pro každý typ výjimky, který potřebujeme na klientu zachytávat, nadefinujeme vlastní fault kontrakt poděděný ze základní třídy ServiceFault. Přitom doporučuji používat jmennou konvenci XYFault pro typ výjimky XYException (tedy např. vlastnímu typu výjimky AuthenticationException bude odpovídat AuthenticationFault).
    Třída fault kontraktu musí mít veřejný konstruktor s jedním parametrem, který odpovídá konkrétnímu typu výjimky (nesmí být pouze typu Exception), a který bude volat konstruktor základní třídy. Kromě toho je možné do kontraktu případně přidat další vlastní data.
  3. Každou operaci, která vyhazuje danou výjimku, označíme atributem FaultContract s příslušným názvem odpovídajícího fault kontraktu (jednu operaci přitom můžeme označit i více atributy FaultContract).

Funkce WCF handleru je taková , že pokud je v operaci vyhozena výjimka projdou se vždy všechny atributy FaultContract uvedené u dané operace a pokud typ vzniklé výjimky odpovídá typu parametru konstruktoru fault kontraktu, převede se výjimka na tento kontrakt. Pokud není pro výjimku žádný kontrakt nalezen, zůstává chování standardní.

  1. Na straně Silverlight klienta necháme vygenerovat proxy třídy pro volání naší WCF služby. Jen pozor, že v konfiguraci Service reference musí být v části “Reuse types in specified referenced assemblies” uvedena systémová assembly System.ServiceModel.
  2. Do Silverlight projektu vložíme tuto partial implementaci třídy ServiceFault (bez této implementace by nám nešlo projekt zkompilovat):
using System;

namespace MyApplication.AuthenticationServiceProxy
{
    public partial class ServiceFault : System.ServiceModel.ExceptionDetail
    {
        #region constructors and destructors
        protected ServiceFault() : base(null) { }
        #endregion
    }
}
  1. Při volání operace WCF služby můžeme výjimku zachytit standardně pomoci definovaného fault kontraktu:
try
{
    await Proxy.Authenticate(this.UserName, this.Password);
}
catch (System.ServiceModel.FaultException<MyApplication.AuthenticationServiceProxy.AuthenticationFault> ex)
{
    this.ErrorMessage = ex.Message;
}

Kde Proxy.Authenticate je zaobalení volání operace WCF služby jako objekt Task.

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:
Heslo:

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback