Lazy Yield Problems

Zanim przejdę do sedna sprawy nakreślę najpierw ciąg wydarzeń który mnie ku napisaniu tego posta skłonił.

Ostatnimi czasy wykonywałem małą biblioteczkę "na własne potrzeby" której podstawą był interfejs który można przedstawić następująco:

public interface IProcessor<T>
{
	IEnumerable<Result> Process(T input);
}

Interfejs ten ma wiele implementacji jednak z punktu widzenia tego postu kluczowy jest "AggregateProcessor" który łączy wyniki kilku procesorów.

class AggregateProcessor<T> : IProcessor<T>
{
	private IProcessor<T>[] subProcessors;

	public AggregateProcessor(params IProcessor<T>[] subProcessors)
	{
		this.subProcessors = subProcessors;
	}

	public IEnumerable<Result> Process(T input)
	{
		return subProcessors.SelectMany(p => p.Process(input));

		/* ekwiwalent
		foreach (var processor in subProcessors)
		{
			foreach (var result in processor.Process(input))
			{
				yield return result;
			}
		}
		 */
	}
}

Następnie dla tej klasy napisałem prosty test sprawdzający czy podprocesor zostanie wywołany po wywołaniu Process na AggregateProcessorze.

Test jest napisany w NUnicie do Mockowania wykorzystałem FakeItEasy.

[Test]
public void CheckIfSubProcessorIsCalled()
{
	// Given
	var parameter = new TestClass();

	var fakeProcessor = A.Fake<IProcessor<TestClass>>();
	A.CallTo(() => fakeProcessor.Process(parameter)).Returns(Enumerable.Empty<Result>());

	var aggregate = new AggregateProcessor<TestClass>(fakeProcessor);

	// When
	aggregate.Process(parameter);

	// Then
	A.CallTo(() => fakeProcessor.Process(parameter)).MustHaveHappened();
}

Wielkie było moje zdziwienie gdy okazało się że Test nie przechodzi.

Przyczyną tej sytuacji jest fakt iż zarówno operacje w zapytaniu LINQ jak i operacja yield wywoływane są w momencie iteracji po wyniku metody której w tym przykładzie nie ma. W większości przypadków fakt ten nie ma większego znaczenia ponieważ iteracja zazwyczaj w końcu następuje, chyba że tak jak w tym przypadku metody będącej funkcją używamy jak metody będącej procedurą, albo wykonujemy na wyniku operacje typu Any, First/FirstOrDefault które nie iterują po całym wyniku.

Wracając do mojego przypadku ostatecznie stwierdziłem że fakt wywołania metody wewnętrznej nie jest dla mnie istotny, a bardziej obchodzi mnie zwrócenie wyniku procesora wewnętrznego.

[Test]
public void CheckIfSubProcessorIsCalled()
{
	// Given
	var parameter = new TestClass();
	var result = new Result();

	var fakeProcessor = A.Fake<IProcessor<TestClass>>();
	A.CallTo(() => fakeProcessor.Process(parameter)).Returns(new[] { result });

	var aggregate = new AggregateProcessor<TestClass>(fakeProcessor);

	// When
	var resultCollection = aggregate.Process(parameter);

	// Then
	CollectionAssert.Contains(resultCollection, result);
}

Drugim zagrożeniem jakie wiąże się z yield i LINQ jest fakt iż wszystkie operacje wykonywane pod spodem wykonują się przy każdej iteracji po wynikowej kolekcji. Sam się na tym nigdy nie przejechałem ponieważ przed błędem tego typu skutecznie chroni mnie Resharper, jednak ludzi nie posiadających tego genialnego narzędzia warto ostrzec że poniższy kod:

static void Main(string[] args)
{
	var sourceCollection = Enumerable.Range(1, 10);

	var resultCollection = from number in sourceCollection
						   select Compute(number);

	foreach (var number in resultCollection)
	{
		Console.Write("{0}, ", number);
	}

	Console.WriteLine();

	foreach (var number in resultCollection)
	{
		Console.Write("{0}, ", number);
	}
}

private static int Compute(int parameter)
{
	// Some Heavy operation
	Thread.Sleep(1000);
	return parameter + 1;
}

będzie uruchamiał się 20 a nie 10 sekund. Jakimś obejściem tego problemu jest użycie metody ToList. Najlepiej w następujący sposób:

