Archive
Monthly
Go
|
|
DNN Blog
Feb
12
Posted by:
Jon Henning
2/12/2007
With my plate relatively clear, I have decided to spend some time investigating the released version of Microsoft AJAX Extensions for ASP.NET 2.0 (formarly Atlas and from now on I will call it MS AJAX). Specifically, I want to see what advantages and disadvantages I would have by utilizing this framework instead of (or addition to) the ClientAPI. One of the first things that I looked for was the ability to make "callbacks". I had heard about Atlas' ability to invoke webservices for some time now and have been on a project that was IE specific and therefore was able to use the WebService behavior to accomplish the same thing. While this functionality is cool, people who know me, know that my real passion is for writing reusable webcontrols. Writing a webcontrol that needs to communicate with the server
- Should not require the end user to expose a webservice.
- Communication to the server should allow the control be able to raise events in the context of the current page.
In my opinion, these two requirements are vital to writing webcontrols.
My initial look through the documentation I found little that would assist me in the answer (eventually I found this last sample). Searching google yielded some promising results. Apparently, there is this concept called PageMethods that appeared to be able to make a callback from the client to the server without the need for a separate webservice. After digging through several sites explaining their use and lack of documentation I soon learned that through the various releases of this feature, quite a few changes were made. Two of these changes are the need to also decorate your method with a ScriptMethod
attribute (though when I try it, this does not seem necessary any more), and the fact that in the later betas these methods are now required to be static!
It appears that PageMethods will satisfy my first requirement (not needing external webservices), however, the second is not met. For we need the context of the current page to be useful. To understand what I mean, lets take the DNNLabelEdit control. A developer can place this on any page or module (for DNN), handle an event (UpdateLabel) and successfully have access to all it needs to update the database (ModuleId for example). If we don't have the context of the page, we will need the information posted to the server through the callback. The control cannot make assumptions on what should and shouldn't be posted, so this leaves it up to the developer.
One of the reasons for the change in requiring static/shared methods is stated here
"They reason the AJAX team decided to make these methods static was so you could avoid page-lifecycle issues and subtleties, such as the inability to modify or access page state"
ASP.NET 2.0 out of the box supports callbacks. These same issues are present, yet they still support the feature! I know, for I have researched and documented them when dealing with the DNNTabStrip control. I suppose I am not as upset as I could be, especialy if I had tried to adopt Atlas earlier and had developed solutions requiring this functionality like others have commented in the blogs listed above. It is looking like I will be continuing the use of the ClientAPI's callback functionality. Note: I could choose to use the ASP.NET 2.0 callback functionality, but I don't think it worth moving, since my callback implementation has a few more enhancments included.
Some people have questioned why even bother with PageMethods if they have to be static. They argue they are just like webservices. The fact that they appear to satisfy my first requirement makes them different.
Two final notes before moving on.
First, I am readily admitting that I just started researching MS AJAX, and therefore may not have all my facts straight. If someone knows another way that MS AJAX solves my requirements above, please comment or send me an email.
Finally, I see many people who decide to use PageMethods do so by setting the ScriptManager's EnablePageMethods property to true. I would highly recommend against this. While it makes your invoking of the method simple, it generates a large amount of payload sent down to the client. To see what I mean, I am listing the output generated within the page for only two simple methods (Add and Add2)
var PageMethods = function() {
PageMethods.initializeBase(this);
this._timeout = 0;
this._userContext = null;
this._succeeded = null;
this._failed = null;
}
PageMethods.prototype = {
Add:function(x,y,succeededCallback, failedCallback, userContext) {
return this._invoke(PageMethods.get_path(), 'Add',false,{x:x,y:y},succeededCallback,failedCallback,userContext); },
Add2:function(x,y,succeededCallback, failedCallback, userContext) {
return this._invoke(PageMethods.get_path(), 'Add2',false,{x:x,y:y},succeededCallback,failedCallback,userContext); }}
PageMethods.registerClass('PageMethods',Sys.Net.WebServiceProxy);
PageMethods._staticInstance = new PageMethods();
PageMethods.set_path = function(value) {
var e = Function._validateParams(arguments, [{name: 'path', type: String}]); if (e) throw e; PageMethods._staticInstance._path = value; }
PageMethods.get_path = function() { return PageMethods._staticInstance._path; }
PageMethods.set_timeout = function(value) { var e = Function._validateParams(arguments, [{name: 'timeout', type: Number}]); if (e) throw e;
if (value < 0) { throw Error.argumentOutOfRange('value', value, Sys.Res.invalidTimeout); }
PageMethods._staticInstance._timeout = value; }
PageMethods.get_timeout = function() {
return PageMethods._staticInstance._timeout; }
PageMethods.set_defaultUserContext = function(value) {
PageMethods._staticInstance._userContext = value; }
PageMethods.get_defaultUserContext = function() {
return PageMethods._staticInstance._userContext; }
PageMethods.set_defaultSucceededCallback = function(value) {
var e = Function._validateParams(arguments, [{name: 'defaultSucceededCallback', type: Function}]); if (e) throw e;
PageMethods._staticInstance._succeeded = value; }
PageMethods.get_defaultSucceededCallback = function() {
return PageMethods._staticInstance._succeeded; }
PageMethods.set_defaultFailedCallback = function(value) {
var e = Function._validateParams(arguments, [{name: 'defaultFailedCallback', type: Function}]); if (e) throw e;
PageMethods._staticInstance._failed = value; }
PageMethods.get_defaultFailedCallback = function() {
return PageMethods._staticInstance._failed; }
PageMethods.set_path("/MSAjax10/PageMethods.aspx");
PageMethods.Add= function(x,y,onSuccess,onFailed,userContext) {PageMethods._staticInstance.Add(x,y,onSuccess,onFailed,userContext); }
PageMethods.Add2= function(x,y,onSuccess,onFailed,userContext) {PageMethods._staticInstance.Add2(x,y,onSuccess,onFailed,userContext); }
While it is nice to be able to invoke these methods like
PageMethods.Add(1,2, methodComplete, methodError);
The amount of overhead for each page request is not worth it IMO.
Instead I recommend calling the method like this
Sys.Net.WebServiceProxy.invoke('/MSAjax10/PageMethods.aspx', 'Add', false, { x:1, y:2 }, methodComplete, methodError);
3 comment(s) so far...
Re: Researching MS AJAX: PageMethods
Your insight is impressive and very useful. Keep up your great postings. Thanks ArntK
By ArntK on
2/13/2007
|
Re: Researching MS AJAX: PageMethods
Hi Jon,
I haven't checked yet, but isn't the bulk of the pagemethod payload created in the cached .js file, and not on the page itself? The dynamic portion of the code you listed reduces to:
PageMethods.Add= function(x,y,onSuccess,onFailed,userContext) {PageMethods._staticInstance.Add(x,y,onSuccess,onFailed,userContext); } PageMethods.Add2= function(x,y,onSuccess,onFailed,userContext) {PageMethods._staticInstance.Add2(x,y,onSuccess,onFailed,userContext); }
Which is lightweight enough to live with. If the rest isn't in the cached portion, I'm sure at some point it will be. It's too easy of an optimization not to be implemented.
Regarding your initial exploration of the callback abilities in ms-ajax... if you want state on the server, shouldn't you be utilizing the postback functionality of asp.net, which already manages this? If you need a partial page rendering, then put your control in an updatepanel (or let the end-user do this, allowing them to control full or partial rendering).
If this isn't what you want -- an asp.net postback -- then you really have no choice but to use a stateless method. One always has the option of passing all the data necessary (moduleId, etc) to such a method. However, your argument that we should be raising server-side EVENTS is a strong one, and the updatepanel route seems the stronger choice.
Am I missing something in my reading of your analysis?
By BrandonHaynes on
2/13/2007
|
Re: Researching MS AJAX: PageMethods
Thanks for responding Brandon, I would have thought the bulk of the js would have been in a cached file, but in my simple test it is not. I even switched it to non-debug and it is still there. I agree that having only the PageMethods.Add portion would be acceptible if that were the case.
Regarding your statements about state. I have spent the last couple months on and off looking at the updatepanel in my design of the DNNTabStrip. If you look at some of my past blogs you will see some of the conclusions I have come up with. The bottom line is any time you try and have a once-size-fits all mentality in your solutions, it almost always means you are sacrificing performance. This is the way I view the UpdatePanel. I want my control to just work, without special notes to the developer that he needs a certain component.
If you look at my blog from tonight you will see that I did investigate the toolkit's rating control, which uses a callback in the manner I am speaking. Inside DNN there are lots of places where a certain amount of state is expected during a callback. Probably the easiest to explain is the Edit In Place functionality. If you look at the code from the Text/HTML module (HtmlModule.ascx.vb) you will see that the UpdateLabel event is simple and straight forward to handle and persist the specific html for the specific module, without the need for the developer to make sure the right information is passed in the callback.
Hopefully this clears things up a bit. Thanks for the comments.
By jhenning@solpart.com on
2/13/2007
|
|