Skip to content

An attempt to make integration with REST services in Flex less painful. Based on the Flex's SerializationFilter architecture, it can be plugged into your project seamlessly without replacing existing data access objects.

License

Notifications You must be signed in to change notification settings

artema/FlexRest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

There are several custom SerializationFilters that can make your life easier (requires Flex 4).

CompositeSerializationFilter

Composite design pattern implementation for HTTP serialization filters. Override any of 5 SerializationFilter’s methods using different filter instances:

<mx:HTTPService>
	<mx:serializationFilter>
		<rest:CompositeSerializationFilter>
			<!-- Serialize requests in JSON -->
			<rest:bodySerializer>
				<rest:JSONSerializationFilter />
			</rest:bodySerializer>
			<!-- Deserialize replies in JSON -->
			<rest:resultDeserializer>
				<rest:JSONSerializationFilter />
			</rest:resultDeserializer>
			<!-- Build REST-style URLs dynamically -->
			<rest:urlSerializer>
				<rest:RESTSerializationFilter />
			</rest:urlSerializer>
			<!-- Use custom HTTP methods -->
			<rest:parametersSerializer>
				<rest:RESTSerializationFilter />
			</rest:parametersSerializer>
			<!-- Set request content type to application/json -->
			<rest:contentTypeProvider>
				<rest:RESTSerializationFilter requestContentType="application/json" />
			</rest:contentTypeProvider>
		</rest:CompositeSerializationFilter>
	</mx:serializationFilter>
</mx:HTTPMultiService>

SerializationFilter’s methods that can be overriden:

  • deserializeResult
  • getRequestContentType
  • serializeParameters
  • serializeBody
  • serializeURL

Methods that are not overridden are handled using a regular SerializationFilter.

RESTSerializationFilter

Implements serializeParameters, contentTypeProvider and serializeURL methods.

Allows you to sent custom HTTP verbs, other than GET or POST, which is not supported by the Flash Player by default. You can specify one of the three possible ways to do that:

  • Send it in a X-HTTP-Method-Override header (methodOverride="header"). Don’t forget to update your crossdomain.xml file to allow headers to be sent to other domains.
  • Append a _method parameter to all URLs (methodOverride="url").
  • Send it a request body as a _method parameter (methodOverride="variable"). This will force your request to be serialized as application/x-www-form-urlencoded.

This serialization filter can also be used to build dynamic URLs that containt tokenized parameters (enclosed in square brackets).

<fx:Declarations>
	<rest:RESTSerializationFilter 
		id="restSerializer" 
		methodOverride="variable"
		requestContentType="application/x-www-form-urlencoded" />
	<mx:HTTPMultiService
		id="service"
		baseURL="http://example.com/api/"	
	>
		<mx:serializationFilter>
			<!-- contentTypeProvider is required in order to convert custom HTTP verbs to POST 
			and to set content type to 'application/x-www-form-urlencoded'. -->
			<!-- urlSerializer will replace square bracket tokens in URLs. -->
			<!-- parametersSerializer will add a '_method' variable to your requests, if needed. -->
			<rest:CompositeSerializationFilter 
				contentTypeProvider="{restSerializer}" 
				urlSerializer="{restSerializer}"
				parametersSerializer="{restSerializer}" />
		</mx:serializationFilter>		
		<mx:operationList>
			<!-- 
				[ID] and any other token will be automatically 
				replaced by variables from operation arguments object 
			-->
			<mx:HTTPOperation
				name="getUser"
				url="User/[ID]" />			
			<!--
				Request will be sent as a POST request
				with a "_method"="DELETE" variable
			-->
			<mx:HTTPOperation
				name="deleteUser"
				url="User/[ID]"
				method="DELETE" />
		</mx:operationList>
	</mx:HTTPMultiService>
</fx:Declarations>
import mx.rpc.AbstractOperation;
import mx.rpc.AsyncToken;
		
import vo.User;
		
private function getUser(userId:uint):AsyncToken
{
	var operation:AbstractOperation = service.getOperation("getUser");
	operation.arguments = { ID: userId }; //replace URL tokens
			
	return operation.send();
	//You can also add your variable to requests:
	//return operation.send({ varname: "varvalue" });
}

JSONSerializationFilter

Implements deserializeResult, contentTypeProvider and serializeBody methods.

Serialize requests and deserialize replies in JSON with a fast native JSON parser (only in FP11 version; FP9 version is relying on as3corelib’s JSON parser). Requests and replies can be converted from and into strongly-typed objects on the fly. The following object mapping features are available:

  • Incoming arrays can be mapped to strongly-typed vectors or arrays of typed objects.
  • ArrayElementType metatag can be used to map arrays of typed objects.
  • Transient metatag is respected both in requests and responses.
  • Date strings in JSON format /Date(1325376000000)/ can be converted to Date objects, and vice versa.

