niedziela, 1 grudnia 2013

Diffuse - Tool for easy find differences between text files

"Diffuse to narzędzie do porównywania i łączenia tekstów. Dzięki widokowi w kilku panelach i kolorowym zaznaczeniom możesz dojrzeć różnice między tekstami w mgnieniu oka. "*

Zdarza nam się porównywać różnice, pomiędzy różnymi plikami tekstowymi. Najczęściej służy nam do tego, nasze narzędzie kontroli wersji (takie, jak np. Git, SVN lub TFS), jednak czasami, zachodzi potrzeba porównania ze sobą 2 tych samych plików, które nie są umieszczone w repozytorium (np. nowa wersja specyfikacji wymagań dostarczona przez analityka biznesowego). Dobrze, jest mieć wtedy pod ręką, jakąś darmową i wygodną "porównywarkę" plików tekstowych. Takie wymagania bardzo dobrze spełnia Diffuse, dostępny pod licencją GNU 2.0.

Link nr.1 - Opis programu (PL) z możliwością ściągnięcia z softonic.pl/ 
Link nr.2 - Strona projektu, na sourceforge.net

* - opis pochodzi z link nr.1

sobota, 16 listopada 2013

Javascript for intermediate with D.Crockford

Dobrze jest znać język, w którym się programuje. Jego założenia, filozofię, składnię, mocne i słabe strony. Jest to szczególnie ważne, gdy specyfika języka jest inna niż specyfika języka, do którego jesteśmy przyzwyczajeni. Takim osobnym, często niezrozumiałym językiem, jest Javascript, który mimo iż posiada podobną składnię do takich języków jak Java czy C# jednak znacznie się od nich różni. Warto więc poświęcić nieco czasu i trochę się nieco o tym języku dowiedzieć (czy to z książek, czy też filmów). Jednym ze źródeł wiedzy o javascript są wykłady Douglasa Crockforda (swoją drogą bardzo znana postać w środowisku Javascropt).

wtorek, 5 listopada 2013

Send SMS message by Mobitex API (HttpWebRequest)

Jedną z podstawowych funkcjonalności niemal każdej aplikacji jest możliwość wysyłania powiadomień mailowych. Coraz częściej zdarza się jednak, że klient chce mieć również możliwość wysyłania krótkich powiadomień tekstowych (tzw. SMS) na telefon komórkowy. Usługę taką w praktyce realizuje się poprzez zewnętrznych partnerów, którzy, w zamian za stosowną opłatę wystawiają nam API, z którym możemy się połączyć. W przeszłości widziałem rozwiązania kilku producentów i... każdy miał na to "swój" sposób (od wystawienia web service, poprzez web request z parametrami wiadomości przesłanymi poprzez URL, na wysyłaniu specjalnie skonstruowanego maila na podaną skrzynkę email ;)).
W takich chwilach jak ta, gdy o tym wspominam zaczynam żałować, że tak późno zacząłem prowadzić mojego bloga ;)

Ale do rzeczy. W tym konkretnym poście opiszę sposób integrowania się z API dostarczonym przez firmę Mobitex. Firma mobitex, wystawia publicznie dostępną specyfikację swojego API (specyfikacja_mt.pdf). Specyfikacja jest krótka, zwięzła i na temat.

W tym konkretnym przypadku, wysyłanie SMS odbywa się poprzez zwykły WebRequest z parametrami przekazanymi w querystringu, a w zamian dostajemy odpowiedź w strumieniu (Stream) za pomocą standardowego WebResponse.

Całość jest całkiem prosta i może być zrealizowana, za pomocą klasy z jedną metodą (co najwyżej należy skonfigurować parametry wedle naszych potrzeb):

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Text;
using System.Web;

namespace sms
{
    public class MobitexApi
    {
        public static List<KeyValuePair<string, string>> SendSms(string number, string text)
        {
            List<KeyValuePair<string, string>> keyValueResponse = new List<KeyValuePair<string, string>>();
            try
            {
                string user = "userName";
                string pass = "passInMD51a2bc4f3f52dac6d872ae12";
                string type = "sms";
                string from = "fromUserName";
                string ext_id = "1234";
                string encodedText = HttpUtility.HtmlEncode(text);

                string requestStringUrl = string.Format("https://api.mobitex.pl/sms.php?user={0}&pass={1}&type={2}&number={3}&text={4}&from={5}&ext_id={6}",
                    user, pass, type, number, encodedText, from, ext_id);

                HttpWebRequest HttpWReq = (HttpWebRequest)WebRequest.Create(requestStringUrl);
                //handle error code: ssl_error_bad_cert_domain
                HttpWReq.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(
                delegate
                {
                    return true;
                });
                HttpWebResponse HttpWResp = (HttpWebResponse)HttpWReq.GetResponse();

                Stream response = HttpWResp.GetResponseStream();
                StreamReader reader = new StreamReader(response);
                string responseString = reader.ReadToEnd();
                HttpWResp.Close();

                var respStringTable = responseString.Split(',');

                for (int i = 0; i < respStringTable.Length; i++)
                {
                    string keyValue = respStringTable[i];
                    if (!string.IsNullOrEmpty(keyValue))
                    {
                        string key = keyValue.Substring(0, keyValue.IndexOf(':'));
                        string value = keyValue.Substring(keyValue.IndexOf(": ") + 1);

                        keyValueResponse.Add(new KeyValuePair<string, string>(key, value));
                    }
                }
            }
            catch (Exception ex)
            {
                //TODO:
                //Error handling in here (for example NLog)
            }
            return keyValueResponse;
        }
    }
}
Całość rozwiązania sprowadza się, do utworzenia odpowiedniego querystringa, w którym przekazujemy wszystkie interesujące nas parametry (patrz dokumentacja), a nast. tworzymy web request. Przed wykonaniem dodajemy obsługę eventu "ServerCertificateValidationCallback". Jest to spowodowane tym, że mobitex posiada nieprawidłowy certyfikat bezpieczeństwa i w trakcie wykonywania httpWebRequest dostajemy pytanie czy jesteśmy tego świadomi i mimo to chcemy kontynuować naszą akcję. Po wszystkim, dostajemy odpowiedź z mobitexu, która wyglada mniej więcej tak:
Status: 002, Id: 03a72a49fb9595f3737bc4a2519ff283, Number: 4860X123456
 więc parsujemy ją do nieco bardziej przyjaznego w obsłudze typu danych, czyli kolekcji obiektów typu "KeyValuePair".

