Thursday, November 29, 2007

Placing Flex Error/Fault Information on the Clipboard

The Flash method flash.system.System.setClipboard(String) is a very useful utility function. This method allows a Flash application to put a String onto the user's Clipboard. In this blog entry, I'll provide a simple example of a use I really like for this functionality. It is very convenient during Flex development to have my fault handler methods automatically put fault and error information into my clipboard so that I can easily paste the text messages associated with the error in my application or text editor of choice.

The following code listing (Clipboard.mxml) shows a simple MXML application that demonstrates the utility of the System.setClipboard(String) method.


<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:flash.display="flash.display.*"
width="500" height="300"
applicationComplete="breakMe();">

<mx:Script>
<![CDATA[
/**
* To compile solely this Flex application from the command line, use
* mxmlc -strict=true Clipboard.mxml
*
* This application demonstrates use of the flash.system.System.setClipboard()
* function. The application intentionally tries to access an unavailable
* HTTPService so that the application's default fault handler is invoked and
* that invoked fault handler then puts its fault message on the clipboard.
*/

import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.utils.ObjectUtil;

/**
* Handle successful result of web service or HTTP service associated with
* this result handler.
*
* @param aResultEvent Successful event.
*/
private function resultHandler(aResultEvent:ResultEvent):void
{
trace( "Successfully returned from "
+ aResultEvent.currentTarget.toString() );
}

/**
* Handle fault result of web service or HTTP service associated with this
* fault handler.
*
* @param aFaultEvent Fault event.
*/
private function faultHandler(aFaultEvent:FaultEvent):void
{
const faultEventTarget:String = ObjectUtil.toString(aFaultEvent.currentTarget);
const faultErrorId:int = aFaultEvent.fault.errorID;
const faultErrorCode:String = aFaultEvent.fault.faultCode;
const faultErrorDetail:String = aFaultEvent.fault.faultDetail;
const faultString:String = aFaultEvent.fault.faultString;
const faultMessage:String = aFaultEvent.fault.message;
const faultName:String = aFaultEvent.fault.name;
const faultErrorString:String =
"Fault " + faultErrorId + " (" + faultName + "):\n"
+ "[" + faultErrorCode + "]\n\n"
+ "FAULT MESSAGE: " + faultMessage + "\n\n"
+ "FAULT DETAILS: " + faultErrorDetail + "\n\n"
+ "FAULT STRING: " + faultString;;
const faultVerboseErrorString:String =
faultErrorString + "\n\n" + "FAULT ON TARGET:\n" + faultEventTarget;
trace(faultVerboseErrorString);
Alert.show(faultErrorString, "Intentional Fault Occurred");
flash.system.System.setClipboard(faultVerboseErrorString);
}

/**
* Function intended to intentionally call a non-existent HTTP Service to
* lead to its fault handler being invoked.
*/
private function breakMe():void
{
brokenService.send();
}
]]>
</mx:Script>

<mx:HTTPService id="brokenService"
resultFormat="e4x"
url="http://marxsoftware.blogspot.com/"
result="resultHandler(event);"
fault="faultHandler(event);" />

</mx:Application>


In the above code listing, I intentionally caused a fault handler to be invoked when the specified HTTPService could not access the provided URL. I took advantage of the information associated with a Fault for all forms of error output (trace(), Alert, and clipboard), but the information on the "current target" associated with the fault event is too verbose for the Alert. Therefore, I only included that information in the trace() and set it to the clipboard. Because the trace output may not always be available (such as when the application is not executed in a Flash Debug Player or a debugger is not connected), it is handy to have this same information sitting in my clipboard waiting for me to paste it into my favorite text editor or IDE.

I intentionally used a different toString() method in the result handler than I used in the fault handler. The toString called directly on ResultEvent.currentTarget only outputs a single line of information (essentially identifying the current target as an HTTPService). The ObjectUtil.toString() applied to the FaultEvent.currentTarget supplies significantly more detail and that is why its output needed to not be placed in the alert. I have compared the inherited Object.toString to the static ObjectUtil.toString(Object) in greater detail in a previous blog entry.

