Exploring the depths and potentials of ASP.NET RSS 2.0 or Subscribe to .BenRush by Email
 Monday, May 08, 2006

Up until now I've blogged about peripheral topics, the bits surrounding the actual functional core of the Atlas framework. All-in-all, a lot of technology comes into play from the clientside to the serverside to make the end-effect seemless for both the end-user and the developer. At the end of the day, however, the basic concept is still that of the partial postback - which posts back to the server without causing an interrupt to the client experience. The "core" behind this whole concept centers around what is communicated with on the server; for browsers are capable of partial postbacks out of the box, but we - as developers - are tasked with building an infrastructure on the server to be communicated with by the client. It might be confusing to use the word "endpoint" as it's something that is a buzzword for WCF too, but it's a good word and useful for our purposes too; so I'll go as far as to say there are four endpoints available for communication on a Atlas-ified server:

  1. Inline [WebMethod]s within an .ASPX page,
  2. Authentication/authorization and personalization services,
  3. Webservice (.asmx) files located within the web application,
  4. Webservices of any type located beyond the current web application.

Atlas supports the web client communicating with any of these endpoints using its services. In a previous blogpost I discussed how the technology supports calling [WebMethod]-decorated functions written inline with .aspx files, but I have mostly focused on the clientside when it comes to communicating with webservices and the authentication/authorization infrastructure available. To a degree I discussed how the authentication/authorization/personalization services are structured too - but, for the sake of completion, I'll briefly discuss each and then go a bit further into how the webservices are included into the whole communication network.