Mobitex posiada też możliwość sprawdzenia stanu konta. Czytając dokumentację techniczną brakuje mi natomiast jakiegoś raportu podsumowywującego wszystkie sms wysłane w danych (sparametryzowanym) okresie czasu z danego konta (gdybym chciał zobaczyć datę, status oraz num. tel na jakie wysłałem wszystkie sms powiedzmy w poprzednim mieś.) i myślę, że w najbliższym czasie im takie pytanie zadam.


p.s. W przypadku korzystania z sieci korporacyjnych, należy się upewnić, aby mieć odblokowany port 443 dla serwera 213.5.10.22.
p.s.2 Uwaga. Nr. tel. w parametrze powinien być w formacie 4860X123456
p.s.3  ServerCertificateValidationCallback dostępny jest dopiero w .NET 4.5

sobota, 2 listopada 2013

Chrome DevTools

Jak powszechnie wiadomo, każda praca wymaga specyficznych dla tej pracy narzędzi. W przypadku hydraulika jest to klucz francuski, w przypadku nauczyciela jest to tablica, a przypadku web developera są to tzn. 'DevTools'. Osobiście, do tej pory, jako 'DevTools' używałem narzędzi dostępnych w Internet Explorer (od wersji 8.0). Owszem, byłem świadomy "firebuga" występującego w 'mozilla firefox', jednak siła przyzwyczajenia była zbyt silna, aby się przemóc.'Narzędzia developerskie IE' pokazał jeden z kolegów z pierwszej pracy i... tak jakoś zostało. Do dzisiaj.

Pod wpływem kilku ost. wykładów Gutka (więcej info w moim poprzednim wpisie), postanowiłem poszerzyć moją wiedzę o to, co zaserwował nam Gutek podczas swoich wykładów.

Jednym z miłych zaskoczeń są narzędzia developerskie dostarczone nam przez firmę Google dla ich przeglądarki Chrome o nazwie "Chrome DevTools".

To co jest naprawdę fajne, to fakt, iż google, oprócz dostarczenia nam porządnych narzędzi udostępnia również mini-tutorial, w którym oprócz "wykładów" (filmiki) mamy też do wykonania część praktyczną (ponieważ jak to mówi miły pan na filmiku "nic nie zastąpi praktyki" ;)).

To co mnie najbardziej urzekło w "Chrome DevTools", to możliwość sprawdzania wydajności stron i sposób konkretnego wyszukiwania "wąskiego gardła" (tzw. "bootle neck"), z dokładnością do linii kodu, zarówno przy ładowaniu strony do przeglądarki, jak i późniejsza obsługa tego kodu w przeglądarce.

Link do samouczka z Chrome DevTools


p.s. W kursie jest również odnośnik do innej strony hostowanej przez google (closure-compiler), służącej do minimalizowania objętości plików js (w celu zwiększenia wydajności stron, które tworzymy).

niedziela, 27 października 2013

JavaScript Intellisense w VS 2012

Ostatnimi czasy, miałem okazję uczestniczyć (jako widz), w dwóch wykładach prowadzonych przez Jakuba Gutowskiego o wdzięcznym tytule "Javascript dla c# developera". Pierwsza prezentacja odbyła się 12 października podczas konferencji "on-line" dotnetconf.pl (można ją obejrzeć na youtube.com). Druga odbyła się 17 października podczas 69 spotkania wg.net (Warszawska Grupa .NET). (materialy ze spotkania)
Generalnie Gutek pokazał kilka ciekawostek związanych z językiem javascript, oraz zachęcał do zapoznania się i stosowania kilku narzędzi. Zanim jednak przejdziemy do esencji tego, o czym mówił i co chciał nas nauczyć, najpierw mała przekąska, tzw. "snack", czyli repost z bloga Gutka, nt. włączenia intelisense dla języka javascript w Visual Studio 2012 link do blogu Gutka jak włączyć intelisense w VS 2012.


p.s. Gdyby kiedyś coś się stało z blogiem Gutka, to należy utworzyć w solucji katalog przeznaczony dla plików javascript i dodać tam plik plik.js, a nast. wejść do Narzędzia->Opcje->Edytor Tekstu->Javascript->IntelliSense->Odwołania-> ustawić grupę odwołań (np. Implict (Web)), a nast. wpisać ścieżkę względną do naszego pliku i go 'Dodać'.  Od tego momentu, mamy działające intellisense dla naszych plików js :)

NLog, czyli logowanie błędów w .NET

Jakiś czas temu, opisywałem na moim blogu bardzo popularny logger dla .NET, o nazwie log4net. Sam log4net jest bardzo ok, i w zasadzie całkiem nieźle się sprawdza w swojej roli, jednak jak to mówią "lepsze jest wrogiem dobrego", a tym lepszym, a przynajmniej wygodniejszym narzędziem jest nlog.

Główne różnice, wady/zalety?
- NLog jest wygodniejszy. Co musimy zrobić? Ddokładamy bibliotekę do projektu,  za pomocą Packer Menager Console pobieramy plik konfiguracyjny, odkomentowywujemy ustawienia i... działa. W przypadku log4net trzeba pamiętać o sekcji inicjalizacji w 'global.asax' oraz odpowiednim ustawianiu pliku konfiguracyjnego
- zaawansowana konfiguracja -> tutaj akurat nie miałem okazji ostro sprawdzać obu narzędzi, jednak czytając różne blogi, zazwyczaj ludzie chwalą sobie intuicyjność oraz bardziej elastyczną konfigurację NLoga
- aktualizacje -> ost. aktualizacja nlog wyszła 09.10.2013, i jest w miarę często aktualizowany (projekt żyje), natomiast odnośnie log4net spotkałem się z opiniami, że ost. aktualizacja była ponad 5 lat temu
- autor nLog'a jkowalski.com na jednym ze spotkań Warszawskiej grupy .NET wykazywał niewielkią, ale jednak wyższą wydajność swojego rozwiązania nad rozwiązaniem log4net (i zdecydowanie wyższą nad standardowym System.Diagnostics).

Osobiście uważam, że nlog jest zwyczajnie wygodniejszy, oraz "nowszy", przez co niewiele, ale jednak lepszy niż log4net.

Całkiem fajnie opisał ten temat Maciej 'procent' Aniserowicz na swoim blogu.
Dobry post z porównaniem jest też na stackoverflow.com - log4net vs nlog
Opis NLog na codeproject.com


p.s. przykładowa konfiguracja, którą stoduje w klasie Bootstrapper projektu Nancy:
        protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
        {
            base.ApplicationStartup(container, pipelines);
            #region event logger (nLog)
            FileTarget fileTarget = CreateNlogFileConfiguration();
            SimpleConfigurator.ConfigureForTargetLogging(fileTarget, LogLevel.Info);
            LogAllRequests(pipelines);
            LogAllResponseCodes(pipelines);
            LogUnhandledExceptions(pipelines);
            #endregion
        }
        private static FileTarget CreateNlogFileConfiguration()
        {
            //SimpleConfigurator.ConfigureForTargetLogging(new AsyncTargetWrapper(new EventLogTarget()));
            FileTarget fileTarget = new FileTarget();
            fileTarget.Name = "CeidgCheckLogger";
            fileTarget.CreateDirs = true;
            fileTarget.Layout = "${date} ${level} ${callsite:className=true:includeSourcePath=false:methodName=true} ${message}";
            //fileTarget.FileName = @"${basedir}/logs/CeidgCheckNancy.${date:format=yyyy.MM.dd}.log"; //for catalog in solition
            fileTarget.FileName = @"D:/logs/CeidgCheckNancy/CeidgCheckNancy.${date:format=yyyy.MM.dd}.log";
            fileTarget.KeepFileOpen = false;
            fileTarget.Encoding = Encoding.UTF8;
            return fileTarget;
        }
        private void LogAllRequests(IPipelines pipelines)
        {
            pipelines.BeforeRequest += ctx =>
            {
                log.Info("Handling request {0} \"{1}\"",
                ctx.Request.Method, ctx.Request.Path);
                return null;
            };
        }
        private void LogAllResponseCodes(IPipelines pipelines)
        {
            pipelines.AfterRequest += ctx =>
            log.Info("Responding {0} to {1} \"{2}\"",
            ctx.Response.StatusCode, ctx.Request.Method,
            ctx.Request.Path);
        }
        private void LogUnhandledExceptions(IPipelines pipelines)
        {
            pipelines.OnError.AddItemToStartOfPipeline
            ((ctx, err) =>
            {
            log.ErrorException(string.Format("Request {0}\"{1}\" failed", ctx.Request.Method, ctx.Request.Path), err);
            return null;
            });
        }

czwartek, 24 października 2013

.NET Client consuming Java IBM WebSphere Web Service

W dwóch moich poprzednich postach opisywałem sposób podłączenia się aplikacji .NET do web serwisów napisanych w PHP z pomocą Zend Framework (link1 link 2 ) i związanymi z tym problemami. W tym wpisie skupie się na podłączeniu się do web service utworzonego za pomocą IBM WebSphere wykorzystującym standardy technologii web services napisanych dla środowiska J2EE (tzw. "Java korporacyjna").

Do poprawnego 'skonsumowania' serwisu używamy standardowego mechanizmu WCF poprzez "Add Service Reference". Po wpisaniu adresu serwisu (z końcówką ?wsdl) powinniśmy wykryć serwis, a nast. utworzyć na jego podstawie klasę proxy. Gdyby automat nie wykrył usługi, to należy wpisać adres usługi do przeglądarki, a nast. zapisać plik na dysk z rozszerzeniem .xml (i w Add Service Reference podać ścieżkę na dysku gdzie zapisaliśmy ten plik). W przypadku mojej usługi, konieczne było jeszcze zgranie pliku .xsd (na szczęście znajdował się pod tym samym adresem co serwis, tylko z innym rozszerzeniem).

W momencie, gdy mamy utworzoną klasę proxy, możemy spróbować ją wywołać. Niestety w moim przypadku, standardowe wywołanie klasy proxy oznaczało błąd:
„Atrybut System.Xml.Serialization.XmlAttributeAttribute obiektu XmlSerializer nie jest prawidłowy w parametrXYZ. Kiedy atrybut IsWrapped ma wartość true, obsługiwane są tylko atrybuty XmlElement, XmlArray, XmlArrayItem i XmlAnyElement.”
Poprosiłem więc o pomoc bardziej doświadczone osoby i one zasugerowały mi aby,  w miejscu, w którym chcemy wywołać usługę dodać przestrzeń nazw:
using System.ServiceModel
 a w kodzie aplikacji EndPoint:
EndpointAddress ea = new EndpointAddress("https://abcd/hash/ws/xyz");
WS ws = new WSClient(new BasicHttpBinding(BasicHttpSecurityMode.Transport), ea);
Posiadając tak wygenerowaną klasę proxy, zasugerowano mi wejść w obiekt "WS" ("go to definition") i tam sprawdzić klasy, które wykorzystują 'parametrXYZ'.
Okazało się, że faktycznie, były tam dwie klasy, które wykorzystywały taki parametr. Rozwiązaniem tego problemu okazało się przemodelowanie tych klas, poprzez implementację System.ComponentModel.INotifyPropertyChanged czyli dodanie i obsługę zdarzenia  
 public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
oraz dodanie zmiennych prywatnych każdemu odpowiadającemu mu publicznemu propertisowi, a także usunięcie atrybutu [System.ServiceModel.MessageBodyMemberAttribute] dla tych propetrtisów.
Przykład poprawnie poprawionej klasy:

    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.MessageContractAttribute(WrapperName="parametr-XYZ", WrapperNamespace="http://xyz.pl/ab/xyz", IsWrapped=true)]
    public partial class xyzRequest : object, System.ComponentModel.INotifyPropertyChanged {

        private string parametrXYZField;
        [System.Xml.Serialization.XmlAttributeAttribute("parametr-XYZ")]
        public string parametrXYZ
        {
            get
            {
                return this.parametrXYZField;
            }
            set
            {
                this.parametrXYZField= value;
                this.RaisePropertyChanged("parametrXYZ");
            }
        }
       
        public xyzRequest()
        {
        }

        public xyzRequest(string parametrXYZ)
        {
            this.parametrXYZField = parametrXYZ;
        }
        public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName)
        {
            System.ComponentModel.PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if ((propertyChanged != null))
            {
                propertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
            }
        }
    }
