in

Foo Theory

Partners in Community - serving up some ice cold Kool-Aid!
Welcome to footheory.com.  The bloggers and contributing members on this site are consultants, project/program managers and software architects working across the US.  Our community will focus on Microsoft technologies, .NET architecture, software patterns & practices and just plain stream of consciousness.

Bennie's Weblog

Auditing ASP.NET pages with ASP.NET AJAX Beta 2

Problem Description & Requirements

As part of a recent e-Commerce project I was asked to provide an auditing trail for certain critical application processes, such as:

  • The signup of a new member.
  • A product purchase.
  • The agreement of a user to terms and conditions.

Typically, such requirements have been addressed by saving the data associated with the transaction, adding a timestamp and the corresponding user id etc. While these measures are definitely appropriate and needed, it would be beneficial if in addition to the this information we could create a permanent record of the web page, as it was shown to the user. This record would contain:

  • All of the page's HTML elements (text boxes, radio buttons etc), with the content as filled in by the user.
  • All of the images that are part of the page.
  • The exact look & feel of the page, including any CSS styling.

Any solution should have a minimal impact on the existing flow of the application, and should require as little as possible development effort for every page that needs to be audited. Also, the run-time overhead of the solution should be minimal.

Requirements Analysis

We looked at a number of ways to implement this, including the application of a HttpModule to capture the Html that was sent back to the browser. At the end, it was concluded that we really should capture the Html that was shown to the user, since this is ultimately a true representation of what the user saw at the time of the transaction.

An Ajax-based solution seems like a very natural fit for this problem. Using Ajax technology, we have the ability to asynchronously call Web Services directly from the client. So, we simply need to get the contents of the Html document, and pass this content to the Web Service. The Web Service can then archive this content in some type of persistent storage, such as a the file system or a relational database.

To keep our example simple, we will save the content of the Html Pages in the file system of the Web Server, where the base name of the created file is the URL of the page. We will append the current time in ticks to base name to create a unique file name.

We will use the beta 2 version of Ajax.Net as our Ajax implementation. Beta 2 shipped a couple of weeks ago, hot on the heels of Beta 1. The CTP version of this technology was also known as Microsoft ATLAS.

ASP.Net AJAX allows the developer to create browser-neutral pages with a rich, responsive UI and and an asynchronous communication model. The framework is tightly integrated with ASP.NET, and this integration will be improved even more by the time Orcas (the next version of Visual Studio) will ship sometime next year. You can download Beta 2 from this location.

Solution

Create the Web Application Project

To implement this solution, we will first create an ASP.NET Web Application. Make sure that you have the Web Application project model installed. Alternatively, you can use a standard "Web Site" project. I have gotten to really like the Web Application Project model, and I'm currently using it for most of my web projects.

In this case, we will call our project "AjaxAuditing", as is shown below:

Configure the Project for ASP.Net AJAX

After the project has been created, we first should go ahead and configure our application for ASP.Net AJAX. When you install Beta 2, a sample web.config file will be provided for you in the following directory:

C:\Program Files\Microsoft ASP.NET\ASP.NET 2.0 AJAX Extensions\v1.0.61025

You can either copy the entire contents of this file, or you can "lift out" those elements that are required for your project. In our case, we only need scripting and Web Service support. This implies that we need the following configuration elements:

  1. A <sectionGroup> element in the <configsections> outer element, which configures the scripting component of ASP.Net AJAX.
  2. A <httpHandlers> element to configure Web Service support.
  3. A <httpModules> element to configure the AJAX Script Module.

For a complete listing of the web.config file of our sample project, I refer the reader to the sample code for this project.

We also need to add a reference to the 'Microsoft.Web.Extensions.dll' assembly. This assembly will be located in the program files folder named above. Copy this file into your /bin directory, and add a reference to it, as shown below:

Note that the 'Microsoft.Web.Extensions' assembly is installed in the GAC, we are simply referencing it here for build purposes. If you don't like running from the GAC, you can set the "copy local" attribute for this assembly to true, and you will be running with a local copy.

Add a Web Service to the Project

Next, we will add the Web Service which will save our Html content to the file system to our project. To do this, follow these steps:

  1. Since we want to separate our Web Services from our main .ASPX code, we will add a new folder named 'WebServices' to our project.
  2. To this folder, we add our Web Service, named 'SaveHtml.asmx', as show below:

In our code, we need a using statement for the Microsoft.Web.Script.Services assembly:

using Microsoft.Web.Script.Services;

