Strona używa cookies (ciasteczek). Dowiedz się więcej o celu ich używania i zmianach ustawień. Korzystając ze strony wyrażasz zgodę na używanie cookies, zgodnie z aktualnymi ustawieniami przeglądarki.    X

Logujemy się do dobreprogramy.pl z poziomu kodu C# (+ wprowadzenie do projektu)

Dwa ostatnie wpisy przedstawiały analizę sposobu logowania się i zarządzania powiadomieniami na portalu dobreprogramy. Przyszedł już czas na stworzenie kodu w C#, który pozwałaby już coś w praktyce zrobić.

Dzisiaj skupimy się na logowaniu do portalu, a zapewne na dniach przedstawię mechanizm do zarządzania powiadomieniami.

Na początku jednak mały wstęp odnośnie samego projektu.

Szkielet aplikacji

Aplikacja tworzona będzie w Visual Studio 2015 Community (wersja pozwala na działania komercyjne, zupełnie za darmo, szczegóły licencji tutaj).

Projektem naszym jest oczywiście aplikacja uniwersalna - Universal Windows. Pozwala ona na tworzenie oprogramowania zarówno pod Windows 10, jak i Winodows 10 Mobile.

Oprócz powyższego projektu, do solucji dorzucę również projekt do zarządzania logiką (Class Library do apek uniwersalnych). Będzie on odpowiadał za komunikację ze stroną portalu, zarządzał powiadomieniami od strony naszej apki, a także zajmie się mapowaniem obiektów i innymi rzeczami, które w przyszłości będą dochodziły do aplikacji. Zapewne będzie trzeba stworzyć również rodzaj lokalnej bazy, do przetrzymywania historycznych powiadomień i ustawień. Nie wykluczam także powiększenie apki o kolejne moduły, które pozwolą na zarządzanie kontem, a może nawet pokuszę się o jakieś rozszerzenie funkcjonalności (jakieś dodatkowe powiadomienia?).

Trzecim projektem jest Unit Test, który będzie służył do testowania kodu. Na początek będzie to główny rdzeń solucji, tuż obok logiki. Ze względu na to, iż w pierwszej kolejności zadbać chcę o podstawowe funkcjonalności komunikacyjne pomiędzy portalem, a aplikacją - to testy jednostkowe będą głównym sposobem na sprawdzenie poprawności kodu.

Refactoring to podstawa

Oczywiście taki szkielet projektu jest tylko przejściowym stadium. W miarę rozrostu aplikacji zajdzie zapewne potrzeba na podzielenie kodu na odrębne projekty, dokonanie zmian i reorganizacji. Jednakże na sam początek taki niewielki podział powinien wystarczyć.

Przejdźmy zatem do mięska...

Logujemy się z poziomu kodu

Logowanie do aplikacji podzielić można na dwa etapy:

  1. Pobranie ciasteczka
  2. Zalogowanie się do portalu z użyciem ciasteczka

Uzyskiwanie ciasteczka

Punkt pierwszy wymaga od nas pobrania ciasteczka z portalu. Sprawa jest prosta, musimy pozyskać ciasteczko, które będzie wykorzystane w logowaniu (po szczegóły odsyłam do wcześniejszego wpisu). W tym celu wystarczy jedynie pobrać stronę, która zwrócimy nam Id (ciasteczko), identyfikujące sesję użytkowania (jeszcze niezalogowanego) po stronie serwera.

W tym celu w z poziomu kodu należy wywołać zapytanie do serwera o dowolną stronę. Do tego zadania wytypowałem stronę z klasycznym logowaniem. Nie będziemy się przez nią w żaden sposób logować, ale pozyskamy z niej ciasteczko. Jest to najlżejsza strona i najszybciej uda się nam pobrać wymagane dane.

Kod wygląda następująco:

WebResponse response = null; WebRequest request = null; string cookie = string.Empty; //get Cookie from old login page request = WebRequest.Create(Const.OldLoginUrlWithTimeStamp); request.Method = "GET"; response = await request.GetResponseAsync(); cookie = response.Headers["Set-cookie"]?.Split(';')?.FirstOrDefault();

