Tuesday, November 10, 2009

Cross Domain Issues Workaround


In a previous post I talked about a change that the Twitter folks did last week on their cross domain policy. The change restricted all external domains, leaving the access to their Search API open only from their own domains. The cross domain restriction meant that RIAs where no longer able to access the Twitter Search API directly without a proxy server.

Checking their cross domain policy file today, it seems that they changed back, since right now it appears to be allowing access from all external domains. Again, they made the change without letting people know. I don't know about you, but to me it seems that somebody made a mistake on their team, and they made the changes in low profile motion to avoid too many buzz about it or maybe they actually aknowledge the mistake and listened to people developing RIAs against their API and decided to keep allowing the direct access.

Anyway, just in case the keep changing things without letting us know, I wanted to share a work around for their cross domain policy restriction. This work around will work for any other cross domain restriction issues that you might have hitting other site’s APIs.

Application Background:


The application I'm working on is a Silverlight 3 application that is being hosted on an ASP.NET web project. This "first phase" is just a simple Twitter Search client where users can enter a search term and retrieve twitter posts containing the term entered by the user. The app will include much more functionality, but for this post, that's pretty much the background you need to understand what I'm trying to accomplish.


Alternative Approaches:

My first idea was to actually build a middle tier WCF service application that will take care of all the Twitter API communication and serve my Silverlight 3 app. This is still an approach I want to follow since is a lot more robust and can provide a lot more functionality, like data parsing, data storage, catching and a better fault control. It will also help me to make the Silverlight app smaller and thinner, which is always good for client side apps if you have a robust server than can handle the work load on the backend side. This is what you need to keep in mind, you need to have a server that can host your middle tier and handle the work load without issues, or your app is going to stop being responsive when having lots of users at the same time.

However, talking again with my colleagues Steve Rettinger, he suggested implementing a call forward to an ASP page, just as a prove of concept to make sure the work around actually worked.

The Workaround:

Ok, so let’s get into some code details. First of all I'm using a subject/subscriber pattern to be able to broadcast and subscribe to global events from any component or user control inside my Silverlight application (I’ll will write a post about it and how to implement it soon). This implementation allows me to fire a global event from a nested user control and subscribe to that event in the main page code behind (actually the pattern implementation let’s me fire and handle global events anywhere I want), handling the event and doing the logic needed.

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
#region Global Event Subcriptions

CommandManager.Instance.Subscribe(
GlobalEventType.SearchRequest,
new GlobalEventDelegate(Search));

#endregion
}


That being said, when a user clicks on the "search" button of my app (which is inside a user control) after entering the search term, I fire a global event "SearchRequest" which I'm handling in my main page code behind (I’m subscribing to the “SearchRequest” global event in the main page code behind and assigning the handler of the event in the Loaded event handler of the main page).

The handler will then catch the event and redirect the call to a private method that uses the WebClient class to do the request to the Twitter Search API method asynchronously.

private void Search(Object data)
{
String searchTerm = data.ToString();
if (!string.IsNullOrEmpty(searchTerm))
{
WebClient wc = new WebClient();
wc.OpenReadCompleted +=
new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
wc.OpenReadAsync(
new Uri(HtmlPage.Document.DocumentUri,
string.Format(urlTemplate, HttpUtility.UrlEncode(searchTerm))));
}
}


The work around is that instead of calling the Twitter Search API directly, I'll be forwarding the call to an ASP.NET (ASMX) page (by specifying HtmlPage.Document.DocumentUri), which will in turn call the Twitter Search API method and return the xml result to my Silverlight application. The urlTemplate variable has the relative URL of my ASP.NET page.

urlTemplate value = "~/../TwitterProxy.aspx?q={0}"

The ASMX page in question (TwitterProxy.asmx) is part of the same ASP.NET web project that hosts my Silverlight application, so all I had to do was to create a new ASP.NET page and include the call forwarding logic in the code behind. You can actually ignore the ASP markup code, cause the forward call is being handled and the result returned directly without actually loading the page itself.

public partial class TwitterProxy : System.Web.UI.Page
{
private const string urlTemplate = "http://search.twitter.com/search.atom?q={0}";

protected void Page_Load(object sender, EventArgs e)
{
HttpWebRequest request =
(HttpWebRequest)HttpWebRequest.Create(String.Format(urlTemplate,

HttpUtility.UrlEncode(Request.QueryString["q"])));
Stream stream = request.GetResponse().GetResponseStream();
StreamReader reader = new StreamReader(stream);
Response.Write(reader.ReadToEnd());
Response.End();
}
}


Finally when the search call is forwarded and processed, and the resulting xml obtained, the response will be passed back to the Silverlight app, and the wc_OpenReadCompleted event handler will bi fired. I'll set the data context here, after cheking for errors or null values.

private void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null && e.Result != null)
{
XmlReader xml = XmlReader.Create(e.Result);
this.PostsViewer.PostsItemsControl.ItemsSource =
TwitterSearchHelper.GetTwittsFromQuery(xml);
}
}


This was an easy way to work around the Twitter Search cross domain policy restriction and be able to test the call and continue working, especially since there's another guy in the team working on the UI and animations in Blend, so I really needed to make the app work and be able to return real data results so he could keep working. Meanwhile I can continue my work on the most robust and scalable WCF Middle Tier.

Hope this helps you a bit to overcome this kind of cross domain issues with Silverlight, and maybe help you understand a little bit more on how Silverlight and ASP.NET can communicate and offer you a few tips I learned from this myself.

Thanks to my colleague Marvin Varela for the implementation help and tips.

Cheers!
-aB

No comments:

Post a Comment