Silverlight 2 Beta 2: Doing Data Part VI: A Generic Request WebService


By Peter Bromberg
Printer Friendly Version
View My Articles

  

Recently I wanted to try to get a user timeline using the Twitter API, which method requires Basic authentication. To my surprise, the Authorization header -- and many others -- are classified as "Restricted" for Silverlight's HttpWebRequest.



Procrastination isn't the problem, it's the solution. So procrastinate now, don't put it off.   - - Ellen DeGeneres

 

Normally, you would add an "Authorization" header with the value "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" where the gobbledegook after the word Basic is "username:password", converted to a Base64 string.  That's standard W3C Header protocol. With this header present on your Request, you would bypass the Login dialog and the request would complete normally.

Unfortunately, even though there is sample code illustrating this from Karen Corby here, it does not work (she does not actually add an Authorization header, just illustrates how to add a header). See here for a complete listing of restricted headers. So, if you have been trying to do this, or add any similar header that is on the restricted list, you'll only get strange exceptions that make no sense at all, and  you can give up and look for a workaround.

I'm still not 100% sure of the logic behind having an often-used request header being "restricted", but I strongly suspect it is because in it's effort to be a true cross-browser plug-in, Silverlight must use the NPAPI plug-in architecture which enforces various limitations including being able to only make asynchronous HTTP requests. Don't blame Microsoft for that - you'd be shooting the messenger. If you want to be browser- neutral you need to implement NPAPI - at least until submissions like Microsoft's advanced cross-domain XHR proposals are accepted by W3C.

Well, how about if we had a "generic" ASMX webservice that could make any kind of WebRequest call we wanted using the full Framework (without the "restrictions"), and return the appropriate results to our Silverlight apps?  We could give it the target url (including querystring), the method ("GET" or "POST"), username and password if authentication is required, the names and values of any request headers we want, and the names and values of any form POST values we needed.  I think this approach has a lot of useability since we would only need one webservice -- which could potentially accomodate any number and type of Silverlight apps. Basically the service is saying, "I'll do whatever you want and I'll send it to wherever you want, just give me all the info I need, and I'll send back your results."

The service I present here is not 100% feature-complete in this regard, but it goes a long way toward this goal. You can send a GET or POST request, you can add as many Request Headers as you want, you can supply a username:password pair for authentication, and you can supply arrays of formfield names and values for a POST. In order to keep things "generic" it always returns a string -- but that's not a big problem since you can Parse this directly into an XElement and LINQ your way to nirvana for databinding.  Besides, webservices are always returning strings over the wire. With that in mind, here's the WebService class and WebMethod to illustrate the idea:

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Services;

