SimpleFeed 4.0.0
dotnet add package SimpleFeed --version 4.0.0
NuGet\Install-Package SimpleFeed -Version 4.0.0
<PackageReference Include="SimpleFeed" Version="4.0.0" />
<PackageVersion Include="SimpleFeed" Version="4.0.0" />
<PackageReference Include="SimpleFeed" />
paket add SimpleFeed --version 4.0.0
#r "nuget: SimpleFeed, 4.0.0"
#:package SimpleFeed@4.0.0
#addin nuget:?package=SimpleFeed&version=4.0.0
#tool nuget:?package=SimpleFeed&version=4.0.0
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:
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.Be extremely fault tolerant. Real-world feeds are wild: obsolete
EST-style timezones, missing dates, malformed authors, barewww.links, spaces inside URLs, mis-declared encodings, garbage bytes. Parsing never throws — failures land asError+Messageson the result, invalid pieces are skipped, absent dates report honestly asDateTimeOffset.MinValue.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, …).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(wasSimpleFeedNS). SFFeedreplaces the oldSimpleFeedtype: parse via staticSFFeed.Parse(bytes)— no more constructing a feed and calling instanceParse. The result is always non-null; entries are onfeed.Items(the old feed-is-an-IListface 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. BasicMimeTypeand friends moved into theSimpleFeednamespace (theDotNetXtensions.MimeTypesdependency was internalized).- Youtube/vimeo detection lives only in the
SFAlterFeedLinkshook now (SFLink'sdetectYtubeVimeoconstructor parameter is gone), under one conservative rule: a link's explicitly declared type is never overridden. KeepXmlDocumentnow defaults to false.- Mutable global statics (
LocalIdTag, rel dictionaries,XSimpleFeeddefaults) are gone or frozen. - A dozen+ long-standing bugs were fixed — several change output (enclosure
Length/Height/Widthnow 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
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 4.0.0 | 94 | 8/21/2026 |
| 3.6.0 | 1,881 | 8/26/2022 |
| 3.5.0 | 762 | 3/10/2022 |
| 3.4.0 | 656 | 3/9/2022 |
| 3.3.0 | 7,829 | 10/27/2020 |
| 3.2.0-beta.2 | 477 | 3/6/2020 |
| 3.2.0-beta.1 | 456 | 2/28/2020 |
| 3.1.1 | 878 | 6/18/2019 |
| 3.1.0 | 776 | 6/17/2019 |
| 3.0.1 | 803 | 5/17/2019 |