Question

I'm building a mvc application which will communicate with flash via AMF, anybody knows if there is any AMF ActionResult available on the web ?

EDIT:

using @mizi_sk answer (but without using IExternalizable) I did this ActionResult:

public class AmfResult : ActionResult
{
    private readonly object _o;

    public AmfResult(object o)
    {
        _o = o;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = "application/x-amf";
        using (var ms = new MemoryStream())
        {
            // write object
            var writer = new AMFWriter(ms);
            writer.WriteData(FluorineFx.ObjectEncoding.AMF3, _o);
            context.HttpContext.Response.BinaryWrite(ms.ToArray());
        }
    }
}

but the response handler on the flash side doesn't get hit.

in Charles at the Response->Amf tab I see this error:

Failed to parse data (com.xk72.amf.AMFException: Unsupported AMF3 packet type 17 at 1)

this is the raw tab:

 HTTP/1.1 200 OK 
 Server: ASP.NET Development Server/10.0.0.0 
 Date: Mon, 14 May 2012 15:22:58 GMT 
 X-AspNet-Version: 4.0.30319
 X-AspNetMvc-Version: 3.0 
 Cache-Control: private 
 Content-Type:application/x-amf 
 Content-Length: 52 
 Connection: Close

  3/bingo.model.TestRequest param1coins name

and the Hex tab:

00000000  11 0a 33 2f 62 69 6e 67 6f 2e 6d 6f 64 65 6c 2e     3/bingo.model.
00000010  54 65 73 74 52 65 71 75 65 73 74 0d 70 61 72 61   TestRequest para
00000020  6d 31 0b 63 6f 69 6e 73 09 6e 61 6d 65 01 04 00   m1 coins name   
00000030  06 05 6a 6f                                         jo            
Was it helpful?

Solution

The trick is to use FluorineFx.IO.AMFMessage with AMFBody as a result object and set a Content property.

You can see this in Charles proxy with other working examples (I've used great WebORB examples, specifically Flash remoting Basic Invocation AS3)

I've updated AMFFilter to support Response parameter that AMFBody needs. Maybe it could be solved more elegantly by some current context cache, don't know.

Code follows:

public class AmfResult : ActionResult
{
    private readonly object _o;
    private readonly string _response;

    public AmfResult(string response, object o)
    {
        _response = response;
        _o = o;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        context.HttpContext.Response.ContentType = "application/x-amf";

        var serializer = new AMFSerializer(context.HttpContext.Response.OutputStream);
        var amfMessage = new AMFMessage();
        var amfBody = new AMFBody();
        amfBody.Target = _response + "/onResult";
        amfBody.Content = _o;
        amfBody.Response = "";
        amfMessage.AddBody(amfBody);
        serializer.WriteMessage(amfMessage);
    }
}

For this to work, you need to decorate method on the controller with AMFFilter

public class AMFFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (filterContext.HttpContext.Request.ContentType == "application/x-amf")
        {
            var stream = filterContext.HttpContext.Request.InputStream;

            var deserializer = new AMFDeserializer(stream);
            var message = deserializer.ReadAMFMessage();

            var body = message.Bodies.First();
            filterContext.ActionParameters["target"] = body.Target;
            filterContext.ActionParameters["args"] = body.Content;
            filterContext.ActionParameters["response"] = body.Response;

            base.OnActionExecuting(filterContext);
        }
    }
}

which would look something like this

[AMFFilter]
[HttpPost]
public ActionResult Index(string target, string response, object[] args)
{
    // assume target == "TestMethod" and args[0] is a String
    var message = Convert.ToString(args[0]);
    return new AmfResult(response, "Echo " + message);
}

Client side code for reference

//Connect the NetConnection object
var netConnection: NetConnection = new NetConnection();
netConnection.addEventListener(NetStatusEvent.NET_STATUS, onNetStatus);
netConnection.connect("http://localhost:27165/Home/Index");

//Invoke a call
var responder : Responder = new Responder( handleRemoteCallResult, handleRemoteCallFault);
netConnection.call('TestMethod', responder, "Test");

private function onNetStatus(event:NetStatusEvent):void {
    log(ObjectUtil.toString(event.info));
}

private function handleRemoteCallFault(...args):void {
    log(ObjectUtil.toString(args));
}

private function handleRemoteCallResult(message:String):void {
    log(message);
}

private static function log(s:String):void {
    trace(s);
}

If you would like to return fault, just change this line in AMFResult

amfBody.Target = _response + "/onFault";

I like ObjectUtil.toString() formatting, but just remove it if you don't have Flex linked.

BTW, do you really need this in ASP.NET MVC? Maybe simple ASHX handler would suffice and performance would be better, I don't know. The MVC architecture is a plus I presume.

OTHER TIPS

I haven't seen an ActionResult implemented on the web but there's FluorineFX.NET which supports AMF.

AFAIK there is not any implementation of AMF ActionResult, but you can create your own using class FluorineFx.IO.AMFWriter from FluorineFx.NET sources

Consider creating an opensource project somewhere (for example on github)

EDIT 1 - Sample

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluorineFx.IO;
using System.IO;
using FluorineFx.AMF3;
using System.Diagnostics;

namespace AMF_SerializationTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var ms = new MemoryStream();

            // write object
            var writer = new AMFWriter(ms);
            writer.WriteData(FluorineFx.ObjectEncoding.AMF3, new CustomObject());

            Debug.Assert(ms.Length > 0);

            // rewind
            ms.Position = 0;

            // read object
            var reader = new AMFReader(ms);
            var o = (CustomObject)reader.ReadData();

            // test
            Debug.Assert(o.I == 3);
            Debug.Assert(o.S == "Hello");
        }
    }   

    public class CustomObject : IExternalizable
    {
        private int i = 3;

        public int I
        {
            get { return i; }
        }

        private string s = "Hello";

        public string S
        {
            get { return s; }
        }

        public void ReadExternal(IDataInput input)
        {
            i = input.ReadInt();
            s = input.ReadUTF();
        }

        public void WriteExternal(IDataOutput output)
        {
            output.WriteInt(i);
            output.WriteUTF(s);
        }
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top