Okay... so we have a client, and on that client there exists script that is based on, probably, the Atlas client framework making requests back to our server (though this isn't necessary). If the client is talking to a webmethod hosted on an .aspx page, it includes special variables in its form post to hint to the server-side framework that it must intercept the rendering phase of the .aspx page and, instead, formulate a call to the proper [WebMethod]-decorated function and have its output serialized back to the client. This, effectively, short-circuits the normal rendering phase of the page itself and provides the client with the bits it expects for its RPC-style call to our [WebMethod].

The other option is that the client needs to gather information regarding the authentication/authorization of the user, or the user's personalized bits. In this case, the client calls to the server with a specially formulated query-string that is comprised of the fully-qualifed name of the type that will support the call, including any variables in the form-post. The server-side framework intercepts this and processes it accordingly.

However, I see that most developers will simply create a new .asmx file and have their client call it, or instead, have their client call a ready-made webservice located outside of their project space (even on another server) in the case of mashups, et al. That's the case with endpoints 3 & 4 above. What's interesting is that I have completely left this discussion out until now. Honestly? I did this I was circling the core, so to speak, and trying to understand the client space first.

Infrastructurally, if you were Microsoft, it would make sense to try your darndest to maintain a consistency in the server-side code when dealing with these four options to prevent as much code-copy as possible. Technically, each one of these endpoints can be represented by a full-fledged .Net framework type: Inline webmethods exist within a compiled .ASPX page, authentication/authorization and profile services exist as types within Microsoft.Web.Atlas, and the webservices can have proxies generated for them (by wsdl.exe) that can be seen as strongly-typed representations for consumption by .Net. As a result, you would want to try trapping the incoming request, turn it into something that looks and feels like a casual .Net method call by generating your parameter stack and, then, executing the method (whether it be a proxy method or not). Have a gander at a few entries in a standard web.config file for a Atlas application:

.....
        <buildProviders>
            <add extension=".asbx" type="Microsoft.Web.Services.BridgeBuildProvider" />
        </buildProviders>
.....
        <httpHandlers>
            <remove verb="*" path="*.asmx"/>
            <add verb="*" path="*.asmx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
            <add verb="*" path="*.asbx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
        </httpHandlers>

        <httpModules>
            <add name="ScriptModule" type="Microsoft.Web.Services.ScriptModule"/>
            <add name="BridgeModule" type="Microsoft.Web.Services.BridgeModule"/>
        </httpModules>
.....

The <add name="ScriptModule" type="Microsoft.Web.Services.ScriptModule"/> entry actualizes inline [WebMethod] calls by punching in our own .Render implementation. We know this, but what of the rest? First, take note of the ScriptHandlerFactory type used as the handler factory for .asmx and .asbx file requests. A client will call a .asmx file if it wants to call a web service method, and a client will call a .asbx file if it wants to call a webservice method located on another web application (this is called bridging). Browsers are very careful not to do cross-site communication with script for security reasons - and so if the client wants to get a resource using script on another site, it must have the server do the call for it; so the server, then, acts like a "bridge" or middle-man. You call methods in a .asbx file if you want to call a bridged web service method. We have explained away the basics of how endpoints 1, 3, & 4 work. 2 - or the profile/authentication/authorization services are done through .asmx calls too - so, technically this is also handled by the ScriptHandlerFactory. So, to recap thus far:

  1. Inline [WebMethod]s within an .ASPX page - supported by Microsoft.Web.Services.ScriptModule
  2. Authentication/authorization and personalization services - supported by Microsoft.Web.Services.ScriptHandlerFactory
  3. Webservice (.asmx) files located within the web application - supported by Microsoft.Web.Services.ScriptHandlerFactory 
  4. Webservices of any type located beyond the current web application - supported by Microsoft.Web.Services.ScriptHandlerFactory.

The type, Microsoft.Web.Services.ScriptHandlerFactory does a lot of work, then. How does it differentiate the different requests, and what does it do internally? Again, I crack open Lutz Roeder's .Net Reflector and looked inside Microsoft.Web.Atlas (which houses all of the code for Atlas - and I mean all). After bouncing around for a while, I discovered that the calls are all REST-based, and that there exists an internal RestHandler which inherits IHttpHandler and is used for all these requests (calls are routed to it by the ScriptHandlerFactory). Within it's ProcessRequest method (called by the framework to process the request), there exists a fairly daunting looking hunk of code - only a few lines of code, however, are noteworthy:

IDictionary<string, object> dictionary1 = this.GetRawParams(context);
object obj1 = Activator.CreateInstance(this._webServiceMethodData.Owner.Type);
DataService service1 = obj1 as DataService;
if (service1 != null)
{
   service1.IsCalledRemotely = true;
}
object obj2 = this._webServiceMethodData.CallMethodFromRawParams(obj1, dictionary1);

Take note of the line containing Activator.CreateInstance(). This tells us that we're basically talking to a real data type (as I hinted at earlier). The result of this call (obj2) is worked on a bit by some plumbing code and returned to the client - but this is the bulk of the code. So, the question still exists, then: how are steps 2, 3 & 4 being dehydrated to basic .Net types that can be instantiated? The secret lies within the method .CreateHandler() of the type RestHandler:

internal static IHttpHandler CreateHandler(HttpContext context)
{
   WebServiceData data1 = WebServiceData.GetWebServiceData(context.Request.Path);
   string text1 = context.Request.QueryString["mn"];
   return RestHandler.CreateHandler(data1, text1);
}

This method creates a RestHandler to handle the incoming request, but also makes a weird call to WebServiceData.GetWebServiceData() passing to it the request path (the virtual path) to the resource (note, also, that it strips out the "mn" or method-name parameter from the querystring and passes it on). We can assume that it is the WebServiceData type that wraps each of the three "method" types that we can automate since, as you can see above, _webServiceMethodData is used above to call our method. GetWebServiceData is an overloaded method, and the call eventually winds its way down to another rather large function that, pretty much, holds the key to everything:

internal static WebServiceData GetWebServiceData(string virtualPath, bool failIfNoData)
{
    virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
    Type type1 = WebServiceData._mappings[virtualPath] as Type;
    bool flag1 = false;
    if (type1 == null)
    {
        flag1 = HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
        if (flag1)
        {
            type1 = BuildManager.GetCompiledType(virtualPath);
        }
    }
    if ((type1 == null) && !flag1)
    {
        string text1 = null;
        int num1 = virtualPath.IndexOf("ScriptServices/");
        if (num1 != -1)
        {
            num1 += "ScriptServices/".Length;
            text1 = virtualPath.Substring(num1, (virtualPath.Length - num1) - 5);
            text1 = text1.Replace('/', '.');
            type1 = typeof(WebServiceData).Assembly.GetType(text1, false, true);
            if (type1 == null)
            {
                type1 = BuildManager.GetType(text1, false, true);
            }
        }
        else
        {
            try
            {
                text1 = Path.GetFileNameWithoutExtension(virtualPath);
                text1 = WebServiceData.DecryptString(text1);
                type1 = Type.GetType(text1);
            }
            catch
            {
            }
            if (type1 != null)
            {
                WebServiceData._mappings[virtualPath] = type1;
                WebServiceData._typeVirtualPath[type1] = virtualPath;
            }
        }
    }
    if (type1 != null)
    {
        return WebServiceData.GetWebServiceData(type1);
    }
    if (failIfNoData)
    {
        throw new InvalidOperationException();
    }
    return null;
}

Phew. Are you ready for the dive? I'm going to try reducing the workload for you so you can kind of reap the benefit of the time I took to crack it. First, notice that the beginning block of code basically looks to see if the file exists on disk (given its virtual path), and if so, then it returns back a compiled data type for this file. This solves the situation where the requested resource is a .asmx file, as we request that the runtime compile the .asmx file (and its associated code files) and return us a full-fledged .Net data type. The check in the code for the "ScriptServices/" string is what identifies the request as a personalization or authentication/authorization request. The end-result is the grepping of the data type from the current assembly for the proper type to return. Note that the type (type1) is given to another overloaded form of GetWebServiceData() - going further we see that this type is wrapped by the WebServiceData type, and this is how it is linked.

...now, after we have the wrapped type (from the code above), the RestHandler.CreateHandler() method in the second code-snippet above iterates over all types that are given the [WebMethod] attribute and builds a familiar array of methods we see being used in the first snippet of code: _webServiceMethodData. So, voila - we are then calling fully-qualified data types from beginning to end.

But, we are missing one final bit. What about the external services? We saw that local web services can be referenced using their virtual path and compiled and instantiated and executed appropriately by the runtime. This is where the final entry in the web.config file comes into play: <add extension=".asbx" type="Microsoft.Web.Services.BridgeBuildProvider" />.

A build provider is a snap-in for the build-time environment of ASP.Net where a developer can generate code for their own file type and have said code included into the final assembly(ies) for the web application. See here for more information, but the basic idea is that when the web application is asked to compile, the runtime will encounter your custom file type and look to see if there is a build provider available for it. This is how Microsoft added supported for the mysterious .asbx file type. Microsoft.Web.Services.BridgeBuildProvider (if we inspect the code) has support for parsing the XML within these files and generating a valid data type that wraps proxy methods for the external web service and provides for the runtime environment all necessary strings to pull when calling the external service. If you look at the goliath piece of code above (the last one) and see that the final bit of control lands on a line of code calling Path.GetFileNameWithoutExtension and Type.GetType(), etc. The type generated by the build provider for the bridge service generates a data type of the same name as the .asbx file (so, foo.asbx will generate a type called foo); and this type is compiled into the app code for the web application. The result? Stripping the file name and doing a get type on the resultant name will locate the data type built by the bridge build provider and return it back to our pipeline we saw earlier.

So...the end result:

  1. Inline [WebMethod]s within an .ASPX page - supported by Microsoft.Web.Services.ScriptModule and implemented by injecting our own .Render method to call [WebMethod] in page class.
  2. Authentication/authorization and personalization services - supported by Microsoft.Web.Services.ScriptHandlerFactory and type wrapped by WebServiceData exists in Microsoft.Web.Atlas.
  3. Webservice (.asmx) files located within the web application - supported by Microsoft.Web.Services.ScriptHandlerFactory and type wrapped by WebServiceData exists on disk as .asmx file.
  4. Webservices of any type located beyond the current web application - supported by Microsoft.Web.Services.ScriptHandlerFactory and type wrapped by WebServiceData is built by the bridge build provider.

kick it on DotNetKicks.com
Monday, May 08, 2006 12:16:17 AM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing
 Thursday, May 04, 2006

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:

  1. The script module is loaded by the ASP.Net runtime due to its entry in the web.config file for the loading application.
    1. The .Init() method is called on the module, and it assigns a delegate for the PostMapRequestHandler event, OnPostMapRequestHandler.
  2. OnPostMapRequestHandler gets called and calls Microsoft.Web.Atlas.PageServiceHandler.HookUpPage() with the page the event was raised for as a parameter.
  3. The PageServiceHandler class registers its own PreRenderComplete event handler for the PreRenderComplete event for the page passed to it from earlier.
  4. The page goes through its normal life cycle and, right after the pre-render stage calls the PreRenderComplete handler method within the PageServiceHandler class.
  5. 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.
  6. 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


kick it on DotNetKicks.com
Thursday, May 04, 2006 4:09:23 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

Alright, you saw in the last blog entry how to get the Atlas client framework to download script that wraps a webservice reference: 

    <atlas:ScriptManager ID="scriptManager" runat="server" EnableScriptComponents="true" >
        <Services>
            <atlas:ServiceReference Path="SimpleService.asmx" />
        </Services>
    </atlas:ScriptManager>

The question now is how does everything tie together? We have  reference to a web service here, but that's about it. Bare in mind that the <atlas:ScriptManager> tag is processed server-side instead of client-side, so the ASP.Net runtime is responsible for processing it and not the clientside Atlas framework. What the clientside Atlas framework gets looks like this:

<page xmlns:script="http://schemas.microsoft.com/xml-script/2005">
  <references>
    <add src="SimpleService.asmx/js" />
  </references>
  <components />
</page>

...this is what the Atlas framework sees. Now, in a later blog post I'll go into the details of how the client framework parses and builds the script out of the XML script block in general, but for now I'll lightly touch on the topic enough to answer how the JavaScript gets downloaded to the client.

There is a class called Sys.MarkupParser whose responsibility is to deal with markup and, well, parse it. It has a method called _processXMLScript whose responsibility it is to, well, processXMLScript. The method is relatively simple (suprisingly) and looks like this:

this._processXMLScript = function(markupContext, references, componentNodes, completionHandler) {
   var xmlScriptContext =
   {
      markupContext: markupContext,
      references: references,
      componentNodes: componentNodes,
      completionHandler: completionHandler
   };

   if (references && references.length) {
      var scriptLoader = new Sys.ScriptLoader();
      scriptLoader.load(references,
         Function.createDelegate(this, this._processXMLScriptComponents),
         xmlScriptContext);
   }
   else {
      this._processXMLScriptComponents(xmlScriptContext);
   }
}

...the section to take note of is, obviously, the one that instantiates the class Sys.ScriptLoader(). It takes all references parsed out of the references block above and snags them. The most pertinent chunk of code from the .load() method above looks like this:

if (_references.length) {
   var reference = _references.dequeue();
   var scriptElement = document.createElement('script');
   _currentLoadingReference = scriptElement;

   if (Sys.Runtime.get_hostType() != Sys.HostType.InternetExplorer) {
      scriptElement.readyState = 'loaded';
      scriptElement.onload = loadReferences;
   }
   else {
      scriptElement.onreadystatechange = loadReferences;
   }
   scriptElement.type = 'text/javascript';
   scriptElement.src = reference;

   var headElement = document.getElementsByTagName('head')[0];
   headElement.appendChild(scriptElement);

   return;
}

See that the scriptElement.src is set to the 'reference' object, which is a string in this case that points to the web service URI with the "/js" appended. What this means is that the browser references this script as a "src" and therefore is responsible for downloading and loading the script into the script runtime, not the Atlas framework itself. When this URI is hit, the Atlas runtime on the server generates the script and everything links up appropriately.

Happy coding.  


kick it on DotNetKicks.com
Thursday, May 04, 2006 1:31:02 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

This will be a multi-part blog post 'cause the topic is rather extensive. This first part will describe the basics of calling web services via Atlas from the pure programmatic standpoint, plus a bit about how web proxies work in JavaScript. The other entries will go deeper.

First off, visit this Url: http://atlas.asp.net/docs/atlas/samples/services/SimpleService.asmx. What you see here is the nicey user interface wrapping this: http://atlas.asp.net/docs/atlas/samples/services/SimpleService.asmx?WSDL, the service description. If you're a web developer dealing with services, you've seen this before. No big deal.

If you append "/js" to the end of the first querystring, you see something new: http://atlas.asp.net/docs/atlas/samples/services/SimpleService.asmx/js. This is javascript; more interestingly, JavaScript wrapping the methods of the web service itself. After nicely beautifying the script, it looks like this:

Type.registerNamespace('Quickstart.Samples');
Quickstart.Samples.SimpleService=new function()
{
    this.path = "http://atlas.asp.net/docs/atlas/samples/services/SimpleService.asmx";

    this.appPath = "http://atlas.asp.net/docs/";
    var cm=Sys.Net.ServiceMethod.createProxyMethod;
    cm(this,"EchoString","s");
}

Don't worry too much about the internals of the script above as I'll go into this later (in another post). But, what you've just seen here is that the Atlas framework is capable of dynamically generating javascript to wrap the methods of the web service (the web service this code wraps has one method, EchoString, with one parameter "s"). Interacting with this downloaded code, then, is as easy as doing this:

function OnbuttonGo_click()
{
    //Call script proxy passing the input element data
   requestSimpleService = Quickstart.Samples.SimpleService.EchoString(
      document.getElementById('inputName').value, //params
      OnComplete, //Complete event
      OnTimeout //Timeout event
      );
    return false;
}

Notice the highlighted method call syntax above. It's really just that easy.

The next question is how does the Atlas client runtime know what services to retrieve proxies for; and the answer lies within the ScriptManager control:

    <atlas:ScriptManager ID="scriptManager" runat="server" EnableScriptComponents="true" >
        <Services>
            <atlas:ServiceReference Path="SimpleService.asmx" />
        </Services>
    </atlas:ScriptManager>


kick it on DotNetKicks.com
Thursday, May 04, 2006 10:24:37 AM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing
 Wednesday, May 03, 2006

The profile service in Atlas is implemented in much the same way that the authentication service is - through a dynamically instantiated web service class via a registered HttpHandlerFactory object (see the previous post). The clientside Atlas runtime hooks up with the profile bits on the server using web services, etc asynchronously and in the background. I noticed an interesting bit of coding sugar in Javascript while exploring the profile classes, however, that I thought was particularly interesting. The profile bits are stored inside the Sys.Profile object in an array called "properties". You can get, say, the 'FirstName' from the profile information of the current, executing user like this:

Sys.Profile.properties["FirstName"]

The "cool" part, however, is that you can also access it this way to:

Sys.Profile.properties.FirstName

Now how is this possible?

As it turns out, each object maintains an associative array of fellow objects (an array of associations), so that you can access sub-objects using array indexers or, as you see above, access array indices using standard OO "property" syntax - effectively, they're one in the same. You may have seen this when you try to access the main form of a web page in script, you can do it (if the main form were called Form1) programmatically either way: document.forms['Form1'] or document.forms.Form1; either way gives you a handle to the Form1 object. Syntactic sugar, yes, but it has a very interesting application as I discovered when going back over the code for the authentication script....

....say you wanted to create a method with a dynamic name and a dynamic parameter list. How would you go about it? Well, given that a function is an object in JavaScript, and that we can create objects using associative arrays, then, technically we can simply create a new function using an array-like syntax. If, in Javascript, you wanted to create a proxy object that encapsulated and "felt" like you're procedurally calling methods on a web service so that the syntax of calling a web method looked like this:

Sys.Services.AuthenticationService.login(username.value, password.value, false, OnLoginComplete);

Then you could dynamically create the "login" method at runtime like this:

proxy["login"] = function() {
   ...
   return;
}

You're simply creating a new object at runtime and assigning it to the parent object; it just so happens that our dynamically created object is a function. This is extraordinarily powerful when coupled with automatic discovery of methods and method parameters at runtime - how else could you build a function that felt like calling a web service method when you didn't know, until runtime, what the method would look like?

Now, what about parameters? Bare in mind that you can always access "extra" parameters passed to a javascript function through the arguments object of a function. So, even though the "login" method above has no parameters, if you pass them, then they're accessible to the method implementation through the arguments array:

proxy["login"] = function() {
   var username = arguments[0];
   var password = arguments[1]; 
   
...
   return;
}


Addendum:

This is nothing special with the Atlas framework itself, but is actually a part of the javascript language. Some of my posts relate to simply showing off those features for those who may not know so that the Atlas framework makes more sense when studying it.


kick it on DotNetKicks.com
Wednesday, May 03, 2006 4:00:17 PM (Central Standard Time, UTC-06:00)  #    Comments [2] - Trackback
Computing

Here, check this out:

When watching a Fiddler dump of the http request/response pattern of the Microsoft sample on Http Forms Authentication in Atlas, you see the following resource being requested:

http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx. Have a look:

POST /docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx?mn=login HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer:
http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/Default.aspx
Content-Type: application/json
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: 75
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: ...

Now, what's interesting about this is that if you actually follow the link (try it), you'll see that you're spat back a 404 - not found error from the web server. However, this is the webservice that gets communicated with for all forms authentication; it literally maintains the interface that all of the Atlas framework expects for this authentication mechanism (the login, verification, et al). How can this web service not exist, but be available for communication? When you see a request for a resource that doesn't exist on disk, you immediately become aware that some sort of magic is happening, and typically the party responsible for said magic is either some sort of Isapi module/extension or an HttpModule/Handler. All of the code for ASP.Net Atlas is stored within a single .Net assembly called Microsoft.Web.Atlas; which rules out the ISAPI idea, leaving us with HttpModules/Handlers.

Inspect the web.config file for your Asp.Net Atlas application and take note of the following:

  <httpHandlers>
   <remove verb="*" path="*.asmx"/>
   <add verb="*" path="*.asmx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
   <!--
          The MultiRequestHandler enables multiple requests to be handled in one
          roundtrip to the server. Its use requires Full Trust.
      -->
   <add verb="*" path="atlasbatchcall.axd" type="Microsoft.Web.Services.MultiRequestHandler" validate="false"/>
   <add verb="*" path="atlasglob.axd" type="Microsoft.Web.Globalization.GlobalizationHandler" validate="false"/>
   <!--
          The IFrameHandler enables a limited form of cross-domain calls to 'Atlas' web services.
          This should only be enabled if you need this functionality and you're willing to expose
          the data publicly on the Internet.
          To use it, you will also need to add the attribute [WebOperation(true, ResponseFormatMode.Json, true)]
          on the methods that you want to be called cross-domain.
          This attribute is by default on any DataService's GetData method.
         
      <add verb="*" path="iframecall.axd" type="Microsoft.Web.Services.IFrameHandler" validate="false"/>
      -->
   <add verb="*" path="*.asbx" type="Microsoft.Web.Services.ScriptHandlerFactory" validate="false"/>
  </httpHandlers>

...I intentionally highlighted the .asmx handler, Microsoft.Web.Services.ScriptHandlerFactory. If you note, the resource being requested is a web service (with the .asmx extension). The way ASP.Net works is to inspect the configuration files (machine.config, web.config) for handlers registered for particular extensions and to insert those handlers into the request/response pipeline. The statement above effectively sticks the Microsoft.Web.Services.ScriptHandlerFactory type into the pipeline for responding to requests for the .asmx extension. Now, this data type exists within the Microsoft.Web.Atlas assembly mentioned earlier, so I cracked open Lutz Roeder's most excellent .Net Reflector and peered inside (yes, I love doing this - in case you haven't noticed).