Czynność taką powtarzamy dla każdej klasy (i propertisu w tej klasie), w której występuje problematyczny dla nas parametr "parametrXYZ". W moim konkretnym przypadku to wystarczyło, a wywołanie usługi nast. poprzez:
string fuu = "Bar";
var raport = ws.pobranieraportu(fuu);

Korzystanie z web serwisów napisanych dla J2EE w .NET może nieść za sobą inne problemy np:
„Element XML 'rezultat-xyz' z obszaru nazw 'http://xyz.pl/ab/xyz' odwołuje się do metody i typu. Zmień nazwę komunikatu metody, używając atrybutu WebMethodAttribute, lub zmień element główny typu, używając atrybutu XmlRootAttribute.”
Powodem błędu jest zastosowanie dla kilku operacji: takiej samej nazwy typu zwracanego, którą jest „rezultat-xyz”. Technologia C#/.NET przewiduje obecność tylko unikalnych nazw elementów zwracanych przez poszczególne operacje. 
W celu wyeliminowania błędu niezbędna jest ręczna modyfikacja automatycznie wygenerowanej klasy Proxy. Modyfikacja polega na nadaniu unikalnych nazw elementów zwracanych przez wszystkie operacje dostępne w WebService, których nazwy elementów zwracanych kolidują ze sobą.

Inne problemy mogą wynikać z niestarannie napisanego XSD, lub innych, trudnych do zidentyfikowania powodów.



środa, 23 października 2013

SQL DateTime i DateTime2

Całkiem niedawno, tworząc insert do bazy danych wygenerowanej przez EF spotkałem się z błędem odnośnie parsowania dat z 'datetime2' na 'datetime'. Z tego co zrozumiałem, chodziło o brak inicjacji zmiennej (bo jak wiadomo, .NET Datetime ma domyślą wartość roku 0, a T-SQL Datetime zaczyna się od 1753 roku). Zainicjowanie tej zmiennej na "DateTime.Now();" rozwiązało ten problem, za to, krótkie spojrzenie na forum stackoverflow.com wyjaśnia nam dodatkowe niuanse pomiędzy tymi typami, taki jak np. precyzja dokładności "3 1/3 milliseconds" do "100ns".

Link do do zgłoszenia na stackoverflow

piątek, 18 października 2013

Rozszyfrowywanie CAPTCHA z pomocą zewnętrznego (płatnego) klienta

CAPTCHA, to rodzaj techniki stosowanej jako zabezpieczenie na stronach www, celem której jest dopuszczenie do przesłania danych tylko wypełnionych przez człowieka link do wikipedii.

Najczęściej jest to rysunek, który wymaga od użytkownika własnoręcznego wpisania, tworzony w taki sposób, aby rozwiązanie tego sposobem mechanicznym było maksymalnie skomplikowane. Same algorytmy rozwiązywania captcha są skomplikowane i bazują na algorytmach szyfrowania i deszyfrowania. Na szczęście, nie musimy się znać na wszystkim i można te zadanie zlecić komuś innemu, kto się na tym zna dużo lepiej niż my. Przykładem firmy, która się specjalizuje w rozwiązywania zabezpieczeń typu CAPTCHA jest deathbycaptcha.

