LINQ i Deferred Execution

Nie każdy początkujący programista, który zaczyna swoją przygodę z platformą .NET jest świadomy działania  mechanizmów zawartych w technologi którą poznaję. Dobrym przykładem może być tu tworzenie zapytań za pomocą LINQ i Deferred Execution (opuźnione wywołanie). No ale co to oznacza, a no mniej więcej tyle , że zapytania tworzone za pomocą LINQ nie są automatycznie wykonywane podczas ich tworzenia. Wykonanie zapytania jest odłożone w czasie. Aby to wszystko lepiej zrozumieć  przejdźmy do przykładu:

[csharp]
int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var result = from n in numbers where n < 5 select n;
numbers[0] = -5;

foreach (var i in result)
{
Console.Write("{0},", i);
}

[/csharp]

Resultat powyższego kody to ciąg liczb: -5,4,1,3,2,0. Zatem jak widać po utworzeniu zapytania nie zostało ono wykonane. Wywołanie zapytania miało miejsce dopiero podczas użycia pętli foreach i element który zmieniliśmy został uwzględniony w końcowym zbiorze.

Skoro już wiemy, że wykonanie zapytania stworzonego za pomocą LINQ nie jest automatycznie wykonywane to pewnie chcieli byście wiedzieć co zrobić żeby od razu dostać dane które chcemy. Odpowiedź jest dość prosta, wystarczy po utworzeniu zapytania wywołać metodę która zwróci nam końcowy zbiór elementów np. ToList, ToArray.

[csharp]int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var result = (from n in numbers where n < 5 select n).ToList();
numbers[0] = -5;

foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();[/csharp]

Wykonanie już takiego kody zwróci nam następujące liczby: 4,1,3,2,0.

Z Deferred Execution w LINQ wiąże się też jedna użyteczna funkcjonalność, a mianowicie możliwość ponownego użycia stworzonego zapytania. Dzięki temu tworzymy zapytanie raz i wykonujemy je dowolną ilość razy na zmodyfikowanym zbiorze.

[csharp]int[] numbers = new int[] { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };

var result = from n in numbers where n <= 8 select n;

Console.WriteLine("Result count: {0}",result.Count());

numbers[0] = 11;
Console.WriteLine("Result count (after data changes): {0}", result.Count());

Console.WriteLine("First run");

foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();

for (int i = 0; i < numbers.Length; ++i)
{
numbers[i] = -numbers[i];
}

Console.WriteLine("Second run (after data changes)");

foreach (var i in result)
{
Console.Write("{0},", i);
}
Console.WriteLine();
[/csharp]
Rezultat wykonania powyższego kody będzie następujący:

Result count: 9
Result count (after data changes): 8
First run
4,1,3,8,6,7,2,0,
Second run (after data changes)
-11,-4,-1,-3,-9,-8,-6,-7,-2,0,

Świadomość istnienia mechanizmów takich jak Deferred Execution w LINQ jest bardzo ważna, gdyż taka wiedza uchroni nas przed błędami, których wykrycie staje się bardzo ciężkie. Tak więc podsumowując warto zagłębiać się w tajniki  technologi aby świadomie używać tego co oferuje nam dana technologia.

Przykładowy program można pobrać klikając na poniższy link:[dm]3[/dm]

Wybieranie elementów klasy (Lambda Expression)

System refleksji dostępny w .NET daje programiście duże możliwości operowanie strukturami klas podczas działania programu. Pobieranie informacji o poszczególnych elementach klasy wiążę się zazwyczaj z wywołaniem odpowiedniej metody i podania nazwy elementu klasy o którym informację chcielibyśmy uzyskać. Taki sposób operowania wewnętrzną strukturą programu jest mało wygodny i może powodować błędy podczas działania aplikacji.

Pewnie nie raz spotkaliście się z poniższym zapisem np. pisząc aplikację w ASP.NET MVC czy korzystając z frameworku Fluent NHibernate do definiowania mapowań dla NHibernate’a.

