Projektowanie, Programowanie, Codzienność – BeniaminZaborski.com

18 stycznia 2014

WCF – logowanie błędów

Może część z Was zastanawiała się kiedyś jak wprowadzić własny mechanizm logowania błędów w WCF? Ja miałem potrzebę implementacji takiego mechanizmu logowania i podzielę się jedną z prostszych opcji.

O co właściwie chodzi?

Mianowicie walczymy o to, aby po stronie serwera hostującego nasz serwis WCF była możliwość zapisu do logów błędów występujących w tymże serwisie. Wiemy, że domyślnie wszystkie wyjątki jakie wystąpią w serwisach WCF nie są wysyłane do klienta. Wiemy także, że to domyślne zachowanie możemy łatwo zmienić ustawiając includeExceptionDetailInFaults="true". Może na etapie developingu (tfu co za słowo) jest to wygodne, ale na pewno w systemie produkcyjnym … hmm no cóż powiedzmy, że klienta nie do końca interesują szczegóły wyjątku i cały stack trace:).  W systemie produkcyjnym chcemy mieć szczegóły błędów w naszym logu … czymkolwiek on będzie.

Z pomocą przychodzi nam prosty interfejs IErrorHandler z przestrzeni System.ServiceModel.Dispatcher. Zaimplementujmy go:

public class CustomErrorHandler : IErrorHandler
{
    public bool HandleError(Exception error)
    {
        Logger.LogError(error); // Tutaj użyj swojego loggera!
        return true;
    }

    public void ProvideFault(Exception error, MessageVersion version, ref Message msg)
    {

    }
}

To wszystko? Tak, a właściwie prawie:). Metoda HandleError to miejsce logowania błędu. Ale to jednak za mało gdyż jak się pewnie domyślacie teraz trzeba jakoś poinformować WCF-a, żeby użył naszej klasy CustomErrorHandler. Z pomocą przychodzi sam WCF, a mianowicie mechanizm rozszerzeń WCF-a, konkretniej mechanizm rozszerzeń zachowania serwisu BehaviorExtensionElement. A zatem:

public class CustomErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
    public override Type BehaviorType
    {
        get { return GetType(); }
    }

    protected override object CreateBehavior()
    {
        return this;
    }

    private IErrorHandler GetInstance()
    {
        return new CustomErrorHandler();
    }

    void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters)
    {
    }

    void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {
        IErrorHandler errorHandlerInstance = GetInstance();
        foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
        {
            dispatcher.ErrorHandlers.Add(errorHandlerInstance);
        }
    }

    void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
    {

    }
}

Dwa najistotniejsze elementy powyższego kodu został wyróżnione, tj. instancjonowanie naszego CustomErrorHandler oraz zarejestrowanie go.

Czy teraz to już wszystko? Prawie :). Teraz przenosimy się do pliku konfiguracyjnego naszego serwera (w wypadu self-hosting to będzie pewnie usługa Windows, a w wypadku hostowania na IIS to jakaś aplikacja Web).

W ramach <system.serviceModel> dodajemy:

<extensions>
 <behaviorExtensions>
   <add name="customErrorHandler" type="TwojaAplikacja.CustomErrorHandlerExtension, TwojeAssembly" />
 </behaviorExtensions>
</extensions>

Rozszerzyliśmy WCF o nasz error handler, teraz pozostaje tylko go użyć. W ramach sekcji behavior naszego serwisu dodajemy <customErrorHandler/>.

Teraz to już naprawdę wszystko i działa!

Reklamy

13 października 2009

Spring.NET vs WCF

Filed under: How Can I Take ... — Tagi: , , , — Beniamin Zaborski @ 23:04

Tytuł artykułu zdradza nieco temat jaki chciałbym podjąć. Może samo versus jest nieco przewrotne, gdyż bardziej odpowiednie byłoby „Spring.NET a WCF” czy też po prostu „Spring.NET i WCF”. Tak naprawdę to tytuł powinien brzmieć „Spring.NET a WCF versus programista” ;). Obserwując programistów zaczynających przygodę ze Spring.NET i próbujących zintegrować z tym frameworkiem technologię WCF właśnie taki tytuł się nasuwa. Wielu programistów napotyka na problemy przy integracji tych rozwiązań. Zatem, moim zadaniem będzie zaprezentowanie prostej aplikacji pozwalającej hostować serwisy po technologii WCF w oparciu o Spring.NET.
W jednym z moich artykułów z serii „Ugryźć Spring.NET” poświęconych serwisom (cz.6) napisałem, że zmiana technologii hostowania serwisów wymagać będzie jedynie zmian w konfiguracji. W dużej mierze jest to oczywiście prawda. Jednak są pewne niuanse, które wymagają wyjaśnienia. W przykładzie w tamtym artykule serwisy były hostowane za pomocą .NET Remotingu, tu będzie to WCF.
A teraz uwaga skierowana w stronę wszystkich tutoriali do WCF jakie widziałem. Uważam, że wszystkie te tutoriale uczą skrajnie niepraktycznego podejścia do jednej z kwestii – client proxy. Chodzi tu o generowanie klas proxy klienta za pomocą bądź to bezpośrednio środowiska Visual Studio, bądź narzędzia svcutil.exe. Jak dla mnie wiąże się z tym podstawowa niedogodność, iż w trakcie tworzenia aplikacji mój serwis musi być już hostowany, abym mógł skorzystać z dobrodziejstw narzędzia svcutil.exe. Poza tym każda zmiana w serwisie wymaga ponownego przegenerowania. Koszmar!!! Wady takiego rozwiązania można by jeszcze mnożyć – serwisy są „mało abstrakcyjne” czyli zbyt powiązane z implementacją opartą na WCF.
Proste podejście do którego się już przyzwyczaiłem to osobne assembly z interfejsem modelu aplikacji i osobne assembly z implementacją tego modelu (czytaj serwisów).
Dlaczego taki model, uważany nie tylko przeze mnie za właściwy, ma być zburzony przez WCF? Ależ wcale nie musi! Gdyby tylko w tych tutorialach więcej pisano o klasie ChannelFactory i metodzie CreateChannel …
Z tego rozwiązania korzysta właśnie Spring.NET. Dzięki temu migrując serwisy naszej aplikacji z .NET Remotnigu do WCF nie burzymy naszej aplikacji, a jedynie dokonujemy zmian w konfiguracji. Pozostaje tu oczywiście jedna kwestia do wyjaśnienia, czyli atrybutów WCF-a, tj. ServiceContract oraz OperationContract. One niestety muszą być użyte, więc to będzie cała zmiana w kodzie aplikacji – reszta to config. Nasze obiekty DTO są zapewne już opatrzone atrybutem Serializable i wcale nie musimy tego zmieniać na DataContract.
Wyjaśniłem chyba już trochę, a zatem czas na przykład. Przykład jest trywialny.
Solution składa się z czterech projektów:

  • interfejs modelu (BeezDev.SpringWCFApplication.Model.Interface)
  • implementacja modelu (BeezDev.SpringWCFApplication.Model)
  • konsolowa aplikacja hostująca serwisy aka server (BeezDev.SpringWCFApplication.Server)
  • okienkowa aplikacja klient (BeezDev.SpringWCFApplication.Client)