Najnowsza wersja (4.2) posiada bibliotekę przygotowaną dla języków:
-.NET (C#, Visual Basic)
- AutoIt3
- iMacros
- C (client and libs' source code)
- Java
- Perl 5+
- PHP v5+
- Python v2.5+ and v3.0+
- Command-line tool for Windows, Linux (i386 and x86-64) (see usage note below)
- Plugin for MyAdTools bots

oraz klienta firm 3 (nie wspierane przez producenta)
- Ruby

Producent udostępnia przykład wykorzystania API w językach:
- PHP
- C#

Osobiście wybrałem przykład działający w C# i działa tak, jak należy, tzn. do projektu dodajemy bibliotekę .dll, w wykonywanym pliku .cs dokładamy nową przestrzeń nazw, za pomocą której tworzymy obiekt. Na obiekcie wywołujemy przeciążoną metodę .Decode (captcha możemy podać, np. jako tablicę bajtów) i... to wszystko :)
Dodatkowo mamy metodę GetBalance(), która sprawdza bilans naszego konta (czyli w razie potrzeby możemy napisać automat przypominający nam o tym, że kończą się pieniądze przeznaczone na rozwiązywanie captcha).


Ile to kosztuje?
1k Captcha - 1.390 dolara, co przy aktualnym kursie dolara daje nam ~ 4.22zł za każde rozwiązane 1k CAPTCHA, czyli nie jest to jakiś nie wiadomo jaki majątek.

poniedziałek, 14 października 2013

.NET Client consuming PHP ZendFramework Web Service - solution no.2 (HttpWebRequest)

Dostałem za zadanie zintegrowanie się z kilkoma web serwisami. Naturalnym rozwiązaniem było wybranie do tego celu technologii WPF, co opisałem we wcześniejszym wpisie. Na szczęście, platforma .NET pozwala wykonywać wiele zdań na wiele, różnorodnych sposobów. W tym wpisie, przedstawię inny sposób odpytania web serwisu, tj. za pomocą klasy HttpWebRequest

W przypadku korzystania z klasy HttpWebRequest, całość zapytania SOAP, jakie wykonamy musimy napisać samemu w stringu, a nast. odpytać serwis za pomocą metody POST.
Odpowiedź dostajemy w strumieniu bajtów, który następnie zamieniamy na XmlDocument

        public string GetMigDZiDByPost()
        {
            try
            {
                WebRequest webRequest = WebRequest.Create("http://xxx.yyy.zzz/Dzsoapserver/queryko");

                HttpWebRequest httpRequest = (HttpWebRequest)webRequest;
                httpRequest.Method = "POST";
                httpRequest.ContentType = "text/xml";
                httpRequest.Headers.Add("SOAPAction: http://xxx.yyy.zzz/Dzsoapserver/queryko");
                Stream requestStream = httpRequest.GetRequestStream();

                //Create Stream and Complete Request
                StreamWriter streamWriter = new StreamWriter(requestStream);
                streamWriter.Write(this.GetSoapString());
                streamWriter.Close();

                //Get the Response
                WebResponse webResponse = httpRequest.GetResponse();
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader streamReader = new StreamReader(responseStream);

                //Read the response into an xml document
                System.Xml.XmlDocument soapResonseXMLDocument = new System.Xml.XmlDocument();
                soapResonseXMLDocument.LoadXml(streamReader.ReadToEnd());

                //return only the xml representing the response details (inner request)
                string test = soapResonseXMLDocument.GetElementsByTagName("organisation")[0].InnerXml;
                return test;
            }
            catch (Exception ex)
            {
                //error 500 mean rekord not found
                return null;
            }
        }
        private string GetSoapString()
        {
            string hash = Constraints.MIGDZ.MigDzHashBase64;
            StringBuilder sb = new StringBuilder();
            sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            sb.Append("<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:ns1=\"http://xxx.yyy.zzz/Dzsoapserver/queryko\" xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body>");
            sb.Append("<ns1:getParcel><parameters xsi:type=\"ns1:Logic_DzSoapServer_Request_BodyKo\"><sy xsi:type=\"xsd:string\">12345678</sy></parameters><hashCode xsi:type=\"xsd:string\">abcdefg0123456VUZ123ABCD01a=</hashCode></ns1:getParcel></SOAP-ENV:Body></SOAP-ENV:Envelope>");

            return sb.ToString();
        }

piątek, 11 października 2013

.NET Client consuming PHP ZendFramework Web Service - solution no.1 (WPF)

 Dostałem za zadanie zintegrowanie się z kilkoma web servisami. Naturalnym rozwiązaniem było wybranie do tego celu technologii WPF, szczególnie, że korzystanie z jego poprzednika zwanego potocznie "(SOAP) Web Services" jest niezalecane przez firmę Microsoft od 2009r:
 W książce "Professional-ASP-NET-4-5-in-C-and-VB.productCd-1118311825.html" jest napisane:

In 2009, ASMX Web Services were marked as legacy. Therefore, the code found within will notbe updated with future ASP.NET releases (the exception would be a security update).

 O ile zintegrowanie się z serwisami wystawionymi przez inne WCF poszło gładko, to pewne problemy napotkałem podczas integracji z web serwisem napisanym w PHP przy pomocy Zend Framework. Początkowo chciałem wykonać standardową operację połączenia, która powinna wyglądać tak samo, jak w przypadku korzystania z serwisu napisanego w WCF (link do artykułu, w którym programista łączy się z php za pomocą wcf). Niestety, w moim przypadku, WCF wykrył usługę, ale wyrzucił błąd:

There was an error downloading 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl/_vti_bin/ListData.svc/$metadata'.
The request failed with the error message:
--
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>Sender</faultcode><faultstring>Invalid XML</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>

--.
Metadata contains a reference that cannot be resolved: 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl'.
The content type text/html; charset=UTF-8 of the response message does not match the content type of the binding (application/soap+xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 1024 bytes of the response were: '<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://xxx.yyy.zzz.pl/Swdsoapserver/query" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="SwdQuerySoap"
    targetNamespace="http://xxx.yyy.zzz.pl/Swdsoapserver/query">
    <wsdl:types>
        <xsd:schema 
 Jak zwykle w takich przypadkach nieodzowne okazało się forum stackoverflow, gdzie zadałem pytanie: "consuming-php-web-service-by-wcf-client".
 Dzięki otrzymanej tam pomocy, zapisałem plik .wsdl na dysku i za jego pomocą utworzyłem klasy proxy (Add Service Reference, tylko zamiast linku, wskazujemy lokalizację pliku na dysku).

Tak wygenerowane klasy proxy potrafiły się połączyć i wykonać zapytanie, ale miały problemy z odczytaniem odpowiedzi. Błędy były dwa:

a) Wielkość przesyłanej odpowiedzi była większa, niż długość standardowej odpowiedzi
"Maksymalny przydział rozmiaru wiadomości dla wiadomości przychodzących (65536) został przekroczony. Aby zwiększyć przydział, użyj właściwości MaxReceivedMessageSize we właściwym elemencie wiązania."
Obejście tego problemu jest stosunkowo proste i polega na modyfikacji ustawień powiązania (ang. binding) wewnątrz pliku web.config/app.config naszej aplikacji:

        <binding name="Logic_DzSoapServer_QueryKoBinding" maxBufferSize="2147483647"
          maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
        </binding>
b) Problem mappera z parsowaniem odpowiedzi wysyłanej przez PHP na obiekty .NET
"Nie można przypisać obiektu typu System.Int32 do obiektu typu System.Int32[]."
Sposobem aby to obejść okazała się ręczna modyfikacja wygenerowanych klas proxy, aby zamiast spodziewanego typu odpowiedzi, odpowiadały typem 'object':

        public object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode) {
            return base.Channel.getParcel(parameters, hashCode);
        }

        public object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode)
        {
            return base.Channel.getParcelAsync(parameters, hashCode);
        }

     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko", ConfigurationName="ko.Logic_DzSoapServer_QueryKoPort")]
    public interface Logic_DzSoapServer_QueryKoPort {
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
    [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
    }

 Co spowodowało, że wywołanie odpowiedzi było stosunkowo proste:

            List<MigDzKo> migDzKoList = new List<MigDzKo>();
            try
            {
                ko.Logic_DzSoapServer_QueryKoPortClient client = new ko.Logic_DzSoapServer_QueryKoPortClient();
                ko.Logic_DzSoapServer_Request_BodyKo request = new ko.Logic_DzSoapServer_Request_BodyKo();
                request.sy = "12345678";
                client.Open();
                var response = client.getParcel(request, Constraints.MIGDZ.MigDzHashBase64);
                XmlNode[] xmlNodes = (XmlNode[])response;
                for (int i = 0; i < xmlNodes.Length; i++)
                {
                    try
                    {
                        string organization = string.Empty;
                        if (xmlNodes[i].ChildNodes[0].HasChildNodes)
                        {
                            MigDzKo migDzKo = new MigDzKo();
                            migDzKo.operation_id = Convert.ToInt32(xmlNodes[i].ChildNodes[0]["operation_id"].InnerXml);
                            migDzKo.hashpublic = xmlNodes[i].ChildNodes[0]["hashpublic"].InnerXml;
                            migDzKoList.Add(migDzKo);
                        }
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message.ToString());
                    }
                }
                client.Close();
            }
            catch (Exception ex)
            {
                log.Error(ex.Message.ToString());
            }
            return migDzKoList;


