In-Depth
HTML5 for ASP.NET Developers
The technologies bundled as HTML5 finally support what developers have been trying to get HTML to do for decades.
Microsoft's announcement that HTML5 and JavaScript would be first-class tools for creating 'Windows 8' applications created consternation among some .NET developers. Would their investments in XAML and Silverlight be wasted? Would they be forced to work with HTML/JavaScript instead of compiled, data-typed languages? Besides, hadn't we heard all this before with Active Desktop for Internet Explorer 4, which enabled HTML on the Windows desktop? And isn't it dead?
Not dead; maybe just reborn. At the Windows 8 unveiling in May, Microsoft Corporate Vice President Julie Larson-Green, who oversees the Windows client, said the announcement meant that developers "can write new applications for Windows using the things that they're doing already on the Internet." That's the point: HTML and JavaScript are tools for Internet development. And, for ASP.NET developers, the technologies bundled under the HTML5 banner are very good news for client-side development. If, as Larson-Green suggests, those technologies can be repurposed to be used in desktop and smartphone development ... well, that's very nice -- but it's secondary.
What's new in HTML5-related technologies is direct support for features that developers take for granted in other environments -- there's more that can be done declaratively without having to drag in JavaScript ("just set an attribute"), there's more support for data structures and there's more support for typical line-of-business (LOB) activities. And that's ignoring what you can do with CSS3 (a topic for a different article).
Getting Set Up
All you have to do to enable a page for processing as HTML5 is add this doctype declaration at the top of your site's Master Page:
<!DOCTYPE HTML>
But, because HTML5 is a collection of technologies rather than a monolithic block, different browsers will have different parts of HTML5 implemented. To use HTML5 but fall back gracefully when a browser doesn't support what you need, add Modernizr (an open source JavaScript library for checking for HTML5 and CSS3 support) to your toolset.
Declarative Forms
HTML has never provided the range of tools that developers expect from standard Windows development tools. HTML5 helps close that gap. For instance, in HTML5, you can have a combobox -- just declare a textbox using the input element and then tie it to a datalist element using the new input tag's new list attribute:
<input list="documentType" id="cbox" type="text">
<datalist id="documentType">
<option value="Review">
<option value="How To">
</datalist>
Many features implemented in Windows Forms with a property setting require the intervention of JavaScript in HTML. Tools often disguise that: "setting a property" in ASP.NET, for instance, often generates both HTML and JavaScript (the SetFocus and AutoPostback properties being the most obvious examples). HTML5 doesn't eliminate the need for JavaScript, but it does reduce it, simplifying life both for Web developers and tool developers by replacing what used to require code with declarative settings.
The workhorse input element demonstrates that movement best. To begin with, in HTML5 the input element has far more states. In addition to the already existing states (text, password), HTML5 adds number, datetime, email, url and a half-dozen more. The new required attribute prevents a page from being submitted until the element is provided a value.
All by themselves, these two changes reduce the need for ASP.NET developers to use Validator controls, while providing a new way for Validator controls to implement their functionality without requiring JavaScript. The new autofocus attribute causes the focus to switch to the element when the page is first displayed, eliminating the need for the JavaScript behind the SetFocus property. And, of course, developers appreciate the additional datatyping control on the input element.
But wait, there's more. If you're working with numeric fields, then the step, max and min attributes provide further declarative control over the user's data entry; on text fields, the pattern attribute allows you to apply a regular expression to the user's input. If you want to display a prompt string in a text box, you can add the new placeholder attribute and set it to the string to be displayed.
A typical input tag that exploits these changes might look like the following code. This sample defines a numeric-only textbox that must have a value, and will only accept single digits between 1 and 9 (and only the integers in between):
<input type="number" id="Number1" placeholder="1"
required="required" step="1" max="9" min="1"
pattern="[0-9]"/>
Many of these new attributes are not tied to specific elements but are considered global attributes. The new contenteditable attribute, for instance, can be applied to any element to prevent the user from changing the content of that element.
Structured Pages
In the past, developers have used div and span tags as structural elements to define sections of a page that were to be styled or managed as a group. HTML5 includes new tags that acknowledge the typical grouping that developers use in pages: That pages usually have headers, footers and navigation sectors in addition to their primary content. The section tag allows you to divide your page into sections, containing articles (the page's primary content) and asides (content that isn't a critical part of the page).
Within a section element, the traditional header tags (h1, h2 and so on) now define the structure of the content rather than the text's format; the new hgroup element represents the introduction material for a section. A figure element marks a section of material that "belongs together" (for instance, a video or image element with its accompanying figurecaption element). These changes mean that you can stop using CSS classes to flag your document's structures and start adding styles for the dedicated elements.
Developers have been creating menus in Web pages using a variety of hacks, but HTML5 adds explicit support for menus. The menu tag specifies a menu with nested command elements that display text tied to JavaScript functions: clicking on the command element's text runs the JavaScript function specified in the element's onclick event. You can also use a command element outside of a menu element as a target for a hotkey (the command element's text won't be displayed) to have it run code when the user presses the correct key combination. Menus can also be used as context menus: just assign the menu an id and use that id in the contextmenu attribute of some other element.
Most ASP.NET developers will probably continue using the ASP.NET Menu control, but that control can now implement its functionality using the new menu and command elements. This has real benefits; for instance, tying menus to the browser should ensure some consistency in menu implementations across sites, at least within the same browser.
Putting it all together, a typical Master Page using the new structure elements and an HTML5 menu might look like Listing 1. In addition to defining a menu within the nav element, this example makes the menu available as a context menu from the footer at the bottom of the form.
The HTML button elements acquire new attributes (formaction, formmethod) that allow the button to control how a form is to be submitted. These new elements transfer control of form processing from the form's definition to the button's definition. The validation invoked by the input element's new attributes can be suppressed by adding the novalidate attribute to a button (useful for buttons that delete data, for instance).
The fieldset element, introduced in HTML 4.0.1, groups arbitrary collections of elements in a form. HTML5 adds three new attributes to the fieldset: disabled, form (discussed later) and name. The disabled attribute allows you to disable all the elements within a form with a single setting in your code.
Defining Data Structures
While these additions make generating forms more declarative, developers creating LOB applications are most interested in managing data. That's where Microdata comes in, by providing a way to organize data within a page into items with properties.
The itemscope attribute, which defines a data item, is another attribute than can be added to any HTML5 element. The itemtype attribute, used with the itemscope, lets you specify a name for your data item. Within the scope of the element to which the itemscope is added, the itemprop attribute defines the properties on the item. The element to which the itemprop attribute is added provides the value for the property. An optional itemid attribute allows you to assign a unique identifier for an item.
This example defines an item called http://phvis.com/customer, with properties called name and email, and sets those properties to "Peter Vogel" and "[email protected]." I've also used the itemid attribute to assign an id to the item:
<div itemscope itemtype="http://phvis.com/customer"
itemid="http://phvis.com/customer/A123">
Author: <div itemprop="name">Peter Vogel</div>
Contact: <div itemprop="email">[email protected]</div>
</div>
The properties that make up an item can be further separated from the structure of the page by using the itemref attribute with the itemscope attribute. The itemref allows you to specify the ids of the elements to be incorporated into the item without embedding your item definition in the page's content.
These tags define the same item as the previous set, but the div element that defines the item is no longer obliged to enclose the elements that specify the item's properties:
<div itemscope itemref="AName AEmail"
itemid="http://phvis.com/customer/A123"
itemtype="http://phvis.com/customer"/>
Author: <div id="AName"
itemprop="name">Peter Vogel</div>
Contact: <div id="AEmail"
itemprop="email">[email protected]</div>
The itemtype is the interesting component in Microdata because it supports two kinds of names: ad hoc names that are used only within the page, and global names that treat the itemtype as a kind of namespace (much like the itemtypes I've used here). Assuming the growth of industry-standard itemtypes, developers will be able to access data in the client without having to decode a page's structure (and create the potential for reusable client-side libraries for processing specific kinds of data).
Accessing data in the client is done through extensions to the JavaScript API for working with the Document Object Model (DOM). The new getItems function on the document object allows you to retrieve all the items with a particular itemtype (presumably, a jQuery extension that supports searching on itemtypes will appear soon). Once an item has been retrieved, property values for each item can be retrieved by name using the item's properties collection.
This example finds all the customer item types and retrieves the values of their name and email properties:
var itms = document.getItems("http://phvis.com/customer");
for (itm in itms) {
alert(itm.properties["name"].content);
alert(itm.properties["email"].content);
}
Flagging Data
Often, the data displayed on the page may have additional data associated with it. That additional data may include extra information or provide a way of organizing the displayed data (another role for which developers have used CSS classes). HTML5 provides the data-* attributes as a simple way of associating additional information with displayed data. You can add attributes prefixed with "data-" to any element and then assign values to those attributes, freeing up the class attribute to do what it was intended to do: control presentation.
This example creates two new data-* attributes, one to hold publisher information and another to hold an internal code:
<input type="date" id="PubDate"
data-publisher="1105media"
data-findit="A123"/>
In JavaScript, data-* attributes can be retrieved from an element using the dataset property. This example retrieves the publisher value:
var elm = document.getElementById("PubDate");
var pub = elm.dataset.publisher;
The word "can" is probably too strong: as of this writing, no browser supports the dataset property. However, you can still access the data-* attributes with the old-fashioned setAttribute and getAttribute methods. The data-* attributes will be even more valuable with extensions to jQuery that will select elements based either on the name of the data-* attribute or on the values assigned to a specific data-* attribute.
Communicating Across Sites and Processes
In a service-oriented architecture (SOA) world, the paradigm for Web applications changes: While pages are delivered to the browser using HTTP requests, each page then engages in a series of conversations with services that retrieve and update data. There's a catch, however: JavaScript code in the browser is prohibited from accessing services on any site except the site that the page came from.
HTML5 Web Messaging provides a mechanism for bypassing that restriction. The site you want to access must, however, create a "gateway" page that can be used by other sites.
The first step is to define an iframe in your page to hold a gateway page on the site you want to access. Your second step is to open the gateway page from the remote site using the window object's open method, targeting the iframe. This example defines a tiny iframe on the page (good for debugging), then opens a gateway page from another site in the iframe:
<iframe name="remoteSite" height="10" width="10"></iframe>
<script type="text/ecmascript">
var siteWindow = window.open(
"http://localhost:49161/about.aspx", "remoteSite");
Once the window is opened, you can send a message to it using the window's postMessage method, passing the data that makes up your request and the domain of the gateway page:
window.postMessage("A123", "http://remotesite.com");
At this point, you require the cooperation of the gateway page: It must be set up to accept messages. To accept messages, the gateway page on the remote site can use the addEventListener method to accept events of type "message," and pass the incoming object to some function for processing.
The function doing the processing must accept a single parameter, which has three properties: data (the message sent from your original page), origin (the domain that the request came from) and source (which allows the function to communicate with the requesting page). The gateway page has the option of checking the domain that the request is coming from before deciding to honor the request.
This example adds an event listener and ties it to a function named retrieveData. The function checks to see if the request is coming from an acceptable domain (you can use "*" to accept any site, but you're opening a very big door for malicious people to walk through). If the request is from an acceptable domain, the function uses the data sent from the local site to make a service request to its own site and retrieve some data. To return the data to the requesting page, the function uses the postMessage method on the origin property of the object passed to the method, as shown in Listing 2.
Back in your original page, you must also add a listener for an event of type "message" to catch the result coming back from the window you opened. The listener's function must accept a single parameter -- the returned result. Again, it's a good idea to check the origin property on the result before using the passed data. This example ties message events to a function called messageProcessor and inserts the returned data into some tag with the id custInfo:
window.addEventListener(
"message",
messageProcessor,
false
);
function messageProcessor(msg)
{
if(msg.origin == " http://remotesite.com")
{
$("#custInfo").text = msg.data;
}
}
The new WebSockets protocol and related API can be useful here (once the changes to address the initial security protocols are addressed), because it supports creating a full duplex connection that could process on a background thread.
Parallel Process
Not all processes finish in seconds -- especially those that make service requests. Web Workers let you run processes in parallel in a Web browser. To kick off a process, you pass the path to a JavaScript file to a new Worker object:
var proc = new Worker("Scripts/GetData.js");
However, the code executing in the Worker has no access to anything in the page, including global variables, the elements that make it up or page scripts. Instead, you'll need to add events to your Worker JavaScript file to catch messages. Your page will send messages through the postMessage method on the variable pointing to the Worker. This example sends some text to the Worker:
proc.postMessage("A123");
To wire up a function to run when the message is sent, you can use code like this in the JavaScript file. As in communicating across sites, the parameter passed to the function has a data property with the data passed in the message:
onmessage = function (msg) {
var res = msg.data;
Within the script file you pass data back to the page using postMessage like this:
postMessage(res);
}
In the script in the page you'll also need to wire up a function to accept messages from the process by attaching a function to the onmessage event on the Worker:
proc.onmessage = function (msg) {
alert(msg.data);
}
Effectively, you'll create a file containing a function that starts running when loaded or when the appropriate message is sent from the main script.
Local Storage
Developers frequently need to store transaction-related or user-specific information on the user's computer. The localStorage object provides a standard (though low-level) way to store, retrieve and delete data with a key using the object's getItem, setItem and removeItem methods.
However, it's just as easy to treat the localStorage object as an array. This code saves "A123" under the key CID and then retrieves it:
localStorage["CID"] = "A123";
var res = localStorage["CID"];
In the Internet Explorer Preview, saved items appear as properties on localStorage. After the previous code has executed, this code also works:
var res = localStorage.CID;
You can also write a for...in loop that processes all the keys in local-Storage. This code, for instance, retrieves all the values in localStorage:
for (itm in localStorage) {
res = localStorage[item];
}
While you can store any JavaScript datatype, the data actually hits the hard disk as a string -- you'll need to convert the data back to the correct type when you retrieve it. When retrieving data, if no matching key exists, no error is thrown. Instead, the returned value will be null.
Evolution of the HTML Species
As client-side technologies become more powerful and Visual Studio provides better support for using those technologies, developers get a new set of questions to deal with: What to do on the server and what to do in the browser?
HTML began as a document-presentation technology, so all processing had to happen on the server. For the last two decades, developers have found increasingly more creative ways to subvert that original intention in order to create applications and transfer some of the processing to the client. While XHTML attempted to strengthen the rigor with which documents could be described, HTML5 and its related APIs make HTML5 a better application-development tool. It's significant, for instance, that as part of this revision, HTML severs its historic connection with the publishing standard SGML.
But these changes in HTML5 are just the inputs to the development process. It's up to Visual Studio vNext, jQuery and the next versions of the major browsers to make those technologies useful.