"Is SilverLight going to use Cross Domain Poilicies?"
I hope no one is thinking, "FLASH?!?! why not Silverlight!!" If so I apologise lol, but a project I am working on, the client has clearly specified they require this small "WIDGET" if you will in flash. Due to the current market take up of flash I would say but either way I had to make a WIDGET in Adobe flash. In hindsight now I think, or I am sure I would have had the same obstacles to over come if I did this in Silverlight as well.
What the widget is, is a small badge which is able to be embedded in HTML code, for the likes of people on FaceBook, MySpace etc... If it was a static Image or Animation there would have been no problem, but the obstacle came when I needed to insert dynamic information into the widget. In this case it was the name and a count of units for a user who was a member of the site. A little bonus was that I could transform and rotate the text which is not possible to such an extent with AJAX, but as you know with Flash and Silverlight, that is there THING, GRAPHICS!
Also to point out I am using ActionScript 3.0 in Flash not Flex, (I will explain that also aswell lol)
So from the outset I was thinking:
- .NET Web Service in C# published to a sub domain of the site.
- Client Code in Action Script 3.0 to use the WebService and AbstractOperation objects.
- Some imagination to get the widget a little more attractive.
BANG!
I am not sure why I did not read this before, I will stand corrected if the book I am reading has already mentioned this but Action Script 3.0, LIKE Silverlight, LIKE AJAX throws a security exception for cross domain calls. It just didn't occur to me with Flash, but then again I am still a novice, or some one who knows what they are doing but is learning a slightly different syntax.
Here comes another obstacle I encountered. The WebService and AbstractOperation objects I spoke of are purely reserved for FLEX in the way they are not included in the standard libraries for Flash, but in the mx namespace and package for flex. OK I thought no problem, so I copied the libraries into my working folder for my Widget. Now straight away, I have an empty .flv file, I publish and I have a .SWF file approx 150KB! So the over head of adding these libraries of course comes with this overhead of a minimum .swf size, but I thought this is a little over kill may be for my small widget. I may look at .SQF compressors next, but anyway.
So scrapping the idea of using the Flex WebService and AbstractOperation objects I was also left with this notion, "If I now want to use my .NET Web Service, I will have to code some objects which will manually construct the SOAP Request, this seemed to much work to be honest for this project timescale and the budget, so I scrapped the idea of a web service. What I came up with was this:
"Using the Action Script 3.0 URLLoader, a .NET Http Handler and the .NET XML Serializer"
As you might have guessed I will be using GET requests here as I am not sure the URLLoader can handle post requests, which means that my HTTP Handler will need to parse the information through the querystring. So I will begin with the Action Script 3.0 Service which I made:
package com.mypackage{
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.events.EventDispatcher;
public class Service extends EventDispatcher
{
private var theResult:XML;
public function GetResult():XML{
return this.theResult;
}
public function Service(url:String):void{
Init();
var loader:URLLoader = new URLLoader();
loader.addEventListener(Event.COMPLETE, processXML);
loader.load(new URLRequest(url));
}
public function processXML(e:Event):void{
theResult = new XML(e.target.data);
dispatchEvent(new Event(Event.COMPLETE));
}
private function Init():void{
}
}
}
so what is happening here. The object is called Service and I instantiate the object with a URL. I attach a callback to the URLLoader which is executed when the request has completed. You will see further down that the request generates XML, which I store as such in ActionScript. I then raise another Complete event for the Service object for use in the .flv file, or the WIDGET.
So the next bit of code I would like to show you is .NET and is the IHttpHandler. In hindsight I should have used an IHttpHandlerFactory, but hey, this is what it is for now.
public class MemberServiceProxy : IHttpHandler
{
private MemberService m;
private HttpContext context;
private NameValueCollection queryString;
private int userID;
public void ProcessRequest(HttpContext context)
{
this.context = context;
this.context.Response.Clear();
this.context.Response.ClearHeaders();
this.context.Response.ContentType = "text/xml";
this.queryString = this.context.Request.QueryString;
this.m = new MemberService();
if (String.IsNullOrEmpty(queryString["fname"]))
return;
else
{
this.userID = Convert.ToInt32(queryString["userid"]);
switch (queryString["fname"].ToLower())
{
case "getnumberofbricks":
GetNumberOfBricks();
break;
case "getuserhandle":
GetUserHandle();
break;
default: break;
}
}
}
private void GetNumberOfBricks()
{
Requests.NumberOfBricksRequest num1 = new Requests.NumberOfBricksRequest();
num1.UserID = this.userID;
num1.NumberOfBricks = m.GetNumberUserBricks(this.userID);
XmlSerializer x1 = new XmlSerializer(typeof(service.package.com.Requests.NumberOfBricksRequest));
using (MemoryStream ms1 = new MemoryStream())
{
x1.Serialize(ms1, num1);
context.Response.BinaryWrite(ms1.ToArray());
}
}
private void GetUserHandle()
{
Requests.GetUserHandle handle1 = new Requests.GetUserHandle();
handle1.UserID = this.userID;
handle1.Handle = m.GetUserHandle(this.userID);
XmlSerializer x1 = new XmlSerializer(typeof(service.package.com.Requests.GetUserHandle));
using (MemoryStream ms1 = new MemoryStream())
{
x1.Serialize(ms1, handle1);
context.Response.BinaryWrite(ms1.ToArray());
}
}
public bool IsReusable
{
get
{
return true;
}
}
}
So what is happening above is ProcessRequest is called. I clear out the content and the headers because I will be outputting XML, and then I attached the QueryString to a System.Collection.Specialized.NameValueCollection, making it a little quicker to code with. I read what function is being called by looking at the fname value of the function. I could have used Reflection here to avoid the tightly coupled switch statement.
In each of the functions I then make a request to a WebService (I still wanted to abstract that part) and then serialize it to XML ready for the output. This is placed inside a MemoryStream and then I use Response.BinaryWrite to output the information to the browser. I have created sort of Data Transfer Objects which I have added instruction to for how they should be serialized. Here is an example of the Requests.NumberOfBricksRequest
[Serializable]
[XmlRoot("Result")]
public class NumberOfBricksRequest
{
private int userID;
[XmlElement("UserID",typeof(int))]
public int UserID
{
get { return userID; }
set { userID = value; }
}
private int numberOfBricks;
[XmlElement("NumberOfBricks",typeof(int))]
public int NumberOfBricks
{
get { return numberOfBricks; }
set { numberOfBricks = value; }
}
}
Do the above makes things so easy to go back into flash with. For example the sample output of the above produces the following:
<?xml version="1.0" ?>
<Result xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<UserID>2248</UserID>
<NumberOfBricks>0</NumberOfBricks>
</Result>
The next part is to use my Action Script Service class and to consume my Http Handler with it. To do this I create some action script in the first frame of my .flv file.
import com.package.Service;
import flash.events.Event;
import flash.events.MouseEvent;
addEventListener(MouseEvent.CLICK,Redirect);
m_movie.addEventListener(MouseEvent.CLICK,Redirect);
function Redirect(e:MouseEvent)
{
ExternalInterface.call("recieveInstructionFromFlash","http://www.msn.com");
}
var userparams:Object = this.loaderInfo.parameters;
if(userparams.userID != null)
{
var service1:Service = new Service("http://service.package.com/MemberServiceProxy.ashx?fname=getnumberofbricks&userid="+userparams.userID.toString());
var service2:Service = new Service("http://service.package.com/MemberServiceProxy.ashx?fname=getuserhandle&userid="+userparams.userID.toString());
service1.addEventListener(Event.COMPLETE,Complete1);
service2.addEventListener(Event.COMPLETE,Complete2);
trace();
function Complete1(e:Event):void{
var resultString:String = "I have " + e.target.GetResult().NumberOfBricks.toString() +" bricks";
m_dynamic.text = resultString;
}
function Complete2(e:Event):void{
var resultString:String = e.target.GetResult().Handle.toString();
m_handle.text = resultString;
}
}
else{
m_handle.text = "no parameters";
}
If you notice any variable which are prefixed with m_ I am referring to instances I have added to the stage. I am creating two instances of the Service class I showed you above. I pass in the URL I want it to load and then add an Event Listener for when it has finished. I have defined two complete events which get called by their respective services and simply change the text property of the dynamic text instances which I have added to my stage.
Allowing the WIDGET to be added to any HTML.
The final part of this is overcoming the fact that we are allowing users to take some code and embed it on any site they choose where they have access to customise page objects. This is where the security exception will get thrown if we simply add the object tag straight in. I am not sure if YouTube have achieved this, but they do allow you to simply embed the object tag. I am not sure how they have got around the security exception which warns of cross script url requests. I would imagine they use an SWY Loader and now thinking about it that might be a miles quicker option that what I have gone through here.
Arrrgghhh! LOL ... Labour in Vain!
So basically I compose a Blog while I work, and it is now apparent that my inexperience of the platform and its capabilies is shining through, but hey "I'M learning here!" It also makes me start thinking now, if Silverlight is using any kind of cross domain policies!
It turns out that you can insert a xml file in the root of your domain called crrossdomain.xml, to get this to work I also had to include the following ActionScript in the stage script.
import flash.system.Security;
Security.allowDomain("*");
And my cross domain xml file is the following:
<?xml version="1.0"/>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" />
</cross-domain-policy>
OK, so to bring this to a close I will explain how I went about achieving what I needed to without knowing this "Correct Method." I use Javascript to dynamically create an iframe of a certain size and I then write it to the document. I pass variables through the querystring so that I can then pick these up inside flash. It is surprisingly easy to get these values using a combination of variable assignments and the split methods. So anyway that was how I originally achieved it but now, NOW! I create a second swf which acts as the containing SWF and I then load in the external SWF. A couple of bonuses to notice here are:
- It is the correct way to do this
- You can progress the loading of your movie so easy and in a custom way it is fantastic for both your ego lol and the users' experience.
So below is the final part of this mamoth part of my day. It is the loading swf if you will and the code is straight out of the Adobe Flash Documentation and I think really nice code. If you are .NET C# you should have very little trouble becoming accustomed to this code, expecially with the advent of C# 3.0 and anonymous types. I am not saying forget SilverLight, I am actually saying "LEARN BOTH!!" why not? ah go on!
The loading SWF
import flash.net.URLRequest;
import flash.display.Loader;
import flash.events.Event;
import flash.events.ProgressEvent;
function startLoad()
{
var mLoader:Loader = new Loader();
var mRequest:URLRequest = new URLRequest("http://service.package.com/service.swf?userID=2248");
mLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onCompleteHandler);
mLoader.contentLoaderInfo.addEventListener(ProgressEvent.PROGRESS, onProgressHandler);
mLoader.load(mRequest);
}
function onCompleteHandler(loadEvent:Event)
{
addChild(loadEvent.currentTarget.content);
}
function onProgressHandler(mProgress:ProgressEvent)
{
var percent:Number = mProgress.bytesLoaded/mProgress.bytesTotal;
trace(percent);
}
startLoad();
Conclusion
I would like to now investigate SilverLight cross domain scripting policies since I now the possibilities in Flash and Flex for that matter. I am really content that I did not have to hack out the javascript method.
Sorry for the staggered conclusion but I kind of like this type of blogging which I am kinda writing in real time. An extension I suppose would be to tiestamp each entry as you work. I will try and coin a phrase for it lol. HOW ABOUT UNTIDY! lol