Introducing XIO (xio.js)

by Jon Davis 3. September 2013 02:36

I spent the latter portion last week and the bulk of the holiday fleshing out the initial prototype of XIO ("ecks-eye-oh" or "zee-oh", I don't care at this point). It was intended to start out as an I/O library targeting everything (get it? X I/O, as in I/O for x), but that in turn forced me to make it a repository library with RESTful semantics. I still want to add stream-oriented functionality (WebSocket / long polling) to it to make it truly an I/O library. In the mean time, I hope people can find it useful as a consolidated interface library for storing and retrieving data.

You can access this project here: https://github.com/stimpy77/xio.js#readme

Here's a snapshot of the README file as it was at the time of this blog entry.



XIO (xio.js)

version 0.1.1 initial prototype (all 36-or-so tests pass)

A consistent data repository strategy for local and remote resources.

What it does

xio.js is a Javascript resource that supports reading and writing data to/from local data stores and remote servers using a consistent interface convention. One can write code that can be more easily migrated between storage locations and/or URIs, and repository operations are simplified into a simple set of verbs.

To write and read to and from local storage,

xio.set.local("mykey", "myvalue");
var value = xio.get.local("mykey")();

To write and read to and from a session cookie,

xio.set.cookie("mykey", "myvalue");
var value = xio.get.cookie("mykey")();

To write and read to and from a web service (as optionally synchronous; see below),

xio.post.mywebservice("mykey", "myvalue");
var value = xio.get.mywebservice("mykey")();

See the pattern? It supports localStorage, sessionStorage, cookies, and RESTful AJAX calls, using the same interface and conventions.

It also supports generating XHR functions and providing implementations that look like:

mywebservice.post("mykey", "myvalue");
var value = mywebservice.get("mykey")(); // assumes synchronous; see below
Optionally synchronous (asynchronous by default)

Whether you're working with localStorage or an XHR resource, each operation returns a promise.

When the action is synchronous, such as in working with localStorage, it returns a "synchronous promise" which is essentially a function that can optionally be immediately invoked and it will wrap .success(value) and return the value. This also works with XHR when async: false is passed in with the options during setup (define(..)).

The examples below are the same, only because XIO knows that the localStorage implementation of get is synchronous.

Aynchronous convention: var val; xio.get.local('mykey').success(function(v) { val = v; });

Synchronous convention: var val = xio.get.local('mykey')();

Generated operation interfaces

Whenever a new repository is defined using XIO, a set of supported verb and their implemented functions is returned and can be used as a repository object. For example:

var myRepository = xio.define('myRepository', { 
    url: '/myRepository?key={0}',
    methods: ["GET", "POST", "PUT", "DELETE"]
});

.. would populate the variable myRepository with:

{
    get: function(key) { /* .. */ },
    post: function(key, value) { /* .. */ },
    put: function(key, value) { /* .. */ },
    delete: function(key) { /* .. */ }
}

.. and each of these would return a promise.

XIO's alternative convention

But the built-in convention is a bit unique using xio[action][repository](key, value) (i.e.xio.post.myRepository("mykey", {first: "Bob", last: "Bison"}), which, again, returns a promise.

This syntactical convention, with the verb preceding the repository, is different from the usual convention of_object.method(key, value).

Why?!

The primary reason was to be able to isolate the repository from the operation, so that one could theoretically swap out one repository for another with minimal or no changes to CRUD code. For example,

var repository = "local"; // use localStorage for now; 
                          // replace with "my_restful_service" when ready 
                          // to integrate with the server
xio.post[repository](key, value).complete(function() {

    xio.get[repository](key).success(function(val) {
        console.log(val);
    });

});

Note here how "repository" is something that can move around. The goal, therefore, is to make disparate repositories such as localStorage and RESTful web service targets support the same features using the same interface.

As a bit of an experiment, this convention of xio[verb][repository] also seems to read and write a little better, even if it's a bit weird at first to see. The thinking is similar to the verb-target convention in PowerShell. Rather than taking a repository and working with it independently with assertions that it will have some CRUD operations available, the perspective is flipped and you are focusing on what you need to do, the verbs, first, while the target becomes more like a parameter or a known implementation of that operation. The goal is to dumb down CRUD operation concepts and repositories and refocus on the operations themselves so that, rather than repositories having an unknown set of operations with unknown interface styles and other features, instead, your standard CRUD operations, which are predictable, have a set of valid repository targets that support those operations.

This approach would have been entirely unnecessary and pointless if Javascript inherently supported interfaces, because then we could just define a CRUD interface and write all our repositories against those CRUD operations. But it doesn't, and indeed with the convention of closures and modules, it really can't.

Meanwhile, when you define a repository with xio.define(), as was described above and detailed again below, it returns an object that contains the operations (get(), post(), etc) that it supports. So if you really want to use the conventional repository[method](key, value) approach, you still can!

Download

Download here: https://raw.github.com/stimpy77/xio.js/master/src/xio.js

To use the whole package (by cloning this repository)

.. and to run the Jasmine tests, you will need Visual Studio 2012 and a registration of the .json file type with IIS / IIS Express MIME types. Open the xio.js.csproj file.

Dependencies

jQuery is required for now, for XHR-based operations, so it's not quite ready for node.js. This dependency requirement might be dropped in the future.

Basic verbs

See xio.verbs:

  • get(key)
  • set(key, value); used only by localStorage, sessionStorage, and cookie
  • put(key, data); defaults to "set" behavior when using localStorage, sessionStorage, or cookie
  • post(key, data); defaults to "set" behavior when using localStorage, sessionStorage, or cookie
  • delete(key)
  • patch(key, patchdata); implemented based on JSON/Javascript literals field sets (send only deltas)
Examples
// initialize

var xio = Xio(); // initialize a module instance named "xio"
localStorage
xio.set.local("my_key", "my_value");
var val = xio.get.local("my_key")();
xio.delete.local("my_key");

// or, get using asynchronous conventions, ..    
var val;
xio.get.local("my_key").success(function(v) 
    val = v;
});

xio.set.local("my_key", {
    first: "Bob",
    last: "Jones"
}).complete(function() {
    xio.patch.local("my_key", {
        last: "Jonas" // keep first name
    });
});
sessionStorage
xio.set.session("my_key", "my_value");
var val = xio.get.session("my_key")();
xio.delete.session("my_key");
cookie
xio.set.cookie(...)

.. supports these arguments: (key, value, expires, path, domain)

Alternatively, retaining only the xio.set["cookie"](key, value), you can automatically returned helper replacer functions:

xio.set["cookie"](skey, svalue)
    .expires(Date.now() + 30 * 24 * 60 * 60000))
    .path("/")
    .domain("mysite.com");

Note that using this approach, while more expressive and potentially more convertible to other CRUD targets, also results in each helper function deleting the previous value to set the value with the new adjustment.