Dla spójności podaję też zawartość klasy MigDzKo

    public class MigDzKo
    {
        public int operation_id { get; set; }
        public string hashpublic { get; set; }
    }

poniedziałek, 30 września 2013

Odrobina kryptografi

W przypadku, gdy zaistnieje potrzeba wykorzystania kryptografii, tj. zakodowania pewnych danych w aplikacji (z możliwością ich późniejszego odszyfrowania), powinno się unikać korzystania ze standardowych algorytmów szyfrujących dostępnych w .NET 2.0, ponieważ nie zapewniają one wystarczającego bezpieczeństwa.  W miarę bezpieczny, wydaje się algorytm  AES, jednak jak to w kryptografii bywa, skuteczna użyteczność poszczególnych algorytmów z biegiem czasu maleje.

Link, do działającego algorytmu w C#, można znaleźć na stackoverflow.com

Osobiście miałem okazję z niego korzystać w momencie, gdy jedynym skutecznym sposobem przekazania danych identyfikacyjnych z głównego okna aplikacji do popupa było wysłanie danych za pomocą querystringa (klient zażyczył sobie przeniesienie wszystkich plików typu BLOB z naszej bazy danych, do zewnętrznego syst. plików obsługiwanego przez firmę zewnętrzną z możliwością obsługi załączników do wniosku przez aplikacje trzecie).

Inne przydatne linki:
stackoverflow.com - how-to-start-learn-cryptography-with-c-sharp
http://stackoverflow.com - c-sharp-rsa-encryption-decryption-with-transmission



czwartek, 26 września 2013

Datatable to CSV (Excel)

Ost. zostałem poproszony o pomoc w pewnej sprawie. Sprawa dotyczyła... problemu z zapisem, a nast. odczytaniem dokumentu CSV na dysku. No cóż. Nie pozostało mi nic innego, jak zamienić FileStream na MemoryStream, a nast. przekazać go do obiektu klasy Attachment (pomijając zbędne zapisywanie załącznika na dysku). Przy okazji poprawiłem też metodę tworzenia samego załącznika (zapytanie SQL zwracało DataTable, który nast. był konwertowany na CSV). Całkiem sprytne i zgrabne rozwiązanie znalazłem na stackoverflow.com (convert-datatable-to-csv-stream).

Dlaczego więc piszę tutaj o tym? Ponieważ, w rozwiązaniu, które jest na stackoverflow, dokonałem kilku kosmetycznych zmian i postanowiłem je sobie tutaj zachować na przyszłość.

W klasie wywołującej kodowanie: "Windows-1250"
W klasie Extensions, w metodzie ToCSV znak oddzielający: ";"

środa, 25 września 2013

Konfiguracja IIS do hostowania aplikacji wykorzystującej SSL

Zgodnie z zapowiedzią, zaprezentowaną jakiś czas temu tutaj na blogu, po blisko 2 latach postanowiłem porzucić sharepointa i skoncentrować się na programowaniu w .NET. Jednym z zadań, jakie zostało mi postawionych, było stworzenie aplikacji webowej\web service, który jako swój element składowy będzie wykorzystywał inny web service zabezpieczony certyfikatem SSL.

Przystąpiłem więc do pracy. Pierwszą rzeczą, którą zrobiłem, to zainstalowałem certyfikat na komputerze (dwuklik na certyfikacie, a nast. 'next->next->wpisz hasło -> next->itd.').

Następnie stworzyłem aplikację webową, która wykorzystywała ten web service i wyświetlała odpowiednie dane na stronie .aspx. Pocz. visual studio nie chciało się u mnie podłączyć do web service (zapewne kwestia servera proxy w korpo), bo z 'iPlusa' wszystko chodziło tak jak należy.
Sposobem, aby zaczęło mi to działać w sieci korporacyjnej, okazało się włączenie programu Fiddler. Początkowo planowałem go użyć do zdiagnozowania problemu, natomiast... samo jego wykorzystanie rozwiązało mój problem na maszynie developerskiej (zapewne wynika to z faktu, że Fiddler w domyślnych ustawieniach wymusza autentykację).


Ok, aplikacja na maszynie developerskiej to jedno. Jeszcze trzeba to postawić na IIS, aby inni ludzie mieli do tego dostęp. O ile stworzenie puli aplikacji i wgranie tam strony było proste, o tyle sprawienie, aby nasza aplikacja mogą bez problemów wykorzystywać zewnętrzny web service z SSL przysporzyło mi nieco pracy (Error 403).


Rozwiązanie polega na tym, że należy stworzyć własny certyfikat i ustawić w ISS 'powiązania' (ang. 'bindings'). Jak to zrobić można przeczytać tutaj oraz tutaj (2 art. do czytania)


oraz upewnić się w ustawieniach puli aplikacji, że aplikacja będzie uruchamiana z konkretną tożsamością użytkownika (ponieważ ta konkretna aplikacja nie posiadała swojego typu 'logowania', to jest personifikowana jako konto admina w puli aplikacji).



wtorek, 30 lipca 2013

Entity Framework Modified State (updating row values)