ASP.Net sees that the type registered for .asmx is the one in the config file, it locates it, loads it into the runtime, JITS it up and requests the method ::GetHandler from the instance it creates. This method is suppose to do inspection on the query string and return the correct HttpHandler object (web form, web service, etc) that is expected to generate the output stream content for the runtime to send back to the client. What gets registered in the config file is either that handler itself (IHttpHandler), or the handler factory (IHttpHandlerFactory). What's registered above is the handler factory. That means this class returns a type that is responsible for handling the .asmx request. What all this boils down to is that the object doesn't really need to exist on disk since, at this point, it's up to the handler to locate and return the resource requested. As it turns out, the type is actually stored within Microsoft.Web.Atlas itself:

/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx resolves to the datatype Microsoft.Web.Services.Standard.AuthenticationWebService housed within the Microsoft.Web.Atlas assembly which inherits from System.Web.Services.WebService (which is a valid http handler that can service the web request). What this means is that the factory class above parses the query string and returns an instance of the right data type to the runtime for execution. The actual method which does all the magic is called GetWebServiceData within the WebServiceData class (also in the Microsoft.Web.Atlas assembly). Decompiled it looks like this:

internal static WebServiceData GetWebServiceData(string virtualPath, bool failIfNoData)
{
   virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
   Type type1 = WebServiceData._mappings[virtualPath] as Type;
   bool flag1 = false;
   if (type1 == null)
   {
      flag1 = HostingEnvironment.VirtualPathProvider.FileExists(virtualPath);
      if (flag1)
      {
         type1 = BuildManager.GetCompiledType(virtualPath);
      }
   }
if ((type1 == null) && !flag1)
{
   string text1 = null;
   int num1 = virtualPath.IndexOf("ScriptServices/");
   if (num1 != -1)
   {
      num1 += "ScriptServices/".Length;
      text1 = virtualPath.Substring(num1, (virtualPath.Length - num1) - 5);
      text1 = text1.Replace('/', '.');
      type1 = typeof(WebServiceData).Assembly.GetType(text1, false, true);
      if (type1 == null)
      {
         type1 = BuildManager.GetType(text1, false, true);
      }
     }
     else
      {
         try
         {
            text1 = Path.GetFileNameWithoutExtension(virtualPath);
            text1 = WebServiceData.DecryptString(text1);
            type1 = Type.GetType(text1);
         }
         catch
         {
         }
         if (type1 != null)
         {
            WebServiceData._mappings[virtualPath] = type1;
            WebServiceData._typeVirtualPath[type1] = virtualPath;
         }
      }
     }
   if (type1 != null)
   {
      return WebServiceData.GetWebServiceData(type1);
   }
   if (failIfNoData)
   {
      throw new InvalidOperationException();
   }
   return null;
}