static void Main(string[] args)
{
	var sourceCollection = Enumerable.Range(1, 10);

	var resultCollection = from number in sourceCollection
						   select Compute(number);

	/*
	 * Przed wywołaniem ToList upewniam się że kolekcja nie jest już listą
	 * W tym przypadku nie ma to sensu ponieważ wiem że kolekcja nie jest listą
	 */
	var resultList = resultCollection as List<int> ?? resultCollection.ToList();

	foreach (var number in resultList)
	{
		Console.Write("{0}, ", number);
	}

	Console.WriteLine();

	foreach (var number in resultList)
	{
		Console.Write("{0}, ", number);
	}
}

Podsumowując zarówno LINQ jak i yield są bardzo fajnymi narzędziami, przed ich użyciem należy jednak przynajmniej w minimalnym stopniu dowiedzieć się jak one działają.

Świąteczne małpki

W ostatnim czasie w pracy spotkałem się z problemem który w uproszczeniu wygląda następująco:

Mamy listę obiektów typu Dział zawierających kolekcje obiektów typu Wydział zawierających kolekcję obiektów typu pracownik. Problemem do rozwiązania jest policzenie średniej Wieku wszystkich pracowników. Zakładamy dodatkowo że baza nie jest tuż za płotem tylko znajduje się za WebServicem do którego dokładanie metod jest z jakiś przyczyn problematyczne, więc nie możemy po prostu złożyć składnego selecta.

Problem wydaje się być banalny wystarczy zmontować coś takiego:

int ilosc = 0;
            int suma = 0;

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        ilosc++;
                        suma += pracownik.Wiek;
                    }
                }
            }

            double wynik = ((float)suma) / ilosc;

Jest to jednak kod mało czytelny. A jeżeli operacja byłaby bardziej złożona niż średnia robi się jeszcze mniej ciekawie.

Rozwiązaniem jakie narzuca się bardziej doświadczonym programistom .Net jest użycie LINQ. Rozwiązanie to ma jednak wadę: nie chodzi na kolekcjach kolekcji tylko na jednowarstwowych kolekcjach. Można ten problem rozwiązać przez wyliczenie sobie pomocniczej kolekcji i na niej wykonania działania:

           List<Pracownik> pracownicy = new List<Pracownik>();

            foreach (Dzial dzial in dzialy)
            {
                foreach (Wydzial wydzial in dzial.Wydzialy)
                {
                    foreach (Pracownik pracownik in wydzial.Pracownicy)
                    {
                        pracownicy.Add(pracownik);
                    }
                }
            }

            double wynik = pracownicy.Average(p => p.Wiek);

Jest to rozwiązanie dobre jednak tworzenie i napełnianie listy w przypadku dużych kolekcji może okazać się zasobożerne.

Dlatego też postanowiłem zrobić świąteczny prezent sobie i innym programistom i stworzyć małpkę która za nas będzie biegała po drzewach i strukturach drzewo-podobnych. W ten sposób powstało rozszerzenie ExtractSubCollection biblioteki Szogun1987.Monkey. Po wykorzystaniu tej metody powyższy kod zostaje skrócony do 4 w miarę czytelnych linijek:

double result = 
                dzialy.ExtractSubCollection(d => d.Wydzialy)
                .ExtractSubCollection(w => w.Pracownicy)
                .Average(p => p.Wiek);

W bibliotece znajdują się dodatkowo rozszerzenia GetAllSubNodes, GetLeafs i GetRoot ułatwiające przeglądanie zwykłych (tj. składających się z węzłów spójnych pod względem typu) Drzew.

Najprostszą z nich jest GetRoot która jako parametr pobiera obiekt – węzeł drzewa i Funkcję zwracającą element nadrzędny węzła. Jako korzeń uznawany jest element dla którego powyższa funkcja zwróci null.

Kolejną metodą jest GetAllSubNodes pozwala ona na przeiterowanie po wszystkich węzłach znajdujących się poniżej węzła na którym wywoływane jest rozszerzenie (łącznie z nim samym). Jako drugi parametr przyjmowana jest funkcja zwracająca kolekcję elementów podrzędnych.

Ostatnią metodą w bibliotece jest GetLeafs pozwalająca przeiterować się po liściach drzewa z niego się wywodzących.

Dodatkowo w bibliotece znajduje się interfejs ITreeNode którego zaimplementowanie sprawia że do funkcji GetAllSubNodes, GetLeafs i GetRoot nie trzeba przekazywać funkcji wyciągających "najbliższych członków rodziny" danego elementu.

Dołożyłem starań aby biblioteka była napisana w sposób powodujący jak najniższe zużycie zasobów systemowych (pamięci i procesora). Podobnie jak w LINQ wszelkie funkcje i metody przekazywane do moich rozszerzeń są wywoływane w momencie iteracji.

Na koniec kwestie prawne, (Co by było wszystko na papierze znaczy się monitorze):

Zezwalam na nieodpłatne korzystanie ze skompilowanej wersji biblioteki. Biorę na siebie poprawki ewentualnie wykrytych błędów, przy czym nie naprawiam błędów wynikających ze współbieżnego dostępu do drzewa. Nie gwarantuję także wprowadzania ulepszeń.

Kodu źródłowego nie udostępnię w tym roku. Zakładam taką możliwość w pierwszym kwartale roku przyszłego (2012).

Biblioteki są do pobrania Tutaj

Problem zwrotnych referencji w WCF

Podczas wielu batalii z WCF'em natknąłem się na konieczność umieszczenia w obiekcie będącej elementem jakiejś kolekcji referencji do obiektu posiadającego tą kolekcję. (Przyznam że konieczność ta była związana z faktem iż w przesyłanej klasie była zaszyta część logiki biznesowej a tak się nie powinno robić ale jak mus to mus). Tak więc posiadałem DataContract'y podobne do tych:

    [DataContract]
    public class Parent
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public List<Children> Childrens { get; set; }
    }

    [DataContract]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Próba pobrania obiektu Parent zakończyła się błędem:

WCFBlad1.png

Znalezienie przyczyny błędu było na tyle trudniejsze że pomiędzy buildem a napisaniem DataContractu robiłem całą masę innych rzeczy.

Błąd ten jest spowodowany wyjątkiem StackOverflow po stronie serwera, wynika on z faktu że przy próbie serializacji klasy Children mechanizm docierając do pola Parent nie orientuje się że już ten obiekt serializował albo właśnie go serializuje.

Rozwiązaniem tego problemu jakie znalazłem googlając było ustawienie właściwości IsReference w klasie rodzica i rzeczywiście w tak prostym przypadku rozwiązanie to działa jak należy.

    [DataContract(IsReference=true)]
    public class Children
    {
        [DataMember]
        public string Name { get; set; }

        [DataMember]
        public Parent Parent { get; set; }
    }

Moja sytuacja była jednak trochę bardziej złożona. Po pierwsze zarówno Parent jak i Children dziedziczyło po jednym typie bazowym:

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
    
    [DataContract(IsReference=true)]
    public class ParentBP : SuperBP
    {
        [DataMember]
        public List<ChildrenBP> Childrens { get; set; }
    }
	
	[DataContract]
    public class ChildrenBP : SuperBP
    {
        [DataMember]
        public ParentBP Parent { get; set; }
    }

Ustawienie znacznika IsReference na klasie ParentBP i pobranie referencji kończy się błędem:

pictures/WCFBlad2.png

W większości przypadków należy postąpić zgodnie z komunikatem błędu i ustawić IsReference na klasie bazowej. Jeżeli jednak z jakiś powodów nie chcemy tego robić możemy usunąć znacznik DataMember z pola Parent w klasie ChildrenBP i posiłkować się znacznikiem OnDeSerialized.

    [KnownType(typeof(ChildrenBP))]
    [KnownType(typeof(ParentBP))]
    [DataContract]
    public class SuperBP
    {
        [DataMember]
        public string Name { get; set; }
    }
	
    [DataContract]
    public class ParentBP : SuperBP
    {
        [OnDeserialized]
        public void OnDeserialized(StreamingContext context)
        {
            foreach (ChildrenBP child in Childrens)
            {
                child.Parent = this;
            }
        }

        [DataMember]
        public List<ChildrenBP> Childrens { get; set; }
    }
	[DataContract]
    public class ChildrenBP : SuperBP
    {

        public ParentBP Parent { get; set; }
    }

Słów kilka o krotkach.

Chciałem dzisiaj opisać trochę moje doświadczenia z krotkami.

Pomimo że ich konstrukcja nie jest złożona, a istnienie klasy o ich cechać było wyczekiwane dość długo to pojawiły się one w .Net dopiero w wersji 4.0, a ich pojawienie się zostało zdecydowanie zagłuszone przez inne nowinki w tej wersji.

Pierwszym narzucającym się zastosowaniem dla krotek jest wielotypowana tablica. Tablica taka przydaje się gdy musimy przerzucić listę kilkuelementowych paczek obiektów pomiędzy dwoma miejscami w kodzie. Przykładem takiego miejsca wywoływanie operacji na bazie dla każdego rekordu zwróconego przez obiekt implementujące IDataReader. Jak wiadomo przy próbie wywołania zapytania przed zamknięciem Readera następuje zgłoszenie wyjątku. Problem taki rozwiązywało się na kilka sposobów (oczywiście nie polegających na rezygnowaniu z Readera):

1) Utworzenie kilku list dla każdego przepisywanego pola – powoduje konieczność stosowania pętli for zamiast foreach

2) Utworzenie listy tablic obiektów (listy słowników, lub innej wariacji generycznej) – fajne rozwiązanie dopuki nie potrzebna nam informacja o rzeczywistym typie elementu (wtedy trzeba rzutować).

3) Utworzenie klasy i przesyłanie listy obiektów tej klasy – C# to nie Java tutaj duża lisczba klas kuje w oczy ;]

4) Klasy anonimowe – byłyby idealne gdyby nie problem z nacechowaniem ich typem listy

5) Stosowanie elementu klasy stworzonej w innym celu (np. KeyValuePair<T1, T2>)

Zastosowanie krotek jest rozwiązaniem łączącym znaczną część zalet powyższych rozwiązań jednocześnie nie posiada większości ich wad:

List<Tuple<int, string>> tempList = new List<Tuple<int, string>>();
using(SqlCommand cmd = new SqlCommand())
{
	cmd.Connection = con;
	cmd.CommandText = "select id, nazwa from Tabela1";
	using(IDataReader reader = cmd.ExecuteReader())
	{
		while(reader.Read())
		{
			tempList.Add(new Tuple<int, string>());
		}
	}
}
using(SqlCommand cmd = new SqlCommand())
{
	cmd.Connection = con;
	cmd.CommandText = "insert into Tabela2(id, nazwa) values (@id, @nazwa)";
	foreach(var tuple in tempList)
	{
		cmd.AddWithValue("@id", tuple.Item1);
		cmd.AddWithValue("@nazwa", tuple.Item2);
		cmd.ExecuteNonQuery();
	}
}

Inną ciekawostką związaną z krotkami jest fakt iż mają solidnie zaprogramowane funkcje GetHashCode i Equals, przez co można je wykorzystać żeby ułatwić sobie implementację tych metod:

class ExampleClass
{
	public int IdPart1 { get; set; }
	public int IdPart2 { get; set; }

	public override int GetHashCode()
	{
		return new Tuple<int, int>(IdPart1, IdPart2).GetHashCode();
	}

	public override bool Equals(object obj)
	{
		if (obj is ExampleClass)
		{
			ExampleClass other = (ExampleClass)obj;
			return new Tuple<int, int>(IdPart1, IdPart2).Equals(new Tuple<int, int>(other.IdPart1, other.IdPart2));
		}
		return false;
	}
}

albo z wykorzystaniem LambdaEqualityComparer

class ExampleClass
{
	public int IdPart1 { get; set; }
	public int IdPart2 { get; set; }

	private LambdaEqualityComparer<ExampleClass, Tuple<int, int>> comparer = 
                     new LambdaEqualityComparer<ExampleClass, Tuple<int, int>>(ec => new Tuple<int, int>(ec.IdPart1, ec.IdPart2));

	public override int GetHashCode()
	{
		return comparer.GetHashCode(this);
	}

	public override bool Equals(object obj)
	{
		return comparer.Equals(this, obj as ExampleClass);
	}
}

Krotki implementują też interfejs IComparable co może być przydatne jeżeli chcielibyśmy zaimplementować ten interfejs w swoich klasach (prawie klasyczny przykład Dekoratora).