Aplikacja posiada jeden serwis HelloService z jedną metodą SayHello przyjmującą jako parametr jeden obiekt DTO i zwracającą inny typ obiektu DTO. Jakże wyszukana logika mówi: „podaj mi swój login, a zostaniesz przywitany”. Aplikacja serwera posiada referencję do interfejsu modelu jak i jego implementacji, natomiast już klient posiada tylko referencję do interfejsu modelu.
Przyjrzyjmy się najpierw konfiguracji serwera. Nie będę oczywiście omawiał całości konfiguracji Spring.NET, a jedynie te elementy które są istotne w kontekście WCF. Pierwsza sprawa to definicja serwisu w kontenerze IoC Springa.
Generalnie przy migracji z .NET Remoting ona istnieje. Jedyna zmiana, której ewentualnie musimy dokonać, to oznaczyć serwis aby nie był uruchamiany jako singleton (singleton=”false”). Jak podaje dokumentacja Spring.NET ten typ aktywacji obiektu nie współpracuje poprawnie z technologią WCF. Przyjmijmy to jako pewnik i na tym etapie nie wnikajmy w szczegóły.
Cała moc Springa drzemie w eksporterach serwisów, a więc należy zdefiniować odpowiedni dla WCF:


<object id=”helloServiceHost” type=”Spring.ServiceModel.ServiceExporter, Spring.Services”>
  <property name=”TargetName” value=”helloService” />
</object>

Nie ma tu nic nowego, poza szczególnym typem eksportera serwisu Spring.ServiceModel.ServiceExporter.
Od serwera wymaga się hostowania naszego serwisu i do tego służy klasa ServiceHost. To kolejna zmiana w kodzie jaką musimy wykonać w stosunku do .NET Remoting. Zakładając, że aplikacja hostująca nie jest częścią modelu aplikacji, a więc ta zmiana się nie liczy;).
Teraz klient czyli to najciekawsze z punktu widzenia WCF. Zmiany w kodzie? Nie! Żadnych zmian! Aplikacja działa tak samo, tj. pobiera instancję serwisu z kontenera IoC Spring-a i wywołuje jego metody. Zatem wszystkie zmiany ograniczą się do pliku konfiguracyjnego.
Definiujemy ChannelFactory dla naszego serwisu:


<object id=”helloServiceChannelFactory” type=”System.ServiceModel.ChannelFactory&lt;BeezDev.SpringWCFApplication.Model.IHelloService>, System.ServiceModel”>
  <constructor-arg name=”endpointConfigurationName” value=”tcpEndpoint” />
</object>

Jako parametr generyczny podajemy typ naszego serwisu i kolejna rzecz wymagająca wyjaśnienia to argument konstruktora. Tutaj przekazujemy nazwę endpointa-a z konfiguracji WCF-a. O tym jeszcze wspomnę później.

Następnie definicja naszego serwisu:


<object id=”helloService” type=”BeezDev.SpringWCFApplication.Model.IHelloService, BeezDev.SpringWCFApplication.Model.Interface”
  factory-object=”helloServiceChannelFactory”
  factory-method=”CreateChannel” />

Jako klasę fabrykującą wskazujemy obiekt zdefiniowany wcześniej, a metoda fabrykująca to oczywiście CreateChannel.
To wszystko! No prawie wszystko;). Pozostaje kwestia konfiguracji samego WCF, która jest niejako poza tematem tego artykułu. Wspomnę tylko, że konfiguracji dokonujemy w tradycyjny sposób, tj. w sekcji system.serviceModel pliku app.config. W przykładzie użyłem hostowania serwisów po protokole TCP z binarną serializacją. Po szczegóły związane z cała magią konfiguracji WCF-a tzw. ABC odsyłam do dokumentacji.

Pełne solution tutaj. Miłej zabawy!

Blog na WordPress.com.