session cookie
xio.set.cookie("my_key", "my_value");
var val = xio.get.cookie("my_key")();
xio.delete.cookie("my_key");
persistent cookie
xio.set.cookie("my_key", "my_value", new Date(Date.now() + 30 * 24 * 60 * 60000));
var val = xio.get.cookie("my_key")();
xio.delete.cookie("my_key");
web server resource (basics)
var define_result =
    xio.define("basic_sample", {
                url: "my/url/{0}/{1}",
                methods: [ xio.verbs.get, xio.verbs.post, xio.verbs.put, xio.verbs.delete ],
                dataType: 'json',
                async: false
            });
var promise = xio.get.basic_sample([4,12]).success(function(result) {
   // ..
});
// alternatively ..
var promise_ = define_result.get([4,12]).success(function(result) {
   // ..
});

The define() function creates a verb handler or route.

The url property is an expression that is formatted with the key parameter of any XHR-based CRUD operation. The key parameter can be a string (or number) or an array of strings (or numbers, which are convertible to strings). This value will be applied to the url property using the same convention as the typical string formatters in other languages such as C#'s string.Format().

Where the methods property is defined as an array of "GET", "POST", etc, for each one mapping to standard XIO verbs an XHR route will be internally created on behalf of the rest of the options defined in the options object that is passed in as a parameter to define(). The return value of define() is an object that lists all of the various operations that were wrapped for XIO (i.e. get(), post(), etc).

The rest of the options are used, for now, as a jQuery's $.ajax(..., options) parameter. The async property defaults to false. When async is true, the returned promise is wrapped with a "synchronous promise", which you can optionally immediately invoke with parens (()) which will return the value that is normally passed into .success(function (value) { .. }.

In the above example, define_result is an object that looks like this:

{
    get: function(key) { /* .. */ },
    post: function(key, value) { /* .. */ },
    put: function(key, value) { /* .. */ },
    delete: function(key) { /* .. */ }
}

In fact,

define_result.get === xio.get.basic_sample

.. should evaluate to true.

Sample 2:

var ops = xio.define("basic_sample2", {
                get: function(key) { return "value"; },
                post: function(key,value) { return "ok"; }
            });
var promise = xio.get["basic_sample2"]("mykey").success(function(result) {
   // ..
});

In this example, the get() and post() operations are explicitly declared into the defined verb handler and wrapped with a promise, rather than internally wrapped into XHR/AJAX calls. If an explicit definition returns a promise (i.e. an object with .success and .complete), the returned promise will not be wrapped. You can mix-and-match both generated XHR calls (with the url and methods properties) as well as custom implementations (with explicit get/post/etc properties) in the options argument. Custom implementations will override any generated implementations if they conflict.

web server resource (asynchronous GET)
xio.define("specresource", {
                url: "spec/res/{0}",
                methods: [xio.verbs.get],
                dataType: 'json'
            });
var val;
xio.get.specresource("myResourceAction").success(function(v) { // gets http://host_server/spec/res/myResourceAction
    val = v;
}).complete(function() {
    // continue processing with populated val
});
web server resource (synchronous GET)
xio.define("synchronous_specresources", {
                url: "spec/res/{0}",
                methods: [xio.verbs.get],
                dataType: 'json',
                async: false // <<==!!!!!
            });
var val = xio.get.synchronous_specresources("myResourceAction")(); // gets http://host_server/spec/res/myResourceAction
web server resource POST
xio.define("contactsvc", {
                url: "svcapi/contact/{0}",
                methods: [ xio.verbs.get, xio.verbs.post ],
                dataType: 'json'
            });
var myModel = {
    first: "Fred",
    last: "Flinstone"
}
var val = xio.post.contactsvc(null, myModel).success(function(id) { // posts to http://host_server/svcapi/contact/
    // model has been posted, new ID returned
    // validate:
    xio.get.contactsvc(id).success(function(contact) {  // gets from http://host_server/svcapi/contact/{id}
        expect(contact.first).toBe("Fred");
    });
});
web server resource (DELETE)
xio.delete.myresourceContainer("myresource");
web server resource (PUT)
xio.define("contactsvc", {
                url: "svcapi/contact/{0}",
                methods: [ xio.verbs.get, xio.verbs.post, xio.verbs.put ],
                dataType: 'json'
            });
var myModel = {
    first: "Fred",
    last: "Flinstone"
}
var val = xio.post.contactsvc(null, myModel).success(function(id) { // posts to http://host_server/svcapi/contact/
    // model has been posted, new ID returned
    // now modify:
    myModel = {
        first: "Carl",
        last: "Zeuss"
    }
    xio.put.contactsvc(id, myModel).success(function() {  /* .. */ }).error(function() { /* .. */ });
});
web server resource (PATCH)
xio.define("contactsvc", {
                url: "svcapi/contact/{0}",
                methods: [ xio.verbs.get, xio.verbs.post, xio.verbs.patch ],
                dataType: 'json'
            });
var myModel = {
    first: "Fred",
    last: "Flinstone"
}
var val = xio.post.contactsvc(null, myModel).success(function(id) { // posts to http://host_server/svcapi/contact/
    // model has been posted, new ID returned
    // now modify:
    var myModification = {
        first: "Phil" // leave the last name intact
    }
    xio.patch.contactsvc(id, myModification).success(function() {  /* .. */ }).error(function() { /* .. */ });
});
custom implementation and redefinition
xio.define("custom1", {
    get: function(key) { return "teh value for " + key};
});
xio.get.custom1("tehkey").success(function(v) { alert(v); } ); // alerts "teh value for tehkey";
xio.redefine("custom1", xio.verbs.get, function(key) { return "teh better value for " + key; });
xio.get.custom1("tehkey").success(function(v) { alert(v); } ); // alerts "teh better value for tehkey"
var custom1 = 
    xio.redefine("custom1", {
        url: "customurl/{0}",
        methods: [xio.verbs.post],
        get: function(key) { return "custom getter still"; }
    });
xio.post.custom1("tehkey", "val"); // asynchronously posts to URL http://host_server/customurl/tehkey
xio.get.custom1("tehkey").success(function(v) { alert(v); } ); // alerts "custom getter still"

// oh by the way,
for (var p in custom1) {
    if (custom1.hasOwnProperty(p) && typeof(custom1[p]) == "function") {
        console.log("custom1." + p); // should emit custom1.get and custom1.post
    }
}

Future intentions

WebSockets and WebRTC support

The original motivation to produce an I/O library was actually to implement a WebSockets client that can fallback to long polling, and that has no dependency upon jQuery. Instead, what has so far become implemented has been a standard AJAX interface that depends upon jQuery. Go figure.

If and when WebSocket support gets added, the next step will be WebRTC.

Meanwhile, jQuery needs to be replaced with something that works fine on nodejs.

Additionally, in a completely isolated parallel path, if no progress is made by the ASP.NET SignalR team to make the SignalR client freed from jQuery, xio.js might become tailored to be a somewhat code compatible client implementation or a support library for a separate SignalR client implementation.

Service Bus, Queuing, and background tasks support

At an extremely lightweight scale, I do want to implement some service bus and queue features. For remote service integration, this would just be more verbs to sit on top of the existing CRUD operations, as well as WebSockets / long polling / SignalR integration. This is all fairly vague right now because I am not sure yet what it will look like. On a local level, however, I am considering integrating with Web Workers. It might be nice to use XIO to manage deferred I/O via the Web Workers feature. There are major limitations to Web Workers, however, such as no access to the DOM, so I am not sure yet.