Kolejnymi interfejsami implementowanymi przez Krotki są IStructuralEquatable i IStructuralComparable jest to związane z silnym wykorzystywaniem ich w języku F# (niestety jeszcze do niego nie przysiadłem). Więcej informacji na ten temat można uzyskać od Marcina Najdera z firmy Comarch który silnie promuje ten język na konferencjach.

Ze względu na fakt iż krotki po zainicjowaniu nie zmieniają swojego stanu mają podobno zastosowanie w aplikacjach współbieżnych (podobno bo sam nie miałem okazji się o tym przekonać).

Na koniec chciałbym dodać kilka słów o wykrytych przeze mnie niedopracowaniach krotek.

Pierwsza niedogodność Nazwy Item1, Item2, Item3… często uniemożliwiają domyślenie się co jest co, jednak po pierwsze nie mam innego pomysłu na nazwy tych właściwości, po drugie z założenia powinny być one wykorzystywane jako tymczasowe pojemniki na obiekty.

Druga fakt iż nie da się ich łatwo zserializować i deserializować (co też nie jest ich poprawnym zastosowaniem).

W komentarzach proszę o umieszczanie pomysłów na inne zastosowanie dla krotek.

Wrażenia po wizycie na 4Developers

4 kwietnia odbyła się w Warszawie konferencja 4 Developers. Z racji że uczestniczyłem w niej w poprzednich latach postanowiłem że nie opuszczę jej także w tym roku.

Monika Konieczny – Gramification

Ciekawy temat, niezbyt ciekawe wykonanie. Wykład dotyczył kolejnej metody motywacji pracowników, w tym programistów. Metoda ta polega na wprowadzeniu elementów gry podczas realizacji projektu, tak aby pracownicy czerpali radość z pracy. Jako przykład podany zostało rozgrywanie jakiejś gry w określonych odstępach czasu przy czym warunki początkowe gracza zależą od jego tygodniowych wyników (np. liczba kulek do Paintboala rozdawanych w comiesięcznej rozgrywce zależy od części projektu jaki wykonał pracownik). Według prelentki powinno się jednak szybko z gier indywidualnych przechodzić do zespołowych. Ogólnie prezentację oceniam na dobry minus.

Greg Young – zapomnij o szczegółach liczy się efekt

O autorze tej prezentacji przeczytałem na blogu Sławka Sobótki. Początkowo sądziłem że Sławek przesadza z mówiąc że wysłuchanie Grega powoduje efekt "I know kung-fu". Jednak później się przekonałem, że ma rację. Prelekcja była skierowana głównie do programistów Java, dlatego skrytykował on na samym początku używanie potężnych Framewoków i bibliotek w projektach w których samo ich skonfigurowanie zajmuje więcej czasu niż wykonanie projektu, dodatkowo powodując całą masę dziwnych błędów. Jak to określił "Frameworks are DSLs to converting XML into exceptions". Przez dłuższy czas było precyzowane stwierdzenie że klasa rozwiązań powinna być dobrana do klasy problemu. Że zupełnie inne rozwiązania powinno się stosować pisząc pamiętnik dla młodszej siostry, a inne tworząc aplikację nadzorującą działanie układu chłodzącego rdzeń elektrowni atomowej. Greg poruszył też zagadnienie zaciągania "technologicznego długu" podczas pracy nad projektem. Zastosował on sformuowania "Dobry dług" i "Zły dług" zaczerpnięte z publikacji Roberta Kiyosaki. Jedna z wypowiedzi mówiła o zasadności (nie)stosowania testów jednostkowych w projektach które będą rozwijane nie dłużej niż 2-5 lat. Według programistów pisanie testów jest dobrym zachowaniem, jednak wg. kadry zarządzającej powoduje całą masę pracy, która kosztuje i zmniejsza dochodowość projektu. Greg bardzo trafniej porównał stosowanie Testów jednostkowych do wzięcia na kredyt Lamborgini. Dla osób dla których kluczowym elementem sukcesu jest wizerunek (np. gwiazdy sportu i filmowe, prezesi dużych przedsiębiorstw) jest to z pewnością inwestycja("Dobry dług"), jednak dla przeciętnego zjadacza chleba koszty kredytu i utrzymania samochodu tej klasy przewyższają zarówno potencjalny wzrost zarobków a nawet same zarobki("zły dług") i analogicznie stosowanie testów jednostkowych ma sens w systemie bankowym gdzie pomyłka w algorytmie naliczania odsetek może kosztować zarówno bank jak i producenta oprogramowania miliony złotych, jednak mija się z celem w aplikacji napisanej dla sklepu ze skarpetkami gdzie na upartego można w ciągu jednej nocy wprowadzić ponownie wszystkie wystawione i odebrane faktury z ostatniego miesiąca. Kolejną kwestią poruszoną przez Grega była kwestia wzorców projektowych i sensu ich stosowania. Skrytykował on podejście w którym na siłę próbuje się stosować wzorce projektowe (tylko po to by je stosować). Podkreślił jednak ich rolę w komunikacji między proramistami (podając jako przykład Fabrykę Fabryk Fabryk której opisanie bez znajomości wzorców zajęłoby więcej niż ten wpis na blogu).

