{"id":260,"date":"2007-05-04T00:00:00","date_gmt":"2007-05-04T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/gathering-rss-feeds-using-visual-studio-and-rss-net\/"},"modified":"2021-05-11T15:56:46","modified_gmt":"2021-05-11T15:56:46","slug":"gathering-rss-feeds-using-visual-studio-and-rss-net","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/gathering-rss-feeds-using-visual-studio-and-rss-net\/","title":{"rendered":"Gathering RSS Feeds using Visual Studio and RSS.NET"},"content":{"rendered":"<p>Integrating RSS feeds into applications has become very popular. There are several tools (such as Microsoft Outlook 2007) available to read blogs and news from RSS feeds. There are also some tools available (such as RSS.NET and the ASP.NET team&#8217;s RSS Toolkit) that can aid you in the development of customizing feeds in your applications. This article will use the RSS.NET assembly, which can be freely downloaded and used from http:\/\/www.rssdotnet.com\/ [editor&#8217;s note: this library has been deprecated].<\/p>\n<h3>Application overview<\/h3>\n<p>This article will explain how to create a custom Windows Service that retrieves posts from multiple RSS feeds and then stores them in a SQL Server database. Once you store the posts in the database you can then retrieve them and display them on a web site, so you can combine multiple feeds and their posts in a list on a web page, or perhaps in a WinForm application. When the Windows Service retrieves the posts from each RSS feed, it filters out any duplicate posts before saving them to the database.<\/p>\n<p>NOTE:<br \/>\n <i>All source code for the Windows Service is available to be downloaded as well as the SQL scripts to generate the database in SQL Server. See the CODE DOWNLOAD link in the header of this article.<\/i><\/p>\n<p>Figure 1 shows a high level overview of the application. The Windows service will poll a list of RSS feeds using the RSS.NET toolkit at a defined interval. The list of RSS feeds and their URLs are stored in a local SQL Server DB. Using the RSS.NET library, each feed&#8217;s posts are retrieved and examined. Duplicate posts are ignored, but new posts are inserted into a SQL Server database.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/388-Figure1.gif\" alt=\"388-Figure1.gif\" \/><\/p>\n<p><em>Figure 1 &#8211; Gathering posts from feeds<\/em><\/p>\n<h3>Setting up the database<\/h3>\n<p>You can execute the entire database setup script from the <b>DBScript.sql<\/b>\u00a0file in the zipped code file attached to this article. The database is used to store two tables:<\/p>\n<ul>\n<li>Feed &#8211; a list of feeds that will be polled for their posts<\/li>\n<li>Post &#8211; a list of the posts for each feed<\/li>\n<\/ul>\n<p>The tables and their basic schema are shown in Figure 2.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/388-Figure2.gif\" alt=\"388-Figure2.gif\" \/><\/p>\n<p><em>Figure 2 &#8211; The BlogRoll Database<\/em><\/p>\n<p>The DBScript.sql file first sets up the SQL Server database and names it <b>BlogRoll<\/b>. You will want to adjust the location of the data and log files on your PC to a place besides C:\\. Once the <b>BlogRoll<\/b> database is created, the <b>Feed<\/b> and <b>Post<\/b> tables are created. The <b>Feed<\/b> table stores the title of the feed, the sequence in which the feed should be polled in regard to other feeds, as well as the URL of the feed.<\/p>\n<p>The <b>Feed<\/b> table also stores a <b>LastPostGuidName<\/b> column. This column represents a unique identifier for the last post that was retrieved for this feed. When the feed is polled again, this column&#8217;s value is compared against each post that is retrieved. When a match is found, the Windows Service will know that the rest of the posts have already been retrieved so it will ignore the remaining duplicate posts. This column helps keep only unique posts in the <b>Post<\/b> table. This column allows NULL values because the first time a feed is polled, there are no posts already in the database.<\/p>\n<p>I started the <b>Post<\/b> table with a list of two RSS feeds which point to my blog (<a href=\"http:\/\/codebetter.com\/blogs\/john.papa\/rss.aspx\">http:\/\/codebetter.com\/blogs\/john.papa\/rss.aspx<\/a>) and the ADO.NET team&#8217;s blog (<a href=\"https:\/\/blogs.msdn.microsoft.com\/adonet\/feed\/\">https:\/\/blogs.msdn.microsoft.com\/adonet\/feed\/<\/a>). The DBScrpt.sql file inserts these two feeds as samples that you can start with. You can add more RSS feeds to this list by adding records to the <b>Post<\/b> table.<\/p>\n<p>The <b>Post <\/b>table stores a reference to the <b>Feed<\/b> table, so the posts can be associated with the feed from which they came. It also stores the title of the post, the date the post was published, its author, the body of the post and a link directly to the post.<\/p>\n<p>Finally, the DBScript.sql file creates a SQL Server login and user named <b>BlogRollUser<\/b>. It gives the <b>BlogRollUser<\/b> permission to access the <b>BlogRoll<\/b> database and grants it permission to perform CRUD (Create, Read, Update, Delete) operations on the <b>Feed<\/b> and <b>Post<\/b> tables. Of course, you can set up your application to use any user that you want if you do not want to use the <b>BlogRollUser<\/b> that the script generates for you.<\/p>\n<h3>Gathering the feed list<\/h3>\n<p>The next step is to create the Windows Service that will poll the RSS feeds. I started by creating a Windows Service project through Visual Studio.NET and naming it <b>FeedGatheringService<\/b>. I then referenced the <b>System.Transactions<\/b> assembly as well as the RSS.NET assembly.<\/p>\n<p>I added a Windows Service with the same name as the project and created a <b>System.Timers.Timer<\/b>, named <b>feedGathererTimer<\/b>, in it. In the Service&#8217;s constructor I initialize the timer to run every 60 seconds. I also add an event handler for the timer&#8217;s <b>Elapsed <\/b>event. This event will be used to grab the list of feeds and to poll them for their new posts.<\/p>\n<p><code>public\u00a0FeedGatheringService() <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0InitializeComponent(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feedGathererTimer\u00a0=\u00a0new\u00a0System.Timers.Timer(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feedGathererTimer.Interval\u00a0=\u00a030000;\u00a0\/\/900000; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feedGathererTimer.Enabled\u00a0=\u00a0false; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feedGathererTimer.Elapsed\u00a0+=\u00a0new\u00a0ElapsedEventHandler(feedGathererTimer_Elapsed); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/code><\/p>\n<p>The <b>feedGathererTimer_Elapsed<\/b> event grabs a list of feeds from the SQL Server database. I created a <b>Feed<\/b> class which has a static method called <b>GetList<\/b>, which queries the database&#8217;s <b>Feed<\/b> table for all of the feeds. Each feed row that is retrieved is used to populate a <b>List&lt;Feed&gt;<\/b>. The following listing shows how a connection is opened to the database using ADO.NET. The feed list is grabbed and each row is used to create a <b>Feed<\/b> entity through the <b>Feed<\/b> entity&#8217;s constructor. Each entity is added to the <b>List&lt;Feed&gt;<\/b> and finally the list of returned so the service can poll them.<\/p>\n<p><code>public\u00a0static\u00a0List&lt;Feed&gt;\u00a0GetList() <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0List&lt;Feed&gt;\u00a0feedList\u00a0=\u00a0new\u00a0List&lt;Feed&gt;(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0using\u00a0(SqlConnection\u00a0cn\u00a0=\u00a0new\u00a0SqlConnection(Settings.default.BlogRollConnectionString)) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0cn.Open(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0string\u00a0sql\u00a0\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 =\u00a0\"SELECT\u00a0ID,\u00a0Title,\u00a0Url,\u00a0LastPostGuidName,\u00a0DateCreated\u00a0FROM\u00a0Feed\u00a0ORDER\u00a0BY\u00a0Sequence\"; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0using\u00a0(SqlCommand\u00a0cmd\u00a0=\u00a0new\u00a0SqlCommand(sql,\u00a0cn)) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0cmd.CommandType\u00a0=\u00a0CommandType.Text; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0SqlDataReader\u00a0rdr\u00a0=\u00a0cmd.ExecuteReader(CommandBehavior.CloseConnection); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0while\u00a0(rdr.Read()) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feedList.Add(new\u00a0Feed(Convert.ToInt32(rdr[\"Id\"]),\u00a0 <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Convert.ToDateTime(rdr[\"DateCreated\"].ToString()),\u00a0 <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0rdr[\"Title\"].ToString(),\u00a0rdr[\"Url\"].ToString(),\u00a0 <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0rdr[\"LastPostGuidName\"].ToString())); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\u00a0feedList; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/code><\/p>\n<h3>Polling the feeds for posts<\/h3>\n<p>Once the <b>List&lt;Feed&gt;<\/b> has been retrieved, the Service iterates through each of them and polls the feeds for their posts. This is where a library, such as RSS.NET or the ASP.NET team&#8217;s RSS toolkit, comes in handy. These types of tools make interacting with RSS feeds much simpler than if you hit them directly and parse the results. There is a considerable amount of exception handling and data cleanup that is handled by these libraries, and they are both freely available to use.<\/p>\n<p>RSS.NET has a <b>RssFeed<\/b> class that exposes a public method called <b>Read<\/b>, which accepts a URL of a feed. It requests a list of posts from the feed and returns them to the application. The posts are contained within the <b>RssFeed<\/b> class&#8217;s <b>Channels<\/b> collection&#8217;s <b>Items<\/b> collection.<\/p>\n<p><code>rssFeed.Channels[0].Items<\/code><\/p>\n<p>The following code listing demonstrates how the RSS.NET <b>RssFeed<\/b> class polls the feed&#8217;s URL and returns the posts. Each post (represented by the <b>rssItem<\/b> variable) is then examined to see if its <b>Guid.Name<\/b> matches the last post that was retrieved for this feed (in previous polling attempts). The first time this code is executed for a feed the <b>LastPostGuidName <\/b>value will be NULL. However once a post has been inserted into the <b>Post<\/b> table, the <b>LastPostGuidName<\/b> value is updated for the <b>Feed<\/b> table. If a match is found then the processing for this feed stops as the rest of the posts already exist in the <b>Post<\/b> table.<\/p>\n<p><code>public\u00a0static\u00a0List&lt;Post&gt;\u00a0GetNewRssPostsFromUrl(Feed\u00a0feed) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0string\u00a0mostRecentPostGuidName\u00a0=\u00a0null; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0List&lt;Post&gt;\u00a0postList\u00a0=\u00a0new\u00a0List&lt;Post&gt;(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0RssFeed\u00a0rssFeed\u00a0=\u00a0RssFeed.Read(feed.Url); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(rssFeed.Channels.Count\u00a0==\u00a00) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\u00a0postList; <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0Get\u00a0Channel\u00a00 <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0RssChannel\u00a0channel\u00a0=\u00a0rssFeed.Channels[0]; <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0foreach\u00a0(RssItem\u00a0rssItem\u00a0in\u00a0channel.Items) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0If\u00a0we\u00a0already\u00a0have\u00a0this\u00a0post,\u00a0exit.\u00a0This\u00a0means\u00a0the\u00a0rest\u00a0of\u00a0posts\u00a0are\u00a0old. <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(feed.LastPostGuidName\u00a0==\u00a0rssItem.Guid.Name) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0break; <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0Grab\u00a0the\u00a0first\u00a0Post's\u00a0Guid <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(mostRecentPostGuidName\u00a0==\u00a0null)\u00a0<br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0mostRecentPostGuidName\u00a0=\u00a0rssItem.Guid.Name; <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0Post\u00a0post\u00a0=\u00a0new\u00a0Post(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.FeedID\u00a0=\u00a0feed.Id; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.Author\u00a0=\u00a0rssItem.Author; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.Link\u00a0=\u00a0rssItem.Link.ToString(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.PostDate\u00a0=\u00a0rssItem.PubDate; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.Body\u00a0=\u00a0rssItem.Description; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.Title\u00a0=\u00a0rssItem.Title; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0postList.Add(post); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0} <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0Update\u00a0the\u00a0Feed's\u00a0last\u00a0post\u00a0setting <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(mostRecentPostGuidName\u00a0!=\u00a0null) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feed.LastPostGuidName\u00a0=\u00a0mostRecentPostGuidName; <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0return\u00a0postList; <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/code><\/p>\n<p>The code then shows that once the post is determined not to be a duplicate that a <b>Post<\/b> entity is created. The <b>Post <\/b>entity is created from the values retrieved from the <b>rssItem<\/b> and then added to a <b>List&lt;Post&gt;<\/b>.<\/p>\n<p>The final step before returning the <b>List&lt;Post&gt;<\/b> is to update the <b>LastPostGuidName<\/b> property. This is set so that the next time the polling process occurs that it will know what the identifier is for the last post that was retrieved. Notice that the posts are not yet inserted into the database. The entire process merely gathers the posts and sets the <b>LastPostGuidName<\/b>.<\/p>\n<h3>Inserting the posts<\/h3>\n<p>Once the <b>List&lt;Post&gt;<\/b> has been returned to the service, they must be inserted into the database&#8217;s <b>Post <\/b>table. The following code in 5 demonstrates how the posts are inserted. First a <b>TransactionScope<\/b> is instantiated using the <b>System.Transactions<\/b> library. I create a transaction since I want all of the posts for a given feed to be inserted together. If any post fails to insert then I want all of the posts to rollback. Using the <b>System.Transactions TransactionScope<\/b> class I can maintain a 2-phase commit that will implement this atomic transaction. Since this uses a 2-phase commit, it requires that the MS Distributed Transaction Coordinator service is running. (Of course, if you wish to eliminate the transactions or write them a different way, you can simply replace the <b>System.Transactions<\/b> code with your own transactional code.)<\/p>\n<p><code>using\u00a0(TransactionScope\u00a0ts\u00a0=\u00a0new\u00a0TransactionScope()) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0{ <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0if\u00a0the\u00a0Feed\u00a0and\u00a0its\u00a0Guid\u00a0changed,\u00a0update\u00a0the\u00a0feed <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(feed.LastPostGuidNameChanged) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0feed.Update(); <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\/\/\u00a0If\u00a0it\u00a0has\u00a0posts,\u00a0add\u00a0them <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0if\u00a0(postList.Count\u00a0&gt;\u00a00) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0foreach\u00a0(Post\u00a0post\u00a0in\u00a0postList) <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0post.Add(); <\/p>\n<p>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0ts.Complete(); <br \/>\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0}<\/code><\/p>\n<p>The first part of the transaction is to update the <b>LastPostGuidName<\/b> column in the <b>Feed<\/b> table. Then, each post is inserted into the <b>Post<\/b> table. Finally, if all goes well and no exception is thrown, the transaction is marked complete and it is committed to the database. A new transaction is created for each feed, so each feed is separate from the next feed.<\/p>\n<h3>Creating an installer<\/h3>\n<p>When creating the Windows Service, I set the timer&#8217;s interval to 60 seconds so I could easily debug and watch the feeds populate. However, the reality is that I probably do not want this service running every minute since feeds do not usually change that often. I suggest that you change the timer&#8217;s interval to something less frequent such as every 15 minutes (an interval of 900000).<\/p>\n<p>If you want to debug the application, you can adjust the interval back down to 60 seconds for testing purposes.<\/p>\n<p>Once the Windows service was created I needed a way to install the service on my machine to try it out (and to debug it). To do this I opened the <b>FeedGatheringService.cs<\/b> file&#8217;s designer and right-clicked in open space to bring up the context menu shown in Figure 3. I selected the <i>Add Installer<\/i> option from this menu, which adds a <b>ProjectInstaller.cs<\/b> file to the project.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/388-Figure3.gif\" alt=\"388-Figure3.gif\" \/><\/p>\n<p><em>Figure 3 -Adding the Installer<\/em><\/p>\n<p>The <b>ProjectInstaller<\/b> provides a component that will be used to install the service process and a component that will be used to install the service itself. You can examine these components by opening the P<b>rojectInstaller<\/b> in design view. I set the <b>serviceProcessInstaller1<\/b> component&#8217;s <b>Account<\/b> property to <b>LocalSystem<\/b>. I could have specified a specific user, and in a production application I recommend doing just that. However, for demonstration purposes, I will use <b>LocalSystem<\/b> so it is easier to debug the application.<\/p>\n<p>I also set the <b>serviceInstaller1<\/b> component&#8217;s <b>ServiceName<\/b> property to <b>FeedGatheringService<\/b> and set its <b>StartType<\/b> to Automatic. This tells the service to start itself automatically when the computer is started. I set the description to &#8220;Gathers RSS Feeds&#8221; so the service will have some basic description when viewing it in the Services list.<\/p>\n<h3>Install the Windows Service<\/h3>\n<p>Once the project is compiled the service can be installed on the computer by following a few simple steps. Open a Visual Studio.NET command prompt and change to the <b>bin\\Debug<\/b> directory of the <b>FeedGatheringService<\/b> project. (You will want to use the <b>bin\\Release<\/b> directory when if you compile a release version.) Execute the following command to install the service on the computer:<\/p>\n<pre>InstallUtil.exe FeedGatheringService.exe<\/pre>\n<p>The <b>InstallUtil<\/b> command will install the service and create the appropriate registry entries for the service. (If you want to uninstall the service you can execute the <b>InstallUtil<\/b> command again with the \/u argument.) It will not start the service immediately. Keep in mind that the next time the computer is restarted that the service will start automatically since I set the <b>StartType<\/b> property to Automatic in the <b>ServiceInstaller<\/b> component. Execute the following command in the Visual Studio.Net command window to start the service:<\/p>\n<pre>net start FeedGatheringService<\/pre>\n<p>Another option to start the service is to right-click on My Computer, select Manage from the context menu, open the Services node in the tree, locate the <b>FeedGatheringService<\/b> in the list of services and start it from there.<\/p>\n<h3>Debugging the Service<\/h3>\n<p>Debugging a Windows service is not difficult, but is does require a few additional steps beyond what is involved with a WinForm or ASP.NET application. First, set a breakpoint in the <b>feedGathererTimer_Elapsed<\/b> event handler so you can watch the code execute. Then, go to the Debug menu and select Attach to Process. Make sure that the checkboxes are selected so you can see all processes. Next, select the <b>FeedGatheringService<\/b> from the list of available processes and click the Attach button (shown in Figure 4).<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/388-Figure4.gif\" alt=\"388-Figure4.gif\" \/><\/p>\n<p><em>Figure 4 &#8211; Attaching to the Windows Service<\/em><\/p>\n<p>If you set the breakpoint in the <b>feedGathererTimer_Elapsed<\/b> event handler then the code will hit the breakpoint and you will be able to walk through the code as it executes. The breakpoint will be hit once the timer&#8217;s interval has been reached.<\/p>\n<h3>Wrapping Up<\/h3>\n<p>At this point you now have a Windows service that will gather posts from a list of RSS feeds and store them in a database. The code included with this article points to a specific database server instance of (local)\\SQL2005. Remember to change this to reflect the name of your SQL Server instance. The posts can now be read using standard ADO.NET data access objects and displayed in any of your applications.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you would like to learn how to build and customize your very own Windows service to retrieve posts from multiple RSS feeds, and then store those posts in a SQL Server database, let John Papa guide you through his tutorial.&hellip;<\/p>\n","protected":false},"author":221825,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143538],"tags":[4143,4204,4157,4168,4769,4767,4150,4151,4768],"coauthors":[50378],"class_list":["post-260","post","type-post","status-publish","format-standard","hentry","category-dotnet-development","tag-net","tag-net-tools","tag-asp-net","tag-database","tag-gather-rss","tag-rss-feed","tag-sql","tag-sql-server","tag-windows-service"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/260","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221825"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=260"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/260\/revisions"}],"predecessor-version":[{"id":79923,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/260\/revisions\/79923"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=260"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=260"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=260"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=260"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}