Updated on

AngleSharp is the most standards-compliant HTML parser for C#: it parses markup exactly the way a browser does and gives us a real DOM we can query with CSS selectors, traverse, and modify.

That makes it the right tool for web scraping and HTML processing where correctness matters. It’s also the main alternative to Html Agility Pack, which tolerates broken markup but speaks XPath instead of CSS.

To download the source code for this article, you can visit our GitHub repository.

What Is AngleSharp?

AngleSharp is an open-source .NET library for parsing and manipulating HTML, and by extension the related web formats: CSS, SVG, and MathML. Its defining feature is standards compliance: it implements the same W3C HTML5 specification (with WHATWG extensions) browsers use, so malformed markup is corrected exactly the way Chrome or Firefox would correct it, and the result is a genuine DOM, not a lenient tag soup.

We query that DOM with the APIs we already know from JavaScript (QuerySelector(), QuerySelectorAll(), GetElementById()) using CSS selectors, and we can also filter elements with LINQ. The most common use case is web scraping: fetch a page, select the nodes that matter, extract text and attributes.

Beyond parsing, AngleSharp can build and modify documents, submit forms, and, with the experimental AngleSharp.Js extension, execute embedded JavaScript. It is MIT-licensed and actively maintained; the current stable release is AngleSharp 1.6.0.

The error-correction behaviour is not incidental — the project’s README states that “the parser uses the HTML 5.1 specification, which defines error handling and element correction”.

Let’s now start using it. To add AngleSharp to our project, we can install it through the .NET CLI:

dotnet add package AngleSharp

Throughout the article, we’re going to be using a simple HTML string:

<!DOCTYPE html>
<html>
  <style>
    .blue {
       {
        color: blue;
      }
    }
  </style>
  <body>
    <h2>Title</h2>
    <section id="section">
      <div id="articles">
        <article id="a1">Article 1 <em>content</em>.</article>
      </div>
      <p class="paragraph">This is a paragraph.</p>
      <ul id="list">
        <li class="blue">Item 1</li>
        <li>Item 2</li>
        <li class="blue">Item 3</li>
      </ul>
      <form id="sign-up-form">
        <label for="username">Username: </label>
        <input id="username" name="username" type="text" />

        <label for="password">Password: </label>
        <input id="password" name="password" type="password" />

        <button id="button" type="submit">Sign up</button>
      </form>
    </section>
    <footer>Footer</footer>
  </body>
</html>

Now, let’s see its most basic usage of parsing our HTML string:

var config = Configuration.Default;
var context = BrowsingContext.New(config);

var document = await context.OpenAsync(req => req.Content(Html));

var articles = document
   .QuerySelectorAll<IHtmlElement>("article")
   .ToList();

var firstArticleTextContent = articles[0].TextContent;

First, we create an instance of the IBrowsingContext interface, which is a required construct for parsing HTML pages. We can think of it like a tab in a standard browser.

Then, we parse the HTML string using our context and get an instance of an IDocument in return. This is the in-memory representation of the DOM (Document Object Model) in AngleSharp.

Then, similarly to the Javascript DOM APIs, we can retrieve all article elements using the QuerySelectorAll() method on the document and specifying the article tag. Lastly, we can get the content of an article using the TextContent property.

Extracting Data Using Different Methods

Let’s get a bit more in-depth about the different capabilities of AngleSharp as well as showcase a web-scraping example illustrating its power.

AngleSharp gives us nice and elegant APIs, similar to the JavaScript DOM APIs, which we can use to query, traverse, and inspect the properties of HTML elements. We can use CSS selectors to find the elements we’re searching for:

var config = Configuration.Default;
var context = BrowsingContext.New(config);

var document = await context.OpenAsync(req => req.Content(Html));

var paragraphElements = document.Body
   .QuerySelectorAll<IHtmlParagraphElement>("p")
   .ToList();

var paragraphElementsLinq = document.All
       .Where(e => e.TagName.Equals("p", StringComparison.InvariantCultureIgnoreCase))
       .ToList();

Here, we’re querying for all paragraph elements using the p tag in our CSS selector. We could achieve the same by using the LINQ syntax via filtering elements on their TagName property.

Also, we could select elements based on their attributes like classes and/or id:

var blueListItemElements = document.Body
   .QuerySelectorAll<IHtmlListItemElement>("li.blue")
   .ToList();