Other notes

If you run the Jasmine tests, make sure the .json file type is set up as a mime type. For example, IIS and IIS Express will return a 403 otherwise. Google reveals this: http://michaellhayden.blogspot.com/2012/07/add-json-mime-type-to-iis-express.html

License

The license for XIO is pending, as it's not as important to me as getting some initial feedback. It will definitely be an attribution-based license. If you use xio.js as-is, unchanged, with the comments at top, you definitely may use it for any project. I will drop in a license (probably Apache 2 or BSD or Creative Commons Attribution or somesuch) in the near future.

Eliminating Postbacks: Setting Up jQuery On ASP.NET Web Forms and Managing Data On The Client

by Jon Davis 19. October 2008 12:11

This is a follow-up to a prior post, Keys to Web 3.0 Design and Development When Using ASP.NET. Now I want to focus solely on getting jQuery and client-side data managmeent working with ASP.NET 2.0 without ASP.NET AJAX or ASP.NET MVC.

So you're stuck with Visual Studio 2005 and ASP.NET Web Forms. You want to flex your ninja skills. You can't jump into ASP.NET MVC or ASP.NET AJAX or an alternate templating solution like PHP, though. Are you going to die ([Y]/N)? N

 

Why would you use Web Forms in the first place? Well, you might want to take advantage of some of the data binding shorthand that can be done with Web Forms. For this blog entry, I'll use the example of a pre-populated DropDownList (a <select> tag filled with <option>'s that came from the database). 

This is going to be kind of a "for Dummies" post. Anyone who has good experience with ASP.NET and jQuery is likely already quite familiar with how to get jQuery up and running. But there are a few caveats that an ASP.NET developer would need to remember or else things become tricky (and again, no more tricky than is easily mastered by an expert ASP.NET developer).

Caveat #1: You cannot simply throw a script include into the head of an ASPX page.

The following page markup is invalid:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
    <script language="javascript" type="text/javascript" src="jQuery-1.2.6.js></script>
</head>
<body>

It's invalid because ASP.NET truncates tags from the <head> that it doesn't recognize and doesn't know how to deal with, such as <script runat="server">. You have to either put it into the <body> or register the script with the page which will then cause it to be put into body.

Registering the script with the page rather than putting it in the body yourself is recommended by Microsoft because:

  1. It allows you to guarantee the life cycle--more specifically the load order--of your scripts.
  2. It allows ASP.NET to do the same (manage the load order) of your scripts alongside the scripts on which ASP.NET Web Forms is running. Remember that Web Forms hijacks the <form> tag and the onclick behavior of ASP.NET buttons and such things, so it does know some Javascript already and needs to maintain that.
  3. When a sub-page or an .ascx control requires a dependency script, it helps to prevent the same dependency script from being added more than once.
  4. It allows controls to manage their own scripts. More on that in a moment.
  5. It allows you to put the inclusion markup into a server language context where you can use ResolveUrl("~/...") to dynamicize the location of the file according to the app path. This is very important in web sites where a directory hierarchy--with ASP.NET files buried inside a directory--is in place.

Here's how to add an existing external script (a script include) like jQuery into your page. Go to the code-behind file (or the C# section if you're not using code-behind) and register jQuery like so:

protected void Page_Load(object sender, EventArgs e)
{
    Page.ClientScript.RegisterClientScriptInclude(
        typeof(_YourPageClassName_), "jQuery", ResolveUrl("~/js/jquery-1.2.6.js"));
} 

A hair more verbose than I'd prefer but it's not awful. In the case of jQuery which is usually a foundational dependency for many other scripts (and itself has no dependencies), you might also consider putting this on an OnInit() override rather than Page_Load, but that's only if you're adding it to a control, where its lifecycle is less predictable in Page_Load() than in OnInit(), but I'll get into that shortly.

There is a way to inject a script into the <head>, such as described here: http://www.aspcode.net/Javascript-include-from-ASPNET-server-control.aspx. However, that is even more verbose, and it's not really considered "the ASP.NET Web Forms way".

If you want to use the Page.ClientScript registration methods for page script (written inline with markup), create a Literal control and put your script tag there. Then on the code-behind you can use Page.ClientScript.RegisterClientScriptBlock().

On the page:

<body>
    <form id="form1" runat="server">
    <asp:Literal runat="server" ID="ScriptLiteral" Visible="false">
    <script language="javascript" type="text/javascript">
        alert($);
    </script>
    </asp:Literal> 

Note that I'm using a hidden (Visible="false") Literal tag, and this tag is inside the <form runat="server"> tag. Which leads me to ..

Caveat #2: ASP.NET controls can only be declared inside <form runat="server">.

Alright, so then on the code-behind file (or server script), I add:

protected void Page_Load(object sender, EventArgs e)
{
    Page.ClientScript.RegisterClientScriptInclude(
        typeof(_YourPageClassName_), "jQuery", ResolveUrl("~/js/jquery-1.2.6.js"));
    Page.ClientScript.RegisterClientScriptBlock(
        typeof(_YourPageClassName_), "ScriptBlock", ScriptLiteral.Text, false);
} 

Unfortunately, ..

Caveat #3: Client script blocks that are registered on the page in server code lack Intellisense designers for script editing.

To my knowledge, there's no way around this, and believe me I've looked. This is a design error on Microsoft's part, it should not have been hard to create a special tag like: <asp:ClientScriptBlock runat="server">YOUR_SCRIPT_HERE();</asp:ClientScriptBlock>, that registers the given script during the Page_Load lifecycle, and then have a rich, syntax-highlighting, intellisense-supporting code editor when editing the contents of that control. They added a ScriptManager control that is, unfortunately, overkill in some ways, but that is only available in ASP.NET AJAX extensions, not core ASP.NET Web Forms.

But since they didn't give us this functionality in ASP.NET Web Forms, if you want natural script editing (and let's face it, we all do), you can just use unregistered <script> tags the old-fashioned way, but you should put the script blocks either inside the <form runat="server"> element and then inside a Literal control and registered, as demonstrated above, or else it should be below the </form> closure of the <form runat="server"> element. 

Tip: You can usually safely use plain HTML <script language="javascript" type="text/javascript">...</script> tags the old fashioned way, without registering them, as long you place them below your <form runat="server"> blocks, and you are acutely aware of dependency scripts that are or are not also registered.

But scripts that are used as dependency libraries for your page scripts, such as jQuery, should be registered. Now then. We can simplify this... 

Tip: Use an .ascx control to shift the hassle of script registration to the markup rather than the code-behind file.

A client-side developer shouldn't have to keep jumping to the code-behind file to add client-side code. That just doesn't make a lot of workflow sense. So here's a thought: componentize jQuery as a server-side control so that you can declare it on the page and then call it.

Controls/jQuery.ascx (complete):

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="jQuery.ascx.cs" Inherits="Controls_jQuery" %> 

(Nothing, basically.)

