Skip to main content
SimpleFeed package icon

SimpleFeed

v4.0.0Latest Stable
net10.0

A rich and extremely tolerant .NET RSS and ATOM feed parser and writer. Parses the wildest real-world feeds without ever throwing, unifies RSS and Atom into one model, and writes clean canonical RSS 2.0 / Atom 1.0 back out.

Published
Aug 21, 2026
2mo ago
Total Downloads
15.1K
15,078

Install

dotnet add package SimpleFeed --version 4.0.0
ProductVersions
.NET
net10.0

README

SimpleFeed

A rich and extremely tolerant .NET RSS and Atom feed parser — and, as of v4, writer. In production parsing wild real-world feeds hourly since ~2018.

The central objectives, unchanged since the beginning:

  1. Get as much information as possible from a feed — including the standard extensions (iTunes podcast tags, media RSS, Dublin Core, content:encoded, rawvoice), and Atom-namespaced elements appearing inside RSS feeds.

  2. Be extremely fault tolerant. Real-world feeds are wild: obsolete EST-style timezones, missing dates, malformed authors, bare www. links, spaces inside URLs, mis-declared encodings, garbage bytes. Parsing never throws — failures land as Error + Messages on the result, invalid pieces are skipped, absent dates report honestly as DateTimeOffset.MinValue.

  3. One set of types for both formats. A single model (SFFeed / SFFeedEntry) represents what was originally RSS or Atom — without losing the format-specific information (typed Atom texts, category schemes, rel values, the original declared mime-type string, …).

  4. v4 makes good on the old promise: serialization. The same model writes back out as clean, canonical RSS 2.0 or Atom 1.0 — so SimpleFeed can now unify any wild feed into one well-formed output.

Install

dotnet add package SimpleFeed

v4 targets .NET 10. (v3.x, targeting netstandard2.0/net6.0, lives on the master branch for legacy patch releases.)

Reading a feed

using SimpleFeed;

byte[] bytes = await httpClient.GetByteArrayAsync(feedUrl); // fetching is yours; SimpleFeed parses
SFFeed feed = SFFeed.Parse(bytes);

if(feed.Error) {
	Console.WriteLine(feed.Messages); // never throws — failures report here
	return;
}

Console.WriteLine($"{feed.Title} — {feed.Items.Count} entries ({feed.SourceFeedType})");

foreach(SFFeedEntry e in feed.Items) {
	Console.WriteLine($"{e.Published:d}  {e.Title}  by {e.Author}");

	SFLink web = e.GetFirstWebLink_AlternateWins(); // the tournament: picks "the" web link among many
	SFLink enclosure = e.GetFirstEnclosure();       // podcast audio, PDFs, images…

	if(enclosure != null)
		Console.WriteLine($"    enclosure: {enclosure.Url} ({enclosure.Type}, {enclosure.Length} bytes)");
}

Parse accepts byte[] (preferred — honors BOMs and the XML prolog's declared encoding) or string. It always returns a non-null SFFeed; check Error.

Settings

SFFeed feed = SFFeed.Parse(bytes, new SFFeedSettings() {
	AlterUTCDatesToThisTimezone = TimeZoneInfo.FindSystemTimeZoneById("America/New_York"),
	NumberOfEntriesToParseLimit = 25,
	KeepXmlDocument = true,             // retain the source XElements (off by default)
	ConvertContentUrlsToLinks = true,   // harvest bare urls out of body text into Links (marked DiscoveredLink)
	GenerateIdForEntriesWithNoId = (entry, idBase) => idBase + Guid.NewGuid(),
	AlterEntryOnComplete = entry => entry.Title == null ? null : entry, // returning null drops the entry
});

Writing a feed (new in v4)

SFFeed feed = SFFeed.Parse(bytes);   // any wild feed in…
string rss = feed.ToRssXml();        // …clean RSS 2.0 out
string atom = feed.ToAtomXml();      // …or clean Atom 1.0

Or build one from scratch:

SFFeed feed = new() {
	Title = "My Feed",
	Language = "en-us",
	Items = [
		new SFFeedEntry() {
			Id = "https://example.com/post/1",
			Title = "Hello",
			Published = DateTimeOffset.UtcNow,
			Content = "The body.",
		},
	],
};

string rss = feed.ToRssXml();

The writer carries the same spirit as the reader: it never throws over imperfect model state. Absent dates are omitted (never emitted as year 0001), XML-illegal characters are stripped, links the parser merely discovered in body text are excluded (opt-in via SFWriteSettings.IncludeDiscoveredLinks), library-generated guid prefixes are restored to their source form, inherited entry authors aren't duplicated onto every item, and the synthetic detection-only mime types (video/vimeo, video/youtube) are never emitted. Parse → write → reparse is a stable cycle, covered by the test suite.

Extension coverage

Extension Read
Atom elements inside RSS (atom:link, atom:published, atom:updated, atom:category) ✔
iTunes podcast (author, subtitle, summary, keywords, image, duration, explicit, episode, season) ✔
content:encoded (with the description-vs-content reconciliation RSS never specified) ✔
Dublin Core (dc:creator, dc:date) ✔
Media RSS (media:content, media:thumbnail) ✔
rawvoice (metamark) ✔
PubSubHubbub (rel="hub" / rel="self" → SFFeed.Hub) ✔

RSS 1.0/RDF is not supported (it errors cleanly).

v4 breaking changes (from v3.x)

v4 is a renewal release: .NET 10, a model/parser split, the write side, and zero dependencies (the last one, DotNetXtensions.Mini, is vendored internal as of 4.0 — only the micro string/LINQ helpers SimpleFeed actually uses, copied verbatim). The headlines:

  • Namespace is now SimpleFeed (was SimpleFeedNS).
  • SFFeed replaces the old SimpleFeed type: parse via static SFFeed.Parse(bytes) — no more constructing a feed and calling instance Parse. The result is always non-null; entries are on feed.Items (the old feed-is-an-IList face is gone).
  • The parse machinery that used to be public (RssItemToFeedEntry, AtomEntryToFeedEntry, SetItunes, …) is internal now; the public surface is the model + Parse + the write methods + the hook points.
  • BasicMimeType and friends moved into the SimpleFeed namespace (the DotNetXtensions.MimeTypes dependency was internalized).
  • Youtube/vimeo detection lives only in the SFAlterFeedLinks hook now (SFLink's detectYtubeVimeo constructor parameter is gone), under one conservative rule: a link's explicitly declared type is never overridden.
  • KeepXmlDocument now defaults to false.
  • Mutable global statics (LocalIdTag, rel dictionaries, XSimpleFeed defaults) are gone or frozen.
  • A dozen+ long-standing bugs were fixed — several change output (enclosure Length/Height/Width now carry real values; more categories and authors survive parsing; declared encodings decode correctly). Every fix, with before/after code: docs/2026-08-21-v4-bug-fixes.md.

License

MIT

Changes from Previous Version

v3.6.0v4.0.0
Added Frameworks
net10.0
Removed Frameworks
net6.0.NETStandard2.0
Removed Dependencies

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.