Fork me on GitHub

Monday, January 18, 2010

Control Methods for ASP.NET AJAX

Page Methods

As of late several things have been calling my attention to get back into JavaScript, most especially in the area of AJAX. Through colleagues and friends I've been introduced to a lot of the popular standards right now, which all seemed to follow the patterns I expected, but there were some things that I payed extra homage to. Most notably, I really enjoy the concept of Page Methods. If you haven't used them yet, Page Methods allow you to define a static WebMethod in the class for an ASP.NET Page and expose them to client script. ScriptManager.EnablePageMethods adds the convenience of generating a proxy to access the method from client script with little hassle, though it turns out the property isn't needed to call the service, it only generates client script for you (probably based on reflection of the page, though I haven't checked into this yet). With some further reading (due to a nod from a colleague) I found Dave Ward's post on using jQuery to directly call ASP.NET page methods. All was well and good... until that curiosity kicked in.

Encapsulation

One of my favorite things about Page Methods is that it allows the logic for asynchronous calls to be grouped in the same body of code that it pertains to. Just as I define event handlers for oldschool postbacks in the page, I can also define the callbacks used by a page there. There are some other differences worth noting, such as a slimmer service model since no WSDL is necessary and only JSON is supported, which can arguably be considered a performance optimization, but ultimately (as an engineer) the encapsulation is what really draws me to this design.

Control Methods

After appreciating the value of being able to call a service whose definition is within the page, one thing that seems to be common is for people to try to do the same within a UserControl. It seems like a great idea to be able to implement reusable AJAX controls keeping the logic contained within the code it relates to. This is especially noteworthy when the asynchronous call is specific to the logic of the control; currently most controls use .asmx web services, which makes sense, especially if a web service will be reused, but all-in-all, seems a mite overboard for some particular control's one-off auto-complete list or the like and, additionally, it unnecessarily complicates organization and maintenance efforts. Unfortunately, as many others have commented, this feature is not supported for methods within a UserControl. When looking into how web services are provided in ASP.NET it seemed to be an unnecessary limitation; true .ascx extensions are, by default, handled by the HttpForbiddenHandler, but Page Method requests shouldn't be (and as research indicates, aren't) handled by PageHandlerFactory or the Page class; the methods are static and initializing the page's object model would defeat the purpose of using real web services instead of UpdatePanel (for those that are less familiar with the life-cycle of a page and why this would be less efficient, this is a good starting point). To me, this implied that the handling of these methods only required a static method marked with some attributes to exist within a class accessible by the web application; the rest was a matter of translating the request into the right calls.

Enter ScriptModule. ScriptModule is configured in Web.config for all ASP.NET AJAX projects. It seems to facilitate a few things, though I didn't look into much detail as my only concern was with the handling of Page Methods. As it turns out, the limitation is strictly superficial; ScriptModule explicitly checks that the current request is a Page before doing anything related to a REST query.

ScriptModule has a very simple process for handling Page Methods and the implementation details were really handled in classes and static methods that received relative paths and method names as string parameters. Perfect! I should be able to reuse the existing implementation after changing little of the request parsing... right? Wrong. Unfortunately all of the helper classes and methods are marked internal so there is no easy way to access the existing functionality. Alas, I really like the idea of encapsulating my control logic in my control class. It would probably be possible to rewrite the implementation, or more suitable for Microsoft to modify the module to handle a few more cases (they could actually modify a few things, like the convenience code generation and properties for Extenders), but for the time being I have settled on creating a solution that used the same implementation as Page Methods through Reflection.

Reflection

The downside to using reflection in this solution is that typical ASP.NET security models do not allow much reflection. Some trust levels provide limited reflection permission, but invoking internal members of an assembly is forbidden as a security measure in all of the default security levels short of Full Trust (which is not ideal for production environments). The other levels can be modified to support ReflectionPermission, but ultimately the rule is in place for a reason. That said I've created two methods to access Control Methods. One is an in-application ashx handler that would require the above-mentioned security modifications, but is rather simple to drop in. The other (recommended) option is an IHttpModule contained in a strong-named assembly registered in the Global Assembly Cache. By default, assemblies in the GAC are granted full trust, even in web applications, so the module can serve Control Methods without compromising the security of the web application.

Source

Since it seems like other people are interested in this as well, I've posted my implementation of Control Methods for ASP.NET AJAX to CodePlex under a BSD style license. It's not a priority of mine, but I do intend to further extend this to support the same functionality for Master Pages as well as trying to test interaction with various AJAX Server Controls (like AutoCompleteExtender). I have not tested standard ASP.NET authorization and security methods extensively yet either. Still, the design model is good, and maybe this will call some attention to the desire for this functionality to get some official support.