I am writing a RSS reader Library which should be able to read the data from different sources. I am very new to writing libraries and not sure if I am going right with it.
These are my base interfaces with implementation
public enum ProcessType
{
Syndicate,
Atom,
API
}
public interface IRssSourceBase
{
string Url { get; }
}
public interface IRssReader
{
string GetLatestFeeds<TRssSource>(TRssSource source, Dictionary<string, LastFetchedFeedInfo> DictLastFetchedFeeds, ProcessType processType)
where TRssSource : IRssSourceBase;
}
public class RssReader : IRssReader
{
public string GetLatestFeeds<TRssSource>(TRssSource source, Dictionary<string, LastFetchedFeedInfo> DictLastFetchedFeeds, ProcessType processType)
where TRssSource : IRssSourceBase
{
try
{
// Add Multiple Feed processors
switch (processType)
{
case ProcessType.Syndicate:
var rssFeeds = RssReader_Syndicate.GetLatestFeeds<TRssSource>(source, DictLastFetchedFeeds);
return Newtonsoft.Json.JsonConvert.SerializeObject(rssFeeds);
case ProcessType.Atom: // Process Atom type feeds
break;
case ProcessType.API: // Process API feeds
break;
}
}
catch (Exception ex)
{
// log
}
return null;
}
}
This is one of the parser/processor using SyndicationFeed
class RssReader_Syndicate
{
internal static IEnumerable<SyndicationItem> GetLatestFeeds<TRssSource>(TRssSource source, Dictionary<string, LastFetchedFeedInfo> DictLastFetchedFeeds)
where TRssSource : IRssSourceBase
{
try
{
XmlReader reader = XmlReader.Create(source.Url);
return ProcessFeeds(source, DictLastFetchedFeeds, reader);
}
catch (HttpException httpEx)
{
if (httpEx.GetHttpCode() == 404)
{
// log
}
}
catch (Exception ex)
{
return Retry.Do<IEnumerable<SyndicationItem>>(() => RetryProcessing(source, DictLastFetchedFeeds), 1, 3);
}
return null;
}
private static IEnumerable<SyndicationItem> RetryProcessing<TRssSource>(TRssSource source, Dictionary<string, LastFetchedFeedInfo> DictLastFetchedFeeds) where TRssSource : IRssSourceBase
{
using (WebClient client = new WebClient())
{
string s = client.DownloadString(source.Url);
MemoryStream memStream = new MemoryStream();
byte[] data = Encoding.Default.GetBytes(s.Trim());
memStream.Write(data, 0, data.Length);
memStream.Position = 0;
XmlReader reader = XmlReader.Create(memStream);
return ProcessFeeds(source, DictLastFetchedFeeds, reader);
}
}
private static IEnumerable<SyndicationItem> ProcessFeeds<TRssSource>(TRssSource source, Dictionary<string, LastFetchedFeedInfo> DictLastFetchedFeeds, XmlReader reader) where TRssSource : IRssSourceBase
{
SyndicationFeed feed = SyndicationFeed.Load(reader);
reader.Close();
var latestFeed = feed.Items.OrderByDescending(t => t.PublishDate).First();
LastFetchedFeedInfo lastProcessedFeed = new LastFetchedFeedInfo();
if (DictLastFetchedFeeds.ContainsKey(source.Url))
{
lastProcessedFeed = DictLastFetchedFeeds[source.Url];
// No latest feed? continue to read the next one
if (latestFeed.Title.Text == lastProcessedFeed.LastReadTitle &&
latestFeed.PublishDate.Date == lastProcessedFeed.LastReadPublishDateOffset)
{
return null;
}
}
// get the feeds which are more likely not processed
IEnumerable<SyndicationItem> latestFeedsToBeProcessed = DictLastFetchedFeeds.ContainsKey(source.Url) ? feed.Items.Where(t => t.PublishDate.Date > lastProcessedFeed.LastReadPublishDateOffset) :
feed.Items;
DictLastFetchedFeeds[source.Url] = new LastFetchedFeedInfo { LastReadTitle = latestFeed.Title.Text, LastReadPublishDateOffset = latestFeed.PublishDate };
return latestFeedsToBeProcessed;
}
}
Additional classes i.e LastFetchedFeedInfo and Retry
public class LastFetchedFeedInfo
{
public string Url { get; set; }
public string LastReadTitle { get; set; }
public DateTimeOffset LastReadPublishDateOffset { get; set; }
}
public class Retry
{
/// <summary>
/// Retry logic, make sure the the function throws the exception to use this
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="action"></param>
/// <param name="retryInterval"></param>
/// <param name="maxAttemptCount"></param>
/// <returns></returns>
public static T Do<T>(Func<T> action, int retryIntervalInSeconds, int maxAttemptCount = 3)
{
var exceptions = new List<Exception>();
for (int attempted = 0; attempted < maxAttemptCount; attempted++)
{
try
{
if (attempted > 0)
{
// exponentially increment the retry time
Thread.Sleep(TimeSpan.FromSeconds(retryIntervalInSeconds * attempted));
}
return action();
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}
throw new AggregateException(exceptions);
}
}
I have few questions regarding this
- Is returning string json a good idea? or shall I add a generic interface with few of the properties pre-defined as return type?
- Which design pattern i should have used? ( I have gone through good number of posts on codereview.stackexchange and I understand that one should not uselessly try to force any pattern and to add unnecessary complexity.)
- If there will be a different source coming tomorrow (ex : feed API returning Json), I have to add the implementation of it in the library. Does it not violate Open Close Principle
I am sure my code looks bad and there can be various points where I can improve this and I will be happy if they can be pointed (criticism accepted). Please suggest the improvement ideas or if the code looks horrible and I should write it again with some guidelines.
LastFetchedFeedInfoand the missing methodRetryProcessingbecause it looks likeRssReader_Syndicateis incomplete. You also write This is one of the parser - so there are others too? It'd be great if they were part of the question as well. \$\endgroup\$