[PL] Debug usług WCF z Dynamics AX 2009 [v1.1]

[wersja 1.1 wpisu, na spokojnie przeczytałem i uznałem, że przyda się trochę poprawek, aby informacje poniżej były bardziej czytelne :)]

Okay.. efekt pierwszego dnia szkolenia. Poodkrywałem parę tricków dotyczacych najnowszej odsłony Dynamics AX (2009).
Tricki dla prograimsty oczywiście.

Gdy wykorzystujemy AIF (Application Integration Framework) do komunikacji z/do AX na poziomie danych to możemy stworzyć serwisy WCF zgodne z AX.
Naturalne pytanie jakie powstało w mojej głowie gdy potworzyłem parę pierwszych serwisów to jak debugować część serwerową w runtimie wywołanym od strony klienta.

Na wersji CTP, którą się bawię można to zrobić na dwa sposoby. Pierwszy jest idealny i na RTM powinien działać bez problemu:
1) W konfiguracji klienta oraz serwera włączamy debugowanie kodu X++ na poziomie Business Connectora
2) Restartujemy AOS
3) Włączamy klienta AX i w MorphX ustaw pułapkę (breakpoint) na kodzie wykonywanej usługi
4) Włączamy Visual Studio z projektem swojego klienta konsumującego usługę
5) Uruchomiamy projekt w konfiguracji Debug

W chwili wykonania kodu po stronie Visual Studio środowisko Dynamics AX powinno wychwycić pułapkę i i zatrzymać się w Debugerze AX.

-
Gdyby to nie zadziałało, jest oczywiście mniej wdzięczne obejście.
Obejście to też może być atrakcyjne dla programistów X++, którzy nie czują się komfortowo w .NET Framework.

Obejście to nazywa się: zasymuluj klienta WCF po stronie AX-MorphX-X++:
Dla osoby, która silniej się czuje w .NET niż w X++ to rozwiązania może się mniej spodobać i będzie tylko protezą.
Napewno jednak zadziała.

Aby wprowadzić, załóżmy, że mamy już serwis stworzony w AX i wyeksponowany na zewnątrz.
Następnie należy stworzyć klienta. Tutaj Visual Studio bardzo pomaga bo automatyzuje nam trzon procesu chociażby poprzez automatyczne stworzenie sobie referencji do webserwisu jak do innego assembly z wygenerowaniem proxy klas, etc.
Dla osoby słabo znającej WCF rozwiązanie jak z bajki, ponieważ strona serwerowa też jest ładnie rozwiązana w środowisku AX, więc technologia WCF z .NET Framework jest jeszcze bardziej transparentna dla takiej osoby, która czuje się silna technicznie w Dynamics AX, a może niekoniecznie tak mocno w .NET Framework.

Mając to zasobą wystarczy prosty kod, jak chociażby poniższy dla projektu aplikacji Windows pod konsolę:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AIF_ServiceApplication.InventInfoServiceRef;

namespace AIF_ServiceApplication
{
    class Program
    {
        static void Main(string[] args)
        {
            // definiujemy klienta
            InventInfoServiceClient client = new InventInfoServiceClient();
            // definiujemy kryteria zapytania potrzebne do znalezienia danych w AX
            QueryCriteria qc = new QueryCriteria();
            CriteriaElement[] qe = { new CriteriaElement() };
            // definiujemy klucz identyfikujący interesujące nas rekordy
            EntityKey[] entityKey = { new EntityKey() };

                 // Przykładowe kryteria wyciągające informację o towarze z testowej firmy produkującej żarówki (Light Company}
                 qe[0].DataSourceName = "InventTable_1";
                 qe[0].FieldName = "ItemId";
                 qe[0].Operator = Operator.Equal;
                 qe[0].Value1 = "B-R14";

qc.CriteriaElement = qe; 

                 Console.WriteLine("start");

            // operacja findKeys zwraca listę unikalnych rekordów spełniających kryteria
            entityKey = client.findKeys(qc);
            if (null == entityKey)
            {
                Console.WriteLine("Item not found.");
            }
            else
            {
                //Wyciągamy unikalne dane pochodzące z indeksu
                Console.WriteLine(entityKey.First().KeyData[0].Field + " = " + entityKey.First().KeyData[0].Value);

                     //.. a dla uzupełnienia sprawdzimy sobie ile rekordów moglibyśmy

                Console.WriteLine("Number of records: {0}", client.resultNumber(entityKey));

            }

            Console.WriteLine("Finish");
            Console.WriteLine("Press any key to close the window...");
            Console.ReadKey();
            client.Close();
        }
    }
}