For example, when you receive this:

{
	"owner":{
		"birthday":"/Date(1325376000000)/",
		"firstName":"John",
		"lastName":"Smith"
	}
	"wheels":[
		{ "position": { "x":1, "y":1 } },
		{ "position": { "x":1, "y":-1 } },
		{ "position": { "x":-1, "y":1 } },
		{ "position": { "x":-1, "y":-1 } }
	]
}

It can be converted into these:
public class User
{
	public var birthday:Date;		
	public var firstName:String;		
	public var lastName:String;
}	
public class Car
{
	public var owner:User;
	[ArrayElementType("vo.Wheel")] 
	public var wheels:Array;	
	//You can just use vectors instead
	//public var wheels:Vector.<Wheel>
}
import flash.geom.Point;
public class Wheel
{
	public var position:Point;
}

Using the following code:

<fx:Declarations>
	<mx:HTTPMultiService
		id="service"
		baseURL="http://example.com/api/"	
	>
		<mx:serializationFilter>
			<rest:CompositeSerializationFilter>
				<!-- Serialize requests from objects to JSON. -->
				<rest:bodySerializer>
					<rest:JSONSerializationFilter />
				</rest:bodySerializer>
				<!-- Deserialize replies from JSON to strongly-typed objects. -->
				<rest:resultDeserializer>
					<rest:JSONSerializationFilter />
				</rest:resultDeserializer>
				<!-- Forces requests content type to be 'application/json'.
				     This is required in order to encode requests in JSON.
				     If content type is not changed either this way, 
				     or manually on an operation, Flex will handle 
				     request body encoding by itself. -->
				<rest:contentTypeProvider>
					<rest:RESTSerializationFilter requestContentType="application/json" />
				</rest:contentTypeProvider>
				<!-- This serializer is required for JSON requests. -->
				<rest:parametersSerializer>
					<rest:RESTSerializationFilter />
				</rest:parametersSerializer>
			</rest:CompositeSerializationFilter>
		</mx:serializationFilter>		
		<mx:operationList>
			<!-- 
				Note that there is a resultElementType
				property set on the operation
			-->
			<mx:HTTPOperation
				name="getCar"
				url="Car/[ID]"
				method="GET"
				resultElementType="{Car}"
				result="onCarLoaded(event)" />
		</mx:operationList>
	</mx:HTTPMultiService>
</fx:Declarations>
import mx.rpc.events.ResultEvent;
		
import vo.Car;
import vo.User;
import vo.Wheel;
		
private function onCarLoaded(event:ResultEvent):void
{
	var car:Car = Car(event.result);
	var wheels:Vector.<Wheel> = car.wheels;
			
	var owner:User = car.owner;
	var birthday:Date = owner.birthday;
}

If you have an array of objects…

[
	{ "x":1, "y":5 },
	{ "x":2, "y":4 },
	{ "x":3, "y":3 },
	{ "x":4, "y":2 },
	{ "x":5, "y":1 }
]

…you can receive it as an array of strongly-typed objects by setting resultFormat="array" and resultElementType="{ElementClass}". By default, all arrays are wrapped into an ArrayCollection. To avoid this behavior, just set makeObjectsBindable="false" on the operation instance.

<mx:HTTPOperation
	name="getPoints"
	url="Points/[FROM]/[TO]"
	resultFormat="array"
	makeObjectsBindable="false"
	resultElementType="{Point}"
	result="onPointsLoaded(event)" />
private function onPointsLoaded(event:ResultEvent):void
{
	trace(event.result.length);
	
	for each(var point:Point in event.result)
		trace(point.x + point.y);
}

JSONSerializationFilter is an implementation of an abstract TypedSerializationFilter that handles types transformation, while a concrete child class handles actual data serialization and deserialization.

Setting resultFormat to any other value, besides array and object (default) will result in a JSONSerializationFilter (or any other TypedSerializationFilter) to be suppressed in a selected HTTPOperation.

XMLSerializationFilter

Implements deserializeResult, contentTypeProvider and serializeBody methods.

Acts the same way as the JSONSerializationFilter. You can use the optional xmlEncode and xmlDecode properties to assign custom serialization methods:

<mx:HTTPService>
	<mx:serializationFilter>
		<rest:CompositeSerializationFilter>
			<!-- Serialize requests from objects to XML. -->
			<rest:bodySerializer>
				<rest:XMLSerializationFilter xmlEncode="{doXmlEncode}" />
			</rest:bodySerializer>
			<!-- Deserialize replies from XML to strongly-typed objects. -->
			<rest:resultDeserializer>
				<rest:XMLSerializationFilter xmlDecode="{doXmlDecode}" />
			</rest:resultDeserializer>
			<!-- Forces requests content type to be 'application/xml'. -->
			<rest:contentTypeProvider>
				<rest:RESTSerializationFilter requestContentType="application/xml" />
			</rest:contentTypeProvider>
			<!-- This serializer is required for XML requests. -->
			<rest:parametersSerializer>
				<rest:RESTSerializationFilter />
			</rest:parametersSerializer>
		</rest:CompositeSerializationFilter>
	</mx:serializationFilter>
</mx:HTTPMultiService>
private function doXmlEncode(body:Object):XMLNode
{
	return new XMLNode(1, '<body id="' + body.id + '"/>');
}

private function doXmlDecode(node:XMLNode):Object
{
	return { id: node.attributes.id };
}

BasicAuthenticationSerializationFilter

Extends the CompositeSerializationFilter with two new properties: username and password. Will add a basic HTTP authentication header to all requests.

<mx:HTTPService>
	<mx:serializationFilter>
		<rest:BasicAuthenticationSerializationFilter username="admin" password="qwerty">
			<rest:bodySerializer>
				<rest:XMLSerializationFilter />
			</rest:bodySerializer>
			<rest:resultDeserializer>
				<rest:XMLSerializationFilter />
			</rest:resultDeserializer>
			<rest:contentTypeProvider>
				<rest:RESTSerializationFilter requestContentType="application/xml" />
			</rest:contentTypeProvider>
			<rest:parametersSerializer>
				<rest:RESTSerializationFilter />
			</rest:parametersSerializer>
		</rest:BasicAuthenticationSerializationFilter>
	</mx:serializationFilter>
</mx:HTTPMultiService>

OAuthSerializationFilter

Extends the CompositeSerializationFilter. Can be used to create and assign an OAuth header to your requests.

<mx:HTTPService>
	<mx:serializationFilter>
		<rest:OAuthSerializationFilter id="oauthFilter" keySecret="kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw" tokenSecret="LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE">
			<rest:bodySerializer>
				<rest:XMLSerializationFilter />
			</rest:bodySerializer>
			<rest:resultDeserializer>
				<rest:XMLSerializationFilter />
			</rest:resultDeserializer>
			<rest:contentTypeProvider>
				<rest:RESTSerializationFilter requestContentType="application/xml" />
			</rest:contentTypeProvider>
			<rest:parametersSerializer>
				<rest:RESTSerializationFilter />
			</rest:parametersSerializer>
		</rest:BasicAuthenticationSerializationFilter>
	</mx:serializationFilter>
	<mx:operationList>
		<mx:HTTPOperation
			name="getUser"
			url="User/[ID]" />			
	</mx:operationList>
</mx:HTTPMultiService>
import mx.rpc.AbstractOperation;
import mx.rpc.AsyncToken;
import mx.rpc.http.OAuthHeader;
import mx.rpc.utils.OAuthUtil;
		
import vo.User;
		
private function getUser(userId:uint):AsyncToken
{
	var oauthData:OAuthHeader = new OAuthHeader();
	oauthData.oauth_version = "1.0";
	oauthData.oauth_consumer_key = "xvz1evFS4wEEPTGEFPHBog";
	oauthData.oauth_token = "370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb";
	oauthData.oauth_nonce = OAuthUtil.generateNonce();
	oauthData.oauth_signature_method = OAuthHeader.SIGNATURE_METHOD_HMAC_SHA1;
	oauthData.oauth_timestamp = OAuthUtil.getTimestamp();

	var operation:AbstractOperation = service.getOperation("getUser");
	operation.arguments = { ID: userId }; //replace URL tokens

	oauthFilter.oauthData = oauthData;

	return operation.send();
}

Static mx.utils.TypeUtil class can be used for all type castings listed above.

mx.rpc.OperationResponder can be used to make operation handlers more straightforward. It mimics Flex’s AsyncResponder class, but with a different result/fault functions signatures. A valid result handler signature is function(valueObject:Object):void. A result handler function will receive ResultEvent.result value, so you can just set your result type in a handler: function(valueObject:MyResultClass):void. A valid fault handler signature is function(fault:Fault):void.

import mx.rpc.OperationResponder;
		
import vo.User;
		
private function getUser():void
{	
	service.getOperation("getUser").send().addResponder(new OperationResponder(onResult, onFault));
}

private function onResult(user:User):void
{
	trace(user.firstName);
}

private function onFault(fault:Fault):void
{
	trace(fault.faultDetail);
}

About

An attempt to make integration with REST services in Flex less painful. Based on the Flex's SerializationFilter architecture, it can be plugged into your project seamlessly without replacing existing data access objects.

Topics

Resources

License

Stars

Watchers

Forks