A screen capture of the output of this forced error is shown next:



This Alert pop-up was intentionally kept rather small by not including the lengthy target information in the output. However, that target information was included in the trace() call and to the clipboard. I cannot really show it here in a way that does it justice, but a very handy thing I can do at this point is to open a text editor and simply "paste" (CTRL-V, "Paste" option, etc.) into the editor. The text shown next will be inserted despite the fact that I never did a "copy" explicitly.


Fault 0 (Error):
[Channel.Security.Error]

FAULT MESSAGE: faultCode:Channel.Security.Error faultString:'Security error accessing url' faultDetail:'Destination: DefaultHTTP'

FAULT DETAILS: Destination: DefaultHTTP

FAULT STRING: Security error accessing url

FAULT ON TARGET:
(mx.rpc.http.mxml::HTTPService)#0
channelSet = (mx.messaging::ChannelSet)#1
channelIds = (Array)#2
[0] "direct_http_channel"
clustered = false
configured = false
connected = true
currentChannel = (mx.messaging.channels::DirectHTTPChannel)#3
channelSets = (Array)#4
[0] (mx.messaging::ChannelSet)#1
connected = true
connectTimeout = -1
endpoint = "http:"
failoverURIs = (Array)#5
id = "direct_http_channel"
protocol = "http"
reconnecting = false
requestTimeout = -1
uri = ""
messageAgents = (Array)#6
[0] (mx.rpc::AsyncRequest)#7
channelSet = (mx.messaging::ChannelSet)#1
clientId = "DirectHTTPChannel0"
connected = true
defaultHeaders = (null)
destination = "DefaultHTTP"
id = "E671BD31-DE99-42D6-7453-8F6F1FE32BC4"
requestTimeout = -1
session = (null)
subtopic = ""
concurrency = "multiple"
contentType = "application/x-www-form-urlencoded"
destination = "DefaultHTTP"
headers = (Object)#8
lastResult = (null)
makeObjectsBindable = true
method = "GET"
request = (Object)#9
requestTimeout = -1
resultFormat = "e4x"
rootURL = "file:///C:/flexExamples/Clipboard/Clipboard.swf"
showBusyCursor = false
url = "http://marxsoftware.blogspot.com/"
useProxy = false
xmlDecode = (null)
xmlEncode = (null)


As you can see, the information on the current target associated with the handled event is much more verbose than the fault information itself. Rather than having to subject myself to so much text in the Alert window or having to try to view the trace output in a debugger window, it was nice to be able to simply paste it all into a favorite text editor. Note that I can always redirect trace output to a file, but this setClipboard(String) tactic is a nice backup in case I forget to do that or forget to run in the appropriate mode or simply do not want to deal with that effort.

One other interesting observation is that Fault.message contains the contents of Fault.faultCode, Fault.faultDetail and Fault.faultString. This means, of course, that you'd normally not need to output all four properties and could go with just Fault.message to get the contents of the other three.

The examples associated with the Flex 2 Language Reference documentation on the flash.system.System class show using a handy property of this class (totalMemory) and writing that to the clipboard with setClipboard(String).

The section "Saving Text to the Clipboard" in the Programming ActionScript 3.0 System Class write-up is what originally gave me the idea of using the flash.system.System.setClipboard(String) method to place error and fault information in the clipboard for easy pasting. Thanks to the author(s) of that for a very useful idea!

Finally, the documentation makes it very clear that there is no getClipboard() method because of security concerns associated with the ability to grab data off of a user's clipboard.

1 comment:

Sudhir Chauhan said...

try to access the crossdomain.cml file with IE. you will find secure="true" for each domain entry & this is creating a problem. Add secure="false" in each URL to resolve this issue.

cross-domain-policy
allow-access-from domain="*" secure="false"