search
Japanese Chinese Nederlands Espanol Italiano Deutsch Francais Twitter Rss Feeds
MicrosoftArticlesForumsFAQs
C# .NET
VB.NET
Visual Studio .NET
ADO.NET
Xml / Xslt
VB 6.0
.NET CF
GDI+
LINQ
Deployment
Security
FoxPro
Silverlight / WPF
Entity Framework
RIA Services

Web ProgrammingArticlesForumsFAQs
JavaScript
ASP
ASP.NET
Web Services

Non-MicrosoftArticlesForumsFAQs
NHibernate
Perl
PHP
Ruby
Java
Linux / Unix
Apple
Open Source

DatabasesArticlesForumsFAQs
SQL Server
Access
Oracle
MySQL
Other Databases

OfficeArticlesForumsFAQs
Excel
Word
Powerpoint
Outlook
Publisher
Money

Operating SystemsArticlesForumsFAQs
Windows 7
Windows Server
Windows Vista
Windows XP
Windows Update
MAC
Linux / UNIX

Server PlatformsArticlesForumsFAQs
BizTalk
Site Server
Exhange Server
IIS

Graphic DesignArticlesForumsFAQs
Macromedia Flash
Adobe PhotoShop
Expression Blend
Expression Design
Expression Web

OtherArticlesForumsFAQs
Subversion / CVS
Ask Dr. Dotnetsky
Active Directory
Networking
Uninstall Virus
Job Openings
Product Reviews
Search Engines
Resumes

 

Build a Multi-Provider Async Methods Search Page


By Peter Bromberg
Printer Friendly Version
View My Articles
14 Views
    

Extending the concepts presented in a previous article, Peter shows how to use Asynchronous Page Tasks to combine the results from seven different RSS Search providers simultaneously, and display the results in a pageable GridView.


In this previous article I explained some of the basics of the ASP.NET 2.0 Asynchronous Page methods framework. You may wish to read that article for background before beginning this one.

Here, I will extend the concepts presented in the previous article to show how it is possible to send the same search term to as many RSS "search results" providers as you want,  have them all be processed in parallel, and combine the results, removing duplicate links, to display in a pageable gridview.

First, I want to show the code. There is only one page, so let's look at the  codebehind, and I'll do some explaining afterward:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
using System.Net;
using System.IO;
using System.Collections.Generic ;

public partial class samples_MultiWebRequest : System.Web.UI.Page
{
    int total = 0;
    Dictionary<string, DataRow> dctFeeds = new Dictionary<string, DataRow>();
    protected void Page_Load(object sender, EventArgs e)
    {
        this.txtSearch.Attributes.Add("onfocus", "javascript:this.value='';");      
    }    

    protected override void OnPreRenderComplete(EventArgs e)
    {
       base.OnPreRenderComplete(e);
        // processing is complete, bind control
       if (Page.IsPostBack && GridView1.PageIndex == 0 && Session["dt"] == null)
           Bind();      
    }  

    IAsyncResult BeginAsyncWork1(Object sender, EventArgs e, AsyncCallback cb, object state)
    {
        // get the search url for this request
       string fullUrl = (string)state;        
       HttpWebRequest req1 = (HttpWebRequest)WebRequest.Create(fullUrl);
       req1.Method = "GET"; 
       IAsyncResult  result1=  req1.BeginGetResponse(cb,req1); 
       return result1;
    }

    
    void EndAsyncWork1(IAsyncResult asyncResult)
    {
        HttpWebRequest req1 = (HttpWebRequest)asyncResult.AsyncState;
        WebResponse response = (WebResponse)req1.EndGetResponse(asyncResult);
        Stream streamResponse = response.GetResponseStream();
        DataSet  ds1 = new DataSet();
        ds1.ReadXml(streamResponse);
        streamResponse.Close();
        ProcessItems(ds1);         
    }
   
    void TimeoutHandler(IAsyncResult asyncResult)
    {
        this.lblMessage.Text = "<strong>Async Request timed out!</strong>";
    }

    private void ProcessItems( DataSet ds)
    {
        if (ds.Tables.Count > 2 && ds.Tables[2].Rows.Count>0)
        {
            // some feeds don't always end up with items in table 3, so....
            int tbl = 2;
            if (ds.Tables[tbl].Columns["description"] == null)
                tbl = 3;
            foreach (DataRow row in ds.Tables[tbl].Rows)
            {
                if (!dctFeeds.ContainsKey((string)row["link"]))
                {
                    dctFeeds.Add((string)row["link"], row);
                    total++;
                }
            }
        }
        
    }
    
