Wednesday, October 24, 2007

An Introduction to PageMethods

PageMethods offer a simple way to asynchronously communicate with a server using Microsoft's ASP.Net AJAX technologies.  Unlike Update Panels which utilize the full page life cycle to synchronously update a section of the page based on the panel's triggers, PageMethods handles the transmissions manually through JavaScript.  Since everything is manual, PageMethods take a small amount of additional time to develop; however, provides an additional level of efficiency that cannot be found in UpdatePanels.

In order to begin utilizing PageMethods in your ASP.Net AJAX enabled webpage, you need to do 3 things:
  • Set the ScriptManager's "EnablePageMethods" property to "true".
  • Write Public Static WebMethods in the Code Behind file of the web page that will return the information required.
  • Write JavaScript that calls the PageMethods and reacts to the return results (information or errors).
Setting up the Script Manager
Setting up the ScriptManager to handle PageMethods is fairly straightforward; however, there's one thing that you'll need to be aware of. The "EnablePageMethods" property is only found on the ScriptManager control and not the ScriptManagerProxy control. This means that if you have a Master Page with a ScriptManager control in it used by the Content Page that will contain the PageMethods, you must set the Master Page's ScriptManger control to include the property. Doing such enables all pages that use that master page to be able to use PageMethods. I haven't investigated to see the impact this has on pages that do not utilize PageMethods.

With that out of the way, in order to enable page methods on a page, simply go to your ScriptManager control that's associated with the page and set the "EnablePageMethods" property to "True". This is "False" by default, but once you set it to "True" you're all set.
Writing the Server Code
Now that we can write PageMethods, we need to flip to our code behind files (or our <script runat=server> sections) to begin. Below are the steps that I traditionally follow when creating PageMethods:
  1. Create a new method/function that returns String or value type (Integers, Doubles, Dates, etc.).
  2. Mark the new method as Public Static (or Shared for the VB coders out there)
  3. Import the System.Web.Services namespace
  4. Add the [WebMethod()] (or <WebMethod()>) Attribute to the Function
In a nutshell, I write the method first. This allows me to get the logic in place just in case I need to modify the implementation when I'm in the middle of writing this code. Next, I ensure the proper scope and modifiers are setup on the method, and finally, mark the method as a WebMethod like I would with a web service.

[WebMethod()]
public static string MyPageMethod(string someParam)
{
return "Hello, PageMethods!";
}

When the page loads, the ScriptManager will examine the page's definition for any public static webmethods. It will then generate JavaScript code that is used to call these methods and append it into the ASP.Net AJAX's PageMethods object. This interaction allows our new method to be called from JavaScript by referencing in JavaScript the method of the same name of the PageMethods object ( PageMethods.MyPageMethod(...) in this case - more on this below).

Making the calls with JavaScript

So far we have enabled PageMethods in our ScriptManager and have made some static web methods in our code that the ScriptManager will create JavaScript from. Now, we need to write our own JavaScript code to call these PageMethods so that we can retrieve the information they provide.

In order to create the JavaScript code to call the PageMethods, you simply have to make have 3 things:

  1. The script that calls the PageMethod itself
  2. A function to call on each successful PageMethod call
  3. A function to call when ever there was an error in the PageMethod call.

The code to call the PageMethod is very simple.

function callerMethod() {
PageMethods.MyPageMethod(PARAM1,
PARAM2,
PARAMn,
callerMethod_Success,
callerMethod_Failure);
}

Let's pick this apart now. What we have above is a simple JavaScript function called "callerMethod". Inside of this function, we do a simple call to our method's JavaScript counterpart that got attached to the PageMethods JavaScript object that's included by the ScriptManager. Next, we pass any parameters required (signified by the "PARAM1", "PARAM2", and "PARAMn" in this case) followed by a pointer/name of the method to call on a successful transmission and the same for the failed transmission.

One optional parameter that I've purposefully excluded can appear at the very end is designated for the current UserContext. In a later posting, I will provide more information revolving around this parameter and how to use it to provide more powerful communication models in the code. For right now, we will exclude the parameter because we can.

Now that we've created the call, we need to create the functions that'll be called when the transmission succeeds or fails.

function callMethod_Success(results, userContext, methodName) {
alert(results);
}

function callMethod_Failure(errors, userContext, methodName) {
alert(errors.get_Message());
}

Again, these are very simple methods that do nothing other than alerting the user of the resulting value on success or the error messsage on failure. The names of the methods are completely up to you naturally; however, whatever you name the methods, each require 3 parameters.

The first parameter of is for the returned value or error that the call to the PageMethod created. On success, you can immediately begin using the results as if they were a normal value passed into any regular function (since it's either a String or Number JavaScript type). On failure, the PageMethod and ScriptManage return an object with information about the error. The get_Message() method of the error object will return the error message that was thrown during the transmission. Once you have the resulting value (success or failure), you can then write JavaScript logic to do whatever you want with it.

The second parameter is the userContext. Like previously stated, this will be covered in a future posting. For now, just remember that you can send the userContext to the PageMethod and read the resulting context on the returning trip.

The last parameter of the success and failure methods is the methodName. This parameter provides a simple string that represents the PageMethod name that was called and caused the success or failure function to be triggered. This is a great addition to the returning functions since it allows you to reuse success and/or failure functions across different PageMethods and produce custom logic based on which PageMethod was called. While I can see some merit in having a single success and a single failure (or even 1 function that does both), I wouldnt' want to maintain the if statements that would spawn from such logic.

Summary

While the example code here isn't too useful (like most Hello, World projects), it hopefully will get you started on using PageMethods. In future postings, I'll provide some addition information and techniques that you can use in order to utilize PageMethods by using the UserContext object, identifying more in depth the data types that can and cannot be passed, and also object serialization through JSON. In addition to these items, I'll also be keeping an eye out on any items that may be not quite so obvious when you're work with PageMethods. Down the road a bit, I'll do a very similar post to this about using local WebServices instead of PageMethods and why.

13 comments:

  1. Great article, really simplfied the PageMethod idea, i think it is a great concept, i used to implement it myself using premade JS templates but this is far more elegant. thanks!

    ReplyDelete
  2. Can I use PageMethods from within a composite control? I have a custom control which needs to make database calls.

    Thanks.

    ReplyDelete
  3. This is possibly the most informative information about page methods I've encountered. However, after trying your simple explanation I have an issue in that the result from the server method returns the full HTML rather than the string 'Hello world' in my case. Any clues as to why? The page methods is enabled and I can see all the _doPostBack javascript functions generated by the C# code but for the life of me I cannot extract the result from the page method.

    ReplyDelete
  4. it's very relevent information regarding pagemethod.
    but could u please tell me how to create autosuggest box through the help of webmethod and pagemethod?

    ReplyDelete
  5. This failed for me until I changed errors.get_Message() to be errors.get_message() - in case it helps anyone...

    ReplyDelete
  6. awesome..thanks a lot.Can we return an object from the page method..?

    ReplyDelete
  7. Great article, brought a lot of clarity.Thanx

    ReplyDelete
  8. Good article, but once your code is on a production server, and in the web.config, you have customErrors set to "On", then errors.get_message always returns the same generic message of "There was an error processing the request." Do you have some workaround for that?

    ReplyDelete
  9. @Anonymous - In the situation you mentioned, you should properly use try/catch blocks around your PageMethods so you can properly trap any exception and report back with any error messages or error codes to indicate to the client that something went wrong. In the extreme case, you could take your PageMethod server code and wrap the entire function body in a try/catch so that it never reports an HTTP 500 error back to the client.

    ReplyDelete
  10. Thanks for replying. I have try/catch blocks around my entire PageMethod functions. But with customErrors set to "On", throwing errors to the client always gives that generic message. Throwing errors is how the failing javascript function gets called.

    The only workaround I've found is to always have my PageMethods be functions, and in the error trap, return text, codes, etc, which the javascript then has to process to determine if an error was thrown or if valid data was returned instead. I believe that's what you're saying.

    If so, then there's no need for the failed javascript function (callerMethod_Failure in your code) as it will never get called. I'd rather depend on it to avoid special javascript code, but that doesn't appear possible, correct?

    ReplyDelete
  11. @Anonymous That's correct. Your page methods need to catch any errors and return a text string (or some value) and not just throw the exception since the client and server are separate. If you want to retry on error or show an error message, you need to catch the error in the PageMethod server code and then send back either some text/key to indicated an error occurred and/or the text error message you wish to send back to the JavaScript code on the client's machine. It's the only way to do it regardless of the AJAX methodology you use (since PageMethods is a way of doing AJAX).

    ReplyDelete
  12. Thank you for this post! I had seen an example of PageMethods in a tutorial and was pulling my hair out trying to figure out what they were doing. Finally, I understand!

    ReplyDelete