Ciekawostką jest fakt iż Greg nie posiadał przygotowanej prezentacji także nawet nie włączył projektora.

Michał Gruchała – Architektura skalowalnych systemów Webowych

Prezentacja dotyczyła zagadnienia optymalizacji prostych serwisów poddawanych bardzo dużemu obciążeniu (rzędu kilku milionów wejść na godzinę). Z racji że nie zajmuje się tym zagadnieniem nie specjalnie słuchałem prelegenta.

Sławomir Sobótka – Nowe bardziej racjonalne podejście do warstw

Prezentacja Sławka dotyczyła CQRS (Command Query Responsibility Segregation) która jest rozwinięciem Domain Driven Design. Podejście to zaleca oddzielenie serwisu zwracającego dane od serwisu je zmieniającego, co w dużym uproszczeniu oznacza rezygnację ze zwracania jakiś wartości przez metody które dokonują jakichkolwiek zmian w składowanych danych, oraz rezygnację z modyfikowania danych w metodach dane zwracające. Sławek podobnie jak Greg zwracał uwagę na zastosowanie na dostosowanie rozwiązania do problemu oraz że Domain Driver Design ma zastosowanie głównie przy pisaniu złożonych aplikacji biznesowych, z resztą wyraźnie widać było że Sławek przesiąkł myślą techniczną Greg'a. Nie czuję się na siłach aby przekazać ideę całego wykładu ponieważ sam muszę ją zgłębić.

Stephan Hochdöerfer – DDD jeszcze raz ale inaczej

Podobnie jak pierwsza prelekcja Ciekawy temat nieciekawe wykonanie.

Greg Young – Uwolnij swoją domenę

Wykład ten można potraktować jako rozwinięcie wcześniejszego wykładu Grega i wykładu Sławka Sobótki. Podobnie jak w przykładzie prezentacji Sławka nie podejmuje się analizy tego wystąpienia.

Ogólnie konferencję oceniam bardzo dobrze, a napewno lepiej niż zeszłoroczną. Lepiej dobrano lokalizację (Hotel w Poznaniu śmierdział według mnie komuną, a budynek Wyższej Szkoły Menagerskiej w Warszawie nawet jeżeli nie był nowy to był bardzo dobrze odnowiony), do zalet należy też zaliczyć fakt iż w tym roku posiłek był wliczony w koszt konferencji, oraz że nie zabrakło przystawek. Do wad lokalizacji należy zaliczyć dużą odległość od centrum Warszawy oraz brak ścieżki .Net oraz przedstawicieli firm Adobe, Microsoft i Oracle (może i byli ale ich widać nie było).

Komunikacja PHP i C Sharp

Ostatnio musiałem napisać aplikację w której aplikacja .Net współpracuje z aplikacją napisaną w PHP za pośrednictwem gołego http.

Sprawa nie jest szczególnie złożona jednak jak się później okazało nie jest taka prosta jak myślałem. Fragmenty kodu zawarte w Blogu pochodzą z przykładowego programu dostępnego do pobrania na końcu postu.

To co było proste:

Komunikacja PHP z dowolną inną platformą odbywa się za pomocą protokołu HTTP który jest ogólnie dobrze opisany np. tutaj. Dodatkowo w większości platform istnieją dedykowane biblioteki obsługujące w mniejszym lub większym stopniu komunikację przez http, a biblioteka standardowa .Net nie jest pod tym względem wyjątkiem(tfu!).