Pogłębiając moją wiedzę nt. Entity Framework zauważyłem pewną ciekawą właściwość, mianowicie w syt. gdy tylko niektóre kolumny w wierszu z danymi zostały zaktualizowane, to Entity Framework zachowuje się w różny sposób dla różnego rodzaju aplikacji:
- dla aplikacji okienkowych (windows forms) aktualizuje tylko wybrane kolumny danego rekordu
- dla aplikacji webowych (asp) aktualizuje wszystkie kolumny danego rekordu


In a desktop application, state changes are typically set automatically. In this type of application, you read an entity and make changes to some of its property values. This causes its entity state to automatically be changed to Modified. Then when you call SaveChanges, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.
However, in a web application this sequence is interrupted, because the database context instance that reads an entity is disposed after a page is rendered. When the HttpPost Edit action method is called, this is the result of a new request and you have a new instance of the context, so you have to manually set the entity state to Modified. Then when you call SaveChanges, the Entity Framework updates all columns of the database row, because the context has no way to know which properties you changed.
If you want the SQL Update statement to update only the fields that the user actually changed, you can save the original values in some way (such as hidden fields) so that they are available when the HttpPost Edit method is called. Then you can create a Student entity using the original values, call the Attach method with that original version of the entity, update the entity's values to the new values, and then call SaveChanges. For more information, see Add/Attach and Entity States and Local Data on the Entity Framework team blog.
 źródło

niedziela, 28 lipca 2013

Log in as a different user in sharepoint 2013

Ten konkretny wpis, to zwykły link do innego bloga, ale zostawiam to tutaj, ponieważ ten blog jest również moim prywatnym notatnikiem.

heblobfarm.wordpress.com <-> sharepoint-2013-ui-log-in-as-a-different-user/

Layouts folder location in sharepoint 2010/2013

No i stało. Oficjalnie wyszedł długo oczekiwany następca sharepointa 2010, czyli sharepoint 2013. Zapewne posiada wiele ciekawych funkcji i udogodnień, natomiast pierwszą rzeczą, która rzuca się w oczy, to fakt, że projekty napisane w sharepoint 2010, które planujemy wdrożyć dla sharepointa 2013 powinny być skonwertowane na typ projektu 'sharepoint 2013'. Projekt po takiej konwersji nie może zostać użyty w 'sharepoint 2010'. W praktyce jesteśmy więc zmuszeni do stworzenia dwóch projektów obok siebie i wymuszenia, aby oba projekty posiadały wspólne pliki .cs (niestety, ale inne pliki, czyli aspx, ascx, css, js, xml, png, i cała reszta plików umieszczana zazwyczaj w 'layouts' musi być zdublowana i znajdować się w obu projektach).

Gdyby jednak tego było mało, to folder layouts w nowym sharepoincie nie nazywa się 'layouts/' (tak jak to było w sharepoint 2010), tylko... 'layouts/15/".
Na szczęście jest sposób, aby można było wykorzystywać te same pliki .cs w obu projektach. Oto on, czyli klasa UrlHelper:

  public class UrlHelper
    {

        public static string LayoutsUrl
        {
            get
            {
                PropertyInfo pi = typeof(SPUtility).GetProperty(
"ContextLayoutsFolder", BindingFlags.Public | BindingFlags.Static);
                if (pi == null)
                    return "/_layouts/";
                else
                    return String.Format("/{0}/", pi.GetValue(null, null).ToString());
            }
        }

        public static string ImagesUrl
        {
            get
            {
                PropertyInfo pi = typeof(SPUtility).GetProperty("ContextImagesRoot", BindingFlags.Public | BindingFlags.Static);
                if (pi == null)
                    return "/_layouts/images/";
                else
                    return String.Format("{0}", pi.GetValue(null, null).ToString());
            }
        }


        public static string ControlTemplatesUrl
        {
            get
            {
                PropertyInfo pi = typeof(SPUtility).GetProperty("ContextControlTemplatesFolder", BindingFlags.Public | BindingFlags.Static);
                if (pi == null)
                    return "/_CONTROLTEMPLATES/";
                else
                    return String.Format("/{0}/", pi.GetValue(null, null).ToString());

            }
           
        }
    }

W praktyce, wszędzie, gdzie odwołujemy się do katalogów związanych z 'layouts', zamiast wpisywać stringi, powinniśmy wykorzystać metodę klasy UrlHelper.

poniedziałek, 22 lipca 2013

Selenium - software testing framework for web applications

Przeglądając wykład Grzegorza Dudy podczas konferencji confitura 2012 pt. From Busy to Effective Developer odkryłem fajne, nowe narzędzie, które zademonstrował Grzegorz. Narzędzie nazywa się selenium i służy do testowania stron internetowych. O ile, nie wgłębiałem się szczegółowo w API tego narzędzia, to jego podstawowa funkcjonalność, dostępna jako plug-in do przeglądardki firefox służy do nagrywania/reprodukcji schematu poruszania się po stronie internetowej.

Czasami zdarza się bowiem, że aby odtworzyć błąd, trzeba się 'przeklikać' przez wiele formularzy (i dopiero wtedy podpiąć debugger). W takich właśnie momentach przydaje się Selenium. Za pierwszym razem, przeklikując się przez formularze włączamy opcję 'rejestrowania', a przy nast., okazji, gdy potrzebujemy wykonać identyczną operację tylko włączamy opcję odtwarzania poprzednio zapisanego scenariusza testowego.

Narzędzie dostępne jest na licencji Apache 2.0 więc spokojnie możemy z niego korzystać, a nawet rozwijać.

niedziela, 21 lipca 2013

Programming never changes (asp mvc)

Czyli... w końcu rozpocząłem moją przygodę z ASP MVC na poważnie. Rozpocząłem ją od wszelkiej maści tutoriali, jakie można znaleźć na oficjalnej stronie asp mvc. O ile pierwszy tutorial z asp mvc 4 (MvcMovie) poszedł gładko, to już w nast. z asp mvc 3 (MvcMusicStorew 5 części tutka wystąpiły błędy, a dokładniej "Using the same DbCompiledModel to create contexts against different types of database servers is not supported". Ale... od czego mamy stackoverflow.com ;)

Parafrazując słynny cytat z serii Fallout: Programming never changes

niedziela, 14 lipca 2013