Controls/jQuery.ascx.cs (complete):

using System;
using System.Web.UI; 

public partial class Controls_jQuery : System.Web.UI.UserControl
{
    protected override void OnInit(EventArgs e)
    {
        AddJQuery();
    } 

    private bool _Enabled = true;
    [PersistenceMode(PersistenceMode.Attribute)]
    public bool Enabled
    {
        get { return _Enabled; }
        set { _Enabled = value; }
    } 

    void AddJQuery()
    {
        string minified = Minified ? ".min" : "";
        string url = ResolveClientUrl(JSDirUrl 
            + "jQuery-" + _Version
            + minified 
            + ".js");
        Page.ClientScript.RegisterClientScriptInclude(
            typeof(Controls_jQuery), "jQuery", url);
    } 

    private string _jsDir = null;
    public string JSDirUrl
    {
        get
        {
            if (_jsDir == null)
            {
                if (Application["JSDir"] != null)
                    _jsDir = (string)Application["JSDir"];
                else return "~/js/"; // default
            }
            return _jsDir;
        }
        set { _jsDir = value; }
    } 

    private string _Version = "1.2.6";
    [PersistenceMode(PersistenceMode.Attribute)]
    public string JQueryVersion
    {
        get { return _Version; }
        set { _Version = value; }
    } 

    private bool _Minified = false;
    [PersistenceMode(PersistenceMode.Attribute)]
    public bool Minified
    {
        get { return _Minified; }
        set { _Minified = value; }
    }
}


Now with this control created we can remove the Page_Load() code we talked about earlier, and just declare this control directly.

(Add to top of page, just below <%@ Page .. %>:)

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register src="~/Controls/jQuery.ascx" TagPrefix="local" TagName="jQuery" %> 

(Add add just below <form runat="server">:)

<form id="form1" runat="server">
<local:jQuery runat="server"
    Enabled="true"
    JQueryVersion="1.2.6"
    Minified="false"
    JSDirUrl="~/js/" /> 

Note that none of the attributes listed above in local:jQuery (except for runat="server") are necessary as they're defaulted.

On a side note, if you were using Visual Studio 2008 you could use the documentation features that enable you to add a reference to another script, using "///<reference path="js/jQuery-1.2.6.js" />, which is documented here:

There's something else I wanted to go over. In a previous discussion, I mentioned that I'd like to see multiple <form>'s on a page, each one being empowered in its own right with Javascript / AJAX functionality. I mentioned to use callbacks, not postbacks. In the absence of ASP.NET AJAX extensions, this makes <form runat="server"> far less relevant to the lifecycle of an AJAX-driven application.

To be clear,

  • Postbacks are the behavior of ASP.NET to perform a form post back to the same page from which the current view derived. It processes the view state information and updates the output with a new page view accordingly.
  • Callbacks are the behavior of an AJAX application to perform a GET or a POST to a callback URL. Ideally this should be an isolated URL that performs an action rather than requests a new view. The client side would then update the view itself, depending on the response from the action. The response can be plain text, HTML, JSON, XML, or anything else.

jQuery already has functionality that helps the web developer to perform AJAX callbacks. Consider, for example, jQuery's serialize() function, which I apparently forgot about this week when I needed it (shame on me). Once I remembered it I realized this weekend that I needed to go back and implement multiple <form>'s on what I've been working on to make this function work, just like I had been telling myself all along.

But as we know,

Caveat #4: You can only have one <form runat="server"> tag on a page.

And if you recall Caveat #2 above, that means that ASP.NET controls can only be put in one form on the page, period.

It's okay, though, we're not using ASP.NET controls for postbacks nor for viewstate. We will not even use view state anymore, not in the ASP.NET Web Forms sense of the term. Session state, though? .. Maybe, assuming there is only one web server or shared session services is implemented or the load balancer is properly configured to map the unique client to the same server on each request. Failing all of these, without view state you likely have a broken site, which means that you shouldn't abandon Web Forms based programming yet. But no one in their right mind would let all three of these fail so let's not worry about that.

So I submit this ..

Tip: You can have as many <form>'s on your page as you feel like, as long as they are not nested (you cannot nest <form>'s of any kind).

Caveat #5: You cannot have client-side <form>'s on your page if you are using Master pages, as Master pages impose a <form runat="server"> context for the entirety of the page.

With the power of jQuery to manipulate the DOM, this next tip becomes feasible:

Tip: Treat <form runat="server"> solely as a staging area, by wrapping it in <div style="display:none">..</div> and using jQuery to pull out what you need for each of your client-side <form>'s.

By "a staging area", what I mean by that is that the <form runat="server"> was necessary to include the client script controls for jQuery et al, but it will also be needed if we want to include any server-generated HTML that would be easier to generate there using .ascx controls than on the client or using old-school <% %> blocks.

Let's create an example scenario. Consider the following page:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MyMultiForm.aspx.cs" Inherits="MyMultiForm" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
        <div id="This_Goes_To_Action_A">
            <asp:RadioButton ID="ActionARadio" runat="server" 
                GroupName="Action" Text="Action A" /><br />
            Name: <asp:TextBox runat="server" ID="Name"></asp:TextBox><br />
            Email: <asp:TextBox runat="server" ID="Email"></asp:TextBox>
        </div>
        
        <div id="This_Goes_To_Action_B">
            <asp:RadioButton ID="ActionBRadio" runat="server" 
                GroupName="Action" Text="Action B" /><br />
            Foo: <asp:TextBox runat="server" ID="Foo"></asp:TextBox><br />
            Bar: <asp:TextBox runat="server" ID="Bar"></asp:TextBox>
        </div>
        
        <asp:Button runat="server" Text="Submit" UseSubmitBehavior="true" />
    
    </div>
    </form>
</body>
</html> 

And just to illustrate this simple scenario with a rendered output ..

Now in a postback scenario, this would be handled on the server side by determining which radio button is checked, and then taking the appropriate action (Action A or Action B) on the appropriate fields (Action A's fields or Action B's fields).

Changing this instead to client-side behavior, the whole thing is garbage and should be rewritten from scratch.

Tip: Never use server-side controls except for staging data load or unless you are depending on the ASP.NET Web Forms life cycle in some other way.

In fact, if you are 100% certain that you will never stage data on data-bound server controls, you can eliminate the <form runat="server"> altogether and go back to using <script> tags for client scripts. Doing that, however, you'll have to keep your scripts in the <body>, and for that matter you might even consider just renaming your file with a .html extension rather than a .aspx extension, but of course at that point you're not using ASP.NET anymore, so don't. ;)

I'm going to leave <form runat="server"> in place because .ascx controls, even without postbacks and view state, are just too handy and I'll illustrate this with a drop-down list later.

I can easily replace the above scenario with two old-fashioned HTML forms:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="MyMultiForm.aspx.cs" Inherits="MyMultiForm" %> 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <%--Nothing in the form runat="server"--%>
    </form>
    <div>
        
        <div id="This_Goes_To_Action_A">
            <input type="radio" name="cbAction" checked="checked" 
                id="cbActionA" value="A" />
            <label for="cbActionA">Action A</label><br />
            <form id="ActionA" name="ActionA" action="ActionA">
                Name: <input type="text" name="Name" /><br />
                Email: <input type="text" name="Email" />
            </form>
        </div>
        <div id="This_Goes_To_Action_B">
            <input type="radio" name="cbAction" checked="checked" 
                id="cbActionB" value="B" />
            <label for="cbActionB">Action B</label><br />
            <form id="ActionB" name="ActionB" action="ActionB">
                Foo: <input type="text" name="Foo" /><br />
                Bar: <input type="text" name="Bar" />
            </form>
        </div>
        
        <button onclick="if (document.getElementById('cbActionA').checked)
                            alert('ActionA would submit.'); //document.ActionA.submit();
                         else if (document.getElementById('cbActionB').checked)
                            alert('ActionB would submit.'); //document.ActionB.submit();">Submit</button>
    
    </div>
</body>
</html> 

In some ways this got a lot cleaner, but in other ways it got a lot more complicated. First of all, I had to move the radio buttons outside of any forms as radio buttons only worked within a single form context. For that matter, there's a design problem here; it's better to put a submit button on each form than to use Javascript to determine which form to post based on a radio button--that way one doesn't have to manually check to see which radio button is checked, in fact one could drop the radio buttons altogether, and could have from the beginning even with ASP.NET postbacks; both scenarios can facilitate two submit buttons, one for each form. But I put the radio buttons in to illustrate one small example of where things inevitably get complicated on a complex page with multiple forms and multiple callback behaviors. In an AJAX-driven site, you should never (or rarely) use <input type="submit"> buttons, even if you have an onsubmit handler on your form. Instead, use plain <button>'s with onclick handlers, and control submission behavior with asynchronous XmlHttpRequest uses, and if you must leave the page for another, either use user-clickable hyperlinks (ideally to a RESTful HTML view URL) or use window.location. The window.location.reload() refreshes the page, and window.location.href=".." redirects the page. Refresh is useful if you really do want to stay on the same page but refresh your data. With no form postback, refreshing the page again or clicking the Back button and Forward button will not result in a browser dialogue box asking you if you want to resubmit the form data, which is NEVER an appropriate dialogue in an AJAX-driven site.

Another issue is that we are not taking advantage of jQuery at all and are using document.getElementById() instead.

Before we continue:

Tip: If at this point in your career path you feel more confident in ASP.NET Web Forms than in "advanced" HTML and DOM scripting, drop what you're doing and go become a master and guru of that area of web technology now.

ASP.NET Web Forms is harder to learn than HTML and DOM scripting, but I've found that ASP.NET and advanced HTML DOM can be, and often are, learned in isolation, so many competent ASP.NET developers know very little about "advanced" HTML and DOM scripting outside of the ASP.NET Web Forms methodology. But if you're trying to learn how to switch from postback-based coding to callback-based coding, we literally cannot continue until you have mastered HTML and DOM scripting. Here are some great books to read:

While you're at it, you should also grab:

Since this is also about jQuery, you need to have at least a strong working knowledge of jQuery before we continue.

The key problem with the above code, though, assuming that the commented out bit in the button's onclick event handler was used, is that the forms are still configured to redirect the entire page to post to the server, not AJAXy callback-style. What do we do?

First, bring back jQuery. We'll use the control we made earlier. (If you're using master pages, put this on the master page and forget about it so it's always there.)

..
<%@ Register src="~/Controls/jQuery.ascx" TagPrefix="local" TagName="jQuery" %>
..
<form id="form1" runat="server">
    <local:jQuery runat="server" />
</form> 

Next, to clean-up, replace all document.getElementById(..) with $("#..")[0]. This is jQuery's easier to read and write way of getting a DOM element by an ID. I know it looks odd at first but once you know jQuery and are used to it, $("#..")[0] is a very natural-looking syntax.

<button onclick="if ($('#cbActionA')[0].checked)
                    alert('ActionA would submit.'); //$('#ActionA')[0].submit();
                 else if ($('#cbActionB')[0].checked)
                    alert('ActionB would submit.'); //$('#ActionB')[0].submit();">Submit</button> 

Now we need to take a look at that submit() code and replace it.

One of the main reasons why we broke off <form runat="server"> and created two isolated forms is so that we can invoke jQuery's serialize() function to essentially create a string variable that would consist of pretty much the exact same serialization that would have been POSTed to the server if the form's default behavior executed, and the serialize() function requires the use of a dedicated form to process the conversion. The string resulting from serialize() is essentially the same as what's normally in an HTTP request body in a POST method.

Note: jQuery documentation mentions, "In order to work properly, serialize() requires that form fields have a name attribute. Having only an id will not work." But you must also give your <form> an id attribute if you intend to use $("#formid").

So now instead of invoking the appropriate form's submit() method, we should invoke a custom function that takes the form, serializes it, and POSTs it to the server, asynchronously. That was our objective in the first place, right?

So we'll add the custom function.

    <script language="javascript" type="text/javascript">
        function postFormAsync(form, fn, returnType) {
            var formFields = $(form).serialize();
            
            // set up a default POST completion routine
            if (!fn) fn = function(response) {
                alert(response);
            }; 

            $.post(
                $(form).attr("action"), // action attribute (url)
                formFields,             // data
                fn,                     // callback
                returnType              // optional
                );
        }
    </script> 

Note the fn argument, which is optional (defaults to alert the response) and which I'll not use at this time. It's the callback function, basically what to do once POST completes. In a real world scenario, you'd probably want to pass a function that redirects the user with window.location.href or else otherwise updates the contents of the page using DOM scripting. Note also the returnType; refer to jQuery's documentation for that, it's pretty straightforward. 

And finally we'll change the button code to invoke it accordingly.

<button onclick="if ($('#cbActionA')[0].checked)
                    postFormAsync($('#ActionA')[0]);
                 else if ($('#cbActionB')[0].checked)
                    postFormAsync($('#ActionB')[0]);">Submit</button> 

This works but it assumes that you have a callback URL handler waiting for you on the action="" argument of the form. For my own tests of this sample, I had to change the action="" attribute on my <form> fields to test to "ActionA.aspx" and "ActionB.aspx", these being new .aspx files in which I simply had "Action A!!" and "Action B!!" as the markup. While my .aspx files also needed to check for the form fields, the script otherwise worked fine and proved the point.

Alright, at this point some folks might still be squirming with irritation and confusion about the <form runat="server">. So now that we have jQuery performing AJAX callbacks for us, I still have yet to prove out any utility of having a <form runat="server"> in the first place, and what "staging" means in the context of the tip I stated earlier. Well, the automated insertion of jQuery and our page script at appropriate points within the page is in fact one example of "staging" that I'm referring to. But another kind of staging is data binding for initial viewing.

Let's consider the scenario where both of two forms on a single page have a long list of data-driven values.

Page:

...
<asp:DropDownList runat="server" ID="DataList1" />
<asp:DropDownList runat="server" ID="DataList2" />
... 

Code-behind / server script:

protected void Page_Load(object sender, EventArgs e)
{
    DataList1.DataSource = GetSomeData();
    DataList1.DataBind();

    DataList2.DataSource = GetSomeOtherData();
    DataList2.DataBind();
} 

Now let's assume that DataList1 will be used by the form Action A, and DataList2 will be used by the form Action B. Each will be "used by" their respective forms only in the sense that their <option> tags will be populated by the server at runtime.

Since you can only put these ASP.NET controls in a <form runat="server"> form, and you can only have one <form runat="server"> on the page, you cannot therefore simply put an <asp:DropDownList ... /> control directly into each of your forms. You'll have to come up with another way.

One-way data binding technique #1: Move the element, or contain the element and move the element's container.

You could just move the element straight over from the <form runat="server"> form to your preferred form as soon as the page loads. To do this (cleanly), you'll have to create a container <div> or <span> tag that you can predict an ID and wrap the ASP.NET control in it.

Basic example:

$("#myFormPlaceholder").append($("#myControlContainer")); 

Detailed example:

...
<div style="display: none" id="ServerForm">
    <%-- Server form is only used for staging, as shown--%>
    <form id="form1" runat="server">
        <local:jQuery runat="server" />
        <span id="DataList1_Container">
            <asp:DropDownList runat="server" ID="DataList1">
            </asp:DropDownList>
        </span>
        <span id="DataList2_Container">
            <asp:DropDownList runat="server" ID="DataList2">
            </asp:DropDownList>
        </span>
    </form>
</div>
...
<script language="javascript" type="text/javascript">
...
$().ready(function() {
    $("#DataList1_PlaceHolder").append($("#DataList1_Container"));
    $("#DataList2_PlaceHolder").append($("#DataList2_Container"));
});
</script>
<div>
    <div id="This_Goes_To_Action_A">
        ...
        <form id="ActionA" name="ActionA" action="callback/ActionA.aspx">
        ...
        DropDown1: <span id="DataList1_PlaceHolder"></span>
        </form>
    </div>
    <div id="This_Goes_To_Action_B">
        ...
        <form id="ActionB" name="ActionB" action="callback/ActionB.aspx">
        ...
        DropDown2: <span id="DataList2_PlaceHolder"></span>
        </form>
    </div>
</div> 

An alternative to referencing an ASP.NET control in its DOM context by using a container element is to register its ClientID property to script as a variable and move the server control directly. If you're using simple client <script> tags without registering them, you can use <%= control.ClientID %> syntax.

Page: 

<script language="javascript" type="text/javascript">
...
$().ready(function() {
    var DataList1 = $("#<%= DataList1.ClientID %>")[0];
    var DataList2 = $("#<%= DataList2.ClientID %>")[0];
    $("#DataList1_PlaceHolder").append($(DataList1));
    $("#DataList2_PlaceHolder").append($(DataList2));
});
</script> 

If you are using a literal and Page.ClientScript.RegisterClientScriptBlock, you won't be able to use <%= control.ClientID%> syntax, but you can instead use a pseudo-tag syntax like "{control.ClientID}", and then when calling RegisterClientScriptBlock perform a Replace() against that pseudo-tag.

Page: 

<asp:Literal runat="server" Visible="false" ID="ScriptLiteral">
<script language="javascript" type="text/javascript">
    ...
    $().ready(function() {
        var DataList1 = $("#{DataList1.ClientID}")[0];
        var DataList2 = $("#{DataList2.ClientID}")[0];
        $("#DataList1_PlaceHolder").append($(DataList1));
        $("#DataList2_PlaceHolder").append($(DataList2));
    });
</script>
</asp:Literal> 

Code-behind / server script:

protected void Page_Load(object sender, EventArgs e)
{
    ...
    Page.ClientScript.RegisterClientScriptBlock(
        typeof(MyMultiForm), "pageScript", 
        ScriptLiteral.Text
        .Replace("{DataList1.ClientID}", DataList1.ClientID)
         .Replace("{DataList2.ClientID}", DataList2.ClientID));
} 

For the sake of brevity (and as a tentative decision for usage on my own part), for the rest of this discussion I will use the second of the three, using old-fashioned <script> tags and <%= control.ControlID %> syntax to identify server control DOM elements, and then move the element directly rather than contain it.

One-way data binding technique #2: Clone the element and/or copy its contents.

You can copy the contents of the server control's data output to the place on the page where you're actively using the data. This can be useful if both of two forms, for example, each has a field that use the same data.

Page:

<script language="javascript" type="text/javascript">
function copyOptions(src, dest) {
    for (var o = 0; o < src.options.length; o++) {
        var opt = document.createElement("option");
        opt.value = src.options[o].value;
        opt.text = src.options[o].text;
        try {
            dest.add(opt, null); // standards compliant; doesn't work in IE
        }
        catch (ex) {
            dest.add(opt); // IE only
        }
    }
} 

$().ready(function() {
    var DataList1 = $("#<%= DataList1.ClientID %>")[0];
    copyOptions(DataList1, $("#ActionA_List")[0]);
    copyOptions(DataList1, $("#ActionB_List")[0]); // both use same DataList1
});
</script> 

... 

<form id="ActionA" ...>
    ...
    DropDown1: <select id="ActionA_List"></select>
</form>
<form id="ActionB" ...>
    ...
    DropDown1: <select id="ActionB_List"></select>
</form>


This introduces a sort of dynamic data binding technique whereby the importance of the form of the data being outputted by the server controls is actually getting blurry. What if, for example, the server form stopped outputting HTML and instead began outputting JSON? The revised syntax would not be much different from above, but the source data would not come from DOM elements but from data structures. That would be much more manageable from the persective of isolation of concerns and testability.

But before I get into that, what if things got even more tightly coupled instead? 

One-way data binding technique #3: Mangle the markup directly.

As others have noted, inline server markup used to be pooh pooh'd when ASP.NET came out and introduced the code-behind model. But when migrating away from Web Forms, going back to the old fashioned inline server tags and logic is like a breath of fresh air. Literally, it can allow much to be done with little effort.

Here you can see how quickly and easily one can populate a drop-down list using no code-behind conventions and using the templating engine that ASP.NET already inherently offers.

List<string>:

<select>
    <% MyList.ForEach(delegate (string s) {
            %><option><%=HttpUtility.HtmlEncode(s)%></option><%
        }); %>
</select>  

Dictionary<string, string>:

<select>
    <%  foreach (
           System.Collections.Generic.KeyValuePair<string, string> item
           in MyDictionary)
        {
            %><option value="<%= HttpUtility.HtmlEncode(item.Value) 
            %>"><%=HttpUtility.HtmlEncode(item.Key) %></option><%
        } %>
</select> 

For simple conversions of lists and dictionaries to HTML, this looks quite lightweight. Even mocking this up I am impressed. Unfortunately, in the real world data binding often tends to get more complex.

One-way data binding technique #4: Bind to raw text, JSON/Javascript, or embedded XML.

In technique #2 above (clone the element and/or copy its contents), data was bound from other HTML elements. To get the original HTML elements, the HTML had to be generated by the server. Technically, data-binding to HTML is a form of serialization. But one could also serialize the data model as data and then use script to build the destination HTML and/or DOM elements from the script data rather than from original HTML/DOM.