Komunikację za pomocą http ułatwiają przynajmniej dwie klasy HttpWebRequest i WebClient. Klasa WebClient ma w moim mniemaniu znacznie bardziej intuicyjny interfejs i to jej będę się starał używać.

Wywołanie GET:

Wywołanie GET jest odwołaniem się do strony www równoważnym z wpisaniem adresu w przeglądarce internetowej, parametry do serwera wysyła się dopisując po adresie strony znak zapytania i wartości tych parametrów w formacie parametr1=wartosc1&parametr2=wartosc2&parametr3=wartosc3. Ograniczeniem tej metody jest fakt iż długość adresu strony wraz z wartościami parametrów nie może przekraczać 256znaków.

Metody tej nie powinno się także stosować na stronach w celu przekazywania danych wrażliwych (jak np. login i hasło użytkownika).

Do zbudowania łańcucha połączeniowego można użyć klasy UriBuilder, jest to klasa która na podstawie fragmentów adresu potrafi złożyć pełną ścieżkę do pliku/katalogu także na serwerze www.

Klasą która się przydaje jest także HttpUtility posiadająca metodę UrlEncode zwalniającą programistę z wstawiania odpowiednich znaków ucieczki w wysyłanym zapytaniu do serwera, niestety klasa ta znajduje się w przestrzeni nazw System.Web w bibliotece o tej samej nazwie, biblioteka ta zaś nie znajduje się w Wersji Client Profile platformy .Net. Tak więc jeżeli klasy tej chcemy użyć w projekcie Windows Forms lub WPF musimy dokonać następujących zmian w projekcie:

1) Zapisanie wszystkich niezapisanych plików.

2) W Solution Explorze klikamy prawym przyciskiem myszy na projekt i wybieramy Properties

3) W zakładce Application znajdujemy Combobox Target platform i wybieramy w nim .Net Framework 4.0

4) Zapisujemy wszysto i zamykamy właściwości projektu

5) Klikamy prawym przyciskiem w katalog refereces w projekcie i wybieramy AddReference

6) Przechodzimy na zakładkę .Net i wybieramy System.Web

7) Zatwierdzamy dialog i możemy używać klasy System.Web.HttpUtility

Najprostszy kod pobierający treść strony www może wyglądać tak.

WebClient client = new WebClient();
// Przy niektórych serwerach aplikacja powinna się przedstawić 
//client.Headers.Add("user-agent", "PHP and dotNet");

UriBuilder queryBuilder = new UriBuilder(tbServer.Text + "helloGet.php");
queryBuilder.Query = string.Format("name={0}", HttpUtility.UrlEncode(tbName.Text));

string result = client.DownloadString(queryBuilder.Uri);
MessageBox.Show(this, result);

plik helloGet.php zawiera chyba najprostrzą możliwą obsługę zapytania GET:

<?php
echo 'Witaj '.$_GET['name'];
?>

Pobranie plików z serwera:

Zadanie trochę mniej związane z PHP, aczkolwiek pojawiające się w pracy z http to pobranie pliku z serwera http. Tutaj klasa WebClient oferuje dwie możliwości.

Metoda DownloadFile podaje się adres pliku i nazwę pliku nic prostszego

using (SaveFileDialog sfd = new SaveFileDialog())
{
    sfd.Filter = "Pliki jpeg|*.jpg";
    if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WebClient client = new WebClient();
        client.DownloadFile("http://www.programy.witraze-plocin.pl/wp-content/uploads/2010/04/cycki226jo5.jpg", sfd.FileName);
    }
}

Metoda DownloadData zwraca tablicę bajtów składających się na plik metoda może być przydatna gdy pobrane dane chcemy tylko obrobić bez zapisywania ich na dysku. Np. wyświetlić obrazek w pictureBoxie.

WebClient client = new WebClient();
byte[] buffer = client.DownloadData("http://www.programy.witraze-plocin.pl/wp-content/uploads/2010/04/cycki226jo5.jpg");

MemoryStream stream = new MemoryStream(buffer);

// Kwestie techniczne
if (pbCycki.Image != null)
{
    Image oldImage = pbCycki.Image;
    pbCycki.Image = null;
    oldImage.Dispose();
}

pbCycki.Image = Image.FromStream(stream);