Note the parsing out of the string "/ScriptServices" and the replacement of forward slash (/) with period (.) to get the fully-qualified path to the data type for use in the reflection services of the runtime. The type found in this method eventually makes it way up the call chain to be instantiated and returned from the handler factory to the runtime. The result is that it, then, processes the web service page and, it executes, giving back the proper bits of data for Forms Authentication.

Happy coding,


kick it on DotNetKicks.com
Wednesday, May 03, 2006 2:14:37 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing
 Tuesday, May 02, 2006

Woah! Yesterday my blog got hit 217 times. Thanks.....


kick it on DotNetKicks.com
Tuesday, May 02, 2006 5:05:57 PM (Central Standard Time, UTC-06:00)  #    Comments [1] - Trackback
Computing

Yes, again, I'm digging into these things....if you'd like to see the ASP.Net "Atlas" sample I'm referring to in this blog post, it's located here.

Notice that the only supported form of authentication in Atlas is Forms (meaning that Windows or "Kerberos/NTLM" and Passport authentication are missing). I'm no expert in Kerberos-based authentication, but I imagine that passing along the ticket in a secure manner might be difficult or impractical via JavaScript, and I imagine the Passport authentication scheme might be rather odd too when implemented in JavaScript. Regardless of why, the only valid authentication scheme supported by Atlas at the moment is Forms; meaning you're dealing with cookies (which is simple for JavaScript to access - another reason why this form was probably chosen).