Aby zrobić sobie wspomnianą protezę w X++ potrzebne są pewne przygotowania:

1) Zakładam, że odpowiednie wizardy stworzyły nam już serwis i wykonaliśmy wszystkie uzupełniające czynności.
2) Do podstawowej klasy serwisu tworzymy klasę pochodną, która różni się od podstawowej tylko tym, że przeciąża konstruktor new i go upublicznia (w oryginale jest protected)
3) Alternatywnie można oryginalną klasę także odpowiednio zmodyfikować, ja bym jednak zastanowił się dlaczego jest protected i poprzez nową klasę zostawił sobie oryginał nieruszony (przy dalszych synchronizacjach może być to niezwykle istotne).
4) Gdy już mamy gotową klasę której obiekt mogę utworzyć tworzymy sobie job, czy klasę czy jakikolwiek inny obiekt wykonywalny, na którym będziemy wykonywali testy (u mnie job wykorzystujący klasę emulatora serwisu o nazwie InventInfoServiceClient). Taki job wykonuje analogiczny kod do tego powyżej w C#:

static void AxdInventInfo_DebugClient(Args _args)
{
    InventInfoServiceClient ws_client; //deklaruję stworzony przeze mnie emulator klienta
    AifQueryCriteria qc; //analogie obiektów Aif istnieją też w X++
    AifCriteriaElement qe; // jak powyżej
    AifEntityKeyList keyList; // i znów jak powyżej
//parę tymczasowych zmiennych aby poniższy kod był nieco bardziej czytelny
    MapEnumerator mapEnum;
    int tabid;

    //tworzę serwis
    ws_client =  new InventInfoServiceClient(); 
    //określam parametry
    qc = new AifQueryCriteria(); 
    qe = AifCriteriaElement::newCriteriaElement(
                "InventTable_1",
                "ItemId",
                AifCriteriaOperator::Equal,
                "B-R14"); 
    qc.addCriteriaElement(qe);

    print("Start");

    //łączę się i pobieram dane
    keyList = ws_client.findKeys(qc);
    if (keyList == null) {
        print("Item not found");
    } else { 
          //wyświetlam
          mapEnum = keyList.getEntityKey(1).parmKeyDataMap().getEnumerator();
          while (mapEnum.moveNext()) {
              tabid = keyList.getEntityKey(1).parmTableId();
              print(fieldId2PName(tabid,mapEnum.currentKey()));
              print(conpeek(mapEnum.currentValue(),1));
          }
          print("Result count:");
          print(ws_client.resultNumber(keyList));
    } 
    print("Done");
    ws_client = null; 
    pause;
}

Mając całość w X++ po stronie AX wydaje mi się, że tematu debugowania już tłumaczyć nie muszę i kwestia staje się trywialna.

Porównując tylko ten sam kod można zobaczyć, że AIF po stronie .NET Framework to o wiele przyjemniejszy organizm do operowania.
Typy danych i obiektów są bardziej ludzkie i używalne. Przykład z wyżej: identyfikator tabeli czy kolumny którą wydobywamy z encji po stronie X++ to Id odpowiednich obiektów, które trzeba jeszcze odszukać w strukturach zwracanych obiektów. Następnie dopiero można je sobie czytelnie przetłumaczyć na stringi, aby wynikt tego debug'u miał jakikolwiek wyraz.
Wartość też w powyższym przypadku zwróciła mi się w kontenerze, do którego musiałem się wpierw dobrać aby wyświetlić tą informację.

Wierzę, że można to jeszcze usprawnić aby takowy szablon do debugu był bardziej generyczny, ale jak sama nazwa wskazuje - to workaround w przypadku gdy idealne rozwiązanie nie zadziała. Stanowczo zalecam najpierw spróbować poklikać trochę po checkboxach i wykonać bardziej administracyjną operację niż się nakodować.

Technorati Tagi: Polish posts,coding,Microsoft Dynamics,Microsoft Dynamics AX