Wadą tej metody jest spore zajęcie pamięci ponieważ cały plik jest wgrywany do RAMu, dodatkowo aplikacja musi pobrać cały plik i dopiero wtedy go obrobić. Przypadkiem gdy żadna z powyższych metod nie zastosowania jest pobranie dużego pliku z serwera i zapisanie go do strumienia nie będącego plikiem(np. wysłać portem COM do jakiegoś urządzenia). W tym przypadku należy wykorzystać metodę WebRequest.

private void FromHttpToStream(Stream outStream, string url)
{
    WebClient client = new WebClient();
    client.OpenRead(url).CopyTo(outStream);
}

Wywołanie POST:

Wysłanie zapytania POST do serwera http przy pomocy klasy HttpClient jest dosyć niskopoziomowe. Ponieważ należy wykonać następujące kroki:

1) Dodać do nagłówka wiadomości informację o rodzaju przesyłanej treści "content-type: application/x-www-form-urlencoded"

2) Sformatować treść wysyłanej wiadomości przy UrlEncode

3) Zakodować informację przy pomocy kodowania ASCII i wysłać na serwer.

4) Rozkodować przesłaną wiadomość przy pomocy kodowania użytego na stronie.

WebClient client = new WebClient();
// Przy niektórych serwerach aplikacja powinna się przedstawić 
//client.Headers.Add("user-agent", "PHP and dotNet");

client.Headers.Add("Content-Type", "application/x-www-form-urlencoded");

byte[] buffer = Encoding.ASCII.GetBytes(string.Format("name={0}", HttpUtility.UrlEncode(tbName.Text)));
buffer = client.UploadData(tbServer.Text + "helloPost.php", "POST", buffer);
string result = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
MessageBox.Show(this, result);

Upload plików:

Upload plików też jest dość prosty klasa WebClient posiada metodę UploadFile, która jako parametry pobiera nazwę wysyłanego pliku i adres pliku do którego wysyłana jest zawartość pliku.

using (OpenFileDialog ofd = new OpenFileDialog())
{
    ofd.Filter = "Pliki jpeg|*jpg";
    if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
    {
        WebClient client = new WebClient();
        client.UploadFile(tbServer.Text + "uploadFile.php", ofd.FileName);
    }
}

Po stronie serwera wysyłany plik jest opisany w tabeli $_FILES w polu 'file' obsługa pliku. Przykładowa obsługa może wyglądać następująco:

<?php
$tempFile = $_FILES['file']['tmp_name'];
copy($tempFile, 'uploaded.jpg');
unlink($tempFile);
?>

Pliki z kodem przykładowego programu są do pobrania tutaj.

Słów kilka o chciwości

Oglądałem właśnie program Johna Stossela na temat chciwości.

Od dłuższego czasu oglądam jego programy i muszę stwierdzić że poglądy przez niego wypowiadane bardzo mi odpowiadają. Wszyscy znamy chciwość jako jeden z grzechów głównych. Prowadzący ten program pokazuje jednak, że bycie chciwym w inteligentny sposób jest dobre nie tylko dla nas ale także dla ludzi w naszym bliższym i dalszym otoczeniu.

Zachęcam gorąco do obejrzenia tego programu.

Google web elements

Przegladajac sieć natknąłem się na stronę Google Web page elements. Zachwyciła mnie łatwość ich zastosowania (wystarczy kilka linijek HTML + JS). Stwierdziłem że u siebie na stronie wykorzystam 2 z nich.

1) Translate pozwala skorzystać z serwisu Google Translate do przetłumaczenia zawartości strony. Sam na 100% nie będę nigdy podejmował prób przetłumaczenia materiałów tutaj prezentowanych, zwłaszcza że nie mam wejść z innych krajów niż Polska. A tłumaczenie zapewnione przez google może nie jest idealne ale wystarcza do zrozumienia sensu treści.

2) Virtual Keyboard pozwala na obsługę strony bez konieczności korzystania z fizycznej klawiatury (przy pomocy myszki lub ekranu dotykowego). Minusem tego gadżetu jest fakt że na Ubuntu pod Chrome rozwinięta klawiatura blokuje wpisywanie polskich znaków (pojawiają się ich polskie odpowiedniki) jednak zwinięcie klawiatury usuwa tą niedogodność.