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

....come and get em.


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

....I would treat this post by Scott Gutherie as the landing page for any questions you may have regarding Roles, Membership and Profile providers.


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

....point yourself here. Notice the smooth, clean user experience and the lack of browser postbacks when using them - this is what Atlas is all about.


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

Atlas developers - especially Atlas developers working with classic ASP.Net applications - are going to fall in love with <UpdatePanel> controls because they make migrating traditional ASP.Net 1.x/2.0 applications to Atlas a breeze (er, for the most part). You will run into issues, at times, when dealing with update panels because, even though they are pretty solid in implementation and require (sometimes) no changes in code on the server - they still can break existing code. Understanding the internals of them is always a benefit.

The syntax for an update panel is simple, and looks like this:

<atlas:UpdatePanel runat="server" ID="UpdatePanel5">
   ... controls that will now be AJAX'd ...

</atlas:UpdatePanel>

Pretty harmless looking actually. What you do is simply surround surround your classic ASP.Net controls with these tags and *voila*, you suddenly enabled them all for partial postbacks. If you have a button and a textbox inside the UpdatePanel tags, and when you click the button the text box says hello, you can now do this without forcing the rest of the web page to render. There are several features to the UpdatePanel, and I'm not going to restate them , so if you're curious look here because the topic of this blog post is how the Atlas framework enables you to simply surround your original ASP.Net controls with a single tag and turn them into something which supports partial postbacks.

The slight of hand behind the whole process is the onsubmit handler in the form tag for every Atlas application:

<form name="form1" method="post" action="UpdatePanel1_cs.aspx" onsubmit="javascript:return WebForm_OnSubmit();" id="form1">

The method is large - too large to paste in this post in its entirety - but its function is simple: to detect if partial postbacks are enabled for the page, and if so, swallow any submit event that originates within an UpdatePanel (which are typically rendered as <span> or <div> tags in the HTML) and, in its stead, send an asynchronous form post back to the server. It registers a handler method for dealing with the response from the server and parses data out and updates the content using standard DHTML tricks. The main lines of code to take note of within the Submit method follows:

var request = new Sys.Net.WebRequest();
request.set_url(form.action);
request.get_headers()['delta'] = 'true';
request.get_headers()['Cache-Control'] = 'no-cache';
request.set_timeoutInterval(90000);
request.set_priority(Sys.Net.WebRequestPriority.High);
request.completed.add(Function.createDelegate(this, this._onFormSubmitCompleted));
request.timeout.add(Function.createDelegate(this, this._onFormSubmitTimeout));
request.set_body(formBody.toString());

_request = request;
this.raisePropertyChanged('inPostBack');
request.invoke();

One main point of interst is the creation and setting of the 'delta' header variable. For partial postbacks, this particular header is always there for it is this that clues the server-side framework into the fact that a partial postback is occuring, and to format the response stream in such a manner that the client can easily and effeciently parse out the change or "delta" in the browser content. A summary of fiddler response for a partial postback follows:

HTTP/1.1 200 OK
Cache-Control: no-cache
Date: Tue, 09 May 2006 03:56:25 GMT
Pragma: no-cache
Content-Type: text/xml; charset=utf-8
Expires: -1
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Vary: Accept-Encoding

<delta>
..........
</delta>

Pay special attention to the fact that the response type is text/xml - this is because the response is, yes, XML. If you scan the method that deals with the server response, it is littered with dozens of XPath expressions that tear out of the response pertinent bits of information for DHTML-based updating of controls. For example:

..........
var
hiddenFields = delta.selectNodes('/delta/rendering//input[@type="hidden"]');
for (var j = 0; j < hiddenFields.length; j++) {
   var id = hiddenFields[j].attributes.getNamedItem('id').nodeValue;
   var value = hiddenFields[j].attributes.getNamedItem('value').nodeValue;

   var hiddenFieldElement = document.getElementById(id);
   if (!hiddenFieldElement) {
      hiddenFieldElement = document.createElement('input');
      hiddenFieldElement.id = id;
      hiddenFieldElement.name = id;
      hiddenFieldElement.type = 'hidden';
      _form.appendChild(hiddenFieldElement);
   }
   hiddenFieldElement.value = value;
}
..........

Now the server-side components that come into play here reside within the server-side code for the ScriptManager control. Particularly, two methods are of interest: first, the one that identifies we're dealing with partial postbacks and the one that generates the response stream for the client. The following bit of code within the ScriptManager hooks the .Render method (in the same way that the .Render method is hooked when inline [WebMethod] calls are made) to override the page's normal output and send custom output:

if (this._inPartialRenderingMode)
{
   RenderMethod method1 = new RenderMethod(this.RenderPageCallback);
   this._page.SetRenderMethodDelegate(method1);
}

When the page housing the ScriptManager control is asked to render, the following method is called instead:

private void RenderPageCallback(HtmlTextWriter writer, Control pageControl)
{
   Page page1 = (Page) pageControl;
   HttpResponse response1 = page1.Response;
   response1.Cache.SetCacheability(HttpCacheability.NoCache);
   response1.ContentType = "text/xml";
   writer.Write("<delta>");
   ....
   writer.Write("</delta>");
}

...which builds the response the client framework expects (an XML document).
kick it on DotNetKicks.com
Monday, May 08, 2006 11:54:48 PM (Central Standard Time, UTC-06:00)  #    Comments [2] - Trackback
Computing
 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

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 2008
Benjamin Rush
Sign In
Statistics
Total Posts: 444
This Year: 0
This Month: 0
This Week: 0
Comments: 127
Themes
Pick a theme:
All Content © 2008, Benjamin Rush
DasBlog theme 'Business' created by Christoph De Baene (delarou)