Check this out: http://atlas.asp.net/docs/util/srcview.aspx?path=~/atlas/samples/services/WebMethodOnPage.src.
Note that, within the .aspx page source, there is a method written in C# decorated with the [WebMethod] attribute. What this means is that you have a standard .aspx page hosting a webmethod, which is typically exposed only on .asmx (webservice) files. What's going on here?
As it turns out, this is a new feature in Atlas and it utilizes an unsupported method exposed within the ASP.Net runtime: Control.SetRenderMethodDelegate. The documentation is fairly self-explanatory:
Assigns an event handler delegate to render the server control and its content into its parent control.
What this means is that you can inject your own handler function to render any control or page in the runtime, thereby circumventing its own rendering code: extremely powerful, albeit sort of a hack. Now, the way this works in the Altas framework is this:
- The script module is loaded by the ASP.Net runtime due to its entry in the web.config file for the loading application.
- The .Init() method is called on the module, and it assigns a delegate for the PostMapRequestHandler event, OnPostMapRequestHandler.
- OnPostMapRequestHandler gets called and calls Microsoft.Web.Atlas.PageServiceHandler.HookUpPage() with the page the event was raised for as a parameter.
- The PageServiceHandler class registers its own PreRenderComplete event handler for the PreRenderComplete event for the page passed to it from earlier.
- The page goes through its normal life cycle and, right after the pre-render stage calls the PreRenderComplete handler method within the PageServiceHandler class.
- PageServiceHandler's implementation of PreRenderComplete includes calling .SetRenderMethodDelete() on the page if the page has, within its form post variables, the string "__serviceMethodName". The method passed as the handler is .Render from within PageServiceHandler.
- PageServiceHandler.Render gets called when the page is requested to render itself, and the implementation of said method is below:
private void Render(HtmlTextWriter output, Control container)
{
Page page1 = (Page) container;
WebServiceData data1 = WebServiceData.GetWebServiceData(page1.AppRelativeVirtualPath);
string text1 = page1.Request.Form["__serviceMethodName"];
WebServiceMethodData data2 = data1.GetMethodData(text1);
string text2 = page1.Request.Form["__serviceMethodParams"];
try
{
IDictionary<string, object> dictionary1 = JavaScriptObjectDeserializer.DeserializeDictionary(text2);
object obj1 = data2.CallMethodFromRawParams(page1, dictionary1);
string text3 = JavaScriptObjectSerializer.Serialize(obj1, data2.Owner);
output.Write(text3);
}
catch (Exception exception1)
{
HttpContext context1 = HttpContext.Current;
context1.Response.StatusCode = 500;
context1.Response.StatusDescription = HttpWorkerRequest.GetStatusDescription(500);
RestHandler.WriteExceptionJsonString(context1, output, exception1);
}
}
Look carefully at the implementation and see the method data2.CallMethodFromRawParams() and the output.Write(), which combine to push out to the response stream the serialized HTTP response from the webmethod. The rabbit hole goes deeper and deeper, but it really does us no good to go much further as we know how to look for methods based on their attributes and dynamically execute them from within the .Net runtime. The secret has been cracked, effectively, and we see how they're intercepting the normal request for an .aspx page and re-routing it to a new Render handler which calls the proper method in the page class.
It is the responsibility of the client framework, then, to include within the form post variables the ever-important __serviceMethodName variable. If you take a Fiddler trace of a request to the webmethod using one of the Microsoft samples, you can see it clearly in there:
POST /docs/atlas/samples/services/WebMethodOnPage.aspx HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer: http://atlas.asp.net/docs/atlas/samples/services/WebMethodOnPage.aspx
Content-Type: application/x-www-form-urlencoded
UA-CPU: x86
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)
Host: atlas.asp.net
Content-Length: 166
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: .....
__serviceMethodName=HelloWorld&__serviceMethodParams={"s":"asdfasdf"}&__VIEWSTATE=/wEPDwUKMjA4Mjk0NDk4M2Rk&nameTextBox=asdfasdf&__EVENTVALIDATION=/wEWAgLlssAyAouxhI4H