Wzorce projektowe w programowaniu systemu Dynamics 365 CE – 01: Klasa PluginBase

Niniejszy artykuł rozpoczyna cykl „Programowanie w systemie Dynamics 365 – wzorce projektowe”. Odnośniki do pozostałych tekstów z ww. serii znajdziecie pod adresem: http://xrmlabs.piotrgaszewski.pl/2019/03/29/programowanie-w-systemie-dynamics-365-wzorce-projektowe/

Czym jest klasa bazowa? W najprostszym ujęciu jest to ogólna klasa, z której dziedziczą inne klasy i która pozwala modelować fragment rzeczywistości na dość wysokim (w porównaniu z klasami, które po niej dziedziczą) poziomie abstrakcji. Klasa bazowa jest często klasą abstrakcyjną, wymuszającą dodatkowo implementację określonych metod w klasie pochodnej. Mechanizm ten daje możliwość pisania reużywalnego kodu, który będzie dostępny dla wszystkich klas pochodnych.

W jaki sposób możemy wykorzystać opisany powyżej mechanizm w tworzeniu kodu, który jest uruchomiany na platformie Dynamics 365CE? Skoncentrujmy się w tym miejscu na klasie reprezentującej plugin, który będzie uruchamiany w momencie wystąpienia określonych zdarzeń w systemie (analogiczne mechanizmy możemy wykorzystać, implementując niestandardową aktywność dla procesu workflow).

Pierwszą istotną kwestią, w przypadku której zastosowanie klasy bazowej jest niezwykle pomocne, jest eliminacja powtarzającego się kodu z bibliotek pluginów. Uruchomienie każdego pluginu rozpoczyna się zazwyczaj od sprawdzenia poprawności kontekstu, wewnątrz którego jest uruchamiany kod. Kolejnym krokiem jest pobranie niezbędnych serwisów, sprawdzenie wartości, które się zmieniły, często również obsługa błędów. Kod obsługujące ww. operacje możemy umieścić w klasie bazowej i wykorzystywać go wielokrotnie w czasie pisania kolejnych rozszerzeń systemu Dynamics 365 CE. Ważną kwestią w tym przypadku jest konieczność implementacji interfejsu IPlugin, co daje nam dostęp do ww. komponentów.

Architektura naszego rozwiązania wygląda w tym przypadku następująco:

Uwaga: W przypadku bibliotek pluginów umieszczanych na dysku serwera oraz korzystających z zewnętrznych bibliotek ważną kwestią jest to, żeby klasa PluginBase (implementujące interfejs IPlugin) umieszczona była w tym samym pliku (assembly), co klasy pochodne. W przeciwnym przypadku narzędzie Plugin Registration Tool, które używają mechanizmu refleksji do odczytywania danych pluginów umieszczonych w Waszej bibliotece, „zgłupieje” i nie będzie w stanie ich odnaleźć.

Przyjrzyjmy się teraz dokładnie niektórym możliwością, które otrzymujemy dzięki klasie bazowej.

Pobranie niezbędnych serwisów za pomocą ServiceProvidera

Jak już wspomniałem powyżej – nasza klasa bazowa PluginBase powinna implementować interfejs IPlugin. W związku z tym jesteśmy zmuszeni do implementacji metody Execute. W przypadku niekorzystania z klasy bazowej musimy każdorazowo pobierać niezbędne serwisy oraz kontekst za pomocą obiektu ServiceProvider. W przypadku zastosowania klasy bazowej – kod odpowiedzialny za wspomnianą czynność znajduje się właśnie w metodzie Execute tejże klasy. Mogą być one później przekazane do metod klasy pochodnej, co powoduje eliminację powtarzającego się kodu oraz ułatwia pisanie testów jednostkowych dla metod pluginów.

Poniższy przykład pokazuje, w jaki sposób w klasie bazowej pobierany jest kontekst oraz TracingService. W analogiczny sposób możemy pobierać inne typu serwisów (IOrganizationService lub IServiceEndpointNotificationService).

Walidacja poprawności kontekstu

Kolejną czynnością, którą ułatwia zastosowanie klasy bazowej, jest walidacja poprawności kontekstu oraz uruchomienie kodu zawierającego logikę biznesową jedynie w przypadku, w którym plugin został zarejestrowany w prawidłowy sposób. Na pewno każdemu zdarzyło się kiedyś zarejestrować wywołanie pluginu dla niewłaściwej encji, zdarzenia lub etapu wykonywania danej operacji. Poniższy kod zamieszczony w metodzie Execute klasy PluginBase wymusza wprowadzenie dodatkowego etapu sprawdzenia zawartości kontekstu, w którym uruchamiana jest logika biznesowa. W przypadku niepoprawnego kontekstu – w prezentowanym przypadku wyrzucany jest wyjątek.

Eliminacja powtarzalnego kodu

Klasa bazowa może zawierać implementację metod pomocniczych umożliwiających wykonywanie najczęściej spotykanych czynności (pobranie obiektu z danymi z parametrów wejściowych, pobranie „Pre” lub „PostImage’a”, pobranie oraz odczytywanie wartości z konfiguracji itp.). Przykładowa metoda umożliwiająca pobranie obiektu „targetu” z kolekcji parametrów wejściowych może wyglądać następująco:

Kod wykorzystujący wspomnianą metodę w klasie dziedziczącej po PluginBase:

Podejście to umożliwia eliminację powtarzającego się kodu.

Inne przykłady metod pomocniczych, które może zawierać klasa bazowa, znajdują się z repozytorium kodu. Link do niego znajdziecie na końcu artykułu.

Obsługa błędów

Zastosowanie klasy bazowej pozwala na wprowadzenie niestandardowego oraz (co jest bardzo istotne) ujednoliconego mechanizmy obsługi wyjątków kodu implementującego logikę biznesową i uruchamianego w metodzie Execute. Możemy dodać dzięki temu dodatkowe logowanie błędów do zewnętrznych źródeł, co jest często spotykaną praktyką w czasie wdrożeń systemu w modelu on-premise. Mamy również możliwość wprowadzenia ujednoliconego lub zupełnie odmiennego mechanizmu obsługi dla poszczególnych typów wyjątków.

Przykładowy kod:

Inna ciekawa możliwość, które daje nam wykorzystanie abstrakcyjnej klasy bazowej, to rejestracja zależności w przypadku wykorzystania wzorca Dependency Injection. Dzięki temu możemy w łatwy sposób wstrzykiwać zależności dla obiektu pluginu w momencie uruchamiania kodu. Technice tej przyjrzymy się w przyszłości w osobnym artykule.

Przykładowa klasa pluginu systemu Dynamics 365 CE, wykorzystująca stworzoną przez nas abstrakcyjną klasę bazową, może wyglądać w następujący sposób:

Tak jak widzimy powyżej – klasa pochodna musi zawierać implementację metod IsContextValid oraz Execute. Ma ona od razu dostęp do kontekstu uruchomieniowego, mechanizmu logowania aplikacji oraz wielu metod pomocniczych, umożliwiających eliminację powtarzającego się kodu. Dodatkowo – wymusza zachowanie określonej konwencji architektonicznej i sprawia, że implementacja każdego pluginu zachowuje spójność oraz jest testowalna w dość prosty sposób.

Podsumowując, klasę bazową dla rozszerzeń systemu Dynamics 365 CE możemy wykorzystać do realizacji następujących celów:

  1. Walidacja poprawności kontekstu uruchomieniowego biblioteki.
  2. Wczytanie konfiguracji biblioteki.
  3. Uruchomienie kodu zawierającego logikę biznesową.
  4. Metody pomocnicze ułatwiające dostęp do składowych kontekstu (Target, PreImage, PostImage).
  5. Zaawansowana obsługa błędów oraz ewentualne dodatkowe ich logowanie.
  6. Rejestracja zależności w przypadku wzorca Dependency Injection.

Pełen kod źródłowy rozwiązania prezentowanego w powyższym artykule znajdziecie pod adresem: https://github.com/gashupl/dyn365devbestpractices/tree/master/XrmLabs.Blog.Dyn365BestPractices/Chapter%2001/Chapter01.FooPlugin

Total Views: 1392 ,
This Article Has 3 Comments
  1. Pingback: dotnetomaniak.pl

  2. Cezary B Reply

    FooPlugin chyba też powinien bezpośrednio implementować IPlugin. Plugin registration tool szukając pluginów po tym interfejsie nie zagląda do klas bazowych i nie będzie widział klasy FooPlugin. Chyba, że to już się zmieniło, a ja klepię to w ten sposób z przyzwyczajenia 🙂

    • PG Reply

      Można oczywiście implementować interfejs IPlugin również dla klasy dziedziczącej. Z punktu widzenia kodu będzie to jednak operacja redundantna i zbyteczna. Plugin Registration Tool potrafi „odnaleźć” plugin, który korzysta z klasy bazowej implementującej interfejs IPlugin w sytuacji, w której obie klasy znajdują się w tym samym .NET assembly. Jeżeli klasa bazowa znajduje się w osobnej bilbiotece (Plugin.Common.dll lub coś podobnego) – PRT faktycznie nie radzi sobie z ww. sytuacją.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *