Web app architecture

This article was originally published in VSJ, which is now part of Developer Fusion.
In the July/August 2006 issue of VSJ, Mike James pondered how best to design new web applications. Good responsiveness means that more code has to be moved client-side into the browser, making Ajax requests to the server and updating the page on the fly or in response to user actions. However, good design also implies a separation between presentation HTML, presentation code, business logic and the data handling layers, as is possible with various server-side technologies such as ASP.NET.

The idea presented here is to keep the client-side code as separate from the server-side code as possible, and to keep on using the good design practices and techniques server-side.

If you generate Ajax requests yourself and update the page DOM, then the data can get out of sync with the server-side representation. For example, in ASP.NET a server-side variable <asp::Label .. /> is rendered as an HTML <SPAN>; if you update the SPAN in JavaScript then the new value is not necessarily present server-side at the next post-back. I tried to resolve this a few months back and got in a terrible mess, so I gave up. Nowadays, for ASP.NET, various frameworks such as Atlas hide these difficulties; essentially you carry on coding server-side as usual and the framework generates the right JavaScript and Ajax behind the scenes to improve responsiveness for your GridView or whatever UI control you are using.

My most recent project required good interactivity on the client-side but also access to good programmability and a database on the server. On the server I decided to stick with what I know best, ASP.NET and SQL Server. Using stored procedures effectively provided a data access API, as well as improving security. I also used the ASP.NET membership and profile features, which provided various levels of abstraction above SQL Server database views, stored procedures and tables. My data tables were keyed by the ASP.NET user information and my stored procedures looked up some data in the ASP.NET membership tables.

There are two client applications: a specialised photo viewer and photo designer. These did not correspond to any available server controls, so there was no quick way of implementing them with high responsiveness.

My first reaction was to make Ajax requests to find which photo to show next. However I eventually realised that the client apps do not need to ask the server for this information because it could be included with the page when it is loaded. This makes the payload for the page a bit bigger, but the pay-off is a better response time because it does not have to query the server – this also reduces the load on the server. This approach also gives better compatibility with some older browsers that do not support Ajax requests.

The design could be summed up as having client applications that are largely separate from the server code. Most pages on the site could be handled by standard ASP.NET code using the full range of server-side facilities. However, the crucial client applications are kept standalone as far as possible – this should also help to achieve the aim of operating standalone off-site or as a “mashup” of other pages.

I had previously decided that the photo information should be stored on the server in an XML file. Some of this information is also contained in the site database, but I thought that it was important to have all the photo-site data together in one easy-to-use place. As a consequence there is a need to maintain consistency between the database and the XML. For example, if a user deletes a photo then the database entry is removed and the XML also needs to be updated.

Client photo viewer app

Code colour-key

  • ASP.NET page code is in green
  • ASP.NET C# codebehind is in blue
  • JavaScript is in red
The XML data needs to be passed to the web page so that the photo viewer client app knows what to do. This is achieved by converting the XML to JSON, and passing it to a client app JavaScript. In the codebehind C# for the ASP.NET page, the XML is loaded and converted to JSON using the code described in last month’s article, VSJ JSON. The JSON is passed to the page using a ClientScriptManager RegisterStartupScript call which eventually invokes the page processJSON() JavaScript function. The one trick to this technique is to realise that the JSON is interpreted twice, once when processJSON() is called, and again when the JSON parser is called. To resolve this issue, simply replace each \ with a \\ before sending the JSON string.
XmlDocument doc = new XmlDocument();
doc.LoadXml(“<whatever.xml>”);
string JSON = XmlToJSON(doc);
JSON = JSON.Replace(@”\”, @”\\”);
ClientScriptManager cs =
	Page.ClientScript;
cs.RegisterStartupScript(GetType(),
	“Startup”, “processJSON(‘“ + JSON
	+ “‘);”, true);
ASP.NET inserts the processJSON() call at the end of the web page so that it is called after almost everything else has been loaded. I’m not sure if this corresponds exactly to the BODY onload event, but it works.

The page HTML should include any JavaScript source files that the project requires:

<script src=”json.js”
	type=”text/javascript”>
</script>
<script src=”ClientApp.js”
	type=”text/javascript”>
</script>
The ClientApp.js should process the JSON as is appropriate for your application:
var obj;
function processJSON( JSON)
{
	obj = JSON.parseJSON();
	if( !obj)
	{
		alert(“JSON decode error”);
		return;
	}
}

Client designer app

The photo viewer client-side app does not need to interact with server-side code (all it does is request new images), although it could be enhanced to log the actions that a user has taken. However, the matching designer client-side app does need to interact with the server to store the design that a user is making. Any pending user changes are held in a JavaScript array and reported to the server when the user clicks on the Done button or opts to move to a new photo. There is a visual indication when there are unsaved changes; if the user navigates away then any unsaved changes are lost.

The changes are reported by storing them in JSON format in a hidden form variable called Changes. This code shows how the Done button and Changes are defined in the .aspx file:

<asp:Button ID=”btnDone” Text=”Done”
	OnClick=”btnDone_Click”
	runat=”server” />
<input id=”Changes” type=”hidden”
	runat=”server” />
In case you are not familiar with this syntax, btnDone and Changes are page variables that can used in server-side code. When the ASPX page is rendered, they are converted into HTML that can be accessed using JavaScript and the DOM. Note that when rendered, the DOM ids may be different, e.g. if the page uses a master page layout.

If the user clicks on Done, any changes are stored before posting back to the server. This is achieved by adding an onclick handler to the Done button in the .aspx.cs Page_Load():

btnDone.Attributes[“onclick”] =
	“javascript:return
	btnDone_OnClick()”;
When the page is rendered, btnDone becomes an INPUT form field with its onclick handler set. The JavaScript onclick handler stores the JSON string version of the ChangesDone variable and then returns true to let the page form be submitted:
function btnDone_OnClick()
{
	ChangesField.value =
		ChangesDone.toJSONString();
	return true;
// Don’t cancel btnDone form submit
}
For the above to work, the JavaScript ChangesField variable needs to be set correctly to the Changes hidden field. Remember that Changes runs server-side as well. This means that ASP.NET may have decorated the generated HTML field name with various prefixes, e.g. if the page uses a master page layout. To get the correct name, my aspx.cs code registers some startup script to tell the JavaScript the correct name for Changes.ClientID. The JavaScript also needs the btnDone.UniqueID so this is passed,too:
ClientScriptManager cs =
	Page.ClientScript;
cs.RegisterStartupScript(
	GetType(), “SetServerControls”,
	“SetServerControls(“+
		“‘“ + Changes.ClientID +
		“‘,’” + btnDone.UniqueID + “‘);”, true);
This is the JavaScript code that is called at startup to store the control names:
var ChangesField = false;
var btnDoneUniqueId = false;
function SetServerControls(
	ChangesField_ClientID,
	_btnDoneUniqueId)
{
	ChangesField =
		document.getElementById(
		ChangesField_ClientID);
	btnDoneUniqueId = _btnDoneUniqueId;
}
As mentioned earlier it is desirable to postback to the server on events other than pressing the Done button. This is achieved in the JavaScript handler for each event by storing the ChangesDone data and then simulating a press of the Done button. This is implemented by calling the __doPostBack() function that ASP.NET will have generated, passing the required Done button UniqueId:
function Move_dblclk()
{
	ChangesField.value =
		ChangesDone.toJSONString();
	__doPostBack(btnDoneUniqueId,’’);
}
Moving back to the server to see how the Done click is handled server-side. This simply picks up the Changes value and decodes it using the Nii.JSON.JSONArray parser along the following lines:
protected void btnDone_Click(
	object sender, EventArgs e)
{
	string sChanges = Changes.Value;
	if (!string.IsNullOrEmpty(
		sChanges))
	{
		JSONArray Changes =
			new JSONArray(sChanges);
	}
}
There is one complication to the above process for the designer application. In the viewer, the startup JavaScript can be registered in the Page_Load() method. However the Done button handler btnDone_Click() will be called after Page_Load(). If the Done handler alters the information that you want to send to the client, then you must generate the startup JavaScript later. I found that this could be done in the page pre-render function Page_Prerender() as follows:
protected void Page_Prerender(
	object sender, EventArgs e)
{
	string JSON = XmlToJSON(
		SiteXmlDocument);
	JSON = JSON.Replace(@”\”, @”\\”);
	ClientScriptManager cs =
		Page.ClientScript;
	cs.RegisterStartupScript(GetType(),
		“SetServerControls”,
		“SetServerControls(“+
		“‘“ + Changes.ClientID + “‘,’”
		+ btnDone.UniqueID + “‘);”,
		true);
	cs.RegisterStartupScript(GetType(),
		“Startup”, “processJSON(‘“
		+ JSON + “‘);”, true);
}
On the server, I made the viewer and designer into .ascx user controls. Incidentally the standard Visual Studio File in Files did not find text in C# .ascx.cs files even though the filter includes *.cs. I reported this as a bug to Microsoft, but was given the bizarre response that this was by design; fix it by adding *.ascx.cs to the filter. I also added *.js to find text in JavaScript and *.master to find master page markup.

Here’s another JavaScript tip: include a version number in the filename, e.g. ClientApp_v1_01.js if the code ever changes. If you don’t do this, a returning user’s browser may use a cached and out-of-date version. When you do a change, rename the file and update all the code that references it.

Conclusion

With this tricky code out of the way, I could concentrate on the client and server code separately which I think was a wise choice. Coming back to JavaScript for a large project was not as bad as I thought it might be, as it was more expressive than I had realized, and DOM interactions worked well and consistently across most browsers. However I did revert to alert() box debugging. A search found various JavaScript debuggers, but I never got round to trying them.

On the server, separation of the codebehind from the presentation HTML is definitely a good thing. If I am honest though, I have to report that my .aspx files still contain a tiny amount of inline code to access certain page fields or fill GridView TemplateFields. Declarative design can only ever take you so far, so my .aspx.cs code is largely linked to the presentation HTML, e.g. filling labels or coping with SelectedIndexChanged. I probably do not make enough of an effort to separate the business logic from the presentation logic, although using stored procedures as a data access layer is a good discipline for security as well as design-separation reasons.


Chris Cant runs PHD Computer Consultants Ltd – his blog is at chriscant.blogspot.com, [email protected].

Resources

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.” - Donald Knuth