var blueListItemElementsLinq = document.All
   .Where(e => e.LocalName == "li" && e.ClassList.Contains("blue"))
   .ToList();

var formElement = document.Body.QuerySelector<IHtmlFormElement>("form#sign-up-form");

var formElementLinq = document.All
   .First(e => e.TagName.ToLower() == "form"
               && (e.Id?.Equals("sign-up-form") ?? false));

var formElementById = document.GetElementById("sign-up-form") as IHtmlFormElement;

Here, we select all li elements that contain the class blue. We do this by using the CSS selector or LINQ to filter the ClassList. Similarly, we retrieve the first form element with the id sign-up-form.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

We can also query for an element using a CSS attribute selector:

var userNameInputElement = document.Body.QuerySelector<IHtmlInputElement>("form > input[name='username']");
var userNameInputElementLinq = document.All
   .First(e => e.LocalName == "input" && e.Attributes["name"]?.Value == "username");

This time, we retrieve the first input element that’s a child of a form element whose name attribute is username.

Apart from querying the DOM or the body, we can also retrieve different properties of each element:

var sectionInnerHtml = section.InnerHtml;
var sectionTextContent = section.TextContent;
var sectionAttributes = section.Attributes;
var sectionChildren = section.Children;

var nextSibling = section.NextElementSibling;
var previousSibling = section.PreviousElementSibling;

Here, we can get various properties such as attributes, class lists, inner HTML / text content, child nodes/elements, and more.

Using AngleSharp for a Mini Web-Scraper

Now, using the above knowledge, let’s illustrate an example where AngleSharp is most useful – we’re going to write a mini web-scraper for an online books catalog:

var booksCatalogUrl = "https://books.toscrape.com/";

var config = Configuration.Default.WithDefaultLoader();

var context = BrowsingContext.New(config);
var document = await context.OpenAsync(new Url(booksCatalogUrl));

var booksSection = document.QuerySelector<IHtmlElement>("div.page_inner section")!;

var bookInfoArticles = booksSection
            .QuerySelectorAll<IHtmlElement>("li > article.product_pod")
            .ToCollection();

Notice that neither .WithJs() nor a scripting engine appears here: the catalog page is static HTML, so WithDefaultLoader() alone is enough to fetch and parse it.

Firstly, we make an HTTP request to the books catalog website to retrieve the page’s document. Then we find the book section element by using a nested selector. Finally, we retrieve all article elements containing product information via another CSS selector.

Next, we retrieve the information for each book. We first create a record to represent the book data structure:

public record Book(string Title, decimal Price, double Rating, string ImageUrl);

Then, we create a helper method for retrieving a Book data structure from an IElement instance:

private static Book ToBook(IElement e)
{
   var imageUrl = e.QuerySelector<IHtmlImageElement>("div.image_container > a > img.thumbnail")!.Source;

   var titleElement = e.QuerySelector<IHtmlAnchorElement>("h3 > a")!;

   var title = titleElement.Title ?? titleElement.TextContent.Trim();

   var price = decimal.TryParse(
       e.QuerySelector<IHtmlParagraphElement>("div.product_price > p.price_color")!.TextContent.Replace("£",
           string.Empty),
       out var productPrice)
       ? productPrice
       : default;

   var ratingElementClassList = e.QuerySelector<IHtmlParagraphElement>("p.star-rating")!.ClassList;

   var otherClassName = ratingElementClassList.First(s => s != "star-rating");

   var rating = otherClassName switch
   {
      "One" => 1,
      "Two" => 2,
      "Three" => 3,
      "Four" => 4,
      "Five" => 5,
      _ => 0
   };

   return new Book(title, price, rating, imageUrl);
}

Here, we utilize different AngleSharp CSS selectors and properties of HTML elements to retrieve the full information for a single Book.

Firstly, we extract the src attribute from an image to get the book’s image URL. Also, we query for the title attribute from an a element. We can find the book’s price by retrieving the text content from a specific paragraph. And finally, we determine the star rating of the book by looking at a paragraph’s class list.

Now, building upon our previous example, we can get the final list of catalog books by using the retrieved article elements:

var books = bookInfoArticles.Select(ToBook).ToList();

DOM Manipulation

AngleSharp also gives us the power to directly manipulate and transform the IDocument object or any of its elements, exposing similar methods to the JavaScript APIs.

We can easily perform operations such as adding elements:

var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(req => req.Content(Html));