namespace GenericRequest
{
    /// <summary>
    /// Generic Request Service for Silverlight Apps
    /// </summary>
    [WebService(Namespace = "http://genericrequest.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class Service1 : System.Web.Services.WebService
    {
        /// <summary>
        /// Makes an HttpRequest.
        /// </summary>
        /// <param name="targetUrl">The target URL.</param>
        /// <param name="headerNames">The header names.</param>
        /// <param name="headerValues">The header values.</param>
        /// <param name="method">The method.</param>
        /// <param name="postValueNames">The post value names.</param>
        /// <param name="postValues">The post values.</param>
        /// <param name="userName">username.</param>
        /// <param name="password">password.</param>
        /// <returns>string (xml, html, etc.)</returns>
        [WebMethod]
        public string MakeRequest(string targetUrl, string[] headerNames,string[] headerValues,
            string method, string[] postValueNames, string[] postValues, string userName, string password)
        {
            string returnString = "Bad Request";
            // This is a GET request with no form values and optional Basic auth
            if(method.ToUpper()=="GET" ) 
            {
                using(WebClient wc = new WebClient() )
                {
                    wc.Proxy = null;
                    if(userName!=null && password!=null)
                    wc.Credentials = new NetworkCredential(userName, password);
                    if(headerNames!=null && headerNames.Length >0)
                    {
                        for(int i=0;i<headerNames.Length ;i++)
                        {
                            wc.Headers.Add(headerNames[i],headerValues[i]);
                        }
                    }
                 returnString=    wc.DownloadString(targetUrl);
                }
            }
            // This is a POST request with form values and optional Basic auth
            if(method.ToUpper()=="POST" && postValueNames !=null) 
            {
                using (WebClient wc = new WebClient())
                {
                    wc.Proxy = null;
                    if(userName!=null && password !=null)
                        wc.Credentials = new NetworkCredential(userName, password);
                    if (headerNames != null && headerValues !=null)
                    {
                        for (int i = 0; i < headerNames.Length; i++)
                        {
                            wc.Headers.Add(headerNames[i], headerValues[i]);
                        }
                    }
                    var data = new NameValueCollection();
                    for(int i =0;i<postValueNames.Length ;i++)
                    {
                      data.Add(postValueNames[i],postValues[i]);
                    }
                    byte[] result = wc.UploadValues(targetUrl, "POST", data);
                    returnString = System.Text.Encoding.UTF8.GetString(result);
                }
            }
            return returnString;
        }
    }
}
To use this, the following code will provide an example:
private void button1_Click(object sender, RoutedEventArgs e)
        {
            ServiceReference1.Service1SoapClient client = new Service1SoapClient();

            // this is for the authenticated friends timeline and requires http Basic auth. 
            string targetUrl = "http://twitter.com/statuses/friends_timeline.xml";
             
            client.MakeRequestCompleted += new EventHandler(client_MakeRequestCompleted);
          
           ArrayOfString headerNames = new ArrayOfString();
           ArrayOfString headerValues = new ArrayOfString();
            // add one header:
           headerNames.Add("Cache-Control");
           headerValues.Add("no-cache");
            ArrayOfString formValueNames = new ArrayOfString();
            ArrayOfString formValues = new ArrayOfString();
            // this is the generic service "GET" request with Basic auth, a header, and no form fields
            client.MakeRequestAsync(targetUrl, headerNames, headerValues, "GET", formValueNames, formValues,"username","password");
        }

        void client_MakeRequestCompleted(object sender, MakeRequestCompletedEventArgs e)
        {
            string s = e.Result;
            //  very simple binding just to show what we got back
           XElement elem= XElement.Parse(s);
            var query = from f in elem.Elements("status").Descendants() select f.Value;
            Grid1.ItemsSource = query;
        }
I don't have a method there for Forms Authentication yet , but it would not be very difficult. Sample code would look something like this:
string loginUri = "http://www.webshots.com/login";
string username = "username";
string password = "password";
string reqString = "username=" + username + "&password=" + password;
byte[] requestData = Encoding.UTF8.GetBytes (reqString);

CookieContainer cc = new CookieContainer(  );
var request = (HttpWebRequest)WebRequest.Create (loginUri);
request.Proxy = null;
request.CookieContainer = cc;
request.Method = "POST";

request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = requestData.Length;
using (Stream s = request.GetRequestStream(  ))
  s.Write (requestData, 0, requestData.Length);

using (var response = (HttpWebResponse) request.GetResponse(  ))
  foreach (Cookie c in response.Cookies)
    Console.WriteLine (c.Name + " = " + c.Value);

// We're now logged in. As long as you store and assign cc to subsequent WebRequest
// objects, you can do such things as download photos.

You can download the complete Silverlight solution including a Test Silverlight app here. Of course, you'll need to supply your own Twitter username and password to try it.


Biography
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking ,financial and telephony for 20 years. Pete focuses exclusively on the .NET Platform, and his samples at GotDotNet.com have been downloaded over 56,000 times. Peter enjoys producing 3D raytraced digital photo collage with Maya, 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: Silverlight 2 Beta 2: Doing Data Part VI: A Generic Request WebService
Peter Bromberg posted at 16-Aug-08 03:31
Original Article

 
index feature
Wilson Chan replied to Peter Bromberg at 07-Oct-08 04:07
I added the index feature, anyone who like to have a look, check out the following link: http://files.cnblogs.com/unruledboy/Serializer.zip

 
RTW same issue?
Braulio Braulio replied to Peter Bromberg at 10-Nov-08 04:32

Excellent article and elegant solution !!!

One question does this limitation applies as well to RTW version? (I have tried and no credentials available :-( ).

 

Thanks

   Braulio


promotion
Silverlight    WPF    WCF    WWF    LINQ   
JavaScript    AJAX    ASP.NET    XAML   
C#    VB.NET    VB 6.0    GDI+    IIS    XML   
.NET Generics    Anonymous Methods    Delegate   
Visual Studio .NET    Expression Blend    Virus   
Windows Vista    Windows XP    Windows Update   
Windows 2003 Server    Windows 2008 Server   
SQL Server    Microsoft Excel    Microsoft Word   
SharePoint    BizTalk    Virtual Earth   
.NET Compact Framework    Web Service   

"Everything" RSS / ATOM Feed Parser
How to send and receive messages through message queuing in .Net
How to Read text file as database
SQL Server 2005 Paging Performance Tip
Display code of web page.
Fully Scalable Excel File Importer class for .net using Microsoft Jet driver
Generic Chart Color Manager class that can be used for any charts
Helper class to style the infragistics wingrid
Using Reflection to detemine as Assembly Info in and out.
Helper class to play with Window (Owners and position)
Resolving displayname from the culture using the XmlLanguage and LanguageSpecificStringDictionary class
Manipulate file attributes in VB.NET
Forms Based Authentication Filtered Content Editor for SharePoint
How to create a Tree View of the Windows Folder and extract all the file-folder info.
How to use AssemblyInfo.cs file in win forms to provide much needed information on Assemblies
Sorting In Datagrid
Helper class to work with NativeMethods in the native api's
Silverlight Line Of Business Applications With Offline WPF Versions
C# : Database monitoring system using XML file
C# : Adding ComboBox to ListView SubItem
Sum of Numbers Captcha: Keeping it Simple
C# Create a Piechart for the specified Hard Disk Drive Utilization
Extension Methods for DataSet and DataTable that makes tasks easier
Accessing IIS Hosted WCF Services from PHP
Helper class that provides most commonly used Extension Methods for DateTime object
Helper class to work with a Status Bar in WPF.
Finding Unmatched Records in Dataset Tables Using Linq
Silverlight Toolkit: Autocomplete TextBox Stock Symbols and Chart
COOL Auto Complete textbox using javascript
Creating a Serializable Log Entry for Microsoft Enterprise Library to log to a Database
ASP.NET Searching Values in Datagrid