Do stworzenia requesta użyjemy metody Create z klasy abstrakcyjnej WebRequest. OldLoginUrlWithTimeStamp jest właściwością zwracająca adres https://ssl.dobreprogramy.pl/Logowanie.html ze znacznikiem czasu (aby nic przypadkiem nie wpadło nam do cache). Dodajemy dodatkowo informację, że wyślemy zapytanie GETem.

Pobieranie danych zrobione jest asynchronicznie, aby nie obawiać się o zamrażanie się UI podczas połączenia. GetResponseAsync zwróci nam odpowiedź z serwera, czyli stronę z logowaniem. Nas interesuje jednak tylko ciasteczko z nagłówka, które pobierzemy prosto w ten sposób:

cookie = response.Headers["Set-cookie"]?.Split(';')?.FirstOrDefault();

Mamy już ciasteczko. Teraz musimy zalogować się z użyciem pobranego ciasteczka.

Logowanie!

Przystępujemy zatem do naszego małego finału. Zakładamy, że login i hasło jest przekazywane nam w metodzie w postaci parametrów login i password.

Budujemy zatem zapytanie do serwera, które zaloguje nam użytkownika.

request = WebRequest.Create(Const.LoginUrl); request.Method = "POST"; request.Headers["Cookie"] = cookie; request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; byte[] form = Encoding.UTF8.GetBytes( "what=login&login=" + Uri.EscapeDataString(login) + "&password=" + Uri.EscapeDataString(password) + "&persistent=true"); using (Stream os = await request.GetRequestStreamAsync()) { os.Write(form, 0, form.Length); }

Podobnie jak wyżej, posłużymy się metodą Create z klasy WebRequest, tym razem jednam wyślemy POSTa po adres:

https://ssl.dobreprogramy.pl/Providers/LoginProvider.ashx

Mając już ciasteczko dodajemy go do nagłówka zapytania. Wymagane jest również ustawienie typu zawartości wysyłanych danych. Dzięki wcześniejszemu postowi wiemy, jakie dane są uzupełnianie w przeglądarce i dokładnie to same informacje przekazujemy do requesta w polu ContentType.

"Formularz" (form) logowania budujemy również w taki sam sposób, jak podsłuchaliśmy w przeglądarce. Oprócz informacji o typie zapytania (what=login) i autologowaniu w przyszłości (persistent=true) musimy tutaj podać hasło i login użytkownika (login i password). Tutaj musimy zadbać o poprawność danych, login i hasło muszą zostać zapisane w formie, która nie spowoduje błędów w przesyłaniu danych w ten sposób. Z pomocą przyjdzie metoda EscapeDataString z wbudowanej klasy pomocniczej Uri, która pozwoli na dodanie tzw. znaków ucieczki. Czyli np. taki tekst:

Nancy krzyknęła "Hello World!" do tłumu.

zamieni na:

Nancy krzykn\u0119\u0142a \"Hello World!\" do t\u0142umu.

Mamy zatem już przygotowany formularz, jedynie co zostało to wysłanie requestu na serwer, aby dokonać logowania. Robimy to poprzez:

response = await request.GetResponseAsync();

Jeśli wszystko się udało, ciasteczko przechowywane w zmiennej cookie wskazywać będzie na sesję zalogowanego użytkownika, którego dane pobraliśmy:

Co jeśli dane do logowanie byłyby niepoprawne? Wówczas przy wywołaniu GetResponseAsync otrzymamy wyjątek z serwera:

Dodajmy zatem jeszcze proste (na ten czas) rozwiązanie w postaci kodu try..catch, który pozwoli na detekcję błędnych danych do logowania:

try { response = await request.GetResponseAsync(); } catch (WebException e) when (((HttpWebResponse)e.Response).StatusCode == HttpStatusCode.Unauthorized) { return string.Empty; } catch (Exception) { return null; }

Kolejne kroki?

Mamy zatem już podstawowy mechanizm do logowania. Śmiało można sprawdzać różne warianty danych przy testach jednostkowych. Zanim będziemy dalej rozbudowywali program i go zabezpieczali, w kolejnym wpisie przedstawię kod do zarządzania systemem powiadomień na portalu.

