4
\$\begingroup\$

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

  1. Is returning string json a good idea? or shall I add a generic interface with few of the properties pre-defined as return type?
  2. 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.)
  3. 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.

\$\endgroup\$
2
  • 4
    \$\begingroup\$ Could you add LastFetchedFeedInfo and the missing method RetryProcessing because it looks like RssReader_Syndicate is 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\$ Commented Sep 16, 2019 at 19:41
  • \$\begingroup\$ @t3chb0t Thanks for looking into this. I have added the LastFetchedFeedInfo and Retry class along with the actual code ( initially added the simplified sample ). Yes there can be other parsers/ processors as well which will be getting the data from Custom APIs, but they are not written yet. \$\endgroup\$ Commented Sep 17, 2019 at 6:29

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.