Contents
Introduction
The web service I have written returns real-time stock quotes. There are two functions. The first takes a string representing the stock ticker and returns a string representing the current price on the ECNs, which are electronic marketplaces for stocks. As will be described below, the prices are scraped from the Yahoo! Finance website. Please note, that not all stocks are traded on ECNs, so some tickers will return ‘N/A’. The second function takes a space-delimited string of tickers and returns a dataset of current prices. The information returned from these web services is freely available on the Internet.
There are many benefits to using a web service. One is that the versatility of the client that consumes the web service. Later in the article, I will provide an example of how to use a web page to consume it, but the client could also be a Windows Form or even another web service. Also, because of the nature of XML, the client is not restricted to the Windows platform. Another is the reusability of the code. In essence, the web service is like a class library. As long as a reference is made to it, there is little distinction between calling a function from a local class versus a web service.
Top 
Let's look at the code
There are two files that contain the main code for the web service. The first is a class library that contains a general purpose function I use to screen scrape. .Net has made it very simple to screen scrape, a process where a web page is returned as a string. Depending on the web page, the string can be parsed to extract valuable information. The only function in this library, GetPageContent(url).
public static string GetPageContent(string url)
{
WebRequest wreq;
WebResponse wres;
StreamReader sr;
String content;
wreq = HttpWebRequest.Create(url);
wres = wreq.GetResponse();
sr = new StreamReader(wres.GetResponseStream());
content = sr.ReadToEnd();
sr.Close();
return content;
}
This function is declared static, so no object needs to be instantiated in my web service in order to use it. GetPageContent performs the screen scraping. First, we create a WebRequest to the url that is passed to the function. Then we assign the response to a WebResponse object and use a StreamReader to assign the response stream to a string. At this point, if we were to use Reponse.Write to write this string to the browser window, we would see an exact replica of the web page. Also, if we were to write the string to a TextBox, we would see the HTML source just as if we would have selected View -> Source in the browser. This is the string we will use to extract the real-time quote.
The trick to extracting data from a web page is knowing where it is in the source. If I were requesting the quote for Microsoft, my URL would be http://finance.yahoo.com/q/ecn?s=MSFT [^]. On this page, you will notice that the quote is just after the text ‘Last Trade:’. In the source, the actual price is wrapped in <B> tags. Our next function will use this information to extract our real-time quote from the HTML source.
private string ParsePage(string page)
{
Int32 i;
i = page.IndexOf("Last Trade:");
page = page.Substring(i);
i = page.IndexOf("");
page = page.Substring(i);
i = page.IndexOf("");
page = page.Substring(0,i);
page = Regex.Replace(page, "","");
return page;
}
The entire page source is passed into the function and the price is returned. So far, we have seen two functions that enable us to extract data from a website. We were able to leverage these functions because the URL for the page is constant with the exception of the stock ticker. In order to extract other types of data, the GetURL (not discussed) and ParsePage functions would need to be modified to suit the scenario. For example, I use a different page on Yahoo! Finance to create dynamic stock charts. The ParsePage function is more complicated since I am extracting more information, but the general concept is the same.
Top 
Pulling it all together
The helper functions have been covered, so let’s look at the actual web services. First of all, you will notice that the code behind looks just like a regular class with the exception of the [WebService] attribute. That is the first requirement. The other is to expose the functions you want as web services by adding the [WebMethod] attribute to them. In our case, we have two web services, GetQuote and GetQuotes.
[WebMethod]
public string GetQuote(string ticker)
{
string stockURL, page, retval;
try
{
stockURL = GetURL(ticker);
page = ScrapeUtility.GetPageContent(stockURL);
retval = ParsePage(page);
}
catch (ArgumentOutOfRangeException)
{
retval = "Invalid Ticker";
}
catch (Exception)
{
retval = "Unknown Error";
}
return retval;
}
Most of this code we have already covered. The string that is passed into the function is used to create our URL. Then, this page is retrieved and parsed to extract the price. I wrap the entire process in a try block. If the ticker is not actually a publically traded company, the ArgumentOutOrRangeException is caught. This occurs when we are attempting to parse the page and can not find the ‘Last Trade:’ string. Notice the price is returned as a string. We could also have returned the price as a decimal, but in the case of an invalid ticker, we would need to send something conforming to that datatype. 0.0 may be a logical value, but I choose to return a string. The client could always convert this to a decimal assuming a valid ticker.
The second web service actually uses the first to extract the data. A point of interest is that in this case, GetQuote is not being called as a web service.
[WebMethod]
public string GetQuotes(string tickers)
{
char[] splitter = {' '};
string [] _tickers = tickers.Trim().Split(splitter);
Int32 i, ticks;
ticks = _tickers.Length;
DataSet ds = new DataSet();
DataTable dt = new DataTable("StockData");
DataColumn dc;
dc = dt.Columns.Add("Ticker", System.Type.GetType("System.String"));
dc = dt.Columns.Add("Price", System.Type.GetType("System.String"));
for (i = 0; i < ticks; i++)
{
DataRow dr = dt.NewRow();
dr["Ticker"] = _tickers[i].ToUpper();
dr["Price"] = GetQuote(_tickers[i]));
dt.Rows.Add(dr); }
ds.Tables.Add(dt);
return ds;
}
The GetQuotes function accepts a string as input. This string is a space-delimited string of stock tickers. The first thing to do is split this string into an array. This function returns a dataset which we will generate dynamically. The dataset contains just one table with two fields, Ticker and Price. After creating the dataset, we create a datatable a call it "StockData." The datatable will have two columns each of which are strings. As discussed above, we could have made the price column a decimal, but we would need to return a decimal value in the case of invalid tickers. For each of the items in the _tickers array, we call the GetQuote function to retrieve the price. As I mentioned above, in this case, GetQuote is being called locally, so no web reference needs to be added. After we have retrieved all of the quotes for the requested tickers, we return the results as a dataset.
Top 
Consuming the services
The easiest way to add a web reference to your project is within the Visual Studio IDE. In Solution Explorer, right-click on References and select "Add Web Reference". In the address box of the window that opens, enter the URL for the web service. On my development machine, I entered http://localhost/services/stockservices.asmx to add the web service I created for this article. Next, give the reference a name (or accept the default) and click "Add Reference". This process adds several files to your project. There is a .disco file that contains the discovery information for the added web reference. There is also a .wsdl file that contains information about the web service's interface. Also, a proxy class is created that you will use when interacting with the web service. If you open this file, you will notice that Visual Studio builds asynchronous functionality into this proxy class, so nothing special needs to be done on the client side to call the web service asynchronously.
Once we have the web reference, the rest is quite simple. On my sample page, I have two sections used to test each of the web service. Each section has a textbox to enter the ticker or tickers and a button to call the web service. In the case of GetQuote, I display the result on a label, with GetQuotes, I use a datagrid.
private void btnGetQuote_Click(object sender, System.EventArgs e)
{
localhost.StockServices ss = new localhost.StockServices();
lblQuote.Text = ss.GetQuote(txtTicker.Text);
}
private void btnGetQuotes_Click(object sender, System.EventArgs e)
localhost.StockServices ss = new localhost.StockServices();
dgQuotes.DataSource = ss.GetQuotes(txtTickers.Text);
dgQuotes.DataBind();
}
As you can see, there is no difference in the code between calling a local function and calling a web service. Create the object and call the function.
Top 
Final thoughts
All of the data returned from the web services is freely available on the Internet. All we are doing is scraping the data from a public website. Our example is fairly simple, but there is much data available to us using this technique. For instance, I have also created a console app that uses Yahoo! Finance to extract end-of-day stock data. I run the app every night during off hours to insert this information into a database I use to track price history.
Here [^] is a link to a working example.
Top 