You could output data as raw text, such as name/value pair collections such as those formatted in a query string. Working with text requires manual parsing. It can be fine for really simple comma-delimited lists (see Javascript's String.split()), but as soon as you introduce the slightest more complex data structures such as trees you end up needing to look at alternatives.

The traditional data structure to work with anything on the Internet is XML. For good reason, too; XML is extremely versatile as a data description language. Unfortunately, XML in a browser client is extremely difficult to code for because each browser has its own implementation of XML reading/manipulating APIs, much more so than the HTML and CSS compliance differences between browsers.

If you use JSON you're working with Javascript literals. If you have a JSON library installed (I like the JsonFx Serializer because it works with ASP.NET 2.0 / C# 2.0) you can take any object that would normally be serialized and JSON-serialize it as a string on the fly. Once this string is injected to the page's Javascript, you can access the data as live Javascript objects rather than as parsed XML trees or split string arrays.

Working directly with data structures rather than generated HTML is much more flexible when you're working with a solution that is already Javascript-oriented rather than HTML-oriented. If most of the view logic is driven by Javascript, indeed it is often very nice for the script runtime to be as data-aware as possible, which is why I prefer JSON because the data structures are in Javascript's "native tongue", no translation necessary.

Once you've crossed that line, though, of moving your data away from HTML generation and into script, then a whole new door is opened where the client can receive pre-generated HTML as rendering templates only, and then make an isolated effort to take the data and then use the rendering templates to make the data available to the user. This, as opposed doing the same on the server, inevitably makes the client experience much more fluid. But at this point you can start delving into real AJAX...

One-way data binding technique #5: Scrap server controls altogether and use AJAX callbacks only.

Consider the scenario of a page that starts out as a blank canvas. It has a number of rendering templates already loaded, but there is absolutely no data on the intial page that is sent back. As soon as the page is loaded, however (think jQuery's "$(document).ready(function() { ... });) you could then have the page load the data it needs to function. This data could derive from a web service URL that is isolated from the page entirely--the same app, that is, but a different relative URL.

In an ASP.NET 2.0 implementation, this can be handled easily with jQuery, .ashx files, and something like the JsonFx JSON Serializer.

From an AJAX purist perspective, AJAX-driven data binding is by far the cleanest approach to client orientation. While it does result in the most "chatty" HTTP interaction, it can also result in the most fluid user experience and the most manageable web development paradigm, because now you've literally isolated the data tier in its entirety.

Working with data in script and synchronizing using AJAX and nothing but straight Javascript standards, the door flies wide open to easily convert one-way data binding to two-way data binding. Posting back to the server is a snap; all you need to do is update your script data objects with the HTML DOM selections and then push that data object out back over the wire, in the exact same way the data was retrieved but in reverse.

In most ways, client-side UI logic and AJAX is the panacea to versatile web UIs. The problem is that there is little consistent guidance in the industry especially for the .NET crowd. There are a lot of client-oriented architectures, few of them suited for the ASP.NET environment, and the ones that are or that are neutral are lacking in server-side orientations or else are not properly endorsed by the major players. This should not be the case, but it is. And as a result it makes compromises like ASP.NET AJAX, RoR+prototype+scriptaculous, GWT, Laszlo, and other combined AJAX client/server frameworks all look like feasible considerations, but personally I think they all stink of excess, learning curve, and code and/or runtime bloat in solution implementations.

kick it on DotNetKicks.com

Currently rated 3.1 by 8 people

  • Currently 3.125/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Web Development

Lockup by AJAX is unacceptable

by Jon Davis 13. April 2008 02:59

Brower Vendors: Please Add This To Your Unit Tests

http://www.jondavis.net/codeprojects/synctest/  

Currently rated 1.0 by 2 people

  • Currently 1/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Hello, Jaxer

by Jon Davis 26. January 2008 17:12

I think this new Jaxer thing just might become the next big new thing for open source web server architectures. It's AJAX all the way, even on the server. DOM on the server!

http://www.aptana.com/jaxer

The more I think about it, the more I can see it competing directly with the well-established open source web server technologies. Javascript is a viable language, and putting Mozilla on the server .. who'da thunk you could build complete client-server apps using only the one language? (Well I guess ASP Classic supported JScript on the server but .. *cough* .. besides, it didn't have HTML DOM running on the server.)

There is huge power in this when you can share APIs across both client and server without any manual proxy or wrapper coding, as this demonstrates:

http://ejohn.org/blog/server-side-javascript-with-jaxer/

I've tried hosting a COM instance of IE on the server in ASP.NET for similar uses but, needless to say, it's not a recommended strategy as it is not scalable on a high-traffic site (uses window handles, takes up a lot of RAM). It's like putting a motor home on a NASCAR race track. Then again, I don't know yet what Mozilla on the server will be like; I'm curious to find out.

I'd love to see something like this ported to ASP.NET.

kick it on DotNetKicks.com

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Sprinkle Javascript library

by Jon Davis 13. September 2007 16:06
<script src="sprinkle.js"></script>
<div src="info.html"></div>

CSI (Client-Side Includes), when SSI (Server-Side Includes) is not available. You can also call it "sprinkle", as that's the name I gave the Javascript library.

http://www.sprinklejs.com/ 

kick it on DotNetKicks.com

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: , , ,

Computers and Internet | Software Development | Web Development

Paying Attention To DinnerNow.net - Microsoft Killer App Best Practices Tutorial for Everything .NET 3.0

by Jon Davis 31. July 2007 18:01

I've been noticing my RSS feeds to Channel9 plugging some "DinnerNow" thing that sounded like some third party company who was just sharing some architectural trick up their sleeve, so I kinda shrugged it off. After all, how many software tricks are up people's sleeves at sourceforge.net or elsewhere?

But being a big new fan of ARCast.TV podcasts, I came across the Architecture Overview and Workflow piece which I didn't realize until I started listening that it was Part 1 of the DinnerNow bits. Listening to it now, I am realizing that this is something rather special.

DinnerNow, which I've only just downloaded and haven't actually seen yet but I'm hearing these guys talk about it in the podcast, is apparently NOT a Starter Kit style mini-solution like IBuySpy was (which was a dorky little shopping cart web site starter kit that Microsoft hacked together in ASP.NET back in the v1.1 days as a proof of concept).

Rather, DinnerNow is a full-blown software sample of an online restaurant food ordering web service application, one that I had wanted to build commercially for years (along with a hundred other ideas I had), that is top-to-bottom, soup-to-nuts, thorough and complete implementation of the entire solution from the servers, the restaurants, the buyer, demonstrating all of the awesome components of the latest long-released .NET Framework technologies, including:

  • Windows Communication Foundation (WCF) on the restaurant and web server
  • Windows Workflow Foundation (WF) on the restaurant and web server
  • Windows Presentation Foundation (WPF) for the restaurant (kiosk @ kitchen)
  • ASP.NET AJAX 1.0 and JSON-based synchronization
  • CardSpace for user security
  • PowerShell for things like administrative querying
  • Microsoft Management Console (MMC) for administrative querying, graphically
  • Windows Mobile / .NET Compact Framework
  • Windows Management Instrumentation (WMI)

Ron Jacobs (the ARCast host) made a good introduction for this with a statement I agree with, something along the lines of, "As architects, what we often try to do is look at someone else's software that was written successfully," and learn and discover new and/or best practices for software from it. In fact, that's probably the most important task in the learning process. If learning how to learn is an essential thing to learn in software architecture, then learn this essential point, that both understanding new technology and discovering best practices is learned by seeing it for yourself.

Update: Ugh. With many technologies being features comes many prerequisites. I had all of the above, but the setup requires everything to be exact. In other words, start with a fresh Virtual Machine with Vista 32-bit, and add the prerequisites. XP? Screwed. Win2003? Screwed. 64-bit? Screwed. Orcas Beta 2 installed? Screwed. And so on the screwing goes. An optional Virtual PC download from MSDN premium downloads with everything installed would have been nice. And I'm not hearing anything from the CodePlex forum / Issue Tracker, not a peep from its "maintainers". I think this project was abandoned. What bad timing, to put it on ARCast.tv now...

Another update: Microsoft did post a refresh build for Visual Studio 2008 Beta 2. I noticed it on CodePlex and they also started replying (for a change) to the multiple discussion threads and Issue Tracker posts on CodePlex. Among those replies were comments about 64-bit support--not supported, by design [which I knew] but they said with workarounds [which I posted multiple reports about] a 64-bit OS target build should be possible. I saw all this a few days ago; should have posted this follow-up update earlier, seems a Microsoft employee posted a comment here first. (Sorry.)

With passion comes passionate resentment when things go wrong. It's a fair give and take; on the other hand, whatever.

AJAX Based Bandwidth Test

by Jon Davis 12. May 2007 23:39

At work, I was tasked to add auto-playing Flash video feeds on the home page of one of our web sites, and I decided that before I do anything I should implement a quick AJAX-driven bandwidth check so that dial-up users aren't fed a heavy video feed without prior user consent. I threw the test together in a matter of three or four hours, and sent the engineering team an e-mail letting them know that this is new working functionality we can use any time.

The funny thing about the timing is that on the same day another co-worker working on another web site needed the same functionality immediately. To be honest, I overheard talk about this, and so I hoped my contribution would be useful for that project as well. I have no doubt my co-worker could have accomplished the same task, but being able to drop off sharable solutions like this is part of what makes a team environment so valuable.

The Bandwidth Test: What It Is and How it Works

The functionality I'm referring to here is a bandwidth test using AJAX.

Actually, truth be told, it's not all-out AJAX as there is no XML in use here, but there is an asynchronous Javascript-driven HTTP request for a string of bytes, and a measurement of the amount of time that took. Actually, there are two asychronous HTTP requests. The first request is for a single byte of data--this measures latency and HTTP overhead. The second request is for a larger set of bytes of data, i.e. 128KB, which is configurable as a control property. The amount of time taken for the first request (1B) is subtracted from the amount of time taken from the second request (128KB), and this measures Pipe Speed. The requests are both sent to a "callback" URL that you can configure, but by default it's an .aspx file stored as "~/controls_callback/GenBytes.aspx?len=number_of_bytes".

(Please excuse the horribly crappy illustration -- I had to use Paint.NET as I don't have Adobe Illustrator installed on my laptop, and then on top of that Live Writer and/or Live Spaces does weird things to the image. Click to enlarge.)

The test is not intended to be exact, but to be an approximation to determine whether your connection is moderately fast or dog slow. But the accuracy of the test gets better the slower the connection is, so a slow connection--which comprises the more vulnerable web audience--will be less likely to have incorrect test results than a higher speed user.

The test is implemented on a site by adding a small, simple ASP.NET tag:

(Add to top of .aspx file:)
<%@ Register Src="~/controls/SpeedTest.ascx" TagPrefix="ajax" TagName="SpeedTest" %>

..

<ajax:SpeedTest runat="server" ExecuteTest="true" UseCookie="false" ClientCompleteFunction="myJavascriptFunction" />

The control automatically registers a block of Javascript to the containing Page. ExecuteTest="true" is the default setting--if you do not include this attribute, the test will execute, but setting it to False will result in no Javascript being added to the page at all. Why would the tag still be useful? Setting UseCookie="true" will add the test results to the cookie ("astLatency" and "astPipeSpeed") and will expose these values as properties. (If the cookies are already set and UseCookies is true, the test won't execute anyway.) You might choose to execute the test on one page and then redirect (ExecuteTest="true"), and only read the resulting cookie on another page (ExecuteTest="false"). But if the first hit can make a safe, untested assumption -- the test is asynchronous, after all -- then you might as well just put the control on your content page, turn on the test, and set ExecuteTest="false" on the next hit from the same visitor, when the cookie has been set.

Properties:

ExecuteTest (gets/sets bool) true = Adds test script which auto-executes.
false = Doesn't do anything client-side.
UseCookies (gets/sets bool) true = Disables test execution if the Request already has the assigned cookie.
false = Doesn't evaluate cookies.
CookieExpires (gets/sets TimeSpan) TimeSpan in granularity of days to retain the cookie if UseCookies == true.
CookieLatency (gets/sets int?) Nullable<Int32> of the cookie value astLatency.
CookiePipeSpeed (gets/sets int?) Nullable<Int32> of the cookie value astPipeSpeed.
ClientCompleteFunction (gets/sets string) The name of your own Javascript function that executes when the test is complete. The function should accept two parameters: latency and pipeSpeed.
CallBackUrl (gets/sets string) The URL of the support .aspx file that dynamically generates bytes of a requested length.
ByteLength (gets/sets int) The number of bytes to be requested from the CallBackUrl.

You can download the whole thing and examine the Javascript in the control file (SpeedTest.ascx) here...

Download: http://www.jondavis.net/CodeProjects/SpeedCheck/SpeedCheck.zip

Demo (from a slow cable connection as host): http://www.jondavis.net/CodeProjects/SpeedCheck/SpeedTest.aspx

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags: ,

Web Development


 

Powered by BlogEngine.NET 1.4.5.0
Theme by Mads Kristensen

About the author

Jon Davis (aka "stimpy77") has been a programmer, developer, and consultant for web and Windows software solutions professionally since 1997, with experience ranging from OS and hardware support to DHTML programming to IIS/ASP web apps to Java network programming to Visual Basic applications to C# desktop apps.
 
Software in all forms is also his sole hobby, whether playing PC games or tinkering with programming them. "I was playing Defender on the Commodore 64," he reminisces, "when I decided at the age of 12 or so that I want to be a computer programmer when I grow up."

Jon was previously employed as a senior .NET developer at a very well-known Internet services company whom you're more likely than not to have directly done business with. However, this blog and all of jondavis.net have no affiliation with, and are not representative of, his former employer in any way.

Contact Me 


Tag cloud

Calendar

<<  October 2018  >>
MoTuWeThFrSaSu
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

View posts in large calendar