We also need to add the 'ScriptService' attribute to our Web Service. The ScriptService attribute indicates that we want our Web Service to be callable from client-side JavaScript, and will enable ASP.Net AJAX to dynamically generate a client-side proxy for the Web Service:

[ScriptService]
public class SaveHtml : System.Web.Services.WebService

The complete code for the Web Service is shown below:

Our Web Method takes two parameters:

  1. The URL of the page. This URL is used to build the first part of the base name of the created file.
  2. The html content itself.

We create a unique file name by concatenating the current time in ticks, map this file into our web context and write the contents of the Html to the file.

Create our Test Web Page

Next, we will enable our Default.aspx Web Page to call our Web Service, using ASP.Net AJAX as follows:

  • First, we need to add a register statement for the Microsoft.Web.Extensions assembly. This will import the AJAX asp extensions, which we need to add a reference to the Script Manager (see next).

  • Next, we add an <asp:ScriptManager> element to the page. The ScriptManager should always be located in the <form> tag, and should be the first element after the form element. Inside the ScriptManager element, we add a reference to our Web Service by specifying it's relative path:

  • In the body of the form, we will add some HTML and ASP elements, such as a TextBox and an image. This will allow us the test our application with some actual Html content. In the 'btnSubmit' button, we add an 'OnClientClick' attribute, which invokes a 'CallSaveHtml()' JavaScript method. This method will make the actual call to the Web Service (see next section for more info). Note that this button also has a OnClick postback handler, which remains unaffected, so we are not altering the normal application flow.

  • Next, we add a JavaScript block to our <head> section, and provide the implementation of our CallSaveHtml() JavaScript method, as shown below:

In the CallSaveHtml() method, we invoke our Web Service by specifying the fully-qualified name of our Web Service method, passing in the following parameters:

  1. The name of our page (in this case Default.aspx).
  2. The html content of our page.
  3. The final argument is NOT an argument of our web service, but rather is the name of the method, which should be called by the ASP.Net AJAX framework when the web service call completes. Remember, by default all AJAX calls are made asynchronously. In this case, we specify the name of the 'SaveCompleted()' method, which displays a 'Save Completed' message in the status bar of the browser.
Testing our Page

Before we run our page, we need to create an "Auditing" directory in our project. This is the directory which will contain our audit files. Now, we are finally ready to test our page. When we run the page:

and click the "Submit" button, you will notice that a file is created in the "Auditing" sub-directory:

Navigating to this page in the browser will show an exact replica of our original page, including the image and the text in our TextBox, producing the exact audit as specified in our requirements:

Also, because we added the call to the 'CallSaveHtml' in the 'OnClientClick' attribute of our submit button, we preserve the standard postback behavior of the button, so we are not affecting the standard flow of the ASP.NET application, which was another one of our requirements.

Enhancements

While our implementation of the 'CallSaveHtml' Javascript method will work, it does have a number of drawbacks:

  1. It has the name of our Page hard-coded as the first argument to the web service call.
  2. We need to add a Javascript block with both of our methods to each web page that we want to audit.

A better solution would be the following:

  1. Move the generation of the JavaScript code into the code-behind of the page, and use Page.ClientScript.RegisterStartupScript to register the code. This code can dynamically generate the URL of the page, so that hard-coding will no longer be necessary.
  2. Move this code into a base class, from which every page with auditing requirements can inherit.

For this purpose, I created a AuditablePage base class, which overrides the OnLoad() method. In the OnLoad() method, we use a string builder to create our JavaScript, and we use Page.ClientScript.RegisterStartupScript() to register the script, as is shown below:

Note that we use Request.Url.AbsolutePath to get the Url of the page, thereby avoiding the hard-coding we had to do previously.

Now, any page which needs auditing can simply inherit from this base class, as shown in the class diagram below:

Note that these additional pages will still need the ScriptManager, and the call to the 'CallSaveHtml()' JavaScript function in the appropriate location, but otherwise, they should be ready to go.

Real-World Application

In a real-world application, you would probably not use the file system to store your page audit. You would probably want to store the audit pages in a relational database, and add additional context information such as:

  • The name of the current user
  • The current date & time

Technorati tags: , , , ,

Comments

No Comments

Leave a Comment

(required)  
(optional)
(required)  
Add

About bennie

I work for a Microsoft Gold Partner, Statera SouthWest as a Strategic Partner and a Solutions Architect
Copyright ASIQS Corporation © 2006, All rights reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems