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.


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.


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.


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.


Ben said...

Your programming knowledge makes me feel like a loser. Haha. Great blog, btw.

saeed neamati said...

Excellent article, this was exactly what I was looking for.
But I can't get any of the projects work. Page methods always result in Error 403 and says that "this type of page is not served" which is probably related to the .ashx extension.
I really like to implement this idea. Can you introduce some resources for further reading?
Thank you anyway, that was awesome !

TheXenocide said...

Hello Saeed,

Sorry to have left things in a state of disrepair; not as many people as I would have thought have showed any interest thus far (probably because I'm "nobody" to google, lol) so I haven't been as clean about my repository maintenance. I've had a bit of work in progress locally trying to refactor to account for a few different strategies that can be used to solve this problem as well as to better support a related project (MicroAjax).

I'll try to get a clean and working setup into the repository within the next couple days, but can I ask what version of IIS and .NET you're using? There are differences in how things must be configured for each of these so knowing what version (or versions) you're working with will help me in explaining what configuration needs to be done.

Thanks very much for saying something, at least I know there's some interest now :)

Remy said...

I think the interest is definitly there, but the project looks like it is in its infancy and as you say it yourself, Reflection is a bit of a hack. So personally I just implemented a proxy on the page itself. Not pretty either, but it works. Would love to see your project develope further.

TheXenocide said...

Thanks for your comments Remy,
I've done the proxying method as well, but it doesn't result in a level of encapsulation and maintainability I would consider ideal. Unfortunately not using reflection would involve basically rewriting the whole stack, give or take a few publicly available helper classes here and there, which seems over the top. Thankfully, at least in .NET 4.0 they've made it a lot easier to grant privilege to a single assembly without placing it in the GAC which mitigates some concerns with this approach. I've been at a loss for time to work on these lately, what with holidays and girls and work and the like, but hopefully I'll get a chance to put some more time in on it soon. I've got some pretty good ideas stored up so we'll see what I can get in :) If you have any suggestions let me know!