var paragraphElement = document.CreateElement<IHtmlParagraphElement>();
paragraphElement.TextContent = "This is a new paragraph.";

document.Body.AppendChild(paragraphElement);

Here, we create a p (paragraph) element using the CreateElement() method and set its text content. Then we append the element to the DOM with the AppendChild() method. Parsing a local string like this needs neither a loader nor the JS engine, so the context here is built from the plain default configuration.

Is this material useful to you? Consider subscribing and get ASP.NET Core Web API Best Practices eBook for FREE!

Conversely, we could remove elements as well:

var ulElement = document.QuerySelector<IHtmlUnorderedListElement>("ul#list")!;
var blueLiElement = ulElement.QuerySelector<IHtmlListItemElement>("li.blue")!;

blueLiElement.Remove();

ulElement.RemoveChild(blueLiElement);

This time, we remove the li element from the list matching the provided CSS selectors by using the RemoveChild() method.

Also, we can change an element’s properties, such as its attributes, classes, and text content / inner HTML:

var article = document.QuerySelector<IElement>("article#a1")!;
article.TextContent = "New article content";
article.InnerHtml = "New article content. <br /> Second article sentence.";

article.ClassList.Add("small-article");
article.Id = "news-article";

article.SetAttribute("data-category", "news");
article.RemoveAttribute("data-category");

By using the SetAttribute() and RemoveAttribute() methods, we can use AngleSharp to manipulate the DOM elements.

Other Advanced Capabilities

AngleSharp extends its capabilities beyond standard HTML parsing and DOM manipulation, offering advanced features like form submission and script execution using C#. With built-in mechanisms for form interaction, we can seamlessly simulate user inputs, submit forms, and capture resulting changes to the DOM.

AngleSharp’s script execution support allows us to interpret and execute embedded scripts in HTML documents, enabling us to manipulate the web content dynamically. These features make AngleSharp a compelling choice for applications requiring not just static analysis but also dynamic interaction with HTML-based interfaces.

AngleSharp.Js is an experimental extension, not a substitute for a real browser engine — for JS-heavy sites that render content client-side, tools built for automated UI tests with Selenium are the better fit.

AngleSharp vs Html Agility Pack: Which HTML Parser Should We Use?

Both libraries parse HTML into a queryable tree, and for most scraping tasks either will work. The difference is philosophy. AngleSharp follows the HTML5 specification strictly: whatever a browser would build from the markup, AngleSharp builds, which matters when we scrape modern sites whose layout depends on browser error-correction rules. It exposes a full DOM and CSS selectors, so anyone who has written document.querySelector() is immediately productive. AngleSharp’s own README puts the difference plainly: “The advantage over similar libraries like HtmlAgilityPack is that the exposed DOM is using the official W3C specified API.”

Html Agility Pack is older and more forgiving by design; it treats HTML as loosely structured data, navigates with XPath, and has a long track record on messy, legacy markup. Performance is comparable for typical documents, so speed should not decide this choice.

We pick AngleSharp when we want browser-accurate parsing, CSS selectors, or CSS/SVG support; we pick Html Agility Pack when we inherit XPath-based code or scrape old, badly broken pages.

CriterionAngleSharpHtml Agility Pack
Parsing modelW3C HTML5 spec (with WHATWG extensions), parses like a browserTolerant, HTML-as-XML style
SelectorsCSS selectors natively (QuerySelectorAll)XPath natively, CSS via extension
API shapeReal DOM (IDocument, IHtmlElement), mirrors JavaScriptHtmlNode tree
CSS/SVG/MathML parsingYes (separate AngleSharp.Css package for full CSSOM)No
Scripting supportOptional (AngleSharp.Js, experimental)No
LicenseMITMIT
Choose it whenScraping modern sites, browser-accurate parsing, CSS selectorsLegacy/malformed HTML, existing XPath skills

For fetching the markup we hand to AngleSharp in the first place, see our guide on fetching data with HttpClient, and for comparing HTTP client options, see HttpClient vs RestSharp.

Conclusion

In this article, we’ve learned how to utilize the AngleSharp library to perform various HTML Parsing operations in C#.

AngleSharp stands as a powerful HTML parsing library within the .NET ecosystem, distinguished by its rich set of features. It offers a robust CSS selector engine, form submission capabilities, reliable script execution support, and more. That combination makes it a strong choice for sophisticated HTML processing in C# applications.

Tested with .NET 10.0.10 and AngleSharp 1.6.0.