    private void Bind ()
    {
          DataSet ds = new DataSet();
          DataTable dt = new DataTable();
          dt.Columns.Add("title", typeof(String));
          dt.Columns.Add("link", typeof(string));
          dt.Columns.Add("description", typeof(string));
          DataRow newRow;
          foreach(DataRow row in dctFeeds.Values)
          {
              newRow = dt.NewRow();
              newRow["title"] = row["title"];
              newRow["link"] = row["link"];
              newRow["description"] = row["description"];
              dt.Rows.Add(newRow);
          }
          lblMessage.Text = "Retrieved " + total.ToString() + " results.";
          Session["dt"] = dt;
          this.GridView1.DataSource = dt;
          GridView1.DataBind();
    }

   
    protected void Button1_Click(object sender, EventArgs e)
    {
        Session["dt"] = null;
        // Register each separate WebRequest as an AsyncTask, 
        // specifying that they should be run in parallel:
        PageAsyncTask task=null;   

        string[] urls = 
        {
        "http://search.live.com/feeds/results.aspx?format=rss&count=50&q="+this.txtSearch.Text,
        "http://blogsearch.google.com/blogsearch_feeds?hl=en&spell=1&oi=spell&ie=utf-8&num=100&output=rss&q="+this.txtSearch.Text,
        "http://search.msn.com/results.aspx?count=200&format=rss&q=" +this.txtSearch.Text,
        "http://lab.msdn.microsoft.com/search/data.ashx?query="+this.txtSearch.Text,
        "http://digg.com/rss_search?area=all&type=both&age=7&search="+this.txtSearch.Text,
         "http://forums.asp.net/search/Searchrss.aspx?q="+this.txtSearch.Text,
        "http://feeds.technorati.com/search/" +this.txtSearch.Text
        };

        foreach (string s in urls)
        {
            task = new PageAsyncTask(new BeginEventHandler(this.BeginAsyncWork1), new EndEventHandler(this.EndAsyncWork1), new EndEventHandler(this.TimeoutHandler), s, true);
            Page.RegisterAsyncTask(task);
        }     
        Page.ExecuteRegisteredAsyncTasks();    
    }
    
    protected void GridView1_PageIndexChanged(object sender, EventArgs e)
    {       
    }
    protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
       GridView1.PageIndex = e.NewPageIndex;
       GridView1.DataSource = (DataTable)Session["dt"];
       GridView1.DataBind();
    }
}
Let's step through the above. Obviously, I've got a textbox to enter your search, and a button. So everything starts off in the Button1_Click eventhandler. Here, we create a string array containing our search urls (live.com, google Blog search, MSN, MSDN, Digg, ASP.NET Forums, and Technorati).

Next, I create a PageAsyncTask for each search and Register it. Finally, I kick it all off with Page.ExecuteRegisteredAsyncTasks().

Note that you can re-use the BeginAsyncWork, EndAsyncWork and Timeout handler methods because we are passing the actual Request for each search in the State object to the EndAsyncWork method. This ensures that we are working with the same Request, and enables us to complete our processing. We get this Request "back" with:  HttpWebRequest req1 = (HttpWebRequest)asyncResult.AsyncState;

The ProcessItems method simply massages the DataSet that we got from reading the RSS XML Result stream, and  checks it against the Dictionary "dctFeeds" to ensure that the link (the http url of the feed item) is not already in the results from a previously stored resultset. We actually store the DataRow itself into the dctFeeds Dictionary.

When everything is complete, the PreRenderComplete event fires (provided we haven't timed out) and we call the Bind method, which simply assembles everything into a new DataTable and binds it to the GridView. This is also stored in Session to aid with Paging of the GridView.

You can download the sample code here.  This is a WebSite model project, so all you need to do is double-click on the solution file, it uses the bult-in Development WebServer. Note in the web.config that there is a place for proxy information, you can fill this in if you are behind a corporate firewall. The actual code for the WebProxy (if used) is in Global.

This is for educational purposes; please take care to observe any restrictions providers may have on the use of their feeds.

Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites.
Please post questions at forums, not via email!

button
Article Discussion: Build a Multi-Provider Async Methods Search Page
Peter Bromberg posted at Thursday, December 28, 2006 2:37 PM
Original Article
 

different Process method for every single response
zekia zekia replied to Peter Bromberg at Monday, January 28, 2008 6:04 AM
Your article is excellent and very usefull. But I would like to ask you, what happens if I need to have more than one ProcessItems(ds) methods? What if I need one ProcessItems method  per response? What do I have to do in the sample code? Do you have any idea to propose?
 

Use a switch statement.
Peter Bromberg replied to zekia zekia at Monday, January 28, 2008 7:04 AM
test for the logical criteria that determines your needing different "processItems" logic, and run the particular code block or method. That way you only need 1 ProcessItems method.
 

Need help on huge number of requests to asp.net page.
jayakumar budamala replied to Peter Bromberg at Wednesday, October 22, 2008 9:41 AM

Hi Paeter,

Excellent stuff on your site always.

I have a requirement, processing say 100s of requests with username as a query param, which I should process,to get the data from the database, which gets the results qucikly (less than a sec).

 

I need to scale out, will this pageAsync works for me. can I increase thread pool size.

Need your comments on this.

 

Page Async methods are for one Page lifecycle and a single request.
Peter Bromberg replied to jayakumar budamala at Wednesday, October 22, 2008 1:34 PM
So they will not help you for 100s of requests. You need to optimize your code to either return the correct results to each user quickly, or cache the results in memory and query and return them from there.