Będzie można prześledzić sposób na pobieranie i oznaczanie powiadomień w C#, a także przedstawię mapowanie danych jak i typy informacji, jakie otrzymujemy z serwera.

Zapraszam do kolejnych odcinków z serii :)

Aktualne źródła można znaleźć na GitHub pod adresem:https://github.com/djfoxer/dp.notification
 

windows programowanie urządzenia mobilne

Komentarze

0 nowych
Over F.A.   5 #1 16.03.2016 20:51

Czekam z niecierpliwością na kolejne odcinki :).

codeobiect   4 #2 17.03.2016 10:38

Dobra robota!
Długo programujesz? Jesteś samoukiem? ;)

djfoxer   17 #3 17.03.2016 17:57

@Over F.A.: Już niedługo ;)

@codeobiect: Pracuję jako programista już kilka ładnych lat :)

kwpolska   5 #4 17.03.2016 19:50

> OldLoginUrlWithTimeStamp jest właściwością zwracająca adres […] ze znacznikiem czasu (aby nic przypadkiem nie wpadło nam do cache).

A nie lepiej wyłączyć cache’owania na poziomie zapytania HTTP?

ajp.c0.pl   5 #5 17.03.2016 22:18

*.*

djfoxer   17 #6 18.03.2016 00:34

@kwpolska: Można, ale wówczas zdajesz się na to, że obie strony będą bezproblemowo akceptowały żądanie w zapytaniu :) Szczególnie, że dostęp mam tylko do strony klienckiej i to jeszcze z poziomu nie przeglądarki, a kodu (w apce uniwersalnej). W sieci jest wiele wątków o problemach z cachowaniem w requestach wysyłanych z aplikacji .net. Nie chciałem bawić się w rozstrzyganie czy w nowych uniwersalnych apkach czegoś nie "udoskonalili" :P , więc odruchowo dodałem timestampa, jako standardowy sposób na tego typu problemy (który też nie jest idealnym rozwiązaniem).

Over F.A.   5 #7 19.03.2016 09:51

Sam myślę o rozpoczęciu swojej przygody z programowaniem. Wiem że nie jest to łatwy kawałek chleba. Liczę że wraz z twoimi wpisami uda mi się zdobyć cenną wiedzę. Najważniejsza jest motywacja która niestety u mnie z czas przygasa :(.

Jim1961   7 #8 20.03.2016 08:59

Dlaczego właściwości w djfoxer.dp.notification.Core.Const nie są faktycznie stałymi, tylko zmiennymi statycznymi?

Jim1961   7 #9 20.03.2016 09:02

"application/x-www-form-urlencoded; charset=UTF-8" - to mogło by być stałą. Już w "dpLogic" użyte dwukrotnie.

djfoxer   17 #10 20.03.2016 11:44

@Over F.A.: Musisz się wkręcić, potem samo już idzie :) Jak chcesz zacząć naukę, to warto w trakcie zdobywania wiedzy wyznaczyć sobie jakiś cel, np. zrobienie jakieś prostej apki, która będzie robiła coś co sobie założysz. Dzięki temu bardziej zaangażujesz się zarówno a naukę jak i chęć zrobienia czegoś samemu.

djfoxer   17 #11 20.03.2016 11:51

@Jim1961: A to zaszłość, którą niedługo pewnie zrefaktoryzuję. Wcześniej planowałem zrobić, aby string z timestampem był generowany tylko raz, przy uruchomieniu apki (jeszcze bez prop), czyli np.

static readonly string url = "url" + DateTime.Now.Millisecond;

Oczywiście wiemy, że taka zabawa z const nie jest możliwa (tworzenie przy kompilacji). Potem jednak od tego odszedłem, ale zostawiłem tego statica, więc w najbliższym czasie, jak już będzie więcej czasu na "zabawę" refactoring pójdzie w ruch :) A ContentType faktycznie też może być stałą :)

Over F.A.   5 #12 23.03.2016 20:23

@djfoxer: Dziękuje za mobilizację :).