Three methods are supported by the Sys.Services.AuthenticationService class:

  • validateUser()
  • login()
  • logout()

In the next blog post I'm going to explain how these methods work under the hood (within the script), or I'm going to at least try to - but for now, I'm going to simply show how they work programmatically (how you use them). What each function does is rather self-explanatory - login() logs a user in and adds the forms authentication cookie to the browser, validateUser() validates a user's login status by examining the forms authentication cookie and logout() removes the forms authentication cookie from the browser and effectively logs the user out. The whole system utilizes partial postbacks and so, therefore, is done asynchronously with the client experience.

The following script call then,

Sys.Services.AuthenticationService.login(username.value, password.value, false, OnLoginComplete);

This is a Fiddler trace of the process occuring on the Microsoft sample:

REQUEST:

POST /docs/atlas/samples/formsandmem_cs/ScriptServices/Microsoft/Web/Services/Standard/AuthenticationWebService.asmx?mn=login HTTP/1.1
Accept: */*
Accept-Language: en-us
Referer:
http://atlas.asp.net/docs/atlas/samples/formsandmem_cs/Default.aspx
Content-Type: application/json
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: 75
Proxy-Connection: Keep-Alive
Pragma: no-cache
Cookie: CommunityServer-UserCookie605740=lv=4/29/2006 8:20:41 PM&mra=5/1/2006 2:18:26 PM; .ASPXANONYMOUS=6mqTPsGZxgEkAAAAOGY3MTdhMWMtNTZkNS00YTVjLTkwMmQtYmYyMzRkZmFkMjAzT5esZ6m9p6Ha5oC1KM8c5LDCmx41; ASP.NET_SessionId=apbic1njfd45gk45x510wa45; .CommunityServer=....

{"userName":"myusername","password":"mypassword!","createPersistentCookie":false}

RESPONSE:

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Date: Tue, 02 May 2006 20:52:23 GMT
Content-Type: application/json; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: .CommunityServer=...
path=/; HttpOnly
Vary: Accept-Encoding

true 

Notice the "true" returned on the body of the HTTP response. If the login attempt was a failure, the return would be "false". The login method simple executes asynchronously and, in the completion event handler, lets the code know what this return value was. Your clien-side experience takes suit from this.

Now, arguably, logging in and handling all the validation on the client may not be the best choice to make in all circumstances since there may be data you don't want in the HTML; but for simple UI changes, etc. I can see the benefit of clientside forms authentication. Personally, I see the most benefit in the validateUser() ethod when having the client render itself. Also note that no data is encrypted (the username/password combo passed to the server).


kick it on DotNetKicks.com
Tuesday, May 02, 2006 3:02:17 PM (Central Standard Time, UTC-06:00)  #    Comments [0] - Trackback
Computing

Computers Blogs - Blog Top Sites

Archive
<May 2006>
SunMonTueWedThuFriSat
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910
Blogroll
About the author/Disclaimer

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2009
Benjamin Rush
Sign In
Statistics
Total Posts: 444
This Year: 0
This Month: 0
This Week: 0
Comments: 128
Themes
Pick a theme: