SOAP w C sharp na przykładzie Allegro WebAPI

yakhub

SOAP w C# .net na przykładzie Allegro WebAPI

0. Uwagi wstępne

Stworzona aplikacja ma jedynie na celu zaprezentowanie, JAK wykorzystać Allegro WebAPI. Zamieszczony kod koncentruje się wyłącznie na tym zagadnieniu. Należy mieć na uwadze, co prawda będzie on działać, ale nie jest kompletny, ani optymalny. W szczególności może zawierać uchybienia dotyczące bezpieczeństwa.

Eksperymentując z funkcjami z WebAPI należy zachować szczególną ostrożność – pamiętajmy, że działamy na „żywym organizmie” Allegro, ze wszelkimi tego konsekwencjami – nasze działania mogą być czasem kosztowne (jeżeli na przykład „przez przypadek” wystawimy aukcję reklamowaną na stronie głównej).

1. Trochę teorii - SOAP

Według Wikipedii:
"SOAP (ang. Simple Object Access Protocol (SOAP)) - protokół wywoływania zdalnego dostępu do obiektów, wykorzystujący XML do kodowania wywołań i najczęściej HTTP do ich przenoszenia, możliwe jest jednak wykorzystanie innych protokołów do transportu danych.

Dokument SOAP składa się z trzech części:
* koperty (envelope) która określa szkielet opisujący, co znajduje się w komunikacie i jak go przetwarzać,
* zbioru reguł kodujących potrzebnych do rozszyfrowania typów danych (również złożonych) zdefiniowanych wewnątrz aplikacji,
* reguł dotyczących wywoływania zdalnych metod i odczytu odpowiedzi."

Czyli - SOAP to takie coś, co wykorzystuje XML, HTTP i diabli wiedzą co jeszcze, i jest potwornie skomplikowane.

W praktyce - SOAP to po prostu protokół, który pozwala twórcom aplikacji sieciowych (w naszym przykładzie - Allegro) na udostępnianie innym programistom funkcji.
Od strony programisty w C# funkcje takie nie różnią się praktycznie od zwykłych funkcji bibliotecznych - to, że są one wykonywane zdalnie na innej maszynie, a zlecenia wykonania i wyniki są przesyłane w dokumentach XML przez HTTP jest właściwie niewidoczne (jedynym widocznym skutkiem jest czasami długie oczekiwanie na wynik).

2. Trochę praktyki, czyli przygotowania

Allegro uznało, że WebAPI to narzędzie zbyt dobre, żeby każdy mógł z niego korzystać "ot, tak". Do połączenia potrzebny nam będzie tzw. klucz. Klucze istnieją w 2 wersjach - podstawowej i rozszerzonej. Wersja rozszerzona jest płatna (600zł/pół roku). Na szczęście wersja podstawowa udostępnia większość możliwości i jest darmowa, trzeba tylko o klucz poprosić. W tym celu otwieramy stronę:
http://www.allegro.pl/contact/contact.php?topic=288
i wysyłamy maila z prośbą o udostępnienie klucza. Klucz ma postać krótkiego ciągu znaków (liter i cyfr). Na ile zdołałem się zorientować, serwis nie robi problemów i udostępnia klucze każdemu, kto o nie poprosi (choć trwa to czasem kilka dni).

Poza kluczem, przyda się nam także konto w serwisie - nasza aplikacja będzie napisana od razu dla Allegro.pl, ale jeżeli nie chcemy eksperymentować na naszym koncie, to specjalnie do tego celu stworzono serwis:
http://testwebapi.pl/

W przykładzie stworzymy aplikację, która pobiera dane naszych kontrahentów – więc dobrze, jeżeli jakieś transakcje już przeprowadziliśmy (w przeciwnym wypadku nie będziemy wiedzieć, czy aplikacja działa). W razie potrzeby – na testwebapi.pl wystawiamy sobie kilka aukcji i w nich kupujemy.

Niezbędny będzie także Visual C#, wystarczy nam wersja Express. Przykład tworzony jest w wersji 2008 – jest to o tyle istotne, że we wcześniejszych wersjach niektóre opcje znajdowały się gdzie indziej.

Dokumentacja do WebAPI teoretycznie znajduje się tutaj:
http://webapi.allegro.pl/

W praktyce jednak – za wiele w tej dokumentacji nie ma, poza listą funkcji.

3. Zaczynamy

Uruchamiamy Visual C# i tworzymy nowy projekt. Żeby się za bardzo nie zmęczyć, niech to będzie Windows Forms Application.
stworzenie_nowego_projektu.png

Szybko sklecamy okienko naszej aplikacji:

widok_okna.png
Na formie znajdują się następujące kontrolki:

  • TextBoxy:
    ** loginTxt (domyślne)
    ** pswrdTxt (PasswordChar = ‘*’)
  • ListBoxy:
    ** auctionsLbx (domyślny)
    ** logsLbx (domyślny – można się bez niego obejść, ale w aplikacji testowej wygodnie jest w nim śledzić stan aplikacji)
  • Buton:
    ** loginBtn

Jak widać, nic szczególnie ambitnego – w końcu chodzi nam tylko o zaprezentowanie działania WebAPI.

Teraz przechodzimy do sedna – importujemy bibliotekę WebAPI. Potrzebna jest nam do tego celu tzw. specyfikacja WSDL. Jest to dokument XML, który znajduje się pod adresem:
http://webapi.allegro.pl/uploader.php?wsdl

Nie przejmuj się, nie musisz go rozumieć.

Aby zaimportować tę bibliotekę do naszego projektu, w pasku Solution Explorer klikamy prawym przyciskiem pozycję References i wybieramy Add Service Reference. W okienku, które nam się otworzy, NIE wpisujemy jeszcze adresu! Zamiast tego, klikamy przycisk Advanced, a następnie Add Web reference. Wtedy dopiero wprowadzamy adres specyfikacji, i nadajemy jej nazwę (jak na screenie):
import_biblioteki.png

4. Programujemy

Skoro już się tyle naklikaliśmy, czas zacząć pisać jakiś kod.

Zaczynamy od poinformowania kompilatora, że zamierzamy skorzystać z biblioteki WebAPI. W tym celu na samym początku kodu, dopisujemy kolejną dyrektywę do tych, które już się tam znajdują:

using Test_WebAPI.webapi;  // będziemy używać biblioteki webapi

Wewnątrz klasy Form1 deklarujemy też kilka stałych – nasz klucz WebAPI i tzw. klucz wersji (znajdziemy je na stronach Allegro w zakładce „Moje konto”). Na dzień dzisiejszy klucz wersji dla Allegro.pl to 95702267 pamiętajmy jednak, że zmienia się on co kilka tygodni przy każdych zmianach w serwisie.

const string webapiKey = "129abc"; // oczywiście wpisujemy tutaj nasz klucz
const int versionKey = 95702267;

Wszystkie funkcje w bibliotece WebAPI są metodami składowymi obiektu AllegroWebApiService (niestety, nie znajdziemy tej informacji w dokumentacji). Ponieważ więc ten obiekt będzie nam bez przerwy potrzebny, więc deklarujmy go sobie jako zmienną globalną dla naszego okna.

A ponieważ przed użyciem obiekt trzeba zainicjalizować – dla świętego spokoju zróbmy to już przy uruchamianiu programu (a dokładniej – przy tworzeniu okienka) – czyli wewnątrz konstruktora. Na tym etapie nasz program wygląda następująco:

public partial class Form1 : Form
{
   const string webapiKey = "naszklucz";	// stała z naszym kluczem WebAPI
   const int versionKey = 95702267;		// stała z kluczem wersji

   AllegroWebApiService service;	// globalny obiekt AllegroWebApiService

   public Form1()
   {
      InitializeComponent();
      service = new AllegroWebApiService();// inicjalizacja obiektu service
   }
}

Przechodzimy do obsługi przycisku „zaloguj się”. Chcemy aby po jego kliknięciu program logował się do serwisu i pobierał z niego listę zakończonych aukcji (sprzedanych przedmiotów).

