Using ASP.NET PageMethods With Silverlight

Most developers are by now familiar with the PageMethod. This is a static method in the codebehind class of an ASP.NET page which is decorated with the [WebMethod] attribute. The methods are called via a POST to the pagename/methodname and the parameters are sent as the POST data.

You can also call PageMethods from a Silverlight application by using the ScriptManager control.


In order to begin utilizing PageMethods in your ASP.Net enabled webpage via Silverlight, you need to do 3 things:
1. Set the ScriptManager's "EnablePageMethods" property to "true".
2. Write Public Static WebMethods in the Code Behind file of the web page that will return the information required.
3. Write Script that calls the PageMethods and reacts to the return results (information or errors).

When the EnablePageMethods attribute is present, the ScriptManager automatically does number 3 above,  injecting all the clientscript needed to call the PageMethods at the server:

<script type="text/javascript">
//<![CDATA[
var PageMethods = function() {
PageMethods.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
PageMethods.prototype = {
_get_path:function() {
var p = this.get_path();
if (p) return p;
else return PageMethods._staticInstance.get_path();},
Hello:function(name,succeededCallback, failedCallback, userContext) {
/// <param name="name" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'Hello',false,{name:name},succeededCallback,failedCallback,userContext); },
Add
:function(x,y,succeededCallback, failedCallback, userContext) {
/// <param name="x" type="Number">System.Int32</param>
/// <param name="y" type="Number">System.Int32</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'Add',false,{x:x,y:y},succeededCallback,failedCallback,userContext); },
TestDict:function(dict,succeededCallback, failedCallback, userContext) {
/// <param name="dict" type="Object">System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
return this._invoke(this._get_path(), 'TestDict',false,{dict:dict},succeededCallback,failedCallback,userContext); }}
PageMethods.registerClass('PageMethods',Sys.Net.WebServiceProxy);
PageMethods._staticInstance = new PageMethods();
PageMethods.set_path = function(value) {
PageMethods._staticInstance.set_path(value); }
PageMethods.get_path = function() {
/// <value type="String" mayBeNull="true">The service url.</value>
return PageMethods._staticInstance.get_path();}
PageMethods.set_timeout = function(value) {
PageMethods._staticInstance.set_timeout(value); }
PageMethods.get_timeout = function() {
/// <value type="Number">The service timeout.</value>
return PageMethods._staticInstance.get_timeout(); }
PageMethods.set_defaultUserContext = function(value) {
PageMethods._staticInstance.set_defaultUserContext(value); }
PageMethods.get_defaultUserContext = function() {
/// <value mayBeNull="true">The service default user context.</value>
return PageMethods._staticInstance.get_defaultUserContext(); }
PageMethods.set_defaultSucceededCallback = function(value) {
PageMethods._staticInstance.set_defaultSucceededCallback(value); }
PageMethods.get_defaultSucceededCallback = function() {
/// <value type="Function" mayBeNull="true">The service default succeeded callback.</value>
return PageMethods._staticInstance.get_defaultSucceededCallback(); }
PageMethods.set_defaultFailedCallback = function(value) {
PageMethods._staticInstance.set_defaultFailedCallback(value); }
PageMethods.get_defaultFailedCallback = function() {
/// <value type="Function" mayBeNull="true">The service default failed callback.</value>
return PageMethods._staticInstance.get_defaultFailedCallback(); }
PageMethods.set_enableJsonp = function(value) { PageMethods._staticInstance.set_enableJsonp(value); }
PageMethods.get_enableJsonp = function() {
/// <value type="Boolean">Specifies whether the service supports JSONP for cross domain calling.</value>
return PageMethods._staticInstance.get_enableJsonp(); }
PageMethods.set_jsonpCallbackParameter = function(value) { PageMethods._staticInstance.set_jsonpCallbackParameter(value); }
PageMethods.get_jsonpCallbackParameter = function() {
/// <value type="String">Specifies the parameter name that contains the callback function name for a JSONP request.</value>
return PageMethods._staticInstance.get_jsonpCallbackParameter(); }
PageMethods.set_path("Default.aspx");
PageMethods.Hello= function(name,onSuccess,onFailed,userContext) {
/// <param name="name" type="String">System.String</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
PageMethods._staticInstance.Hello(name,onSuccess,onFailed,userContext); }
PageMethods.Add= function(x,y,onSuccess,onFailed,userContext) {
/// <param name="x" type="Number">System.Int32</param>
/// <param name="y" type="Number">System.Int32</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
PageMethods._staticInstance.Add(x,y,onSuccess,onFailed,userContext); }
PageMethods.TestDict= function(dict,onSuccess,onFailed,userContext) {
/// <param name="dict" type="Object">System.Collections.Generic.Dictionary`2[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</param>
/// <param name="succeededCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="failedCallback" type="Function" optional="true" mayBeNull="true"></param>
/// <param name="userContext" optional="true" mayBeNull="true"></param>
PageMethods._staticInstance.TestDict(dict,onSuccess,onFailed,userContext); }
//]]>
</script>

In this simple Silverlight Test app, I have the following XAML Markup:

<Grid x:Name="LayoutRoot" Background="White">
        <StackPanel Orientation="Vertical">
            <Button HorizontalAlignment="Left" Width="100" Height="20" Content="Hello + Time" Click="Hello_Click" />
            <Button HorizontalAlignment="Left" Width="100" Height="20" Content="Add" Click="Add_Click" />
            <Button HorizontalAlignment="Left" Width="100" Height="20" Content="Dictionary" Click="Dictionary_Click"  />
            <TextBox HorizontalAlignment="Left" Name="txt" Height="60" Width="200" />
        </StackPanel>
    </Grid>



And the codebehind:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightPageMethods
{
    public static class PageMethodExtensions
    {
         public static object ToJSDictionary<T>(this Dictionary<string, T> dict)
        {
            var jsdict = HtmlPage.Window.CreateInstance("Object");
            foreach (string key in dict.Keys)
                jsdict.SetProperty(key, dict[key].ToString());

             return jsdict;
        }
     }

    public partial class MainPage : UserControl
    {
        delegate void PageMethodDelegate(object result, object context);

         public MainPage()
        {
             InitializeComponent();
        }

         public ScriptObject PageMethods
        {
            get
            {
                 return (ScriptObject)HtmlPage.Window.GetProperty("PageMethods");
            }
        }

         private void Hello_Click(object sender, RoutedEventArgs e)
         {
             // Invoke the Hello PageMethod, passing in "There" as the second parameter
            PageMethods.Invoke("Hello", "There", new PageMethodDelegate(Success), new PageMethodDelegate(Fail), "Context");
        }

         private void Add_Click(object sender, RoutedEventArgs e)
         {
             // Invoke the Add method, passing in 10 and 20 as the two parameters
            PageMethods.Invoke("Add", 10, 20, new PageMethodDelegate(Success), new PageMethodDelegate(Fail), "Context");
        }

         private void Dictionary_Click(object sender, RoutedEventArgs e)
         {
             //Create a dictionary we want to send
            Dictionary<string, string> dict = new Dictionary<string, string>();
             dict.Add("test", "1");
            dict.Add("test2", "2");

             //Call PageMethod, providing delegate callbacks
            PageMethods.Invoke("TestDict", dict.ToJSDictionary<string>(), new PageMethodDelegate(Success2), new PageMethodDelegate(Fail), "Context");
        }

         public void Success(object result, object context)
         {
             this.txt.Text = result.ToString();
        }

        public void Success2(object result, object context)
        {
            txt.Text = "";
            ScriptObject so = result as ScriptObject;
            txt.Text += so.GetProperty("test").ToString() +"\r\n";
            txt.Text += so.GetProperty("test2").ToString() +"\r\n";
            txt.Text += so.GetProperty("FromServer").ToString();
        }

        public void Fail(object result, object context)
        {
            txt.Text = "FAIL";
        }
    }
}

Note that we have an extension method on the Dictionary class which will convert a dictionary to its Javascript object equivalent using the Silverlight HtmlPage.Window.CreateInstance("Object") method.

The PageMethods script object is easily obtained in Silverlight with:

(ScriptObject)HtmlPage.Window.GetProperty("PageMethods");

This returns a Silverlight ScriptObject on which we can call the Invoke, or GetProperty methods.

The rest is simply providing callbacks that match the signature of the Invoke Method - the PageMethod name, a success callback, a failure callback, and something for the context. Invoke is very flexible as it accepts a params Object[] args second parameter (the first being the method name).

The actual PageMethods in the ASP.NET page are easy:

[WebMethod]
    public static string Hello(string name)
    {
         return "Hello: " + name + " at " + DateTime.Now.ToShortTimeString();
    }

     [WebMethod]
    public static int Add(int x, int y)
    {
         return x + y;
    }

    [WebMethod]
    public static Dictionary<string, string> TestDict(Dictionary<string, string> dict)
    {
        if (dict.Count > 0)
             dict.Add("FromServer", "Howdy From Server!");
        return dict;
    }

You can download the sample Visual Studio 2010 Silverlight 4.0 Solution here.

By Peter Bromberg   Popularity  (5968 Views)