lis
23
2017

Integracja .NET z SalesForce (SOAP)

Hej, dzisiaj poruszę temat kwestii integracji aplikacji .NET-owej z SalesForce. W naszym projekcie postanowiłem użyć API SOAP-owego, chociaż dostępne jest również API REST-owe. Dlaczego tak ? Ano, użycie API REST-owego wymaga całej złożonej autoryzacji z wykorzystaniem OAuth, co przekłada się na sporo więcej pracy do wykonania. A OAuth nie jest w naszym systemie potrzebne bo logujemy się zawsze na koncie użytkownika systemowego, nie potrzebujemy logować innych użytkowników z zewnętrznym logowaniem. Wracając do SOAP, niestety widać wyraźnie że to API nie jest zbytnio rozwijane co widać zarówno w niekiedy archaicznej i nieaktualizowanej dokumentacji jak i różnych czasem dziwnych błędach. Wracając do meritum, przedstawiam w etapach kroki które należy zrobić aby integracja zadziałała: Logujemy się do SF (najlepiej najpierw działać na sandboxie), wchodzimy do Setup->Quick Find->Custom Code->API Klikamy w Generate Enterprise WSDL, otrzymany XML zapisujemy sobie gdzieś np. w projekcie Z otrzymanego WSDL-a tworzymy Proxy, w Visual Studio na projekcie dajemy Add Service Reference, w polu URL dajemy ścieżkę do naszego pliku WSDL np: "C:\files\wsdl.xml" i dajemy Discover, dodajemy referencję I teraz ciekawa rzecz, wygenerowane Proxy jest błędne tj. kod się będzię kompilował ale podczas łączenia się z SF będą lecieć dziwne błędy. Trzeba zrobić pewien myk: mianowicie otwieramy utworzony plik Reference.cs (uwaga! nie będzie widoczny w VS, trzeba go odszukać ręcznie) a następnie zamieniamy wszystkie wystepienia ciągu znaków "[][]" na "[]" tj. zmieniamy definicję tablic dwuwymiarowych na jednowymiarowe. Nie pytajcie dlaczego, SF twierdzi ze WSDL jest dobry, VS generuje ewidentnie zły Proxy... Teraz już jestesmy gotowi aby zalogować się do SF, poza nazwą użytkownika i hasłem potrzebujemy jeszcze URL usługi oraz SecurityTokenu. URL usługi jest w WSDL, możemy sobie go podejrzeć w web.config-u jeśli dodaliśmy Proxy przez VS. Natomiast aby otrzymać Security Token musimy wejść znowu do SF, kliknąć na ikonkę użytkownika -> Settings -> My Personal Information -> Reset Security Token. Token otrzymamy wkrótce na maila. Proces odpytywania API przebiega w sesjach, najpierw należy się zalogować, otrzymujemy sessionId, oraz url na jaki będziemy wysyłać nasze następne requesty. Requesty te wymagają każdorazowo podania sessionId. Na końcu wylogowujemy się, również podając id sesji.  Przykład: var client = new SoapClient(); var loginRes = await client.loginAsync(new LoginScopeHeader(), _configuration.userName, _configuration.Password + _configuration.securityToken); if (loginRes.result.passwordExpired) { throw new ApplicationException("Password expired"); } var sessionHeader = new SessionHeader { sessionId = loginRes.sessionId }; var apiClient = new SoapClient(new BasicHttpsBinding(), new EndpointAddress(loginRes.result.serverUrl); var query = "SELECT Email from Contact"; var someResults = apiClient.queryAsync(sessionHeader, null, null, null, query); // inne wywołania apiClient.logout(sessionHeader);   Należy zwrócić tu uwagę na dwie sprawy, po pierwsze przy logowaniu podajemy hasło złączone z Security Tokenem. Po drugie tworzymy dwa SoapClient-y z różnymi url-ami. Ten pierwszy służy tylko po to żeby sie zalogować oraz uzyskać Url właściwy do który będziemy wykorzystywać przy właściwych wywołaniach API.
paź
29
2014

Impersonacja w kodzie w .NET

Ostatnio dość długo szukałem sposobu aby zaimplementować model uprawnień dla aplikacji .NET umożliwiający jej dostęp do wielu lokalizacji sieciowych które mogą potrzebować różnych poświadczeń. Tradycyjne podejście (impersonacja) działa, ale można podać tylko jeden konkretny profil pod jakim aplikacja będzie działać. Ja potrzebowałem mieć możliwość dynamicznego przełączania użytkowników w zależności do tego czego aplikacja w danym momencie potrzebowała.  W końcu odnalazłem kod który spełaniał moje wymagania: http://support.microsoft.com/kb/306158 Niestety, nie działał z takim poświadczaniem w którym autentykacja jest nie domenowa tylko na konkrentego użytkownika komputera. Okazało się, że trzeba było dokonać w nim kilku poprawek i teraz działa również w takim przypadku. Przedstawiony kod opakowałem w wygodną klasę statyczną która jest poniżej: public static class Impersonation { public const int LOGON32_LOGON_INTERACTIVE = 2; public const int LOGON32_PROVIDER_DEFAULT = 0; public const int LOGON32_LOGON_NEW_CREDENTIALS = 9; [DllImport( "advapi32.dll" )] public static extern int LogonUserA( String lpszUserName, String lpszDomain, String lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken ); [DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )] public static extern int DuplicateToken( IntPtr hToken, int impersonationLevel, ref IntPtr hNewToken ); [DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )] public static extern bool RevertToSelf(); [DllImport( "kernel32.dll", CharSet = CharSet.Auto )] public static extern bool CloseHandle( IntPtr handle ); public static WindowsImpersonationContext Impersonate( string username, string domain, string password, bool isDomainAccount ) { WindowsIdentity tempWindowsIdentity; IntPtr token = IntPtr.Zero; IntPtr tokenDuplicate = IntPtr.Zero; if( RevertToSelf() ) { if( LogonUserA( username, domain, password, isDomainAccount ? LOGON32_LOGON_INTERACTIVE : LOGON32_LOGON_NEW_CREDENTIALS, LOGON32_PROVIDER_DEFAULT, ref token ) != 0 ) { if( DuplicateToken( token, 2, ref tokenDuplicate ) != 0 ) { tempWindowsIdentity = new WindowsIdentity( tokenDuplicate ); var impersonationContext = tempWindowsIdentity.Impersonate(); if( impersonationContext != null ) { CloseHandle( token ); CloseHandle( tokenDuplicate ); return impersonationContext; } } } } if( token != IntPtr.Zero ) CloseHandle( token ); if( tokenDuplicate != IntPtr.Zero ) CloseHandle( tokenDuplicate ); throw new SecurityException( "Could not impersonate user: " + username ); } } Użycie klasy jest banalnie proste, wywołujemy metodę Impersonate (przekazując nazwę użytkonika, domenę / nazwę komputera i hasło) otrzymując w zamian objekt kontekstu. Jeśli impersonacja się nie powiedzie (np. brak uprawnień) kontekstu nie dostaniemy, zostanie rzucony wyjątek SecurityException. Po zakończeniu kodu który miał być impersonowany kończymy ową impersonację wywołując na kontekście Undo(). Przykład użycia WindowsImpersonationContext impersonationContext = null; try { impersonationContext = Impersonation.Impersonate( entry.UserName, entry.Domain, entry.Password ); // nasz kod do wykonania } finally { impersonationContext.Undo(); }
wrz
9
2014

AngularJS - generyczna obsługa błędów

W aplikacjach webowych nieodzownym stałym elementem jest obsługa błędów. Dla "normalnej" aplikacji (np. MVC) sprawa jest w miarę prosta, możemy wyjątki przechwycić po stronie serwera, umieścić w modelu (jako model errors) i później na widoku wrzućić w jakieś ValidationSummay. W przypadku aplikacji typu SPA (Single Page Application) sprawa robi się trochę bardziej skomplikowana ponieważ dysponujemy jedną stroną html której zawartość "podmieniamy". Oczywiście możemy przy każdym requeście do serwera sprawdzać kod odpowiedzi i wyświetlać jakiś stały element DIV z odpowiednim komunikatem ale jest to wielce niewygodne, a poza tym kopiowanie tej samej logiki obsługi błędu (nawet wydzielonej do osobnego modułu) nie jest dobrym rozwiązaniem architektonicznym. Jeżeli używamy angular-a jako framework javascriptowy możemy skorzystać z jednego z jego dobrodziejstw i przechwycić response jaki dostajemy ze standardowego modułu angular-resource. Angular-resource jest to moduł ułatwiający komunikację REST-ową z serwerem, możemy tam wysyłać zapytanie typu query (dla listy elementów), get (dla konkretnego elementu), update i delete. Przykład wykorzystania tego modułu: [Więcej]
wrz
4
2014

EF - wywoływanie swojej logiki przy konkretnych getterach/setterach na entity

Dzisiaj natrafiłem na następujący problem: potrzebuję wyświetlać na ekranie daty dla użytkowników przekonwertowane na ich strefy czasowe. Każdy użytkownik ma zapisaną w bazie strefę czasową i ustawiam ją przy logowaniu (dla wygody trzymam w User.Identity). Daty w bazie danych są trzymane w formacie UTC. Teraz, chcę aby dla każdego użytkownika daty wyświetlały mu się w jego strefie czasowej. Nie chcę tego robić na poziomie kontrolerów (aplikacja jest w MVC) ponieważ logika tam jest już zakodowana i zmiana jej wymagałaby dużo czasu. Chcę, aby konwersję przeprowadziło za mnie automatycznie EF. Z tym że nie wszystkie property DateTime na encjach powinny być w ten sposób konwertowane - jedynie te które chcę. Po przeanalizowaniu wyników google znalazłem pewien pattern którym chcę się podzielić. W skrócie polega on na utworzeniu customowego atrybutu którym dekorujemy interesujące nas property na encji. Następnie, overridujemy zdarzenie OnMaterializing wywoływane przy utworzeniu obiektu encji i tam przez reflekcję odnajdujemy interesujące nas property i wykonujemy akcję. [Więcej]
wrz
14
2012

Komunikacja pomiędzy procesami za pomocą MemoryMappedFile

Czasami zdarza się, że potrzebujemy wymienić jakieś proste informacje pomiędzy procesami a nie chcemy budować całej infrastruktury komunikacyjnej w stylu WCF, socketów itp. Na taką prostą sytuację wymagającą komunikacji interprocesowej ostatnio natrafiłem.Otóż mam sobie aplikację WPF-ową która działa w Tray-u. Okno główne odpala tylko po kliknięciu na Tray-a. Oczywiście chcemy, żeby w danej chwili uruchomiona była tylko jedna instancja takiej aplikacji, więc zrobiłem zabezpieczenie na Mutexie (działa to w ten sposób, że aplikacja próbuje utworzyć nowy obiekt typu Mutex, a w przypadku kiedy już taki istnieje to znaczy że mamy odpaloną inna instancję aplikacji i tę należy zamknąć). I działa to OK, tylko problem w tym że jeśli aplikacja wisi tylko w Tray-u i ktoś klika na ikonkę aplikacji to nic się nie dzieje (nie otwiera nowego okna ani nie pokazuje starego). A chcemy, żeby ten "drugi" proces nim zostanie zamknięty wymusił w jakiś sposób otwarcie głównego okienka należącego do "pierwszego" procesu. Prostym i niezwykle wydajnym rozwiązaniem jest użycie w tym przypadku MemoryMappedFile. Należy on do przestrzeni System.IO i pełni dwie funkcje: Zapewnia "obróbkę" w pamięci bardzo dużych plików Umożliwia dostęp do danego obszaru pamięci wielu procesom Nam chodzi oczywiście o to drugie zastosowanie. W moim przypadku rozwiązanie problemu polega na implementacji dwóch kroków: Ustawienie przez proces "zamykany" wartości w pamięci informującej o konieczności uruchomienia okna aplikacji Sprawdzanie przez główny proces wartości tej pamięci i w przypadku zmiany odpalenia okna [Więcej]
lip
18
2012

Komunikacja dwustronna (duplex) pomiędzy procesami za pomocą WCF

Aby omówić przedstawioną w temacie kwestię opiszę krótko projekt który obecnie realizuję. Otóż jest to prosty system do automatycznej aktualizacji aplikacji. Przez weba można załadować paczkę instalacyjną, natomiast po stronie klienta działa usługa Windows Service mająca za zadanie ściągać tę paczkę i ją zainstalować. Stworzyłem również proste GUI w postaci aplikacji WPF-owej dla klienta, które komunikuje się z usługą po WCFie za pomocą named-pipes. Początkowo zamierzałem wszystkie wywołania zrobić synchronicznie i tak też zacząłem implementację: ściąganie listy aktualizacji i zarządzanie konfiguracją jest zrealizowane przez synchroniczne wywołania realizowane przez specjalnie tworzony do tego BackgroundWorker (żeby nie blokować głównego wątku GUI). Niestety, takie podejście nie sprawdza się w następnym zadaniu które chciałem wykonać, a mianowicie chciałem żeby usługa na bieżąco informowała GUI o postępie wykonywanych zadań. Czyli, żeby na GUI ładnie wyświetlała się lista aktualizacji a przy nich stan: "oczekująca", "w trakcie instalacji" itp. W takim podejściu model synchroniczny "wysiada", bo żeby komunikacja była płynna należałoby co kilkaset milisekund odpytywać usługę o to co się dzieje (tworząc za każdym razem oczywiście osobny wątek do obsługi!) co oczywiście wydajnościowo "nie wydoliłoby". Jednym sensownym rozwiązaniem jest asynchroniczna komunikacja dwustronna - klient zgłasza się, że chce otrzymywać informację od usługi a usługa bezpośrednio komunikuje się z nim tylko w momencie kiedy ma coś ciekawego do powiedzenia. Taka komunikacja jest możliwa w WCF-ie przy zastosowaniu trybu "duplex" i dodatkowo musi być odpowiedni binding (named pipes albo net-tcp). Aby cosik takiego zrealizować należy uskutecznić kilka kroków [Więcej]
cze
18
2012

Problemy z WCF i diagnozowanie za pomocą MS Service Trace Viewer

Usługi WCF są generalnie fajne w użyciu ale mają tę wadę, że jak coś nie działa to często nie wiadomo o co chodzi. Niekiedy komunikaty o błędach potrafią być mało tego że niezbyt pomocne to jeszcze wprowadzać w błąd. Na tego typu komunikat natrafiłem dzisiaj, podczas próby wywołania mojej usługi. Podczas próby wywołanie tejże poleciał taki oto błąd: An error occurred while receiving the HTTP response to http://xx.xx.x.xx:8200/Services/WCFClient.svc. This could be due to the service endpoint binding not using the HTTP protocol. This could also be due to an HTTP request context being aborted by the server (possibly due to the service shutting down). See server logs for more details.] [Więcej]
cze
11
2012
.NET // WatiN

WatiN i okna dialogowe

Kontyunuując wątek testów automatycznych (rozpoczęty 2 posty temu) muszę stwierdzić, że największym problemem jaki napotkałem podczas implementacji testów była obsługa wyskakujących okienek dialogowych. Oczywiście wzorzec polegający na wrzucaniu na strony mnóstwa Pop-Upów jest już delikatnie mówiąc przestarzały ale wciąż może się zdarzyć. Przykładem takiego zdarzenia jest nasz system, którego logika oparta jest w uproszczeniu na stronie na której znajduję się gromada kontrolek (jedna pod drugą). Obsługa takiej kontrolki polega na kliknięciu przycisku, który wyświetla okienko dialogowe na którym osadzona jest kolejna strona z formularzem. Po jego wypełnieniu okienko jest zamykane a uzupełnione parametry są przekazywane do strony wywołującej. Wymagane jest więc jak widać odpowiednie obsłużenie wyskakującej strony poprzez "dobranie się" do iframe na którym jest osadzona. Poza tym innym często spotykanym rodzajem okienek są Confirm. Są to okienka potwierdzające wykonanie danej czynności ("Czy na pewno chcesz usunąć ten plik?") z możliwymi akcjami "tak/nie". Jest to trochę inny obiekt Javascriptowy niż "normalne" okno i wymaga całkiem innej obsługi. Z innych rodzajów okien można jeszcze wymienić np. Alert ale powiedzmy że jego użycie jest na tyle rzadkie że nie będziemy się nim zajmować. Podsumowując, mamy problem z 2 typami okien: zwykłe okienka z osadzoną treścią oraz okienka typu Confirm. WatiN radzi sobie zarówno z jednymi jak i drugimi. [Więcej]
cze
6
2012
.NET // WatiN

WatiN - nie zamykanie przeglądarki po każdym teście

W poprzednim poście napisałem, że ostatecznie jako rozwiązanie do testów automatycznych Web w naszym systemie wybrałem WaiN. Zacząłem więc pisać testy i od razu uwidocznił się pierwszy problem. Otóż standardowy wzorzec zakłada odpalenie przeglądarki, zrobienie testu i jej zamknięcie czyli: using (var browser = new IE("http://perszewski.pl")) { // Rób test } W przypadku większej ilości testów każdorazowe odpalanie IE i jego zamykanie zaczyna znacząco wydłużać czas testów. Pasowało więc wymyśleć sposób na to, żeby przeglądarkę odpalić przy pierwszym teście a zamknąć po ostatnim. Rozwiązanie udało się opracować a zacząłem od stworzenia klasy statycznej, która będzie trzymała referencję do mojej przeglądarki: [Więcej]
maj
30
2012

Testy automatyczne GUI w aplikacjach webowych

Niedawno w ramach poprawy jakości naszego systemu dostałem zadanie rozpoznania i wdrożenia jakiegoś sposobu na automatyzację testów wykonywanych dotychczas manualnie. Do tej pory przed każdą większą podmianę produkcyjną był wykonywany przez testera zestaw testów regresyjnych. Miało to liczne wady, przede wszystkim: Baardzo długi czas wykonywania (przejście wszystkich ścieżek w systemie mogło trwać nawet tydzień) Monotonność i powtarzalność (to mnie akurat nie bolało, ale w sumie po co tracić czas na ponowne wykonywanie tych samych czynności przy każdej podmianie ?) Średnia skuteczność wykrywania nieprawidłowości - wynika to z faktu, że tester nie do końca dobrze zna biznes systemu, a samo przejście ścieżki nie zawsze oznacza że test się powiódł Szukałem rozwiązania które: Pozwoli na zdefiniowanie testów (sekwencji czynności do wykonania na stronie) i ich uruchomienie w dowolnej chwili Umożliwi sprawdzenie, czy generowane strony odpowiadają scenariuszowi testowemu Będzie łatwe w obsłudze (żeby czas poświęcony na stworzenie testu nie był dłuższy niż czas implementacji danej funkcjonalności :)) Będzie możliwe w obsłudze przez testera, który nie posiada żadnych umiejętności programistycznych [Więcej]