[csharp]
GetProperty(x => x.Name);
GetProperty(x => x.LastName);
GetProperty(x => x.Address.City);
[/csharp]

Wielu z was pewnie zastanawiało się jak zastosować taki mechanizm w pisanej bibliotece czy aplikacji. Z uwagi na to, że jakiś czas temu pisałem bibliotekę wykorzystującą taki sposób wskazywania elementów klasy postanowiłem, że opiszę pokrótce jak go zastosować. Główną zaletą stosowania takiego zapisu jest fakt, że dostajemy już na etapie kompilacji weryfikację poprawności takiego wyrażenia i automatyczne podpowiadanie składni na etapie jego tworzenia.

Żeby więcej nie przynudzać przejdę od razu do zaprezentowanie przykładu zastosowania wspomnianego mechanizmu. Zacznijmy od dwóch prostych klas których elementy będziemy wybierać:

[csharp]public enum Gender { Male, Female };

public class Person
{
public long Id { get; private set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Profession { get; set; }
public Gender Gender { get; set; }
public DateTime BirthDate { get; set; }
public Address Address { get; set; }
}
[/csharp]

[csharp]public class Address
{
public string City { get; set; }
public string Street { get; set; }
public int Home { get; set; }
}
[/csharp]

Jak widzimy są to klasy reprezentujące odpowiednio osobę jak i adres. Aby móc pobierać informację o elementach klas musimy mieć narzędzie które nam to umożliwi. Do tego celu stworzymy prostą klasę generyczną która zwróci nam odpowiednie obiekty zawierające informację które chcemy uzyskać.

[csharp]
public class Info<T> where T: class
{
public PropertyInfo GetProperty(Expression<Func<T,object>> expression)
{
return ReflectionHelper.GetMemberExpression(expression.Body).Member as PropertyInfo;
}

public PropertyInfo[] GetAllPropertiesFromExpression(Expression<Func<T, object>> expression)
{
return ReflectionHelper
.GetPropertiesFromExpression(expression.Body);
}
}
[/csharp]

Jak widać powyżej klasa posiada dwie meody:

  • GetProperty – zwracająca obiekt informacyjny o właściwości wskazanej przez wyrażenie lambda
  • GetAllPropertiesFromExpression –  metoda zwracająca wszystkie właściwości w całym wyrażeniu

Obie metody jako parametr przyjmują System.Linq.Expressions.Expression<Func<T, object>>. Fakt, że w wyrażeniu operujemy delegatem funkcji daje nam to możliwość zastosowania wyrażeń lambda do ich tworzenia dzięki czemu uzyskujemy prosty zapis i podpowiadanie składni przy ich pisaniu.

Aby pobrać interesujące nas informację nie korzystamy z całości wyrażenia, a jedynie z jego części po operatorze  “=>”. Dostęp do tej części uzyskujemy poprzez właściwość Body.  Obiekt klasy System.Linq.Expressions.Expression zwracany przez właściwość Body nie posiada elementów z których możemy bezpośrednio uzyskać informację o członkach klasy które nas interesują. System.Linq.Expressions.Expression jest bazową klasą wszystkich wyrażeń i musimy ją rzutować, w naszym przypadku na obiekty klasy MemberExpression. Metoda realizująca to zadanie znajduje się poniżej.

[csharp]
public static MemberExpression GetMemberExpression(Expression expression)
{
MemberExpression memberExpression = null;
if (expression.NodeType == ExpressionType.Convert)
{
UnaryExpression body = (UnaryExpression)expression;
memberExpression = body.Operand as MemberExpression;
}
else if (expression.NodeType == ExpressionType.MemberAccess)
{
memberExpression = expression as MemberExpression;
}
if (memberExpression == null)
{
throw new ArgumentException(
String.Format("Expression {0} is not MemberExpression", expression.NodeType));
}
return memberExpression;
}
[/csharp]

W metodzie tej zawarta jest również część odpowiedzialna za rzutowanie wyrażenia na UnaryExpression i dopiero po tym uzyskujemy z niego obiekt klasy MemberExpression. Taka operacja jest wymagana aby dodać obsługę obiektów ValueType.  Bez zastosowania UnaryExpression nie moglibyśmy pobierać informacji o członkach klasy będących ValueType.

Metoda  GetMemberExpression umożliwia pobranie tylko ostatniego elementu wyrażenia. Jeżeli byśmy chcieli pobrać wszystkie właściwości z wyrażenia składającego się z kilku elementów musimy pobierać te elementy pojedynczo zaczynając od ostatniego i cofając się do elementu będącego parametrem. Metodę realizująca do zadanie widzimy poniżej.

[csharp]
public static PropertyInfo[] GetPropertiesFromExpression(Expression expression)
{
List properties = new List();
MemberExpression memberExpression = null;
Expression ex = expression;
do
{
memberExpression = ReflectionHelper.GetMemberExpression(ex);
properties.Add((PropertyInfo)memberExpression.Member);
ex = memberExpression.Expression;
}
while (ex.NodeType != ExpressionType.Parameter);

properties.Reverse();
return properties.ToArray();
}
[/csharp]

Mając gotowe narzędzia możemy je wykorzystać w testowym programie który mógłby wyglądać mniej więcej tak:

[csharp]
Console.WriteLine("Person class:");
Info personInfo = new Info();

Console.WriteLine("Get \"Name\" Property: x => x.Name");
PropertyInfo personName = personInfo.GetProperty(x => x.Name);
Console.WriteLine("Type: {0}, Property: {1}",
personName.DeclaringType.Name, personName.Name);

Console.WriteLine();
Console.WriteLine("Get all properties from expression: x => x.Address.City.Length");
var properties = personInfo.GetAllPropertiesFromExpression(x => x.Address.City.Length);
foreach (var item in properties)
{
Console.WriteLine("Type: {0}, Property: {1}",
item.DeclaringType.Name, item.Name);
}
[/csharp]

Po wykonaniu powyższego kawałku kodu na ekranie powinniśmy zobaczyć następujący efekt:

Person class:
Get "Name" Property: x => x.Name
Type: Person, Property: Name

Get all properties from expression: x => x.Address.City.Length
Type: Person, Property: Address
Type: Address, Property: City
Type: String, Property: Length

Kod zawierający program testowy wraz z wszystkimi opisanymi klasami można pobrać tutaj:
[dm]2[/dm]

Budowanie i parsowanie adresów URL w .NET

Tworzenie i parsowanie adresów URL wykorzystując narzędzia dostarczone przez platformę .NET.

Traktowanie adresu URL jako stringa w programach jest dość wygodne jeżeli służy on tylko do przechowania adresu danego zasobu sieciowego. Jeżeli jednak takie adresy musimy generować lub wyciągać z nich niektóre parametry używanie stringa staje się nie praktyczne i wymusza na nas pisanie dodatkowych narzędzi. Wszystko w tym było by ok jeżeli pominąć fakt że framework .NET dostarcza nam właśnie zestaw klas do całej gamy operacji na adresach sieciowych.

Relacje między URI, a URL i URN.

Główną klasą odpowiedzialną za przechowywania adresów sieciowych jest klasa Uri. Jak widać nazwa klasy wskazuje, że posługujemy się URI (Uniform Resource Identifier), czyli czymś nadrzędnym niż sam URL.

Zatem platforma .NET dostarcza nam narzędzie o szerszym zastosowaniu niż budowanie samych adresów URL. Aby móc korzystać z klasy Uri jak i innych wymienionych w tym wpisie musimy do naszego projektu dodać referencję do System.ServiceModel.

Dzięki klasie Uri oprócz przechowywania danego identyfikatora sieciowego mamy możliwość weryfikacji jego poprawności przy tworzeniu takiego obiektu, jak również dostęp do poszczególnych jego części. Za pomocą tej klasy możemy tworzyć zarówno adresy względne (relative) jak i bezwzględne (absolute).

[csharp autolinks=”false”]Uri relative = new Uri("/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net", UriKind.Relative);

Uri absolute = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net", UriKind.Absolute);
[/csharp]

Warto zapamiętać fakt, że obiektów klasy Uri nie można modyfikować gdyż wszystkie właściowści tej klasy umożliwiają tylko pobranie danych. Jeżeli chcemy mieć możliwość modyfikacji obiektu po jego utworzeniu powinniśmy użyć do tego celu klasy UriBuilder.

[csharp]
UriBuilder blogUri = new UriBuilder();
blogUri.Scheme = Uri.UriSchemeHttp;
blogUri.Host = "blog.pietowski.com";
blogUri.Query = "file_id=2&lang=pl";

Console.WriteLine(blogUri.Uri);
[/csharp]

Jednak najbardziej przydatną i użyteczną klasą wydaje się być UriTemplate gdyż umożliwia nam definiowanie szablonów naszego identyfikatora sieciowego. Dzięki czemu możemy np. tworzyć automatycznie wiele adresów URL z różnymi parametrami jak również pobierać wybrane parametry z gotowych już adresów.

Elementem niezbędnym do utworzenia obiektu klasy UriTemplate jest string zawierający szablon tworzonych URI, który przekazujemy do konstruktora tej klasy. Format takiego stringa może mieć różne postacie, na przykład:

  • /2010/08/12/budowanie-i-parsowanie-adresow-url-w-net
  • /2010/{mounth}/{day}/{title}
  • /{year}/*?file_id={id}
  • /{year}/{mounth}/{day}/{title}?file_id={id}
  • /{year}/{mounth}/{day}/{title}?file_id=2

Przy tworzeniu obiektu weryfikowana jest poprawność wprowadzonego formatu zatem nie musimy się martwić, że przez przypadek wprowadzimy błędne dane. Nazwy zmiennych zamiast których będą wstawiane odpowiednie wartości zapisujemy między nawiasami klamrowymi “{” “}”. Wstawienie odpowiednich wartości do adresu uzyskujemy przy pomocy metody BindByPosition(Uri, String[]) do której przekazujemy bazowy adres i wszystkie parametry w kolejności w jakiej występują w szablonie.

[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri positionalUri = template.BindByPosition(baseAddress, "2010", "08", "12","budowanie-i-parsowanie-adresow-url-w-net");
[/csharp]

Innym sposobem przekazania parametrów jest  utworzenie kolekcji NameValueCollection. Do obiektu takiej kolekcji dodajemy pary: nazwę parametru i jego wartość. Wynikowy Uri uzyskujemy wykonując metodę BindByName(Uri, NameValueCollection)do której przekazujemy bazowy adres i stworzoną kolekcję parametrów, co obrazuje poniższy przykład.

[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}");
Uri baseAddress = new Uri("http://blog.pietowski.com");

NameValueCollection parameters = new NameValueCollection();
parameters.Add("year", "2010");
parameters.Add("mounth", "08");
parameters.Add("day", "12");
parameters.Add("title", "budowanie-i-parsowanie-adresow-url-w-net");

Uri namedUri = template.BindByName(baseAddress, parameters);
[/csharp]

Szczególne znaczenie w szablonie identyfikatora URI ma gwiazdka “*”, która służy do zastępowania pozostałej część ścieżki adresu.  Symbol gwiazdki musi wystąpić raz  i musi znajdować się na końcu ścieżki tworzonego szablonu URI. Stosowanie takiego zapisu przydaje się najbardziej w czasie dopasowywania gotowych Uri do stworzonego szablonu, służy do tego metoda Match(Uri, Uri). Metoda ta zwraca obiekt klasy UriTemplateMatch, z którego możemy pobierać poszczególne parametry szablonu ze wskazanego obiektu klasy Uri. Jeżeli wskazany Uri nie pasuje do szablonu zostanie zwrócony null.

Właściwość WildcardPathSegments w klasie UriTemplateMatch umożliwia pobranie segmentów pozostałej ścieżki, którą w szablonie zastąpiliśmy “*”:

[csharp autolinks=”false”]UriTemplate template = new UriTemplate("/{year}/*?file_id={id}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri fullUri = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net?file_id=2");

// Match a URI to a template
UriTemplateMatch results = template.Match(baseAddress, fullUri);
if (results != null)
{
Console.WriteLine("WildcardPathSegments:");
foreach (string segment in results.WildcardPathSegments)
{
Console.WriteLine("{0}", segment);
}
}
[/csharp]

Powyższy kod zwróci nam następujący wynik:

WildcardPathSegments:
08
12
budowanie-i-parsowanie-adresow-url-w-net

Klasa UriTemplateMatch posiada poza wspomnianą powyżej wiele różnych właściwości dzięki którym możemy pobierać poszczególne elementy ścieżki, nazwy i wartości parametrów jak i wiele innych. Poniższy przykład prezentuję zastosowanie kilku z nich:

[csharp autolinks=”false”]
UriTemplate template = new UriTemplate("/{year}/{mounth}/{day}/{title}?file_id={id}&lang={lang}");
Uri baseAddress = new Uri("http://blog.pietowski.com");
Uri fullUri = new Uri("http://blog.pietowski.com/2010/08/12/budowanie-i-parsowanie-adresow-url-w-net?file_id=2&lang=pl");

UriTemplateMatch results = template.Match(baseAddress, fullUri);
if (results != null)
{
Console.WriteLine("BoundVariables:");
foreach (string variableName in results.BoundVariables.Keys)
{
Console.WriteLine("{0}: {1}",
variableName, results.BoundVariables[variableName]);
}
Console.WriteLine();

Console.WriteLine("QueryParameters:");
foreach (string queryName in results.QueryParameters.Keys)
{
Console.WriteLine("{0} : {1}",
queryName, results.QueryParameters[queryName]);
}
Console.WriteLine();

Console.WriteLine("RelativePathSegments:");
foreach (string segment in results.RelativePathSegments)
{
Console.WriteLine("{0}", segment);
}
}
[/csharp]

Po wykonaniu takiego kodu ujżymy w konsoli następujący wynik.

BoundVariables:
YEAR: 2010
MOUNTH: 08
DAY: 12
TITLE: budowanie-i-parsowanie-adresow-url-w-net
ID: 2
LANG: pl

QueryParameters:
file_id : 2
lang : pl

RelativePathSegments:
2010
08
12
budowanie-i-parsowanie-adresow-url-w-net

Teraz przed pisaniem wyrażeń regularnych czy budowaniem własnych parserów warto przemyśleć zastosowanie gotowego rozwiązania jakie dostarcza nam platforma .NET gdyż w większości przypadków może to być najlepsze wyjście, które znacznie ułatwi nam posługiwanie się adresami sieciowymi w naszej aplikacji.

Więcej informacji odnoście opisywanych klas można znaleźć na stronach MSDN.

Przywracanie GRUB’a

Przywracanie bootloadera GRUB za pomocą systemu systemu linux LiveCD na przykładzie Ubuntu.

Aby móc przywrócić nasz boot loader po pierwsze musimy zaopatrzyć się w jakąkolwiek wersję linuxa dostępną na LiveCD, najpopularniejszą i chyba najbardziej znaną jest Ubuntu. Najnowszą wersję tego systemu możemy pobrać tutaj. Jeżeli wolimy Ubuntu w ojczystym języku pobieramy go stąd.

Jeżeli już posiadamy wspomniany wcześniej system, uruchamiamy nasz komputer bootując go z płyty CD.

Po uruchomieniu systemu włączamy terminal i wpisujemy:

$ sudo fdisk -l

Polecenie to wylistuje wszystkie dostępne partycje w naszym komputerze. W wyświetlonej liście poszukujemy partycji, na której zainstalowaliśmy naszą dystrybucję systemu linux i zapamiętujemy, które do urządzenie. W moim przypadku będzie to /dev/sda1.

Kolejnym krokiem jest zamontowanie wybranej partycji w wybranym katalogu, co robimy poleceniem:

$ sudo mount /dev/sda1 /mnt

Jak widzimy montujemy naszą partycję linuksową w katalogu /mnt.

Jeżeli posiadamy katalog /boot na oddzielnej partycji musimy ją również zamontować (na przykład przyjmijmy, że katalog ten znajduje sie na /dev/sda2):

$ sudo mount /dev/sda2 /mnt/boot

Następnie montujemy pozostałe elementy, które będą nam potrzebne przy zmianie głównego katalogu systemu:

$ sudo mount –bind /dev /mnt/dev
$ sudo mount –bind /proc /mnt/proc
$ sudo mount –bind /sys /mnt/sys

Teraz możemy przystąpić do zmiany głównego katalogu w jakim będzie pracował system. Zmiany dokonujemy poleceniem chroot.

$ sudo chroot /mnt

Dzięki komendzie chroot możemy pracować na plikach zainstalowanej dystrybucji linuxa na naszej zamontowanej partycji.

Obecnie posiadamy prawa root’a w systemie i nie musimy już wywoływać polecenia sudo.

Możemy teraz przystąpić do naprawy naszego bootloadera. Edytujemy plik /etc/default/grub aby zmienić konfigurację GRUB’a.

$ nano /etc/default/grub

Kiedy skończymy edycję musimy wykonać polecenie:

$ update-grub

w celu stworzenia nowego pliku konfiguracyjnego.

Jeżeli chcemy dokonać instalacji GRUB 2 w MBR, musimy uruchomić polecenie grub-install i wskazać partycję, na której dokonujemy instalacji:

$ grub-install /dev/sda

Jeżeli podczas wykonywania powyższego polecenia wystąpią jakieś błędy możemy spróbować wykonać polecenie:

$ grub-install –recheck /dev/sda

Jeżeli wszystko przebiegło pomyślnie wciskamy Ctrl+D aby powrócić do wcześniejszego katalogu głównego systemu.

Przed zakończeniem pracy musimy odmontować wszystkie wcześniej zamontowane urządzenia zaczynając od:

$ sudo umount /mnt/dev
$ sudo umount /mnt/sys
$ sudo umount /mnt/proc

Teraz już możemy odmontować główny system plików, ale jeśli montowaliśmy oddzielną partycję bootującą (/boot) musimy ją pierwszą odmontować i dopiero potem główny system plików:

$ sudo umount /mnt

Gdy odmontujemy już wszystkie urządzenia możemy wyłączyć komputer i wyjąć płytę z Ubuntu. Po ponownym włączeniu komputera powinien ukazać się naszym oczom ekran wyboru systemu GRUB’a.

SuperGrubDisk

Alternatywnym rozwiązaniem jest wykorzystanie niedużej dystrybucji linuxa SuperGrubDisk umożliwia ona wykrywanie i bootowanie systemu, jak również przywrócenie i instalację GRUB’a. Na stronie projektu znajdziemy również wiele przydatnych informacji dotyczących problemów z bootowanie systemu i GRUB’em.


Tekst powstał na podstawie: Recover Grub 2 via LiveCD

Parsowanie dokumentu HTML w .NET

Pewnie nie raz zastanawialiście się jak szybko i bez nadmiernego wysiłku wyciągnąć informacje z dokumentu HTML. Niedomknięty znacznik czy brak apostrofów to już standard w większości stron. XHTML po części rozwiązuje niektóre problemy, ale stron w pełni walidowanych też za dużo nie uświadczymy. Mimo podobieństw większość stron HTML nie  możemy traktować jak dokumentów XML. Platforma .NET nie dostarcza nam narzędzi do parsowania dokumentów HTML, pozostaje nam wiec korzystanie z zewnętrznych bibliotek.

W niniejszym wpisie chciałbym przedstawić bibliotekę którą poznałem już jakiś czas temu: Html Agility Pack, która nie raz już ułatwiła mi pracę. Główną zaletą tej biblioteki jest możliwość poruszanie sie po dokumencie HTML jak po dokumencie XML. Do wybierania elementów dokumentu mozemy uzyci języka XPath lub korzystając z LINQ (od wersji 1.4.0). Sam proces używania i posługiwania się wspomnianą biblioteką jest dość prosty i pokrótce zaprezentują go poniżej.

Oczywiście pierwszą czynnością jaką musimy wykonać aby móc korzystać z tej biblioteki jest dodanie do projektu referencji do pliku HTMLAgilityPack.dll. Po tej czynnosci mozemy korzystac z elementow jakie dostarcza nam biblioteka.

Klasą reprezentującą nasz dokument HTML jest HtmlDocument. Obiekt tej klasy tworzymy korzystając z domyślnego konstruktora.

[csharp]
WebClient client = new WebClient();
string html = client.DownloadString("http://blog.pietowski.com");

HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(html);
[/csharp]

Dokument HTML możemy wczytać korzystając z metody Load do której możemy przekazać strumień, obiekt klasy TextReader lub ścieżkę do pliku. Alternatywą jest użycie medody LoadHtml, którą wykorzystałem w powyższym przykładzie, wczytującej dokument bezpośrednio z obiektu klasy System.String. Przed wczytaniem dokumentu możemy ustawić odpowiednie opcje parsowania ustawiając odpowiednie wartości polom o nazwach w formacie:  OptionsXXX.

W celu pobranie błędów parsowania korzystamy z  dostępnej właściwości ParseErrors:

[csharp]
Console.WriteLine("Parse errors:");
foreach(HtmlParseError error in doc.ParseErrors)
{
Console.WriteLine(error.Reason);
}
[/csharp]

Główny węzeł dokumentu dostępny jest pod właściwością DocumentNode korzystając z tego obiektu możemy przeglądać kolejne węzły wczytanego dokumentu. W celu pobrania elementu na podstawie identyfikatora używamy metody GetElementbyId.

[csharp]
HtmlNode blogDescription = doc.GetElementbyId("blog-description");
if(blogDescription != null)
{
Console.WriteLine("Blog description: {0}",blogDescription.InnerText);
}
[/csharp]

Jeśli chcemy wyszukać konkretne węzły w naszym dokumencie możemy skorzystać z LINQ:

[csharp]
IEnumerable<HtmlNode> links = from link in doc.DocumentNode.DescendantNodes()
where link.Name == "a" && link.Attributes["href"] != null
select link;

IEnumerable<HtmlNode> links2 = doc.DocumentNode.DescendantNodes()
.Where(x=>x.Name == "a" && x.Attributes["href"] != null);
[/csharp]

lub wykorzystując język XPath:

[csharp]
HtmlNodeCollection xpathLinks =
doc.DocumentNode.SelectNodes("//a[@href]");

Console.WriteLine("Links:");
foreach(var link in links)
{
Console.WriteLine(link.Attributes["href"].Value);
}
[/csharp]

Najnowszą wersję opisywanej biblioteki można znaleźć na stronie http://htmlagilitypack.codeplex.com/

Projekt demonstrujący wykorzystanie HtmlAgilityPack można pobrać tutaj.