Index oraz Index Klastrowany (Clustered Index)

Przygotowując się do głębszego zagłębienia się w temat ASP.NET MVC robię sobie powtórkę z starszego materiału, tj. asp.net oraz bazy danych. A skoro już jesteśmy przy temacie baz danych, to wypadało by wspomnieć m.in. o indeksach oraz ich typach występujących w bazie danych MS SQL.
Indeksy bazodanowe, podobnie jak indeksy w książkach służą do szybszego wyszukiwania interesujących nas informacji, dzięki czemu zdecydowanie zwiększają szybkość wyszukiwania danych z bazy danych, ale nieznacznie zwiększają czas zapisu/modyfikacji/usuwania danych, dlatego nie należy umieszczać ich ponad miarę. Indeks może zawierać maksymalnie 16 kolumn.

W bazie MS SQL występują dwa typy indeksów:
- indeks klastrowany (clustered index)
- indeks zwykły (non-clustered index)

Index klastrowany odpowiada za fizyczną reprezentację danych w bazie danych (fizyczny zapis danych na dysku twardym) oraz może być tylko jeden (dane mogą być poprawnie posortowane w strukturze B-drzewa tylko na jeden sposób). Każda operacja typu insert/update/delete na danych, na których jest założony index klastrowany powoduje fizyczne sortowanie B-drzewa z danymi.

Indeks zwykły (nieklastrowany) jest osobną strukturą danych, składowanych osobnie i posiadającym 'wartość' oraz wskaźnik do fizycznych danych które reprezentuje. Indeks zwykły, jeżeli tylko może wykorzystuje index klastrowany, aby zwiększyć efektywność przeszukiwania. W przypadku operacji insert/update/delete na danych, wartości indeksów zwykłych są aktualizowane. Na tabeli może być wiele indeksów zwykłych.

Przykład współdziałania indeksów dość dobrze wyjaśnia





Control State vs View State

Jak powszechnie wiadomo, aplikacje napisane w asp.net są bez stanowe, a wywołanie każdego postbacka (np. za pomocą naciśnięcia przycisku) powoduje usunięcie aktualnej strony i utworzenie nowej. Aby zachować informacje pomiędzy postbackami stosuje się m.in. mechanizmy 'Control state' oraz 'View state'. A jakie są pomiędzy nimi główne różnice?

a) View state można wyłączyć (jeżeli inny developer korzysta z Twoich kontrolek, to może wyłączyć w nich 'view state', przez co kontrolki przestaną działać).
b) Control state nie da się wyłączyć (jest zawsze włączony), ale posiada ograniczenie co do ilości danych, jakie można w nim składować (nigdy nie próbujcie składować 'Grid View' w Control State).

Linki:
'Control state vs View State' na forum asp.net
'Control state vs View State' na forum stackoverflow

sobota, 6 lipca 2013

LoggingService, czyli logowanie błędów w Sharepoint 2010

W pracy programisty bardzo ważne jest logowanie błędów. Gorsze od nie logowania błędów jest tylko błędne logowanie błędów (np. poprzez napisanie własnego logera, co niestety miałem już okazję widzieć w praktyce). Aby poprawnie logować błędy, najlepiej użyć jakiegoś gotowego rozwiązania.

W przypadku aplikacji typu sharepoint nie powinniśmy używać zewnętrznych bibliotek. Jak opisuje to avishnyakov za pomocą spdevlab.com:


Suggestion #0 – Do not use 3rd part logging framework

Even if you stick to log4net, NLog, EventLog or System.Diagnostic.Trace, or trying to invent your own wheel, then DO NOT EVER USE THAT STUFF AGAIN.
SharePoint has its own logging system called “Unified Logging System (ULS)“. If you still wonder why would you use ULS instead your lovely logging framework, then consider the following facts:
  • ULS is used by SharePoint, so if you used 3rd part logging, you would need to consolidate logs from few different sources
  • ULS can be easily configured from Central Administration as well as by PowerShell
  • ULS can write to Windows Event Log without elevated privileges (!)
  • ULS shrinks text log files on the hard drives (!)
  • ULS does not require web.config changes/modifications (!). That’s quite important concern for multi-server farm
  • SharePoint can aggregate ULS log (and not just logs..) from multiple servers to the logging data base (Usage and Health Data Collection Service Application)

Dlatego w rozwiązaniach typu sharepoint 2010 dużo lepszym rozwiązaniem jest napisanie/skopiowanie klasy LoggingService dziedziczącej po SPDiagnosticsServiceBase.
Takie rozwiązanie znajdziemy w wielu miejscach w sieci, więc wydaje się ono być najlepsze.

Sama klasa, wygląda następująco:
public class LoggingService : SPDiagnosticsServiceBase
{
 public static string DiagnosticAreaName = "My";
 private static LoggingService _Current;
 public static LoggingService Current
 {
 get
 {
 if (_Current == null)
 {
 _Current = new LoggingService();
 }
 return _Current;
 }
 }
 private LoggingService()
 : base("My Logging Service", SPFarm.Local)
 {
 }
 protected override IEnumerable<SPDiagnosticsArea> ProvideAreas()
 {
 List<SPDiagnosticsArea> areas = new List<SPDiagnosticsArea>
 {
 new SPDiagnosticsArea(DiagnosticAreaName, new List<SPDiagnosticsCategory>
 {
 new SPDiagnosticsCategory("WebParts", TraceSeverity.Unexpected, EventSeverity.Error)
 })
 };
 return areas;
 }
 public static void LogError(string categoryName, string errorMessage)
 {
 SPDiagnosticsCategory category = LoggingService.Current.Areas[DiagnosticAreaName].Categories[categoryName];
 LoggingService.Current.WriteTrace(0, category, TraceSeverity.Unexpected, errorMessage);
 }
}
natomiast jej użycie wygląda tak:

LoggingService.LogError("WebParts", ex.ToString);
Do przeglądania logów polecam program ULSViewer dostępny na mocy licencji  MICROSOFT PUBLIC LICENSE (Ms-PL)

Przydatne linki:
wpis Avishnyakov'a na spdevlab.com
wpis na blogu Waldka Mastykarza
wpis na blogu Jurgena Baurle