W tym celu najpierw musimy się zalogować (przeglądając dokumentację znajdziemy funkcję doLogin: http://webapi.allegro.pl/uploader.php?apiMethod=doLogin#)

Nie znajdziemy tam jednak prototypu tej funkcji – z pomocą przyjdzie nam jednak środowisko. W funkcji odpowiedzialnej za obsługę przycisku wpisujemy więc:

service.doLogin(

a resztę podpowie nam Visual Studio.

Wartość zwracana przez funkcję to uchwyt sesji – będzie on nam potem potrzebny dosyć często, więc też deklarujemy go jako zmienną globalną. Funkcja zwraca też (przez referencje) dwie wartości: serverTime (nie będzie nam potrzebny) i hashOffset (nieużywana). Po zalogowaniu wyświetlamy sobie to wszystko, żeby lepiej zrozumieć co w trawie piszczy (kod poniżej).

Próbujemy uruchomić aplikację i sprawdzamy skutki działania. Widzimy wartości zwracane przez funkcję – możemy się między innymi przekonać, że nieużywana wartość hashoffset jest równa 0 i zobaczyć jak wygląda uchwyt sesji.

Przede wszystkim jednak – próbujemy wpisać błędne hasło, aby się przekonać co się stanie. Czeka nas drobna niespodzianka, bo w dokumentacji nie ma ani słowa o tym, że funkcja wtedy rzuca wyjątkiem (choć można się było tego domyślać). Musimy więc dopisać najprostszą możliwą obsługę wyjątku. Kompletny kod programu na tym etapie – poniżej.

Na tym etapie program wygląda tak:

const string webapiKey = "9abd234ca";
const int versionKey = 95702267;

AllegroWebApiService service;
string sessionHandle;   // uchwyt sesji jako zmienna globalna

public Form1()
{
   InitializeComponent();
   service = new AllegroWebApiService();
}

private void LoginBtn_Click(object sender, EventArgs e)
{
   int hashoffset = 0; long serverTime = 0;    //deklaracje zmiennych zwracanych przez funkcję doLogin
   try //logowanie może się przecież nie powieść - wtedy zostanie zwrócony wyjątek, który musimy złapać
   {
      sessionHandle =
         service.doLogin(
         loginTxt.Text,      //login został wpisany w polu tekstowym
         pswrdTxt.Text,      //hasło
         1,                  //parametr "countrycode", lista wartości w dokumentacji – dla Polski = 1
         webapiKey,          //klucz webapi - zadeklarowany wcześniej jako stała
         versionKey,         //klucz wersji - jw.
         out hashoffset,     //  
         out serverTime);    // dodatkowe wartości zwracane przez funkcję.
   }
   catch //jeżeli logowanie nie powiodło się - wyświetlamy komunikat i kończymy
   {
      logLbx.Items.Add("Logowanie nie powiodło się");     
	return;
   }

   logLbx.Items.Add("session handle: " + sessionHandle);   //po to stworzyliśmy logLbx, żeby teraz sobie w nim wyświetlać komunikaty. 
   logLbx.Items.Add("hash offset: " + hashoffset.ToString());//Wyświetlamy więc wszystkie zmienne.
   logLbx.Items.Add("server time: " + serverTime.ToString());
}

Sprawdzamy działanie programu – poza dziwnymi komunikatami co prawda nic się nie dzieje, ale za to jest stabilny jak skała i nie wysypuje się – nawet jak wpiszemy bzdury w miejsce hasła.

Skoro już się zalogowaliśmy, czas wyświetlić listę naszych sprzedanych przedmiotów. Na zdrowy rozum można się domyślać, że funkcja do tego na pewno istnieje. Na zdrowy rozum możemy też próbować zgadywać, jak ona się nazywa, jednak na wiele nam się to nie zda. Pozostaje nam więc przeglądanie po kolei opisu każdej z funkcji (tak czy inaczej warto to zrobić, bo a nóż znajdziemy coś, co się nam do czegoś przyda?).

Funkcja której szukamy znajduje się dopiero pod koniec listy i nosi wszystko mówiącą i łatwą do odgadnięcia nazwę „doMyAccount2”. Aby się dowiedzieć jakie wartości ta funkcja przyjmuje i co zwraca, porzucamy dokumentację (bo tam na ten temat przeczytamy bzdury) i przechodzimy do środowiska. Zaczynamy więc pisać:

service.doMyAccount2(

a środowisko nam grzecznie podpowie prototyp funkcji. Widzimy więc, że będą nam potrzebne:

  • sessionhandle – uchwyt sesji, otrzymaliśmy go od funkcji doLogin.
  • accounttype – wartość typu string. W dokumentacji czytamy, że może ona przyjmować wartości: bid, sell, won, not_won, sold, not_sold, watch, watch_cl, future. Odpowiadają one poszczególnym zakładkom w serwisie. Nas interesuje wartość „sold”.
  • Offset i Limit – liczby całkowite – funkcja nie potrafi za jednym zamachem zwrócić dużej liczby przedmiotów. Wartość Offset to numer przedmiotu, od którego chcemy zacząć, Limit – ilość przedmiotów, jakie chcemy otrzymać w jednej „paczce”. Dokładnej maksymalnej wartości w dokumentacji nie ma, doświadczalnie jednak udało mi się ustalić, że bez problemów jesteśmy w stanie pobrać 100 aukcji za jednym zamachem. Dla uproszczenia programu, przyjmujemy, że więcej aukcji nie mamy, ale należy pamiętać o tym, że powinniśmy tę funkcję wywołać kilka razy, aby otrzymać kompletną listę.
  • items-array – tablica liczb całkowitych (a nie obiekt ArrayOfAuctionID, jak pisze dokumentacja). Autorzy dokumentacji o tym zapomnieli, jednak jakaś życzliwa dusza dopisała komentarz: „Jeśli chcemy otrzymać wszystkie przedmioty pasujące do danej grupy należy przesłać jako wartość tego parametru pustą tablicę.”. Serdecznie temu komuś dziękujemy, bo chyba bym na to nie wpadł.
    Funkcja zwraca tablicę obiektów typu MyAccountStruct2 (a nie obiekt ArrayOfMyAccountList2).
    Nie pozostaje nam nic innego, jak zadeklarować sobie taką tablicę i tę funkcję wywołać. Pomni niespodzianek przygotowujemy się na złapanie wyjątku:
MyAccountStruct2[] accountStructTable;
try
{
   accountStructTable = service.doMyAccount2 (
   sessionHandle,
   "sold",
   0,
   new int[0],	//pusta tablica typu int
   100);
}
catch
{
   logLbx.Items.Add("Wystąpił błąd w funkcji doMyAccountStruct2");
   return;
}

Zostaliśmy więc z tablicą AccountStructTable. Jak można się domyśleć, każdy element tej tablicy opisuje jedną aukcję. Warto by to było wyświetlić – w tym celu przecież stworzyliśmy listbox „auctionsLbx”.
Obiekt typu MyAccountStruct2 zawiera w sobie tylko jeden element myaccountarray – a jest to po prostu tablica stringów. Oczywiście dokumentacja tej klasy nie zawiera kompletnie żadnych informacji na temat tego, co w tej tablicy jest. Te informacje znajdują się jednak w opisie funkcji doMyAccount2.
Interesuje nas numer aukcji (pod indeksem 0), jej tytuł (9) i nick kupującego (8).
Musimy więc przelecieć całą tablicę AccountStructTable, z każdego elementu wyłuskać interesujące nas informacje i wyświetlić:

foreach (MyAccountStruct2 accountStruct in accountStructTable)
{
   auctionsLbx.Items.Add(
      accountStruct.myaccountarray[0] + " " + 
      accountStruct.myaccountarray[9] + " " + 
      accountStruct.myaccountarray[8]
   );
}

Uruchamiamy naszą aplikację i sprawdzamy działanie.

Być może program nie robi (na tym etapie) nic mądrego, ale wydaje mi się, że dosyć dokładnie opisałem podstawy omawianej biblioteki i proces myślenia, jaki należy podczas korzystania z niej stosować. Mam nadzieję, że w obec skąpej dokumentacji i braku jakichkolwiek przykładów ten artykuł okaże się przydatny. Wszelkie komentarze mile widziane.

Pliki projektu możesz pobrać tutaj:
Test_WebAPI.zip

5 komentarzy

W kodzie trzeba poprawić "int hashoffset = 0; long serverTime = 0;" na "long hashoffset = 0; long serverTime = 0;"

czy moglby ktos zamiescic jak wyglada plik wyjsciowy xml (to co idzie na serwer) dla dowolnej metody allegro webapi. Nie znam sie na c#, pisze na iPhone i dostaje bledy, chcialbym zobaczyc czy moj format xml jest poprawny.

Bardzo fajny art. Jedna mala aktualizacja: obecnie biblioteka webAPI dostepna jest wylacznie za posrednictwem polaczenia szyfrowanego: https://webapi.allegro.pl/uploader.php?wsdl

Bardzo pomocny artykuł dla początkujących w temacie - takich jak ja.

P.S - mała uwaga: aby uzyskać nick kupującego wpisujemy pozycje 21 w tablicy a nie 8

Świetny artykuł. Podczas googlowania o SOAP-ie i WebAPI na ogół trafia się na arty, które tylko odstraszają stopniem skomplikowania i poziomem abstrakcji. A tutaj, proszę, jednak można. Prosto, przystępnie, jak krowie na rowie. Brawo.