code blog foo - tag line bar

Calling ASP.NET MVC 2.0 Controller Methods from Silverlight

source code to RestFacilitator and AsyncDelegation

Here we go...Prerequisite

ASP.NET MVC is extremely powerful (to say the least). In a previous blog post, I talked about how to "seamlessly" integrate Silverlight and ASP.NET MVC. The blog post helped set the mental stage for communicating asynchronously with ASP.NET MVC via jQuery and then showed that same communication approach with Silverlight (so be sure to read that blog post).

Setting the Stage

This whole endeavor started with a simple question: Why can I communicate with ASP.NET MVC via client side javascript, and yet can't find any documentation on doing the same thing via Silverlight? Well here is the code to do that (again, it may be a good idea to read the blog post provided in the prerequisite):

Here is the code for a RestFacilitator class I've created to call HTTP/GET and HTTP/POST controller actions: (source code download link is at the top of this post)

public class RestFacilitator
{
    private SynchronizationContext _synchronizationContext;
    public RestFacilitator(SynchronizationContext synchronizationContext)
    {
        _synchronizationContext = synchronizationContext;
    }

    public void Post(string resource, Dictionary<string, string> parameters, 
                     SendOrPostCallback onComplete)
    {
        string uriPath = resource;
        Uri uri = new Uri(uriPath, UriKind.Absolute);
        HttpWebRequest httpWebRequest = 
            (HttpWebRequest)WebRequest.Create(uri);
            
        httpWebRequest.Method = "POST";

        AsyncCallback readResponse = null;
        AsyncCallback writeRequest = null;

        writeRequest = new AsyncCallback(
            (e) =>
            {
                httpWebRequest.ContentType = 
                    "application/x-www-form-urlencoded";
                    
                Stream stream = httpWebRequest.EndGetRequestStream(e);
                string postData = string.Empty;

                foreach (string key in parameters.Keys)
                {
                    postData += key + "=" + parameters[key] + "&";
                }

                StreamWriter streamWriter = new StreamWriter(stream);
                streamWriter.Write(postData);
                streamWriter.Close();
                stream.Close();
                httpWebRequest.BeginGetResponse(readResponse, null);
            });

        readResponse = new AsyncCallback(
            (e) =>
            {
                _synchronizationContext.Post(onComplete, null);
            });

        //go statement
        httpWebRequest.BeginGetRequestStream(writeRequest, null);
    }

    public void Get<T>(string resource, SendOrPostCallback onComplete) 
        where T : class, new()
    {
        string uriPath = resource;
        Uri uri = new Uri(uriPath, UriKind.Absolute);
        HttpWebRequest httpWebRequest = 
            (HttpWebRequest)WebRequest.Create(uri);

        AsyncCallback done =
            new AsyncCallback(
                (e) =>
                {
                    HttpWebRequest request = 
                        (HttpWebRequest)e.AsyncState;
                    HttpWebResponse response = 
                        (HttpWebResponse)request.EndGetResponse(e);

                    Stream stream = response.GetResponseStream();
                    DataContractJsonSerializer dataContractJsonSerializer = 
                        new DataContractJsonSerializer(typeof(T));

                    T result = new T();
                    result = 
                        dataContractJsonSerializer.ReadObject(stream) as T;
                    _synchronizationContext.Post(onComplete, result);

                });

        //go statement
        httpWebRequest.BeginGetResponse(done, httpWebRequest);
    }}

With the code above, you can call an ASP.NET MVC controller action from Silverlight (which is awesome...cause then you don't have to use WCF). Have I mentioned that it would be a good idea to read the blog post in the prerequisite? Cause this is where things get interesting....

Call WCF from Silverlight or call MVC Controllers from Silverlight?

Now that you have the RestFacilitator, you can use MVC Controllers as restful services. So is this better than using WCF? You decide. Lets take a "Calculator Service" and see how we would implement it in both WCF and ASP.NET MVC:

Calculator Service in WCF

Here is the data and service contract:

[ServiceContract]
public interface ICalculator
{
    [OperationContract]
    IntResult Add(int x, int y);

    [OperationContract]
    IntResult Subtract(int x, int y);

    [OperationContract]
    IntResult Multiply(int x, int y);
}

[DataContract]
public class IntResult
{
    [DataMember]
    public int Value { get; set; }
}

And here is the concrete implementation of the WCF service:

public class Calculator : ICalculator
{
    IntResult ICalculator.Add(int x, int y)
    {
        return new IntResult { Value = x + y };
    }

    IntResult ICalculator.Subtract(int x, int y)
    {
        return new IntResult { Value = x - y };
    }

    IntResult ICalculator.Multiply(int x, int y)
    {
        return new IntResult { Value = x * y };
    }
}

Calculator Service in ASP.NET MVC

Here is the implementation of the "Calculator Service" in ASP.NET MVC:

public class IntResult
{
    public int Value { get; set; }
}

public class CalculatorController : Controller
{
    [HttpGet]
    public ActionResult Add(int x, int y)
    {
        return Json(new IntResult { Value = x + y }, JsonRequestBehavior.AllowGet);
    }

    [HttpGet]
    public ActionResult Subtract(int x, int y)
    {
        return Json(new IntResult { Value = x - y }, JsonRequestBehavior.AllowGet);
    }

    [HttpGet]
    public ActionResult Multiply(int x, int y)
    {
        return Json(new IntResult { Value = x * y }, JsonRequestBehavior.AllowGet);
    }
}

Service Reference in Silverlight

Service Usage

For this demonstration, I created a calculator application that does the follow (notice the daisy chaining that occurs):

  1. The Silverlight app takes two numbers from the user and adds them.
  2. The two numbers are added, and another number is subtracted from the result.
  3. The subtraction happens, and another number is multiplied with that result.
  4. The final result (after the add, subtract and multiply) is the answer.

Pretty easy right? Well the biggest hurdle is that every server side interaction in Silverlight must be asynchronous.

Here is how the daisy chaining described above would be implemented using the WCF "Calculator Service" asynchronous proxy:

public void Calculate()
{
    CalculatorProxy.CalculatorClient client = new CalculatorProxy.CalculatorClient();

    //wire up the event for when add completes
    client.AddCompleted += (sender, e) =>
    {
        //call the subtract asynchronously
        client.SubtractAsync(e.Result.Value, Subtract);
    };

    //wire up the event for when subtract completes
    client.SubtractCompleted += (sender, e) =>
    {
        //call multiply asynchronously
        client.MultiplyAsync(e.Result.Value, Multiply);
    };

    //wire up the event for when multiply completes
    client.MultiplyCompleted += (sender, e) =>
    {
        //set the answer
        Answer = e.Result.Value;
    };

    //now all the events are wired up....Add...
    client.AddAsync(X, Y);
}

Here is how the daisy chaining described above would be implemented using the ASP.NET MVC "Calculator Service" and the RestFacilitator:

public void Calculate()
{
    RestFacilitator rf = new RestFacilitator();
    //call the add mvc controller action passing in X and Y
    rf.Get<IntResult>(
        "http://www.calculatorservice.com/add?x=" + X + "y=" + Y,
        (result) =>
        {
            //when returned, call the subtract mvc controller action
            rf.Get<IntResult>(
                "http://www.calculatorservice.com/subtract?x=" + (result as IntResult).Value + "y=" + Subtract,
                //when returned, call the multiply mvc controller action
                (subtractResult) =>
                {
                    rf.Get<IntResult>(
                        "http://www.calculatorservice.com/multiply?x=" + (subtractResultas IntResult).Value + "y=" + Multiply,
                        //when multiply returns, set the answer
                        (multiplyResult) =>
                        {
                            Answer = (multiplyResult as IntResult).Value; 
                        });
                });
        });
}

So far, so good bad...

Neither approach shown above is ideal...both are somewhat hard to follow (even just a simple add, subtract, then multiply). Imagine if this was "real" code for a line of business application. But with the RestFacilitator, I can use a fluent interface to create a nice little domain specific language that makes things more legible...a fluent interface along with the use of anonymous types (and a little bit of imagination), gives birth to AsyncDelegation. With AsyncDelegation (another custom class), you can write the following code to accomplish the same daisy chaining:

public void Calculate()
{
    //new up AsyncDelegation
    AsyncDelegation ad = new AsyncDelegation();

    int temp = 0;

    //Get Add
    ad.Get<IntResult>(
          new 
          { 
              controller = "Calculator", 
              action = "Add", 
              x = X, 
              y = Y
          })
        //when finished set the temp variable
        .WhenFinished(r => temp = r.Value)
        //then get subtract
        .ThenGet<IntResult>().ForRoute(
            () => 
            new
            {
                controller = "Calculator", 
                action = "Subtract", 
                x = temp, 
                y = Subtract 
            })
        //when finished set the temp variable again
        .WhenFinished(r => temp = r.Value)
        //then get multiply
        .ThenGet<IntResult>().ForRoute(
            () => 
            new
            {
                controller = "Calculator", 
                action = "Multiply", 
                x = temp, 
                y = Multiply
            })
        //set the answer
        .WhenFinished(r => Answer = r.Value);

    //when everything is setup, execute.
    ad.Go();
}

Intriguing, yes? Well if you're interested in this approach here is the source code to RestFacilitator and AsyncDelegation (please keep in mind this is a work in progress). Constructive feedback is always welcome.


Written: 6/21/2010