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\$
  • 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\$ – t3chb0t Sep 16 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\$ – Shivang MIttal Sep 17 at 6:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.