Component Software for Business

Component Software for Business

<p>Database Development for the World-Wide-Web</p><p>Introduction</p><p>The Internet is a well-established fact of life, and while a lot of the Internet’s content comes in the form of ‘static’ web pages composed of text files in HTML format and images, our interest is in the methods that we can use to publish data from database tables on web pages. Web sites that derive their content from database files are often referred to as ‘dynamic’ web sites because if any of the data in the source database is changed (new data added, existing data deleted or updated), the next person to access the web site will see these changes immediately. Dynamic web sites are created by producing programs or scripts that generate the web pages on demand. While this may initially seem like a very wasteful use of computer processing power since there may be long periods during which the data does not change and yet the web server continues to slavishly re-generate web pages that it has generated many times before, the process of publishing this ‘live’ data on the web is only marginally less efficient that that required to copy existing files of HTML text stored on a disk. In these tutorials, you will discover the basic mechanisms for creating dynamic web sites and by following the included practical sessions, you will gain practice in the techniques necessary to implement dynamic, data-driven web sites.</p><p>Practical Issues</p><p>If you intend to follow the practical sessions throughout these tutorials, you will need only a few tools to allow you to do so. The first and most important tool is a Web Server: don’t panic, this does not mean you need to go out a purchase a big, industrial strength computer and start negotiating with an Internet Service Provider to provide you with a T2 connection to the Internet. In fact, web servers tend to come free with many computer operating systems, and operate perfectly well for executing web pages with active content for delivery to a browser on the local machine. If you have a computer with a copy of Windows 98, Millennium, 2000 or XP, you will almost certainly have the software necessary to set up a web server. For the practical exercises described here, you won’t even need an internet connection. If you have Windows 95, you can download a copy of Personal Web Server from the Microsoft web site (although of course you do need a connection to the web to do this). If you are running a computer installed with Linux, or an Apple Macintosh, you will also have web server software either already installed or easily available, although it is most likely that the practical sessions described here will not be compatible with your specific system – you will have to access one of the web-sites specific to your own computers, operating systems and web servers to find similar support material.</p><p>Web Sites and Web Pages</p><p>The Request-Response structure</p><p>A Website is a collection of files stored on a computer that is connected to the Internet and organised in a way that allows them to be accessed by any other computer that is connected to the internet and running a Web Browser. The World-wide-web was invented by Tim Berners-Lee, a physicist at Cern, the international atomic research facility located near Geneva in Switzerland. It was devised as a way for computers connected to the Internet to share documents easily, and is based on the idea of Hypertext – textual documents which are interlinked by a system of document references within them. Berners-Lee devised the Hyper-Text Transport Protocol (HTTP) and the Hyper-Text Mark-up Language (HTML) that became the core format for every website. HTTP is a simple system which is understood by web servers. When a request for a document arrives at a web server, the server responds by sending the document back to the address that issued the request – a very simple client-server arrangement not unlike the way database servers respond to SQL. From the client end, the request incorporates the address of the web server and the name and location (actually, a virtual location) of the document on the server. When this request is issued, a large database distributed across the internet (the Domain Name Service or DNS) is used to work out the actual Internet address of the server (a numeric address that locates it uniquely among all of the networked computers in the world) and the request is directed there. Request</p><p>Response</p><p>Web Browser Web Server Figure 1: Request-Response. The Internet Address of a web page or media file is issued at a browser and received by the web server. The HTML text of the web page is returned to the browser. Typically, the request comes from a Web Browser running on some computer, and is for a specific web page or resource, for example, www.BigBusiness.com/Documents/SomeDocument.html. In this case, the DNS would look up the Internet address for www.BigBusiness.com and pass the request for Documents/SomeDocument.html to it. The very clever part (alluded to by the word Hypertext in the acronym HTML) is that the document can contain text marked up in a special format understood by web browsers, and in particular, is likely to contain the addresses for other web pages. In this way, web pages contain links to other web pages, which may also contain links to other web pages and so on. The world-wide- web is a hugely interconnected network of web pages that refer to each other. This request-response organisation makes it trivially easy to create a web site by simply taking a bunch of documents and embedding inter-links within them at strategic points – a simple example is where index document contains a list of hyper- links to other pages on the site, and these pages contain links back to the index. An easy extension to this is that most web browsers can now display graphical content, and a web page can contain hyper-links to graphics files using the same request-response mechanism. In fact, a current web browser can cope with a large range of different types of content including audio, animations and video, all retrieved by the same request-response method. Static Pages, Server-Side Scripting and Client-Side Scripting</p><p>Using simply HTML text files and a smattering of other types of file containing graphics or other information, it is easy to build a ‘static’ website that will provide access to information about you, your hobby or your business. The down side of this is that as the details you wish to display change (obvious fundamental things like your age, which changes annually, or the products your company sells, which may change on a daily or weekly basis), so you need to maintain the files that make up the website to make the information up to date. The way around this is to create your website as a dynamic entity; one in which the information published can be updated automatically because the source material is kept up to date not by you altering the text that makes up the web pages, but by changes made to the source of the material. For example, if you were to display your age on a web page by simply putting a number in the HTML text, this will never change and so you will need to manually change the HTML text every year to keep it accurate. If however, you published your age by having it automatically worked out from your date of birth and the current date (something that is made available by just about every computer operating system), then it will always be correct and you will never need to update the page. If you published your company’s list of products not by simply listing them in a HTML file, but by extracting the product data from your company’s inventory database and sending this data back with some formatting to the browser, again the page will always be up to date (or, at least, as up to date as the source database). This type of publishing, known as server-side scripting, is done by embedding instructions into the web pages stored on the server to calculate or extract the necessary information and format it as necessary before sending it on to the client browser that issued the request. There is a growing variety of ways in which this can be done – programs can be written to generate HTML from data extracted by a database and connected to the web server, usually by placing them is a special directory on the server that is used to host these CGI (Command Gateway Interface) programs. Alternatively special ‘script’ statements can be embedded in web pages that are executed by the web server as it processes the request for the page. The generic name for the second method is Active Server Pages, and describes the general mechanism where the web server is configured to interpret scripted instructions embedded in web pages. Active Server Pages can be written in a variety of languages that can be ‘plugged-in’ to a web server; e.g. JavaScript, PHP, Python and VBScript. We will use the latter in the practical sessions, since this language is similar to the Macro language used in Microsoft Access, and is the standard scripting language for Microsoft web servers.</p><p>Active Server Page</p><p>The Internet Response</p><p>Database Request</p><p>Web Server</p><p>Figure 2: Active Server pages can generate a ‘live’ response to a web page request Script can also be created to execute in the client’s browser. This client-side script is an efficient way of doing some of the work, but has limitations that make it not particularly useful for our purposes. For a start, you can not publish data from a database that you host on the server by executing a script on the client’s computer; apart from it being an absurd idea (the database is at your end of an internet connection – not the client’s), the browser would not allow such a thing on the basis of security, your web server would need to have been set up to trust scripts run from an anonymous source (a security nightmare), and there would need to be software on either side of the connection capable of maintaining the direct access the client has to your database. For these and other reasons, client-side script is never used to do much more than alter the way that information is presented in the browser, ‘jazz-up’ interactions between the user and the page on the browser or validate information that the user is required to send to the server. Even then, we can never be sure that the browser accessing a web page is capable of allowing script to run, or that if the browser is capable of executing scripts, that the facility has not been disabled. All in all, client-side scripting is a non-starter for publishing data.</p><p>Generating a page from a web server</p><p>If we have a computer with a web-server running on it, for example Microsoft’s Internet Information Server (IIS), we can create a website by simply adding files that contains some HTML text to a directory that the web server is set up to ‘publish’. For example, the following simple file, placed in a folder that was configured to be published by the web server, would be enough to display a static message on a client’s browser: <html> <head> <title>Static Web Publishing Test Page</title> </head> <body> <h1>Hello HTML</h1> </body> </html> Listing 1: Hello.html – this is a static html file, which would simply be sent from the server unchanged. The text in listing 1 is HTML. Everything enclosed in angled brackets <> is treated by the web browser as ‘mark-up’, special code to indicate something to the browser. The only pieces of non-mark-up text in this listing are the page title (enclosed in <title>...</title> tags), and some text to be displayed in a heading style (<h1>… </h1>). This would result in the following display on a web browser (Internet Explorer, or IE) running on any computer connected to the internet:</p><p>Figure 3: The Output from Hello.html Note that the web address (shown above the page in IE’s address bar) is given as http://paddington/DreamHome/Hello.html. The Web Server is hosted on a PC that I call Paddington (it is a laptop and I carry it around in a small suitcase – you figure it out), and I have set up a website in it called DreamHome (after the example database in the book). The file was saved to the folder that this website is hosted in with the name Hello.html. Of course, this is a static web page. To make a dynamic page, we need to change two things. First, the page needs to be named with an ASP file extension instead of HTML, so that IIS recognises it as a file containing script that is to be executed, and the page also need to have some script embedded in it.</p><p><%@ LANGUAGE = VBScript %> <html> <head> <title>Dynamic Web Publishing Test Page</title> </head> <body> <h1>Hello HTML: the time is <%=Time%></h1> </body> </html> Listing 2: Hello.asp – this page contains script code embedded in the html to generate ‘live’ results</p><p>Figure 4: The output from LIting 2 – note the display of the time. The significant difference between listings 1 and 2 is that the code in listing 1 will always display the same information, while the code in listing 2 will display information that changes as the time changes. The script ‘tag’, <%=Time%>, indicates a function that will be executed by the server when the page is sent to the client – in this case, the function returns the current time-of-day. Any text in an ASP file that appears within the ‘script tags’, <% and %>, will be directed to the script engine of the server. Provided this text is valid ASP code (i.e. provided it is syntactically correct for the language named at the top of the page – VBScript in this case), the script engine will execute it.</p><p>Pages, sessions and applications</p><p>A file with an ASP extension will generate a single page of a web site. For a web site to display multiple hyperlinked pages there will need to be a collection of HTML and/or ASP files on the server (it is normal to mix HTML pages and ASP pages on the same site to mix static and dynamically generated pages). The full set of ASP pages is referred to as an ASP Application; all of these pages are designed to operate together to fulfil some specific business purpose. Within an ASP application, there can be some specially named pages – typically Global.asa and index.htm, which have a specific purpose for a web application. With a normally configured installation of IIS, index.htm is the page that someone who points their web browser to the site without giving a specific page name (e.g. http://paddington/DreamHome) would get to. Typically, this page is the home page of a site and should be the ‘welcome’ page, providing links to key pages in the web and acting as a top-level menu. Global.asa performs a different function. This page contains a number of blocks of script (subroutines) that will execute automatically when a website is in use, providing code that initializes (sets-up) the site when it is first accessed, closes down database connections or other housekeeping if it has not been accessed for a while, and sets up and manages sessions – sequences of page accesses from a user of the site.</p><p>HomePage.htm LogIn.htm Login.asp CheckCredit.asp</p><p>Index.htm</p><p>Global.asa Catalogue.asp PlaceOrder.asp ViewOrders.asp</p><p>Figure 5: A small active web site, with a mixture of static (htm) pages, acti9ve (asp) pages and application pages (index.htm and Global.asa). The pages link (arrows) to each other to provide navigation through the site and to organize necessary services (e.g. login and credit checking). An ASP-based site typically interacts with many users, effectively doing so concurrently so that at any one time, there may be many different users all currently interacting with any page within the site. The sequence of pages accessed by each user is regarded as a session, and it is useful to be able to track any one user through all of the pages that they visit, either to gather statistics on the use of the site, or simply to save users from having to repeatedly send the same information to the site from page to page.</p><p>Session state</p><p>Tracking users through a site brings up one problem that is central to the way that Active Server Page websites are designed. Each request for an ASP page will execute some script. In sequence these ASP pages may, for example, allow a user to log-in to the site (to provide them with personalized information, or simply to prevent unauthorized access to restricted facilities), allow them to browse through several pages, perhaps selecting products to add to a shopping cart, and then to continue to a check-out page where the sale can be completed by the user entering credit card and delivery details (see figure 5). The problem is simply that the web application has no ‘memory’ that goes beyond a single page, so there is no simple way to know what stage in a sequence a client is on. Good website design demands this, because there is no compulsion for a user who has, say, added some items to a shopping cart to complete the process by going through the checkout page, or even to stay at the same website. You might think that a way around this would be to keep track of the user as they browse from page to page of the site, gathering information as they go and keeping this information ready for when the next page request arrives. However, as designers of a website, we have no knowledge of, or way of controlling the number of simultaneous users of the site. A site could at any time have zero, 100 or 1000000 current visitors, and if the web server was required to actively track each of these, it could take up a lot of time and memory (or none). For the site to remain efficient regardless of how many current visitors there are, it is necessary for the server to NOT be aware of the sequence of page requests for each and every visitor. However, for it to keep track of the current stage that each visitor to the site is at, the website must have some way of retaining their current status. Well designed web-sites are built to be ‘stateless’ – that is, there is no specific storage of data between sequential visits to active server pages. This makes is difficult to track where in a sequence a user of a site is – have they just logged in or are they proceeding to the checkout? ASP allows a number of techniques to be used to track a user through a site, from the deployment of special ‘Session’ objects which are embedded in each page returned to a client so that they can be retrieved on the next request, to the use of ‘Cookies’ – strings that can be stored in the client’s computer from page to page (or even session to session). We can also use a couple of web programmers’ tricks, such as storing session data in variables hidden in web forms to be passed back and forth between server and client. All of these techniques involve embedding session data in the response to a page request, with the page organised so that the same data will be returned if a link on that page is used. </p><p>The Response World- Wide- Web Next Request </p><p>Web Server Figure 6: Session state is maintained by sending data back and forth between the server and the browser embedded in the request-response data. The envelope here depicts this session data. All of the methods used to store data between ASP pages are there to save having to store the data at the server, where they could take up valuable space that would limit the number of simultaneous clients that could be serviced. The result is that well- designed web sites are inherently ‘scalable’, meaning that the software imposes no limit on the number of clients that the site can deal with at any one time. Of course there are limits imposed by the hardware the site is running on, the speed of the internet connection that the server has and other factors, but not the ASP website design.</p><p>Application state</p><p>ASP applications place demands on a web server, and it would be unwise to create a site that subsequently hogged space on the server whether it was servicing any clients or not. Managing session state goes a long way to reducing the load on a server, but a server could easily fill up with inactive ASP sites because each site must maintain a certain amount of information simply to function. A website typically keeps track of such things as the number of current users, page statistics (how many times each page has been visited) and database connections that can be used by a number of simultaneous sessions. Note that while session status multiplies up by the number of current users, application status is effectively one lot of stored data per application (or active website). i.e. the problem of maintaining application data is not anywhere near as bad. Even so, an application consumes some resources, and so it would be useful if we could cut-back on it (or even remove it completely) if there were no requests coming in for pages within it. Global.asa, one of the code files that are automatically associated with a web application, can contain code to perform application housekeeping. A realistic example of a suitable Global.asa file for a website is given later in listing 13.</p><p>Development Choices</p><p>When we decide to create a dynamic website, we have a number of choices to make – which web-server software, how the site will be hosted, what language to use, how we will write and test the code, how we will arrange the files stored on the server etc. The choice of web-server is one that should be a simple matter of preference – a number of web servers are available for free to install on most PC operating systems. If you are lucky (or wealthy) enough to have a permanent broadband connection to the Internet, or are creating a web-site for a business that has a permanent internet connection and a computer designated as a web-server, you can use whatever combination of operating system and web-server takes your fancy. This is easy for your Internet Service Provider to manage, because since you host all of the website and server facilities on your own computer, they need only provide a path to your PC from the Internet. Also, the risk of a badly designed site causing a server to crash, or worse, be open to malicious access from the community of Internet crazies, is yours to take and will not seriously affect the ISP or their other clients. If you have a dial-up internet connection, you will be restricted in how you create your active website (with some Internet Service Providers [ISPs] you may not be allowed to host an active website at all). ISPs often provide a restricted range of facilities for hosting dynamic websites, limiting your choice of database (if any), scripting language and configuration options. The reason for this is simply that with a dial-up connection, your entire website resides on the ISP’s own servers, and when a script is executed it runs on the ISP’s server, so you can only use those facilities the ISP has decided to install on their server machines. ISPs also have to restrict what your scripts can do on the server to ensure that they do not leave themselves open to malicious attack, and some find that the safest approach is simply to not host such business. Fortunately, you are free to choose among many ISPs, and so there ought to be a good range that provide the facilities you need to operate your site. In the practical sessions, we will be going down a decidedly Microsoft route. The server you will write script for is IIS (or PWS, which provides very similar facilities), the database you will write code to interact with will be Access, although you will find that the code you create will also work well with a SQL Server database, give or take a minor configuration change or two.</p><p>Tools</p><p>Once you have mastered the basics of writing ASP code, you will also be able to choose which tools you make use of to build the website. However, in these early practical sessions, we will use a very minimal toolset – a copy of Microsoft Access (for creating the database and checking the results of our ASP endeavours), and a simple text editor (Windows Notepad will do at a pinch). Microsoft FrontPage is a good application program for developing ASP websites (or static ones for that matter), providing good facilities for testing page behaviour, highlighting code syntax (by colouring the HTML/ASP text), different ways of viewing a page of a website or the structure of the whole site, and some tools that make it easy to insert some of the more complex HTML mark-up elements into a web page. FrontPage also allows databases tables to be inserted into a web page quickly, without the need for ASP script. However, the built-in database facility only allows simple data from a specific table or query to be inserted, and does not allow the creation of more complex data management pages. In these tutorials, we’ll use ASP to provide as much flexibility as we can.</p><p>IIS – Installation Issues</p><p>If you have a computer that runs either Windows 2000 or XP professional, you are likely to have Internet Information Server installed already. If it is not installed, you will need to run the Windows Setup program from the CD-ROM that your operating system was supplied on, and select the option to install IIS – currently this option appears on the Internet section of the Tools page of the setup program. Different versions of the Windows operating system do not include a copy of IIS. Windows XP home does not include a web server at all, although Windows 98 and Millennium (ME) come with a copy of Personal Web Server (PWS), which is effectively a cut-down version of IIS that has all of the capabilities that we need for developing websites. Consult the manuals or help files of the CD to find out how to locate and install the web server with these operating systems. Once you have a web server installed, you will need to create web sites on your computer to place your ASP code in. In IIS and PWS, creating a website is a simple matter. To create a new site in IIS on a Windows XP machine: 1. Select StartAdministrative ToolsInternet Information Services 2. In the tree view on the left of the IIS display, right-click on Default Web Site, and select New…Virtual Directory from the context menu that pops up 3. Follow the directions of the New Virtual Direction Wizard to set up a site within your web server. The steps are: a. Provide a name for the virtual directory – this will become the root name of the site your are creating (e.g. Dreamhome) b. Type or (more likely) browse to a folder that will house the files on the virtual directory. In a standard IIS installation, it is expected that you will place the files in a folder off C:\Inetpub\wwwroot. You can place the files in a different folder or on a different drive if you wish. c. Select the required settings for file access for your web page files from the Internet. The default is Read and Run Scripts, and this is a very sensible default. You are best to have a very good reason for choosing other settings for this, since you may be allowing hackers to gain access to your hard disk if your website is every published across a broadband connection d. Press the Finish button at the last stage of the wizard. You now have a root folder for a named website. 4. At this stage it is useful to test the website. Drop a very simple HTML file or ASP file into the root folder you created (e.g. in C:\Inetpub\wwwroot\MySite) – listing 1 or 2 (later) are ideal for this. You should now be able to access these files via the web server as follows: a. Run Internet Explorer b. Enter http://MyMachine/MySite/Hello.asp into the address bar and press enter. Of course, you should replace MyuMachine with your actual PC’s name, and MySite with the name you gave the virtual directory. Alternatively, you can use http://localhost/MySite/Hello.asp since the name localhost is reserved to refer to the web server that resides on the local computer (the one that you are running the browser on). Note that the localhost name will only work from the local computer, and browsing from any other machine will require the actual server name. The result (with Hello.asp from listing 2) should be a page that displays a message and the current time. This last part is important – if the time is not displayed, you have typed up the ASP file with an error in it, or your server is not executing the script properly. Fix this now, before you go on to try to create any other active web pages.</p><p>ADO</p><p>Before we move on to examine the ways we can move data between web pages and a database, a quick recap of ActiveX Data Objects, or ADO, will be useful. ADO is a database access layer developed by Microsoft using ActiveX. ActiveX is no more than a marketing term used by Microsoft to describe their component-based object technology COM, or Component Object Model. The term ActiveX has generally been unpopular with developers, and even Microsoft employees have been known to explain that the ‘X’ is silent in Active(X) Data Objects. ADO is a simple API (Application Programmers Interface) for accessing databases. It is essentially a software component library hosting the small number of components that are necessary to retrieve data from and update data in databases. There are three essential types of object you will need to learn to use to access data:  Connection objects: a connection is a software ‘object’ that provides an interconnection between a piece of software and a database. It has been designed so that it is easy to access data from almost any type of database, including Microsoft Access, Paradox, dBase, Oracle, SQL Server, and even structured text files and the data stores used by email servers. Typically a connection object is deployed in a script to create a connection to a particular database and send commands to it  Command objects: some of the commands that are sent to a database are more complex than a simple connection object (which has a job of its own to do maintaining a database connection) can handle easily. Typically, when a command contains a number of associated pieces of information (parameters), a command object is used to package and dispatch it via a connection. There is no real need to use command objects for most database operations, but it can make some of them easier to handle  Recordset objects: one thing we often want to do is to retrieve data from a database. Using ADO, this data is almost always returned in the form of a Recordset, which acts like a table of data held in computer memory. A Recordset can contain a whole table of data, but more typically, Recordsets are used to collect the results of a query – a single record from a table or joined tables, several selected columns from a defined set of rows in a table, in fact any data that can be the result of a SQL select statement. Recordsets give us access to the information that is in a database, and also allow us to add or change data in databases from an ASP script In addition, there are other types of ADO object that we do not normally deal with specifically – a Field (a single column of a single record of data) will give us access to actual data attributes, and an Error will provide us with information that indicates what went wrong when an ADO command can not be executed for some reason. Fields come to us as component parts of Recordsets and so we tend to work with them without explicitly referring to them (this will become clearer after a practical example). A collection of Errors is maintained by the Connection object, and can be examined by script code whenever some ADO operation is executed to determine whether it worked or not.</p><p>Object Model</p><p>ADO objects are packaged for deployment in a Dynamic Link Library (DLL). This is nothing to worry about – it simply means that we can access ADO objects from a running script. Each ‘object’ we wish to use must first be loaded into computer memory (a service provided for us by IIS) and then set up for use. The steps in doing this may at first seem complex, but they are a simple recipe we repeat any time we want to make use of an ADO object, and are hugely less complex than the underlying functions that the objects are performing for us. The ADO object model is organised as shown below: Connection Recordset</p><p>Errors Fields</p><p>Properties Properties</p><p>Command Properties</p><p>Parameters</p><p>Properties</p><p>Figure 7: The ADO Object model A Connection object potentially contains a collection of Error objects and a collection of Properties. Error objects only exist if, after executing some operation, something has gone wrong, and they are there to provide information as to the cause of the problem. Properties are ‘settings’ that apply to a connection – for example, the type of database it connects us to, the name of the database and an indication of whether it supports transactions (coherent sequences of commands). Similarly, a Recordset object contains a number of Fields (each field represents the name, type and value of an attribute in a database tuple), each of which can have Properties, and a set of Properties of its own (for example, the number of records in it, an indication of whether we are at the end of the set of records). A Command object has a set of Parameters (each indicating a piece of information that can be sent to or retrieved from a query) and some Properties (the type of command, its name and whether it will return a Recordset or not) This small set of objects is enough to enable us to do whatever we wish to an existing database. The objects contained in ADO are interrelated so that they collaborate on performing data access tasks for us. A typical sequence of operations we might perform to retrieve some data for display from a database might be something like: 1. Connect to a database 2. Open the database (making the data in it accessible) 3. Send a SQL query to the database connection defining the set of information we want 4. Using the Recordset that is returned from the connection as the result of the query, step through the current record in it, displaying the contents of each field in the record 5. Try to move to the next row in the Recordset 6. If the next row is not empty, return to step 4 7. Close the Recordset 8. Close the database connection This sequence of steps indicates something about the structure that relates the objects in ADO. A connection is capable of creating a Recordset, and a Recordset contains a sequence of records (rows), each of which may have a number of individual Fields (attributes). The relationships between these objects is structural, and is sometimes referred to as an Object Model – the way in which objects are interconnected to form a coherent and useful assembly. The way we step through the operations is typical of the flow of instructions in an ASP script - a step by step sequence of small tasks that incrementally perform a larger overall task. As we step through such a sequence, the various ‘objects’ in ASP are made to interact so that they collaborate on some overall task (for example, in steps 3 and 4, we send a SQL query to a connection, which responds by giving us back a Recordset full of data). In this way, the object model, rather than any individual object, is responsible for performing some task.</p><p>Database Neutrality</p><p>One important feature of ADO is that it tries not to make distinctions between different types of database. The ADO code to retrieve data from an Oracle server is exactly the same as the code we would use to retrieve the data from an Access database with a similar data structure. It is impossible for ADO to be 100% neutral in this respect (some database systems provide advanced features that not all of them do), but as much as possible, ADO hides the idiosyncrasies of the underlying databases, which is a very good reason to use it. By providing for transparent interaction with any of a range of database types, ADO makes it possible to change the underlying database without ADO code needing to be radically re-written. For example, I may start with a small website that caters for a small number of visitors and use an Access database as the underlying database. If my website became very popular over time, it might be sensible to transfer the data to a SQL Server database, since this provides for much higher data throughput. Using ADO, only minimal changes would be needed to make the transition from Access to ADO (provided I did not immediately need to make use of some of the more industrial-strength features of SQL server, since for these, it would almost certainly be necessary to change the ADO code significantly).</p><p>IIS Server Objects</p><p>IIS (and Personal Web Server) make a number of standard objects available to ASP code. Typically these objects perform complex functions such as extracting useful information from web requests, packaging up web responses to send back to the clients’ browsers, and maintaining the state of clients’ sessions and the application itself. It also provides a simple mechanism for deploying other objects that are not part of IIS, such as ADO connections and commands, on request. IIS separates out ASP code from html code as it sends a response back to a request. While plain HTML is passed directly into the response, ASP code is executed by one of the available script engines attached to IIS. The script engine interprets the ASP code (e.g. VBScript), and returns a result to IIS, which then passes this on into the response. ASP script is lines of script code within any script tags <% and %> in an ASP file. A typical script statement might evaluate an expression (e.g. 2+2) and calculate its result (e.g. 4). An ASP file may contain any number of blocks of script interspersed between blocks of HTML, and in each case, IIS will direct HTML straight back into the response but will have ASP code executed by the script engine. Within the ASP statements, there will usually be references to a number of objects that IIS is aware of. These objects perform standard functions that would be too complex or too mundane to write script code for. Objects in IIS are responsible for representing information about the page request (the Request object), sending and controlling the sending of results back into the response (the Response object), managing various aspects of the server engine itself (the Server object) and keeping track of information that is used by the entire ASP application (the Application object) and by individual sessions (the Session object). Other objects collect data from a client’s browser (Form), store data on a client’s PC (Cookies) and allow script to access information specific to the server (ServerVariables).</p><p>Server</p><p>The Server object is used mostly to create other, non-native (to ASP) ob jects. For example, to create an ADO Connection object, we would use a statement like: Set conn = Server.CreateObject(“ADODB.Connection”) where “ADODB.Connection” is the name used to identify database connection objects to the Windows registry. We can also get the server to do other things that are not specific to any one website on the server, such as: Server.ScriptTimeOut = 90 to set the maximum amount of time (90 seconds) the server will allow any one ASP application to spend on responding to a single ASP request, or DatabaseName = Server.MapPath(“Dreamhome.mdb”) to work out the actual location of a file (in this case a database file) on the computer running the server.</p><p>Global.asa</p><p>Global.asa is the name we should give to a file of ASP code in the root folder of a website that defines routines that will be run automatically when certain things occur – the first page request for the site is received, the site has been inactive for a preset period of time, a new user sends a request in for a page, or a user completes a session.</p><p>Application</p><p>This object allows us to define variables that will be available throughout an ASP website. For example, if we wanted to create an ADO database connection object that could be accessed from anywhere on the site, we could use this code: Set conn = Server.CreateObject(“ADODB.Connection”) conn.open DBCONNECTSTRING Set Application(“Conn”) = conn</p><p>Now, wherever we were on the website, we could access the database, for example to execute a SQL Select statement, in code like this… Set RS = Application(“Conn”).Execute(“SELECT * FROM Client”) ‘ Can now go on to use the Recordset RS… The sequence Application(“Conn”) is used here to set or retrieve a variable that is accessible to the whole application – Conn is the name given to the variable – in this case a Connection object. The Application object is also used within Global.asa to define things to do when an application starts (when its very first request is received). For example, we might place the code listed above to open a database connection in Global.asa so that a connection is automatically opened as soon as the first visitor arrives (like opening the first bottle of wine as soon as the first guest arrives at a party – the wine will be open for subsequent guests to arrive):</p><p>‘ Code in Global.asa</p><p>Sub Application_OnStart() Set conn = Server.CreateObject(“ADODB.Connection”) conn.open DBCONNECTSTRING Set Application(“Conn”) = conn End Sub Listing 3: The Sub in Global.asa that initializes an active website. The code Sub Application_OnStart() defines the entry point to a subroutine (a block of code that is executed as a sequence of instructions) that will execute automatically when a site starts up. </p><p>Session</p><p>A session object can be maintained for each and every current user of the site. This allows us to store a small amount of information for each user between pages – for example, we can establish who a user is when they first log in to the site by making them visit a log-in page, which would allow us to authenticate users of a restricted site, or provide us with an identity that we can use to record purchases in a shopping cart style website. Once someone has logged in for the first time and we have collected some data on their identity (typically in a database Recordset), we would not want to ask them to log in again for each page they visit, so we could store the user’s login-identity in a session variable: Session(“UserID”) = RS(“UserID”)</p><p>In the above statement, RS is the database Recordset containing user data, and UserID is the name of the field in the current record that stores the user’s identification (typically a customer number, or possibly a nickname or email address). Now, in each subsequent page-visit, the user’s ID will be accessible as: Session(“UserID”)</p><p>Since we can create and use as many session variables as we like, we could continue to define more variables to store, for example, user’s name and other details, details of current purchases in a shopping cart, and so on. However, since each session variable will require a block of information to be passed back and forth between the web server and the browser each time an ASP page is accessed, the result of too many session variables can be to slow down the web accesses and make the site appear more sluggish. For good website design, it is better to minimize the amount of data being passed back and forth between browser and server – typically we would store the bulk of the data in a database and just use session variables to pass key values back and forth, so that we could always identify the database entries that stored the information we needed.</p><p>Request</p><p>The Request object is used in ASP script to gain access to information that comes from the browser. It has four properties that give this access:  QueryString: simple interactions with an ASP page (GET operations) can contain further information in the (modified) URL in the form of Name=Value pairs. The QueryString property allows these to be accessed.  Form: this property allows you to retrieve values entered into and POSTed from a form on the requesting web page. Each control on a HTML form is represented in the form object, and the collection of Form values can amount to a significant quantity of information sent from a browser to a website  ServerVariables: Using this property, you can determine the type of browser that sent the request (e.g. Internet Explorer 5.0, Netscape) and other information (e.g. the visitor’s IP address)  Cookies: this property allows you to access data that you previously stored in the user’s computer; these are typically small configuration files that allow you to recognise a browser that had previously visited the site, and are typically used to allow you to ‘personalize’ the pages you send back. QueryString There are two types of request that can be sent from a user’s browser. A GET operation (the simplest one) can contain up to 255 characters worth of data appended to the URL used to access your site. For example, a HTML file might contain a number of different hyperlinks which all lead to the same ASP page. To identify which link was used, the hyperlinks could include a different Name/Value pair in each link. For example, </p><p>Figure 8: A hyperlink with a query string (link is displayed in the status bar). Note the four links at the bottom of the page (First, Previous, Next and Last), and the URL shown in the status bar, which is the URL that will be used if the mouse is clicked when the pointer is over the ‘previous’ link, as shown. This would be coded into the html page as a set of page-links as shown in bold below: <body> <h1>DreamHome Estate Agents</h1> <p><font face="Comic Sans MS" size="2">Information: Select a page...</font></p> <a href=http://Paddington/dreamhome/getInfo.asp?info=firstpage>First</a> <a href=http://Paddington/dreamhome/getInfo.asp?info=prevpage>Previous</a> <a href=http://Paddington/dreamhome/getInfo.asp?info=nextpage>Next</a> <a href=http://Paddington/dreamhome/getInfo.asp?info=lastpage>Last</a> </body> Listing 4: HTML code with hrefs defining links with query strings. The file getInfo.asp will be able to determine which link was clicked on by retrieving the value QueryString(“info”) (which will be ‘prevpage’ if the ‘Previous’ link was pressed). Form GET operations are ideal for sending a small amount of information to the server. Several values can be passed to the server, provided the length of the entire set of name and value pairs passed does not exceed 255 characters (this limit must include the delimiters ‘?’ at the start of the list of QueryStrings, ‘=’ between each name and value pair, and ‘&’ between successive name/value pairs). If it is necessary to send more data from the browser to the website, a POST operation can be used. The POST method can deal with much larger amounts of information. It is also a more convenient way of sending data from a Web Form – an area embedded in a web page that contains controls that the user can enter data in to. In a POST operation, data is again sent in Name/Value pairs, but now these are sent as a separate stream of binary information, retrievable from the Form property of the Request object. For example, a web form can be used to pass registration details to the website:</p><p>Figure 9: A HTML form – each control on this will provide data that can be collected from the Request.Form object All of the values entered into this page on the browser by the user will be sent as name/value pairs by the POST method. The html for the form is: <body></p><p><h1>Join our Mailing List</h1> <p>Please enter the details below (do not omit any items marked *):</p> <form method="POST" name="frmRentals" action="AddToMailList.asp"> <p>* First Name: <input type="text" name="txtFName" size="30"></p> <p>* Last Name: <input type="text" name="txtLName" size="30"></p> <p>Telephone: <input type="text" name="txtTel" size="20"></p> <p>* Street Address: <input type="text" name="txtStreet" size="30"></p> <p>* City : <input type="text" name="txtCity" size="30"></p> <p>* PostCode: <input type="text" name="txtPostCode" size="10"></p> <p>Email: <input type="text" name="txtEmail" size="40"></p> <p>Where do you want to find a house: <select size="1" name="cboRegion"> <option>(N/A)</option> <option>London (North West)</option> <option>London (South)</option> <option>Bristol</option> <option>Glasgow</option> <option>Aberdeen</option> </select></p> <p>Preferred Rental Type: <input type="radio" name="optPreType" value="House">House <input type="radio" name="optPreType" value="Flat">Flat</p> <p>Maximum Monthly Rent: £ <input type="text" name="txtMaxRent" size="10"></p> <p><input type="submit" value="Join Mailing List" name="btnMailListSubmit">&nbsp; <input type="reset" value="Reset" name="B2"></p> <input type="hidden" name="Client" value="NONE"> </form></p><p></body> Listing 5: HTML code to produce the form shown in figure 9 All of the controls on the above form are encoded in the web form as <input>, or <select> tags. At the receiving ASP page, the information can be extracted using Request.Form(“<field-name>”), where <field-name> represents whatever tag name has been given to each of the controls on the form. For example, Request.Form(“cboRegion”) will refer to the selection made in the <selection> drop- down box labelled “Where do you want to find a house”, and will return one of the specified <Option> values. ServerVariables ServerVariables are an ideal way to retrieve data about the browser, computer and, in some cases, client using the browser. For example, the server variable ServerVariables(“HTTP_USER_AGENT”) will return the name and version of the browser being used and the user’s operating system, while ServerVariables(“REMOTE_ADDR”) returns the IP address of the client computer (note that this is often not as useful as it might appear, since most Internet Server Providers issue a ‘dynamic’ IP address when a user logs-in, so the user’s IP address will change from session to session). Cookies Where they are allowed by the browser, Cookies provide the ideal way to track the clients of your active web-site. A Cookie is a small amount of data that is distinguished by it being stored (legally) on the PC that is used to access a web site. Cookies are retrieved as members of the Cookies property of the Request object, and set as members of the Cookies property of the Response object. Because Cookies are stored ‘client-side’, they do not take up any space on the server, and so are the ideal scalable storage mechanism – it does not matter how many clients your web site has since they all store (at least some of) their own data. A small amount of data left in a cookie on the client’s PC can be retrieved by a statement list: LastVisitDate = Request.Cookies(“LastVisited”) </p><p>The data would have been left on the site by assigning a value to a cookie in the Response object’s Cookies collection. Storing small amounts of data like this can do no harm to a client’s computer and can be used to enhance the way they use your site. For example, you can personalize a page so that it is able to greet the client by name, remind them of their current status or guide them to new information that might have been added since the last time they visited the site. However, the Internet is no longer a medium that you can trust (whether you are a client browsing web-sites or a host of a web site); some clients may be malicious and try to extract sensitive information from your site, some websites act in highly suspect ways leaving small bits of software that can track your web page usage or even relay all your keystrokes back to the site (a serious security problem if you do Internet Banking, for example). Since cookies can store data that other websites might misuse, some people turn off the facility for their browser to store them, and since we can not every guarantee that a client will allow cookies, it is best not to assume that any client does. Response</p><p>The response object is there to allow you to direct information back to the browser that issued a request. It provides ways to control how the information is sent to the browser (whether it is sent in small chunks as and when the ASP page issues its Response.Write statements, or collated into larger blocks of data to save information ‘dribbling’ back to the browser if a request may take some time to process) and allows data to be sent to the browser directly (Response.Write) or from another file (Reponse.Redirect). Mainly, the Response object is used to ‘write’ data back to the browser, whether this is literal text (Response.Write “Hello Client”), the result of Script functions (Response.Write Date), data retrieved from an ADO data session (Response.Write RS(“ClientName”), or a mixture of all of these: Response.Write “Hello “ & _ RS(“ClientName”) & “ we’ve not heard from you since “ & _ Response.Cookies(“LastDate” (Note that in VBScript, a statement, which normally must appear in a single line of code, can be broken to run over several lines, by inserting line continuation sequences [a space followed by an underscore] between elements.) Everything we want to do in an ASP site will require some support from some or all of these server objects.</p><p>Web Page Development Issues and Strategies</p><p>Now that we’ve been introduced to the components that are used to make up an active website, we ought to look at some of the problems inherent in creating a website that derives most of the information it publishes from a database.</p><p>Database Connections</p><p>The ADO connection object described earlier is our conduit to the source of information for our web sites – one or more databases. If we were building a desktop application, we could figure out in advance how many distinct connections we would need to support the functions we will need to provide. Some developers open and close connections at will, creating a new connection and opening it at the drop of a hat. Others try to minimize the number of simultaneous connections open since each consumes computer resources and will present a certain ‘load’ on the database server (or on the database ‘driver’ if the database is on the local computer, as an Access DB would be). The former strategy can work well if it is not taken to extremes, the latter will always work but may involve a lot of creating and destroying of connections which can add significantly to the work the processor (and database server) must do. Connections into a database that will be accessed by an unknown number of users via their web browsers is a different story. Since we can’t ever predict how many users there will be, we need to be a bit more frugal with database connections. Each connection takes up memory and processor time, so allowing new connections to be created willy-nilly is a sure-fire recipe for a system that will eventually grind to a halt. There are three ways around this: 1. Every ASP page that uses a database connection can be required to create the connection object, open the connection, use it, close the connection and finally destroy the connection object (remove it from the server’s memory). This approach requires discipline on the part of the developer. 2. A connection can be created and opened on receipt of the first request for an ASP page. This connection can be shared as a part of the Application object’s state by all users. The reduced workload on the server will be significant, since creating and destroying objects over and over again can impose a significant burden. However, this approach will cause problems if the website grows to such an extent that additional web servers have to be deployed. In this case, all the web servers would now share the one connection and the resulting contention where two pages try to simultaneously access a server would cause some delays 3. A ‘pool’ of connections can be managed by a group of servers up to some specified limit. Initially, there will be no servers in the pool, but as requests come in, connections will be opened and left open. Whenever a request arrives and there are no open connections lying idle, another connection is opened, up to the maximum number. Facilities for this type of connection management are built into Microsoft’s Enterprise Server software For our purposes (reducing the server’s workload), we’ll choose the second method as the most appropriate. If the website were to grow significantly we might need to revise the strategy, but that is a problem we don’t need to consider until our website has made us rich enough not to worry about re-developing the site. The ideal approach to deal with a single connection left open for all users is to create it in Application_OnStart() (the event handler we add to Global.asa) and make it available as an Application() state variable. To make this work, we’d add a text file called Global.asa to the root folder of the website, and then place the following code into it:</p><p>‘ This will make sure the database connection is open and ready… Sub Application_OnStart() Set conn = Server.CreateObject(“ADODB.Connection”) conn.open GetConnectionString(“Dreamhome.mdb”) ‘ *See later Set Application(“Conn”) = conn End Sub</p><p>‘ This will close the connection if we shut down the site… Sub Application_OnEnd() Application(“Conn”).Close() End Sub Listing 6: Code in Global.asa to automatically open and close a database connection. In any ASP page that requires database access, the following code will allow it: Dim RS Set RS = Application(“Conn”).Execute( …<some SQL or Query name>…) Listing 7: Code that uses the database connection</p><p>Client ‘state’</p><p>If our site makes use of sequences of ASP pages to perform compound tasks (as most active websites do), we will need to make sure we keep track of each client from page to page, and possibly track other things, such as the state of a shopping cart (e.g. all the items currently in it), the state of a currently active transaction (e.g. to indicate that data submitted on a form has been stored successfully).or some other business processes. The most convenient way of doing this is to use ‘session’ variables. For example: Session(“ClientID”) = Request.Cookiies(“ClientID”)</p><p>The above statements sets up a session variable, Session(“ClientID”) so that it can be retrieved at any subsequent point on the current session. If the client returns to the current or some other page the site within the session timeout (a number of minutes that you can set in ASP code), and if the current session has not been terminated (using the statement Session.Abandon), we can retrieve the value of ClientID with this statement: ID = Session(“ClientID”) Rather than store all ‘live’ information in session variables, it is often best to store this information in a database as and when it arrives. Part of the web database then becomes a repository for data that is in some half-way state, and so we would need to add to the tables that stored this type of data a column that indicated whether a given record was ‘pending’ or ‘completed’. For example, in a shopping cart application, we could have an OrderDetail table which stored individual items from an overall purchase. We might organise the table as:</p><p>Figure 10: A data table that could be used in an e-commerce site to store the state of a client’s shopping cart Note the last column of this table, LineTotal. You may consider that this attribute breaks the normalization of the database since it contains some information that is derivable from other attributes in this and other tables (the LineTotal value is the Quantity attribute of this table multiplied by the UnitCost attribute of the Product table, referred to by the ProductID attribute) – you are right, but this is a situation in which that redundant information serves two very good purposes. Firstly, if in the future the UnitCost of a given product was to change, we would not want to retrospectively alter all of the order information about purchases that were made before the price change – this attribute protects against that by storing the actual cost at the time of purchase. Secondly, and most importantly for our purpose of tracking a series of purchases over time, the LineTotal value can be inserted as and when an order is completed. We now have a way of tracking which orders are yet to be completed – i.e. those which are currently in the shopping cart but have not yet been checked out, by examining whether the LineTotal field is Null or not. From this we can see that OrderDetails 3 and 4 have not yet been checked out. When customers 3 and 2 next visit the site, these orders will be waiting for them to make up their mind on, and we can arrange the site to give them the chance to either cancel or complete the orders. If you have ever gone to a web site to make a purchase and, reaching the checkout stage, discovered items that you had selected on a previous visit and then forgotten about sitting in your shopping cart, you will have been a participant in this method or some variation on it. From the website manager’s point of view, this is a very positive thing – customers can visit the site over a period of time adding purchases to a shopping cart, and every time they come back, the cart is still waiting for them, and the purchases are more likely to go ahead.</p><p>Cookies and client tracking</p><p>Ideally, a client who returns to an eCommerce web site ought to be recognised. In the same way that when a customer is happy to be recognised and greeted by name when they return to a shop, so a website that deals with customers can, by some very simple means, keep track of them to make them feel like valued customers. The only reliable ways we have to track visitors to a website are that we either ask them to log in on the first page in a session, or (ideally) we ‘remember’ them by using a cookie. Unfortunately, some people will disallow the use of cookies, and so if we do intend to use them, we should always have a fall-back position. When a session starts, we should check for a cookie that identifies the client (a number in a database can be used as a primary key, and we can also make this the ID we store in the cookie). If one exists, we can look up the client’s details in the database and send back a personal greeting at the top of a page. If not, we can ask the client to log in as an existing customer, or click on a link to a log-in screen if they are not an existing customer. Given a database table of client details, we can arrange for this as follows:</p><p>‘This code should be in Index.asp so that visitors start here. If Request.Cookies(“CustID”) = “” Then ‘This is either a new client or one who does not allow cookies… Response.Redirect “login.html” Else ‘We know this client… Session(“CustID”) = Request.Cookies(“CustID”) Dim SQL SQL = “select Name from Customers where CustID = ‘“ & _ Request.Cookies(“CustID”) & “’” ‘ Now go on to greet the customer and show the initial page… ‘ …. End If Listing 8: Retrieving a client’s ID from a Cookie, and using this to get their name from a database The page login.html would provide a form for the customer to enter their email address and password and a link to lead new customers to a registration page. Note that while I am using a numeric CustID field here (this is easily generated by Access using an autonumber field), it is sometimes useful to make a client’s email address as their log-in identity – it is guaranteed to be unique and they are unlikely to forget it. Whatever is used for the customers’ ID field, they should also be required to provide a password on log-in, since otherwise it would be possible for strangers to get access to client’s details by fishing with random numbers or email addresses.</p><p>Queries, Actions and Recordsets</p><p>There are two types of operation we can ask an open ADO connection to perform. One is to ask for data to be returned in the form of a Recordset, and the second is to ask for some update-type operation to be executed. In the case of the second of these, we do not expect a Recordset to be returned as a result, but could expect some indication of whether the operation – either an Add, Delete or Update – was successful or not. The distinction made is that a query that results in a Recordset is a select query, while the other type is an action query. When we issue a select query, we need to assign the resulting Recordset to a variable:</p><p>Dim RS ‘ This will act as a reference to the returned object Dim conn Set conn = Application(“Conn”) Set RS = conn.Execute(“select * from OrderDetail where CustID = “ & _ custID) ‘ We can now go on to use the returned Recordset… ‘ …. Listing 9: Getting an ADO recordset In most normal situations, the Recordset returned from a select query will have either a single record (e.g. if the query issued was to retrieve the details of a particular customer with a specific ID number) or an unknown number of records (e.g. all of the current list of items in a customer’s shopping cart). In the former case, you will generally want to pick up the data and do something with it. First you should check whether any actual data was returned: Set RS = conn.Execute(“select * from Customers where CustID = “ & _ custID) If Not RS.EOF Then ‘ A customer record has been retrieved – do something with it… Response.Write “Hello “ & RS(“Name”) ‘ more actions… Else ‘ No customer record, so we need to log the client in… Response.Redirect “login.html” End If Listing 10: Checking whether a specific client exists in the database RS.EOF (End Of File) returns true if we are at the end of a Recordset (i.e. there are no more records in it). In the case where the Recordset has just been obtained, the first record should be current, so if EOF is true, there can be no matching record (the required match is for the given CustID value). For a query where any number of records can be expected (e.g. a customer can have zero, one or any number of matching orders in the OrderDetail table), we would typically step through them one at a time performing some action: Set RS = conn.Execute(“select * from OrderDetail where CustID = “ & _ custID & “ and LineTotal Is Null”) Do Until RS.EOF ‘ A customer record has been retrieved – do something with it… Response.Write “Order Item “ & RS(“ProductID”) & “ Quantity “ & _ RS(“Quantity”) RS.MoveNext Loop Listing 11: Stepping through each record in a recordset Note that here we have asked for all of the OrderDetail records for a specific customer (CustID) where LineTotal is a null value. This was our indication that the order item had not been processed yet, and so we’re displaying the id and price of items left in the shopping cart. The Do..Loop surrounding the statements is effectively – keep doing this until there are no more uncompleted orders to display, and note that the whole thing hinges on the RS.MoveNext statement at the end of the loop – without this, the same order’s retails will be written out to the client’s browser forever (or until either the web response times out or the client gets bored and moves away from the site). An action query does not return a Recordset. However, we can get some indication of whether the action was successful or not by checking for any errors after the command is executed: Dim conn Set conn = Application(“Conn”) Conn.Execute(“delete from OrderDetail where CustID = “ & custID) If conn.Errors.Count = 0 Then ‘ Success… Else ‘Something did not work… End If Listing 12: Finding out if an action query worked</p><p>The Dreamhome Active Website</p><p>You should be familiar with the Dreamhome example database from the Connolly and Begg book. While that database and the examples of tables, queries, forms etc. shown in the book were organised to make the links between data tables clear, a database that was to be managed by ASP code would never be constructed in quite that way. For a start, the primary key fields used (CRXX for client record, SXXX for staff, PXXX for rental property etc.) were devised to be easy for a reader to recognise and not for the practical necessities of running a commercial website (e.g. no real company would allow only 2 digits for the client number since a genuine realtor would be looking for that many clients per week). The sample application you can build as an Active website uses a variation on the Dreamhome database. Only a few tables are defined, and these are organized to be ‘automation friendly’. The structure of this Access database is shown below:</p><p>Figure 11: The Dreamhome rental database – this is a subset that will be managed by ASP code In this variation of the database, the PropertyForRent table contains attributes that specify a property (address, size and links to pictures of the house and its floor-plan), as well as links to the Branch that deals with the property, and the member of Staff who is responsible for it. The Viewing table lists details of appointments made by clients (through the web site) to view a rental property, and the Client table lists the details of the clients who have made the viewing appointments. There are a number of practical issues that make this database structure as unrealistic as the original for the purposes of running a property agency, but the structure is perfectly adequate for illustrating how a database can be managed by a web site, and easy to understand.</p><p>The database tables</p><p>For practical purposes, we would normally group database tables into two distinct groups – those that would be managed locally by the staff who run the company, and those that need to be managed by web pages. For the tables to be managed by web pages, we need to ensure that creating and managing records is as simple as possible. Specifically, this means we must ensure that data can be added to the Viewing and Clients table without intervention from staff; the primary key fields should be a simple numeric value since Access can generate these freely, while the coding style used in the other tables may be more meaningful, but would be more awkward to generate programmatically. The only other significant change is to the Staff table. Since this is information that will be viewed on a website, it makes sense to add contact information to the staff table (Telephone, Mobile and Email). </p><p>The site structure</p><p>An active website can be complex, even though individual pages may be easy to develop. Each page of a website is a potential target location that a client can bookmark in their browser, and so it is possible that clients will arrive at a page without having gone through any necessary pre-requisite pages. For example, if a client arrives directly at the page to book a viewing of a property without having first identified either which property they wish to view, or who they are, we must direct them off to pages that will let them do these things first. It is always a good idea to start with a list of the specific operations you wish to allow to be performed from a web browser and then considering all of the prerequisites for these. For example, we’ll go on to create web pages to do the following with the Dreamhome database: 1. Contact a member of staff at a branch if necessary 2. Join a mailing list of clients, so that you can be informed of any new properties that might meet your interest 3. View a list of properties that meet your criteria – right area, right size and right rental cost 4. Arrange to view a property at a time that is mutually suitable to you and the staff of the branch that has the property on its list 5. Send comments about a property you have viewed – is it suitable, do you want to rent it. This is a fairly small list of requirements compared to most commercial websites, but it does cover all of the necessary ground – you can view the ‘stock list’, reserve an item from the list (for viewing), arrange to take a particular stock item (a specific property viewing at a specific time) and complete the deal (reject or rent a property). However, in common with other e-commerce websites, there are a number of requirements that are not stated, implicit in the need to make the above requirements work. For example, joining a mailing list (requirement 2) requires that you must be able to upload some information about yourself – contact details as a minimum, but it would be as well also to include preferences about the types of properties you would be interested in. A mailing list enables the realtor to send information to you (by email or mail) whether you visit the website again or not, so it makes business sense for this facility to exist. When you find an interesting property listed on the site, either by browsing (requirement 3) or because a mailing list directed you to it, you will wish to book a viewing for it (requirement 4). That means you will need to create a viewing record, and this in turn will mean the site must be able to identify you by locating the appropriate mailing list entry. This will mean you will either have to log-in to the site before booking a viewing, or the site will have had to identify you automatically. When you wish to add comments about a property you have subsequently viewed (requirement 5), you will need to return to the viewing record created earlier, and again either the site will need to identify you in some way. Overall, in addition to the main requirements listed above, we need to provide for the following: 6. A visitor who accesses the site to book a viewing must either have their mailing list record retrieved, or, if they are not yet on the mailing list, must join it, so that we are able to create a booking for a known client 7. A visitor to the site must be able to view past booking records which are not yet resolved, so while the visitor must already be a client to make this possible, we must be able to recognise them as such. Creating the Dreamhome Active Website</p><p>The remainder of this document will go through the various stages needed to create the website, assuming a database as shown in figure 11. The entire site is available on the book website (where you got this document) as a zip file. You can either create all of the html and active server page files by entering the code in the listings below, or extract the files from the zip file into a folder that IIS has Virtual Directory access to. In either case, you will need to set up an appropriate virtual directory, and an associated folder in your wwwroot folder. Note that the following instructions assume that you have a working installation of Internet Information Server: 1. Create a folder directly under wwwroot to house the Dreamhome website – call this folder Dreamhome (i.e. c:\Inetpub\wwwroot\Dreamhome)</p><p>2. Run the Internet Information Services applet (from the Start button, select Administrative Tools, and then click on Internet Information Services). Click on Default Web Site and make sure that it is currently operational (in the Action menu, the Start item should be greyed out)</p><p>3. Right-click on the Default Web Site entry on the tree-view at the left, and select New, then Virtual Directory</p><p>4. In the Virtual Directory Creation Wizard that appears, click on Next, and in the VIrtual Directory alias page, enter the name Dreamhome in the text box marked Alias. Click Next again</p><p>5. In the Web Site Content Directory page, press the Browse button, and browse to the folder you create under wwwroot (Dreamhome). Select this and press OK</p><p>6. In the Access Permissions page, accept the default selections (Read and Run Scripts). Click next</p><p>7. If no error has been reported, click Finish to complete the creation of the new Virtual Directory to host your website.</p><p>All of the files that are needed to run the website will be placed in the Dreamhome folder, or folders placed under it. Do not delete the folder (_vti_cnf) that was automatically created by IIS – this is needed for the management of the site. Adding folders and files</p><p>Apart from the htm and asp files you will need to run the website, you will also need to install the database file (Dreamhome.mdb) and image files (*.jpg) available within the zip file from the book website. Once you have downloaded it, open the zip file (either double click on it from Windows XP Explorer, or use a zip manager, e,g, Winzip, PowerArchive or some other to open it) and extract Dreamhome.mdb and the all of the .JPG files from it. Dreamhome.mdb should be placed inside the Dreamhome folder, and all of the image files copied into a folder created for them. Create a new folder under the Dreamhome folder, call it Images, and place the graphic files there. </p><p>Code files</p><p>We will need to create HTML and ASP pages to fulfil the list of requirements shown above, and the normal requirements of operating a website. As a starting point, we need to create global.asa to set up Application and Session variables necessary to operate the site. All of the remaining files should be created directly in the Dreamhome folder:</p><p>Global.asa</p><p><SCRIPT LANGUAGE="VBScript" RUNAT="Server"> '************************************************** '*** GetConnectionString function '************************************************** '*** Written 9/5/2004 '*** Author: A.McMonnies '*** Revision 2.2 '*** Purpose - Function returns a string specifying '*** a full connection string for opening an Access '*** (JET 4.0) database in the same folder as the '*** page the function is run from. '************************************************** '*** Input parameter - db As String - name of MDB '*** database file. '*** Result - Connection-String As String '*************************************************** Function GetConnectionString(db) GetConnectionString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath(db) & _ ";Persist Security Info=False" End Function</p><p>' This will make sure the database connection is open and ready… Sub Application_OnStart() Set conn = Server.CreateObject("ADODB.Connection") conn.open GetConnectionString("Dreamhome.mdb") Set Application("Conn") = conn Session.TimeOut = 20 End Sub</p><p>' This will close the connection if we shut down the site… Sub Application_OnEnd() Application("Conn").Close() Set Application(“Conn”) = Nothing End Sub</p><p>Sub Session_OnStart() 'Keep track of the client for this session... Session("ClientID") = Request.Cookies("ClientID") 'and keep track of where to return to (from Login etc.)... Session("ReturnTo") = "" End Sub </SCRIPT> Listing 13: Global.asa – Code to set up and clear up Application and Session variables for the site Global.asa contains a number of functions. The first of these, </p><p>GetConnectionString(), is a simple housekeeping function that returns the connection string needed by an ADO Connection object to create a link to the database. It works on the assumption that the database is an Access database file (.MDB) and that it is in the same folder as the ASP code that uses the connection. We can move the database to a different folder provided the parameter ‘db’ passed to the function includes the relative location of the database – e.g. “databases/Dreamhome.mdb”, if the database was in a folder called database within the site home folder.</p><p>Application_OnStart() is a Sub (subroutine) that ensures that a database connection is formed and open as soon as the first request for a page within the site arrives. It makes this connection available through the Application(“Conn”) variable. We could make it the responsibility of each page that needed database access to create its own connection and open it, but this is a time-consuming process and it is best to minimize the amount it is done. By storing an open connection in an Application variable for the entire website, we have a single connection that any page can interact with to get data from or send data to the database. Note that this strategy will work fine where there is a single web server at work, but in a very large ASP application that used a ‘server farm’ (a set of servers that can all process HTML and ASP page requests to the same site), we would need to provide an active connection for each server to prevent problems with two or more pages trying to work a single connection object simultaneously. Application_OnStart() also sets a session timeout of 20 minutes. This is the time that a session’s state will be maintained without page requests, before IIS shuts it down.</p><p>Application_OnEnd() undoes the work of Application.OnStart() by closing the database connection and destroying the connection object. The Application object will keep the connection open while requests are coming in, but once every session that the application supports has closed or times out, an open connection sinple takes up memory for no purpose. Application_OnEnd() is executed automatically when IIS has determined that the application is currently idle.</p><p>Session_OnStart() is executed at the start of each new session, and helps to keep track of that session (separately from any others that may currently be active). In it we set up two session variables – Session(“ClientID”), which is retrieved from a client-side Cookie and which may be null, and Session(“ReturnTo”) which will be a useful way of keeping track of the page a user is currently on if we need to direct them away from it to log-in to the system. In addition to these functions, which are best performed for the Application as a whole and for specific Sessions, there are a number of functions that we can consider to be general enough to merit making them available throughout the website. The standard way of doing this is to place Subroutines and Functions into a special code file called an include file. As its name suggests, we can include the code in this in any ASP file we wish, so making the functions available to it.</p><p>DHFunctions.inc</p><p><!--Create and open a connection to the database--> <% '************************************************** '*** GetConnectionString function '************************************************** '*** Written 9/5/2004 '*** Author: A.McMonnies '*** Revision 2.1 '*** Purpose - Function returns a string specifying '*** a full connection string for opening an Access '*** (JET 4.0) database in the same folder as the '*** page the function is run from. '======'*** Input parameter - db As String - name of MDB '*** database file. '*** Result - Connection-String As String '*************************************************** Function GetConnectionString(db) GetConnectionString = "PROVIDER=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & Server.MapPath(db) & _ ";Persist Security Info=False" End Function</p><p>'************************************************** '*** GetClientName function '************************************************** '*** Written 19/5/2004 '*** Author: A.McMonnies '*** Revision 1.0 '*** Purpose - Function returns a string containing '*** client's name '======'*** Assumes a connection is already made '======'*** Input parameter - id - a string of clientNo '*** Returns Client's name. '======Function GetClientName(id) Dim RS Set RS = Application("Conn").Execute("select fName + ' ' + _ lName as Name from Client where ID=" & id) If Not RS.EOF Then GetClientName = RS("Name") Else GetClientName = "Client no " & id & " does not exist" End If RS.Close Set RS = Nothing End Function</p><p>'************************************************** '*** GetStaffName function '************************************************** '*** Written 19/5/2004 '*** Author: A.McMonnies '*** Revision 1.0 '*** Purpose - Function returns a string containing '*** staff member's name '======'*** Assumes a connection is already made '======'*** Input parameter - staffNo - a string of staffNo '*** Returns Staff member's name. '======Function GetStaffName(staffNo) Dim RS Set RS = Application("Conn").Execute("select fName + ' ' + _ lName as Name from Staff where staffNo='" & staffNo & "';") If Not RS.EOF Then GetStaffName = RS("Name") Else GetStaffName = "Staff no " & id & " does not exist" End If RS.Close Set RS = Nothing End Function</p><p>'************************************************** '*** RSToHTMLTable function '************************************************** '*** Written 9/5/2004 '*** Author: A.McMonnies '*** Revision 1.0 '*** Purpose - Function returns a complete HTML '*** table as a text string. Table content is all '*** of the data from a ADO Recordset. First row '*** is column (field) names, followed by one row '*** per record for the full table. '*** If table has an 'ID' column, a 'link' param '*** -eter can be supplied to specify the URL of '*** a linked ASP which will be sent the ID as a '*** GET parameter. '*** NOTE - use SQL AS syntax to set column names. '*** e.g. SELECT CustName As Name FROM.... '======'*** Input parameters '*** 1. RS As Recordset - an ADO recordset which is '*** currently open. '*** 2. borderwidth As Integer (normally a small '*** integer value - 1 or 2. '*** 3. URL of an ASP for accessing individual '*** records from the table (i.e. a detail view '*** page. Only to be used if the table in RS '*** has an ID field. '*** Result - HTML Text As String '*************************************************** Function RSToHTMLTable(RS, borderwidth, link) Dim TBL, i ' Response.Write "<h2>" & link & "</h2>" ' Generate a HTML Table from the recordset data... TBL = "<TABLE border=" & borderwidth & ">" ' Start with a header row... TBL = TBL & "<TR>" F = "" For Each fld In RS.Fields F = F & "<TH>" & fld.Name & "</TH>" Next If link<>"" Then F = F & "<TH>Select</TH>" End If TBL = TBL & F & "</TR>" ' Now the data... If Not RS.EOF Then RS.MoveFirst Do TBL = TBL & "<TR>" F = "" For Each fld In RS.Fields F = F & "<TD>" & fld.Value & "</TD>" Next If link<>"" Then F = F & "<TD><a href='" & link & "?ID=" & _ RS.Fields("ID").Value & "'>View Detail</a></TD>" End If TBL = TBL & F & "</TR>" RS.MoveNext Loop Until RS.EOF End If TBL = TBL & "</TABLE>" RSToHTMLTable = TBL End Function</p><p>'************************************************** '*** GenerateDateOptions function '************************************************** '*** Written 19/5/2004 '*** Author: A.McMonnies '*** Revision 1.2 '*** Purpose - Function returns a complete HTML '*** tag set for an option button with a range of '*** dates pre-loaded. This HTML should be embedded '*** in a form for user-input. '======'*** Input parameters '*** 1. StartDate As String - date at start end of range. '*** 2. EndDate As String - date at end of range. '*** Result - HTML Text As String '*************************************************** Function GenerateDateOptions(StartDate, EndDate) Dim HTML Dim Q Q = Chr(34) HTML = "<select name='optDate'>" Dim D D = StartDate Do While D <= EndDate HTML = HTML & "<option value=" & Q & D & Q & ">" & _ D & "</option>" D = DateAdd("d", 1, D) Loop HTML = HTML & "</select>" GenerateDateOptions = HTML End Function '************************************************** '*** GenerateHourOptions function '************************************************** '*** Written 13/6/2004 '*** Author: A.McMonnies '*** Revision 1.0 '*** Purpose - Function returns a complete HTML '*** tag set for an option button with a range of '*** hour-intervals pre-loaded. This HTML should '*** be embedded in a form for user-input. '======'*** Input parameters '*** 1. startHour As String - hour at start end of range. '*** 2. endHour As String - end at end of range. '*** Result - HTML Text As String '*************************************************** Function GenerateHourOptions(startHour, endHour) Dim h, options Dim Q Q = Chr(34) options = "<select name='optHour'>" h = startHour If endHour < startHour Then endHour = endHour + 12 End If Do While h < endHour options = options & "<option value='" & h & "'>" & h & _ ":00 to " & h+1 & ":00</option>" h = h+1 Loop options = options & "</select>" GenerateHourOptions = options End Function %> Listing 14: The DHIncludes.inc file – functions and subs for general use by ASP files. DHIncludes.inc is an include file that we refer to at the top of any ASP file we wish to incorporate its functions into. To make the functions in the file available, we use the following statement in an ASP file (note – DHFunctions must be in the same folder): <!--#INCLUDE FILE="DHFunctions.inc"--></p><p>Now we can call any of the functions within this file from our ASP file. The utility functions in here are:</p><p>Function GetClientName(id) – this function retrieves the full client name from the database given the ClientID. We can incorporate a call to it within ASP code, like: Response.Write GetClientName(3) which would display the name of the client with ID = 3 in the Client table of the database, or can even incorporate it directly into a HTML statement: <p>Hello <%=GetClientName(Session("ClientID"))%>. Nice to see you back.</p></p><p>Function RSToHTMLTable(RS, borderwidth, link) – this very useful function will display the entire contents of an ADO Recordset within a HTML table. On the potentially many times you wish to display the information retrieved from a database in a Recordset, simple call this function passing the Recordset into the function as the first parameter, a number (0, 1 or 2 is normal) for the second parameter, and, if you wish each record in the table to display a link to a page to show the data in more detail, the name of the ASP file to do the detail display in the third. The function displays the table with headings taken from the Field names in the Recordset, so for example, a Recordset that was created with the SQL statement “select Name, email from Client” would be displayed as a two column table with headings “Name” and “email”. You can take advantage of the SQL ‘as’ clause to define the headings you wish to be displayed. For example, given the Client table in the DreamHome database, the SQL statement: select FName + ‘ ‘ + LName as Name, telNo as Telephone, email from Client; could be incorporated into an ASP file with a statement like: Response.Write RSToHTMLTable(RS, 1, "") This would display the data selected with the headings, Name, Telephone and email as follows:</p><p>Name Telephone email Fred Flintstone 555 1234 [email protected] Wilma Flintstone 555 1234 [email protected] Albert Johnstone 555 6677 [email protected] Clark Kent 555 9999 [email protected] Joe Bloggs 123 4567 [email protected] Edward Scissorhands 123 4567 [email protected] Albert Enistein 555 6789 [email protected] Snorrie Sturrluson 333 4567 [email protected] Ferdinand Oblogiotta 123 5555 [email protected] Joe Schmoe 123 45678 [email protected] Bill Gates 123 5555 [email protected] Ermentrude Llama 000 2222 [email protected] Table 1: Results from the RSTOHTMLTable function in DHIncludes.inc If the third parameter is included, it should be the name of an ASP file in the same folder as the one that uses the RSToHTMLTable function. This page should expect a single GET value – Request.QueryString(“ID”) – where ID is the ID column of a database table. The ASP page can then display more information about this particular record.</p><p>Function GenerateDateOptions(StartDate, EndDate) – this function will display a drop-down box (sometimes called a ComboBox) containing the specified range of dates. You should call this function within HTML form code to provide the client with a range of dates to select. Date entry is always a problem in software if the user has to type a date, particularly in web pages where an invalid date that is entered may be sent back to the server before it is recognised as an error. Users can enter an invalid date (such as 30th February), a date the past or even text that is nothing like a date, and if this is sent to the server, the result can only waste time and processing. Using the GenerateDateOptions function, you can ensure that the user is only able to enter a valid date from a selected range. The resulting date form a form can be picked up as Request.Form(“optDate”).</p><p>Function GenerateHourOptions(startHour, endHour) – this function will display a drop-down box containing a list of 1-hour ranges (e.g. 11:00 – 12:00) to allow a user of the site to select a time for an appointment. The resulting hour is a single number (the starting hour of the range) that can be picked up from Request.Form(“optHour”).</p><p>Now that we have set up the basic housekeeping for our active web site, we can think about what pages we will need to fulfil the functions listed earlier. Page name Purpose Links to Index.asp Display company information, and JoinMailingList.htm, list the available options. Note Rentals.htm, that these will be different for ReviewViewings.asp visitors depending on whether they are on the mailing list or not. This page can also display contact details. Rentals.htm This page allows a visitor (whether Rentals.asp on the mailing list or not)to view Index.asp properties that meet some selected criteria (size, location, price etc.). This page does not need to be active, since forms are supported in standard HTML Rentals.asp This page displays the list of Index.asp rentals that matches the criteria ViewProp.asp entered into Rentals.htm. ViewProp.asp Returns details of a property Index.asp selected on the Rentals.asp page BookViewing.asp Login.htm Provides a form for entering log-in information. Again, this page does not need to be active. JoinMailingList.htm Provides a form for a new client to AddToMailingList.asp add their contact and other details Index.asp to. Again, this pages can be plain HTML AddToMailingList.asp Takes the information from the Index.Asp JoinMailingList form and enters it into the Client table of the database BookViewing.asp This will have a form for booking Index.asp a property viewing, but since it CreateBooking.asp will be accessed via the Rentals.asp page, it will need to be active so that it can identify the Client and the Property CreateBooking.asp This page will do the job of adding Index.asp a booking record (details entered at BookViewing.asp). ReviewViewings.asp This page will allow a registered ViewingComments.asp client (on the mailing list) to Index.asp examine the records of any viewings they have made, either to cancel the viewing or to make comments and/or rent the property. ViewingComments.asp This page will accept any Index.asp comments or a reservation to rent a property Table 2: The files in the Dreamhome website</p><p>Exercise 1: Index.asp - the Home page</p><p>In this page, we should be able to identify a client who has already registered with the site provided their browser supports Cookies. For requests from browsers that do not support Cookies, we will need to make it possible for a client to log-in to the site. When a Session commences here, we can set up a session variable, ClientID, to indicate whether we know the client or not, having first checked for a Cookie. Of course, we may have the client on the system but their browser does not support Cookies, in which case we need to treat the client as a newcomer anyway. The logic of the page is straightforward: If the ClientID session variable has a value Greet the user Display registered user site options Else Display standard (non-client) site options End If What is displayed depends on the value in Session(“ClientID”) (which will have been set up in the Session_OnStart() routine in Global.asa. If this is nothing, we will display only the standard options available to non clients. If there is a ClientID value, we can retrieve and display that client’s name so that the page can greet them, and then display the options available to registered clients: view properties for rent or display details of any property viewings the client has had in the past. If there is not a ClientID value, we will display the standard options: view properties for rent, join the mailing list and log-in as a client. This last option is necessary is case the person visiting the site is a client, but does not allow Cookies in their browser. Joining the mailing list will of course create a ClientID for a visitor so that their next visit to this page will display the client-only options. A small amount of ASP code performs this functionality… <!-- This sequence will start every page. --> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <html> <head> <title>DreamHome Estate Agents</title> </head> <body> <h1>DreamHome Estate Agents</h1> <% ' Do we know this client... If Session(“ClientID”) <> "" Then Name = GetClientName(Session(“ClientID”)) Response.Write "<h3>Hello " & Name & "</h3>" Response.Write _ "<p>Welcome back to the Dreamhome website.</p>" Response.Write _ "<p><a href='rentals.htm'>Properties for Rent.</a></p>" Response.Write "<p><a href='ReviewViewings.asp'>” & _ “Check your viewing appointments.</a></p>" Else Response.Write _ "<p><a href='rentals.htm'>Properties for Rent.</a></p>" Response.Write "<p><a href='AddToMailList.htm'>” & _ “Join our Mailing List</a></p>" End If %> </body> </html> Listing 15: The Index.asp file – recognising a client (or not). Given what we have already covered, the only notable features of this page are the statements that display hyper-links. For example: Response.Write _ "<p><a href='rentals.htm'>Properties for Rent.</a></p>"</p><p>Note how the address of the linked page is embedded in single quotes (‘) as the href attribute of the <a> (anchor) HTML tag. Testing the Index page is a bit more complex than simply entering the URL into a browser and checking whether the correct result appears, since we are aiming to support both systems that allow cookies and systems that do not. For this purpose, I’ve found it useful to place an extra page in the website – SetClient.asp. This simple ASP lets you set up a client cookie with a specific client value, clear the current setting and find out what the current cookie setting is: <%@ LANGUAGE = VBScript %> <html> <head><title>SetClient.ASP</title></head> <body> <% If Request.QueryString("ID") = "none" Then Response.Cookies("ClientID") = "" Response.Cookies("ClientID").Expires = Date Session.Abandon Response.Write "Client reset" ElseIf Request.QueryString("ID") = "who" Then Response.Write "'ClientID' Cookie now set to : " & _ Request.Cookies("ClientID") ElseIf Request.QueryString("ID") = "" Or _ Request.QueryString("TimeOut")= "" Then Response.Write _ "Usage: SetClient.asp?ID=[id_value_to_set]&TimeOut=[number_of_days]" Else Response.Cookies("ClientID") = Request.QueryString("ID") Response.Cookies("ClientID").Expires = DateAdd("d", _ Request.QueryString("TimeOut"), Date) Response.Write "'ClientID' Cookie now set to : " & _ Request.QueryString("ID") & "<br>" Response.Write "Cookie will time out on " & DateAdd("d", _ Request.QueryString("TimeOut"), Date) End If %> </body> Test Listing 1: An ASP page for setting up, changing and clearing a client cookie. Using the ASP in Test Listing 1 we can set up the test browser to have a particular customer ID by entering the following into the address bar: http://<ServerName>/Dreamhome/SetClient.asp?ID=25&TimeOut=30 This setting would make the client ID 25, and would set the cookie to persist for 30 days, after which the browser would discard it. If you forget what the current client ID setting is: http:// <ServerName>/Dreamhome/SetClient.asp?who remembering to supply the correct server name (or localhost) for your own machine. Removing the cookie is as simple as: http:// <ServerName>/Dreamhome/SetClient.asp?none Finally, if you were to forget any of this, the following would display the various ways of setting up the cookie in the browser: http:// <ServerName>/Dreamhome/SetClient.asp</p><p>Using this utility, we can now test the Index.asp page by setting various client ID’s and checking that the client’s name is displayed in the browser. Clearing the client ID, we can then test that Index.asp displays appropriate options for a new client.</p><p>Exercise 2: Rentals.htm – The form for indicating rental preferences</p><p>This file does not include any ASP code (as an HTML file, any code that it contained would be ignored by the server’s script engine). However, it does serve a very important purpose for an active website, since it defines a form that will collect user- input and send it back to the browser. The form code is as follows: <html> <head><title>Rentals.htm</title></head> <body> <h1>House Rentals</h1> <p>Select the criteria for your ideal home...</p> <form method="POST" name="frmRentals" action="rentals.asp"> Area <select name="RentalArea"> <option selected value="ANY">Don't care</option> <option value="B002">North London</option> <option value="B005">South London</option> <option value="B007">Aberdeen</option> <option value="B003">Glasgow</option> <option value="B004">Bristol</option> </select></p> <p> <input type="radio" value="House" checked name="RentalType">House <input type="radio" name="RentalType" value="Flat">Flat</p> <p>Number of Rooms <select name="RentalSize"> <option value="1">1</option> <option value="2">2</option> <option value="3" selected>3</option> <option value="4">4</option> <option value="5">5</option> <option value="6">6</option> <option value="7">7</option> <option value="8">8</option> </select></p> <p>Maximum rental (per month) £ <input type="text" name="RentalMaxPrice" size="10" value="500"></p> <p> <input type="submit" value="Find suitable houses" name="RentalSubmit"> <input type="reset" value="Reset"></p> </form> <p><a href="index.asp">Home</a></p> </body> </html> Listing 16: rentals.htm – This defines the form shown in Figure 12 The code in listing 16 defines a HTML form. In the form (shown in figure 12), there are two drop-down boxes for the user to select from, plus a pair of option buttons, a text box and a pair of buttons. A HTML form is created in HTML code using the <form>…</form> tags. Since a form is usually required to have an action (in this case, to call up a specific ASP page), this is defined in the opening <form> tag as the ‘action’ property. When the form is submitted, this page will be executed at the server. The HTML code applies names and values to a range of form-based controls – text boxes, option buttons, lists etc. The values entered into these control can be retrieved in ASP code by referring to the specific control; for example Request.Form(“RentalMaxPrice”) will provide the value that the user entered into the box labelled “Maximum rental (per month)”. In some cases (e.g. select controls), a set of options is grouped within an overall tag, and the chosen option is referred to by the name of the outer tag; e.g. Request.Form(“RentalArea”) will retrieve whichever value was selected from the drop-down box. The end result is a file that is defined to inter-operate with a related ASP file. Rentals.htm sends its data to rentals.asp courtesy of the form’s action property (set to “rentals.asp”). </p><p>Request.Form(“RentalArea”)</p><p>Request.Form(“RentalType”)</p><p>Request.Form(“RentalSize”)</p><p>Request.Form(“RentalMaxPrice”)</p><p>Figure 12: The rentals.htm page. Given this form, generating a list of rentals from the database that match the user’s input is a remarkably simple task, and is done by rentals.asp which provides the response to Rentals.htm.</p><p>Exercise 3: Rentals.asp</p><p><!-- This sequence will start every page. --> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <HTML> <HEAD><TITLE>Rentals View Page</TITLE></HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <p>Rentals that match your criteria...</p> <% Set Conn = Application("Conn") ' Formulate a SQL Select query, uising # to mark values... SQL = "SELECT propertyNo As ID, street, city, " & _ "postcode, fname+' '+lname " & _ "As contact FROM PropertyForRent INNER JOIN staff " & _ "ON PropertyForRent.staffNo=staff.staffNo " If Request.Form("RentalArea") = "ANY" Then SQL = SQL & "WHERE type = '#2' AND rooms >= #3 & rent <= #4" Else SQL = SQL & "WHERE PropertyForRent.branchNo = '#1' AND “ & _ "type = '#2' AND rooms >= #3 AND rent <= #4" End If ' Now replace the values... SQL = Replace(SQL, "#1", Request.Form("RentalArea")) SQL = Replace(SQL, "#2", Request.Form("RentalType")) SQL = Replace(SQL, "#3", Request.Form("RentalSize")) SQL = Replace(SQL, "#4", Request.Form("RentalMaxPrice")) Set RS = Conn.Execute(SQL) If Not RS.EOF Then Response.Write RSToHTMLTable(RS, 1, "viewProp.asp") Else Response.Write("No matches found for your criteria.") End If RS.Close %> </BODY> </HTML> Listing 17: Rentals.asp Rentals.asp performs a task that will be familiar to any database developer – given a number of criteria, such as the area that a property is in, the number of rooms needed and the maximum price per month a client will pay, it will form a SQL query that sets these criteria and execute the SQL on the database. The data that is returned will be in the form of an ADO Recordset, but we already have a facility for displaying that in a browser (RSToHTMLTable). Forming the SQL query is interesting. The general format of the query will be: select <field list> from PropertyForRent where <criteria list></p><p>Our problem is that the criteria will come from form controls sent from Rentals.htm, so we need to embed these into the SQL. This can be messy, since the overall SQL statement will be a sequence of database column names interspersed with values extracted from the form. However, VBScript has a neat feature – a string function called Replace(), that we can use to form the overall statement in a sequence of logical and orderly steps. We start by storing the basic SQL statement in a string variable, ignoring any criteria (‘where’ clauses): SQL = "SELECT propertyNo As ID, street, city, postcode, “ & _ "fname+' '+lname " & _ "As contact FROM PropertyForRent INNER JOIN staff “ & _ "ON PropertyForRent.staffNo=staff.staffNo "</p><p>Note that we’re incorporating contact details into the result by joining the PropertyForRent table with the Staff table, and concatenating the fName and lName files in the Staff table to simplify the result. Now we need to limit this overall query, by selecting only those properties that match the requirements set out on the form. The criteria specified can be branchNo (the location), type (house or flat), rooms (minimum number) and rent (maximum), or simply type, rooms and rent, if the user accepted the “Don’t Care” (ANY) option for area. We can determine which version of the criteria is needed by checking the Request.Form(“RentalArea”) value, and then form the ‘where’ clause of the SQL, and adding it to the existing SQL statement: SQL = SQL & "WHERE type = '#2' AND rooms >= #3 & rent <= #4" or SQL = SQL & "WHERE PropertyForRent.branchNo = '#1' AND " & _ "type = '#2' AND rooms >= #3 AND rent <= #4"</p><p>Note however that instead of specifying each criterion in this clause, we simply have place markers #1, #2, #3 and #4. These are distinctive values that we know will not appear in a normal SQL statement. We can now go on to replace each marker with the actual value taken from the form: SQL = Replace(SQL, "#1", Request.Form("RentalArea")) SQL = Replace(SQL, "#2", Request.Form("RentalType")) SQL = Replace(SQL, "#3", Request.Form("RentalSize")) SQL = Replace(SQL, "#4", Request.Form("RentalMaxPrice"))</p><p>The end result is a complex SQL string that specifies all of the criteria stated on the form, without what would be a mess of partial statements joined up with bits of form variables. It is now a comparatively trivial matter to execute the SQL on the database, and display the returned result using our RSTOHTMLTable function (from DHIncludes.inc), not forgetting to close the Recordset once it has been formatted for display. Note that “viewProp.asp” is given as the third parameter in the call to RSToHTMLTable(); this will have the purpose of adding a HTML <A> tag – a hyperlink, to every ID tag in the table (we have engineered one of these in every row by specifying “select propertyNo As ID…” in the SQL). The result is that each entry in the table will link to a page that provides details of the property. The end result is shown in figure 13. Note that the ‘View Detail’ link in the last column is a hyperlink (the target for the first on is shown in the status bar because the mouse pointer is hovering oven the link):</p><p>Figure 13: The result of executing Rentals.asp with the form values shown in figure 12 Note that the hyperlink in each row is to a file called viewProp.asp. We should move on to develop this file now.</p><p>Exercise 4: ViewProp.asp</p><p>A user will only get to this page by selecting a property from the list of matches generated by Rentals.asp (the client could bookmark a specific property having navigated to it, but the bookmark would still include the specification of the property due to the way that the GET method works, incorporating data after the ? in the page URL). Look closely at the status bar in figure 13, and you see that the URL that links to the viewProp page includes a QueryString setting (in this case, ID=PA14). The viewProp ASP file will simply need to retrieve the property record that matches this ID and display its details. <!-- This sequence will start every page. --> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <HTML> <HEAD> <TITLE>View Property Page</TITLE> </HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <p>Your selected rental...</p> <% Dim propID propID = Request.QueryString("ID") Dim SQL SQL = "SELECT * FROM PropertyForRent WHERE propertyNo = '" & propID & "';" Set RS = Application("Conn").Execute(SQL) If RS.RecordCount <> 0 Then ' Display the property details... Response.Write "<b>Property Code: </b>" & RS("propertyNo") & "<br>" Response.Write "<b>Street Address: </b>" & RS("street") & "<br>" Response.Write "<b>City: </b>" & RS("city") & "<br>" Response.Write "<b>Property Type: </b>" & RS("type") & "<br>" Response.Write "<b>Number of Rooms: </b>" & RS("rooms") & "<br>" Response.Write "<b>Monthly Rental: </b>" & RS("rent") & "<br>" Response.Write "<b>Staff Cotntact: </b>" & RS("staffNo") & "<br>" Response.Write "<b>Branch Code: </b>" & RS("branchNo") & "<br>" Response.Write "<hr>" ' Display picture... If Not IsNull(RS("picture")) Then Response.Write "" End If ' and plan... If Not IsNull(RS("floorPlan")) Then Response.Write "" End If Response.Write "<hr>" Response.Write "<a href=bookViewing.asp?PropID=" & _ propID & ">Arrange a Viewing</a><br>" Response.Write "<a href=index.asp>Home</a><br>" Else Response.Write("No matches found.") End If RS.Close %> <hr> </BODY> </HTML> Listing 18: viewProp.asp – a script to display details of a selected rental property. This page also displays up to two pictures associated with a given database entry. The data table, PropertyForRent, includes two fields that refer to picture files. Note the two attributes – picture and floorPlan. Both are text attributes that will hold the path and filename of an image file. The first is for a photograph of the property, the second for a floor plan drawing. Inserting a picture into a HTML document is a simple matter of creating an <img> tag, which contains a ‘src’ attribute that can reference a picture file in GIF (line-art) or JPG (photograph) format. For example, the tag will refer to a file named ‘imageFile.jpg’ in a folder named ‘images’ contained in the folder that the HTML file is in. If this file exists, it will be displayed in the browser. In listing 18, two If..Then statements control whether an image tag is inserted into the web page. The IsNull() function returns True if the specified attribute has a null value (i.e. it is empty). Provided the attribute is not empty, the controlled statements: Response.Write "" write out a HTML <img> tag that inserts these pictures. The result is that if photographs and line art further describing the propertyForRent are available and have been referenced properly, these will be displayed in the web page: Figure 14: The viewProp.asp page, showing a photo and a floorplan image of the property.</p><p>Exercise 5: Booking a viewing</p><p>If the user were to select the link “Arrange a Viewing” in the viewProp page, the action of arranging a viewing would rely on there being a data record for that user in the Client table. Recall that when the session started we attempted to identify the client by accessing the ClientID Cookie (using the Session_OnStart() sub in globals.asa). There were three possible outcomes to this: 1. The ClientID cookie existed, and so we will have set the ClientID session variable to this value, and we now know exactly who we need to arrange a viewing for 2. The ClientID cookie did not exist because this was a new client who had never registered with the site, so currently the ClientID session variable is empty 3. The ClientID cookie did not exist because although the client was already registered, the client’s browser does not accept cookies – again Session(“ClientID”) would be empty. Because of this, if a client is to arrange to view a property, we must start by trying to retrieve the client’s ID from a session variable. If the session variable is empty, we need to re-direct the client’s browser to a log-in page. The logic to do this is easy enough, but does require that a page we are using for input will also need to contain logic to test the client’s ID and perform the re-direction. <!--bookViewing.asp--> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <html> <head><title>Book A Viewing</title></head> <body> <% ' First we need to establish who the booking is for... If Session("ClientID") = "" Then ' Don't know who - the client will need to log-in. ' Start by storing the url used to access this page so we can easily ' return to it... Session("ReturnTo") = Request.ServerVariables("URL") & "?" & _ Request.ServerVariables("QUERY_STRING") Request.ServerVariables("QUERY_STRING") Response.Redirect "login.htm" Else ' We know this client, so can continue with the booking... Dim propID, SQL, RS Session("ReturnTo") = "" propID = Request.QueryString("propID") ‘ Storing the propID in a session variable will simplify things… Session("propID") = propID SQL = "select * from PropertyForRent inner join Staff " & _ "on PropertyForRent.StaffNo=Staff.StaffNo " & _ "where PropertyForRent.PropertyNo='" & propID & "';" Set RS = Application("Conn").Execute(SQL) Response.Write "<h2>Book a viewing</h2>" Response.Write "<form method='POST' action='createBooking.asp'>" Response.Write "<h3>Property: " & RS("street") & ", " & _ RS("city") & ", " & RS("PostCode") & "</h3>" Response.Write "<h3>Client Name: " & _ GetClientName(Session("ClientID")) & "</h3>" Response.Write "<h3>Agent Name: " & _ GetStaffName(RS("Staff.StaffNo")) & "</h3>" Response.Write "<h3>Viewing Date: " & _ GenerateDateOptions(Date+1, Date+8) & "</h3>" Response.Write "<h3>Preferred Hour: " & _ GenerateHourOptions(9, 5) & "</h3>" Response.Write "<br><input type='submit' value='Submit'>” & _ "<input type='button' value='Cancel'></form>" End If %> </body> Listing 19: bookViewing.asp – note the test for a ClientID cookie at the start In bookViewing.asp, we first check whether we know the client. Depending on the outcome, we need to arrange the page to perform one of two possible functions – it will either send the client’s browser off to the login page, or it will act as a form, providing the user with input controls on the browser. If there is no Session(“ClientID”) value, we send the client’s browser to the login page. However, before we do this, it is a good idea to keep track of where we currently are. The Session(“ReturnTo”) variable can be used to store a URL for a ‘return address’. We can also include the ID of the property the client is arranging a viewing for as a QueryString parameter, by simply appending it to the end of the URL. The formatting for this is simple – Request.ServerVariables(“URL”) gives the address we want to return to (the address of this ASP page), while Request.ServerVariables(“QUERY_STRING”) is the name and value of the query string we sent to this page to identify the property to be viewed (the Property ID). The advantage of using a query string instead of a session variable here is that the user can bookmark specific properties in his/her browser since the property ID is contained in the URL. Having set up the address to return to after the log-in process, we can now send the client’s browser to the log-in form (we will deal with this in listings 21 and 22). Response.Redirect() simply re-orients the client’s browser at the specified URL. If we already know who the client is, the Else case in listing 19 takes over. In this, we build a SQL statement to retrieve more details about the viewing (more as a way of providing the client with better information than because we need to – the SQL statement simply returns more information about the property and the agent who looks after it), collect the information and then set about building a form to allow the user to say when the viewing is requested for. Figure 15 shows the form: Figure 15: the form for arranging a viewing – note this form is generated in ASP code. There are two statements in particular that make the creation of this form stand out from other HTML forms: Response.Write "<h3>Viewing Date: " & _ GenerateDateOptions(Date+1, Date+8) & "</h3>" Response.Write "<h3>Preferred Hour: " & _ GenerateHourOptions(9, 5) & "</h3>"</p><p>In the first of these statements, the GenerateDateOptions() function is used to create the drop-down box of dates you can see in Figure 15. Given two dates (in this case, tomorrow’s date and a date 8 days from now), the function will generate HTML code to provide the user with a box from which to select a date in this range. If we were to ask the user to type a date into a text box, we would be asking for trouble, since the user could enter a date in the past, one on a day so far in the future that it might affect office planning, an invalid date (such as the 30th February) or any of a range of other circumstances we could not cope with. By providing the client with a fixed list of dates to choose from, we have simplified the whole procedure. The second statement calls on GenerateHourOptions() to provide the same sort of facility for selecting a suitable time for a viewing. Of course, the combination of date and time selected by the user might cause another problem, since the required agent could be busy with some other job, but once a provisional viewing date and time is booked, the agent would be able to contact the customer and negotiate a more suitable appointment. Once a client has filled in this form and pressed the Submit button, an ASP page will have to deal with recording the viewing data. This is a simple enough process: <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <html> <head><title>Create a new booking</title></head> <body> <% Dim SQL If Session("propID") <> "" Then SQL = "insert into Viewing(clientID, propertyNo, " & _ "viewDate, viewHour) values(#c, '#p', '#d', #h);" SQL = Replace(SQL, "#c", Session("ClientID")) SQL = Replace(SQL, "#p", Session("propID")) SQL = Replace(SQL, "#d", Request.Form("optDate")) SQL = Replace(SQL, "#h", Request.Form("optHour")) Application("Conn").Execute SQL Response.Write "<h2>Booking created</h2>" Response.Write "<h3>Property: " & Session("propID") & "</h3>" Response.Write "<h3>Date: " & Request.Form("optDate") & "</h3>" Response.Write "<h3>Hour: " & Request.Form("optHour") & "</h3>" Response.Write "<a href='index.asp'>Home</a>" Else Response.Write "No property has been selected." Response.Write "<a href='index.asp'>Home</a>" End If %> </body> </html> Listing 20: createBooking.asp – ASP code to insert data for booking a viewing into a database table The ASP code in createBooking.asp starts by checking whether a property has been specified in a session variable. If it has not, the user has probably got here by having bookmarked it in their browser – we should avoid trying to deal with it in this case as there is too much other information that will be needed. The database insertion is done simply by creating the SQL statement needed to do it, and then passing this to the database connection’s Execute command. Note we have again made use of the Replace() function to make adding the parameters to the SQL Insert statement easier. Once the data has been inserted, all that remains is to confirm the appointment to the client (by writing back the property, date and time to their browser) and provide a link for the client to go back to the home page.</p><p>Exercise 6: Logging-in</p><p>If a new or unknown client attempts to create an appointment to book a viewing, we need to get them to log-in to the site. If he/she is already registered on the site, we simply need to ask for a username and password. If, not, we need to go through the entire registration process. The problem is, we don’t know which. The usual strategy is to provide a page for existing registered users to log-in, but to provide a link to a registration page for new users, and hope that they will realize they need to follow this link (we can always spell it out on the page). Providing this page is easy since it will contain only HTML <!-- Login.htm --> <html> <head><title>Log-in</title></head> <body> <h2>Registered users</h2> <h4>If you have previously registered with Dreamhome, please enter your email address and password</h4> <form method="POST" action="login.asp"> <h3>Email <input type="text" name="txtUsername" size="30"></h3> <h3>PostCode <input type="text" name="txtPostCode" size="10"></h3> <br><input type="submit" value="Submit"> <input type="button" value="Cancel"> </form> <h2>New users</h2> <h4>If you have not registered with Dreamhome, please follow this link <a href="AddToMailList.htm">Register with Dreamhome</a></h4> </body> </html> Listing 21: login.htm – a simple HTML page to allow a user to log in. Note, a link is provided to allow a new user to go to a registration page. To get to this page (login.htm), a user should have been redirected from the bookViewing.asp page. An existing client will log in to the site using their email address and PostCode. Using the client’s email address and PostCode in combination provides some minimal security here (a stranger is unlikely to guess both) – probably enough given the nature of the operation. However, you ought to consult the most up to date data-protection laws before you consider using this strategy for allowing people to log in to a site which contains sensitive information. The form appears in a browser as shown in figure 16: Figure 16: The login form. Note that the form on this page invokes login.asp, which contains code to retrieve the client’s ID if the correct combination of email and PostCode are used. Users of the site who are not registered clients will need to select the link to AddToMailList.htm, which provides a form for registering a client. Login.asp uses a SQL query to try to retrieve an ID from the client table, given an email address and a postcode: <!-- Login.asp --> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <html> <head><title>login.ASP</title></head> <body> <% Dim SQL, PostCode, Email Email = Request.Form("txtUserName") PostCode = Request.Form("txtPostCode") SQL = "select ID, FName + ' ' + LName as Name from Client" SQL = SQL & " where email = '" & email & _ "' AND PostCode = '" & PostCode & "';" Set RS = Application("Conn").Execute(SQL) If Not RS.EOF Then Session("ClientID") = RS("ID") Response.Cookies("ClientID") = RS("ID") Response.Cookies("ClientID").Expires = Date+30 Response.Write "<h2>Welcome back " & RS("Name") & "</h2>" Response.Write "<p><a href='" & Session("ReturnTo") & _ "'>Return to arrange a viewing.</a>" Else Response.Write "<h2>Log-in failed</h2>" Response.Write "<p>Press the Back button on the " &_ "browser to try again, " & _ "or follow this link: " & _ "<a href='AddToMailList.htm'>Register</a></p>" End If RS.Close Set RS = Nothing %> </body> </html> Listing 22: Login.asp. Retrieving the client’s ID Having issues a SQL statement to try to retrieve the client’s ID, we can determine whether the client record was found by testing whether the Recordset returned is empty – the EOF function does this test. Note that having retrieved a client’s ID in Login.asp, we can set the ClientID Session Variable so that other pages on the site know who the client is, and attempt to set a Cookie on the client’s computer to save them having to log in in future. The Cookie is set to expire in 30 days, which is an ample window of time for the client to return in. If a cookie already exists, refreshing it will re-set the expiry date to 30 days in the future.</p><p>Exercise 7: Registering</p><p>A client who can not log-in must register before he/she can arrange a viewing, and can get to the page to do this by selecting the ‘Register’ link set up at the end of the Else clause in listing 22. Registration is another user-input arrangement where the user’s data is entered in to an HTML form, and then sent by the form to an ASP page which updates the database. The HTML form is shown in figure 16, with the HTML code to generate it shown in listing 23. Figure 16: AddToMailList.htm – the HTML form for collecting client data <html> <head> <title>New Page 1</title> </head> <body> <h1>Join our Mailing List</h1> <p>Please enter the details below (do not omit any items marked *):</p> <form method="POST" name="frmRentals" action="AddToMailList.asp"> <p>* First Name: <input type="text" name="txtFName" size="30"> * Last Name: <input type="text" name="txtLName" size="30"></p> <p>Telephone: <input type="text" name="txtTel" size="20"></p> <p>* Street Address: <input type="text" name="txtStreet" size="30"></p> <p>* City : <input type="text" name="txtCity" size="30"></p> <p>* PostCode: <input type="text" name="txtPostCode" size="10"></p> <p>* Email: <input type="text" name="txtEmail" size="40"></p> <p>Where do you want to find a house: <select size="1" name="cboRegion"> <option>(N/A)</option> <option>London (North West)</option> <option>London (South)</option> <option>Bristol</option> <option>Glasgow</option> <option>Aberdeen</option> </select></p> <p>Preferred Rental Type: <input type="radio" name="optPreType" value="House">House <input type="radio" name="optPreType" value="Flat">Flat</p> <p>Maximum Monthly Rent: £ <input type="text" name="txtMaxRent" size="10"></p> <p><input type="submit" value="Join Mailing List" name="btnMailListSubmit"> <input type="reset" value="Reset" name="B2"></p> <input type="hidden" name="Client" value="NONE"> </form> <p><a href="index.htm">Home</a> </body> </html> Listing 23: AddToMailList,.htm - the HTML code for the form As you can see from the Form tag in listing 23, when the user presses the Submit button, the form information is sent to AddToMailList.asp, an active server page for creating a new client record. Because adding a client record is a bigger operation than adding a record for a viewing (the Client table has more fields), the ASP page to do this is a bit longer than we have seen so far. However, the operation is no more difficult than that of adding a viewing record. The method for adding to the Client table is different however, since instead of constructing a SQL INSERT statement, it uses the AddNew operation of a Recordset. This method has the advantage that we can add a record a field at a time, and since the Recordset provides a more direct link between ASP code and the database, we can also retrieve the Client’s ID field value, which, as an autoincrement field, we do not set explicitly. Since we need this to set up the ClientID session variable and Cookie, the facility is helpful. Since the code listing is longer, we’ll look at it in two chunks: <TITLE>Add to Mailing List</TITLE> </HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <h3>Mailing List</h3> <% ' Need to insert a record to Clients table of the database. ' We need to add a clientNo, so best approach is to open a writable recordset... Dim RS, conn Set conn = Application("Conn") Const adOpenDynamic = 2 Const adLockOptimistic = 3 Const adCmdTable = 2 Set RS = Server.CreateObject("ADODB.Recordset") RS.Open "Client", Conn, adOpenDynamic, adLockOptimistic, _ adCmdTable ' The client table is now open. Add a new record... RS.AddNew RS("fName") = Request.Form("txtFName") RS("lName") = Request.Form("txtLName") RS("TelNo") = Request.Form("txtTel") RS("Street") = Request.Form("txtStreet") RS("City") = Request.Form("txtCity") RS("PostCode") = Request.Form("txtPostCode") RS("email") = Request.Form("txtEmail") RS("JoinedOn") = Date RS("Region") = Request.Form("cboRegion") RS("preType") = Request.Form("optPreType") RS("maxRent") = Request.Form("txtMaxRent") RS.Update ' Now set the clientNo up in the user's browser... clientNo = RS("ID") Response.Cookies("ClientID") = clientNo Response.Cookies("ClientID").Expires = DateAdd("m", 3, Date) Session("ClientID") = clientNo ‘.... ‘ Continues… Listing 24: AddToMailList.asp part 1 – this code creates a new data record, retrieves the ID field for it and sets the Session variable and Cookie to suit When we retrieve a Recordset from a SELECT statement sent to a connection, ADO sends back the simplest form of Recordset available. This type of Recordset is capable of allowing the data to be read but not added to or changed, and allows us to look at records only in the order in which they appear in the Recordset – we can not scroll backwards to examine a record we’ve visited already. Technically, the Recordset is set as a ForwardOnly Recordset. How we create the Recordset influences what it can do, and to make a Recordset that we can write new data into, we need to open it in a more capable mode. The statements: Set RS = Server.CreateObject("ADODB.Recordset") RS.Open "Client", Conn, adOpenDynamic, adLockOptimistic, adCmdTable first create a new Recordset (in much the same way that we create a database connection), and then opens it for dynamic access (we can move to and update any record in the database or add new ones), with optimistic locking (updates to the database will be guarded against conflicting updates from another user of the database happening concurrently) and whole table access (the Recordset that is opened is the whole Client table, rather than a subset of it or a join with another table). This type of Recordset allows us to do what is needed to add new data. Having opened the correct form of Recordset, we simply use its AddNew method to create a new (initially blank) record, and then go through each field in the table assigning values to them. The values come from the Form fields defined in AddToMailLIst.htm. Note that we assign values to every field except the ID field – this is an Access auto-increment field, and so it will take on a number automatically. Having assigned values, a call to the Update operation in the Recordset sends this data back to a new record in the database table, and refreshes the Recordset so that it is completely in sync with the database table. This then allows us to read the newly assigned ID field into the ClientNo variable, and from there we can set up the Session variable and the Cookie for ClientID. At this stage the data is safely in the database, and we now have a way of identifying the client within the remainder of this session (using Session(“ClientID”) or in some other session (using Request.Cookies(“ClientID”), provided cookies are allowed on the client’s PC). We could stop at this point, the client’s data having been sent to the database, but since this type of operation requires a lot of things to happen correctly, it is good practice to first check that it all went well, and then report back to the client: ‘Continued… ‘... If Err.Number > 0 Then Response.Write "<p>An error has occurred. Please try " & _ "to join our list later.</p>" Else Response.Write "<p>Details stored in your mailing list " & _ "entry are: </p>" Response.Write "<p>Customer ID: </b>" & RS("ID") Response.Write "<b>Name : </b>" & RS("fName") & _ " " & RS("lName") & "<br>" If RS("telNo") <> "" Then Response.Write "<b>Telephone no.:</b>" & _ RS("telNo") & "<br>" End If Response.Write "<b>Address : </b>" & _ RS("Street") & "<br>" Response.Write "<b></b> " & RS("City") & "<br>" Response.Write "<b></b> " & RS("PostCode") & "<br>" Response.Write "<b></b> " & RS("City") & "<br>" If RS("email") <> "" Then Response.Write "<b>Email address : </b> " & _ RS("email") & "<br>" End If If RS("Region") <> "(N/A)" Then Response.Write "<b>Preferred area : </b> " & _ RS("Region") & "<br>" End If If RS("preType") <> "" Then Response.Write "<b>Preferred rental type : </b>" & _ RS("preType") & "<br>" End If If RS("maxRent") > 0 Then Response.Write "<b>Maximum monthly rent : </b>" & _ RS("maxRent") & "<br><br>" End If Response.Write "This information will be stored securely." End If RS.Close Set RS = Nothing If Session(“ReturnTo”) <> “” Then Response.Write “<br><a href=’” & Session(“ReturnTo”) & _ “>Arrange a viewing</a> End If %> <br><A HREF="index.asp">Home</a> </BODY> </HTML> Listing 25: AddToMailList.asp part 2 – checking for an error and reporting back. This half of the ASP page is simpler. When the Recordset’s Update operation is called, one of the outcomes is to check for an error report from the database. If anything bad happened, an Error object, Err is created. Err has a number – 0 for no error, some other value to indicate a problem. If there has been an error, all we can do is let the user know something bad happened. However, an error number of 0 tells us all has gone well, and we can read back the values from the Recordset and report them back to the client as confirmation that their data is now stored in the database, The only remaining task is to guide the user back to where they came from. If Session(“ReturnTo”) is not empty, we have come from the Arrange a Viewing page, and need a link to return there. The last If..Then block in the script does this. Beyond the closing script tag (%>), we can provide a plain link back to the home page. When the user returns to the index page, it should now be able to welcome them by name.</p><p>Exercise 8: Revising viewing records</p><p>A registered client has the option of examining their past and future viewing appointments. This will make it possible to allow them to select a viewing and make a comment on it, or even indicate that they are interested in renting the property. Given the groundwork code for creating an HTML table from a Recordset, retrieving the client’s name etc, it takes only a small amount of code to provide this facility: <!--ReviewViewings.asp--> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <HTML> <HEAD> <TITLE>Review Property Viewings</TITLE> </HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <h2>Review Property Viewings</h2><br> <% Response.Write "<h3>Client : " & _ GetClientName(Session("ClientID")) & "</h3>" Dim RS Dim SQL ' Set up a SQL statement to retrieve the current client's ' viewing records... SQL = "select ID, propertyNo, viewDate, viewHour, " & _ "Comment, WishToRent from Viewing where clientID=" & _ Session("ClientID") Set RS = Application("Conn").Execute(SQL) ' Write out the result as a HTML table... Response.Write RSToHTMLTable(RS, 1, "viewViewing.asp") If RS.EOF Then Response.Write "<br>You have not viewed any properties.<br>" End If %> <a href="index.asp">Home</a> </BODY> </HTML> Listing 26: ReviewViewings.asp – ASP code to create a list of the current client’s viewing records. Note that things are a lot easier now because we don’t have to worry about whether the client is identifiable or not. Quite simply, we could not get to this page unless the client’s ID was in a Session variable (see the condition code in Index.asp). The list of viewings appears as a simple HTML table: Figure 17: ReviewViewings.asp – Viewing records for a client</p><p>Exercise 9: Amending a viewing record</p><p>In this final exercise, we will allow the client to update a viewing record, adding a comment and/or indicating an interest in the property. We need two items for this – an HTML form that displays a specific viewing (and so will need to be generated in ASP code), and an ASP page that accepts updates to this viewing entered into the form. First we’ll consider the code to generate the HTML form. This form will allow the client to enter comments about the property viewed and also to indicate whether they are interested in renting it. <!--ViewingComments.asp--> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <HTML> <HEAD><TITLE>Comments on viewing</TITLE></HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <% ' The viewing record ID is passed in the URL.. viewID = Request.QueryString("ID") Dim RS, SQL SQL = "select PropertyForRent.propertyNo, street, city, " & _ "viewDate, viewHour, Comment, WishToRent " & _ "from Viewing inner join PropertyForRent " & _ "on Viewing.propertyNo = PropertyForRent.propertyNo " & _ "where Viewing.ID = " & viewID Set RS = Application("Conn").Execute(SQL) Response.Write "<h2>Viewing record</h2>" Response.Write "<h3>Property No.:" & RS("propertyNo") & _ "</h3>" Response.Write "<h3>Property: " & RS("Street") & ", " & _ RS("city") & "</h3>" Response.Write "<h3>Viewed on: " & RS("viewDate") & " at " & _ RS("viewHour") & ":00</h3>" Response.Write "<form action='updateViewRecord.asp'>" Response.Write "<input type='hidden' name='viewID' " & _ "value = '" & viewID & "'>" Response.Write "<h3>My comments: <textarea name='comment'>" & _ RS("Comment") & "</textarea></h3>" Response.Write "<h3>I wish to rent this propertry: " & _ "<input type='checkbox'” & "name='chkRent' " & _ "value='" & RS("WishToRent").Value & "'" If RS("WishToRent") = "True" Then Response.Write " checked" End If Response.Write "></h3>" Response.Write "<input type='submit' value='Update Response'>” Response.Write "<input type='reset'>" Response.Write "</form>" RS.Close %> <a href="index.asp">Home</a> </BODY> </HTML> Listing 27: ViewingComments.asp – ASP code to generate a form for updating a viewing record. Listing 27 starts with the construction of a SQL statement. This selects, from a join of the PropertyForRent and Viewing tables, the required fields and incorporates the ID of a row from the Viewing table, which was passed as a QueryString in the page request. Once the SQL is executed on the database, we can write out the fields returned. The twist here is that the two fields that the user is able to update (the Comment and the WishToRent fields) are enclosed as form elements in a <form> tag. Since the WishToRent field is an access Yes/No field, is makes sense to display it as a checkbox, although this is quite awkward to do. HTML code for a checkbox is organised as follows: <input type=’checkbox’ name=’some_name’ value=’some_value’ checked> In this, the word ‘checked’ is only applied if the checkbox is to appear selected. The value property is what will be returned if the checkbox is checked when the user submits the form; if it is not checked, the value will be returned as an empty field. The first named field in the form will not appear in it. It is a ‘hidden’ input field, which means that the user will not be able to use it for input. It is instead, another way that we can pass data between pages within a session. The value of the hidden field is set to be the viewID of the current viewing record, since this is the record we will want to update. The statement: Response.Write "<input type='hidden' name='viewID' value = '" & _ viewID & "'>" embeds the current value of viewID as the value of the hidden field. This allows us to identify the record to update in the ASP page that responds to this form.</p><p>The other significant form field is a TextArea. This can display and accept multiple lines of text typed into the user’s browser. The form generated by this ASP code appears as follows:</p><p>Figure 18: The ASP generated form for viewing and updating a viewing record. When the client updates this form by pressing the ‘Update Response’ button, it will execute an ASP called UpdateViewRecord.asp. This ASP applies any changes made in the above form to the current viewing record in the database. <!--UpdateViewRecord.asp--> <%@ LANGUAGE = VBScript %> <!--#INCLUDE FILE="DHFunctions.inc"--> <HTML> <HEAD> <TITLE>Update Viewing Response</TITLE> </HEAD> <BODY> <H1>DreamHome Estate Agents</H1> <h2>Update Viewing Response</h2> <% Dim comments, wishToRent, SQL, RS comments = Request.Form("comment") wishToRent = Request.Form("chkRent") SQL = "update Viewing set comment='#1', WishToRent=#2 where ID=" _ & Request.Form("viewID") & ";" SQL = Replace(SQL, "#1", comments) If wishToRent = "True" Then SQL = Replace(SQL, "#2", "Yes") Else SQL = Replace(SQL, "#2", "No") End If Response.Write SQL Application("Conn").Execute(SQL) Response.Write "<h3>Comments updated.</h3>" %> <a href="ReviewViewings.asp">Return to list of viewings</a> <a href="index.asp">Home</a> </BODY> </HTML> Listing 28: UpdateViewRecord – this page writes the updates entered into the ViewingComments.asp form to the database. UpdateViewRecord.asp makes the updates to the database and sends a simple message back to the client’s browser to acknowledge this. It contains links to return to ReviewViewings.asp and the home page. Its most significant feature is the formation of the SQL statement to perform the update. This has the format: update Viewing set comment=’some comment’, WishToView=Yes where ID=some_ID To make the SQL parameters easy to insert into the statement, we again use the Replace() function to replace easily identifiable markers (#1, #2) in the SQL. You should note the placement of quote marks (‘) around the text parameter, but none around the Yes/No parameter, since the values Yes and No are valid for this type of field in Access and SQL server databases.</p><p>Summary</p><p>This completes the code for the Dreamhome active website. Of course, a real website would be more attractive, having better text styles, more graphics and some more carefully considered wording to provide the client with more explanations. However, the main point of this code is to demonstrate the various methods used to pass data from web browsers to a database and vice-versa. The HTML code in more elaborate design would be more likely to obscure the ASP code that does the work. Appendix: File Permissions for Internet Information Server</p><p>On a home PC that is not connected to the internet, it would be reasonable to trust the user or users not to do anything malicious to the computer or its files. Unfortunately, it is not possible to extend this level of trust to all of the users that could access the files on a PC that is connected to the Internet. Even if you are not running a website, if you are connected to the Internet there are various ways in which the creeps who write computer viruses can get access to files on your system and corrupt or subvert them for their own sleazy ends. As a result of this, operating systems need to provide various means of securing your system and its files against attack, especially where the computer is connected to a network that could allow other people to gain direct or indirect access to the file system. One level of security that is applied to the files on all types of modern operating system is that users need to be granted permission to do specific things with a file – read it, change its contents or, in the case of scripts and executable files, execute it. In a computer that has been installed with a NT-class operating system (Windows NT, 2000 or XP), file permissions are set so that a given file has an ‘owner’ (usually the person who created it) who is able to do anything with a file – delete it, change it, execute it etc. Other users are granted permission to do various things depending on the policies set up by system administrators. In a home PC, usually the person who owns the computer also owns the filing system and has full permission to do as he or she wishes. The owner is the person who can access the PC with ‘administrator permissions’. All other users of the PC are usually granted more restrictive permissions by dint of the administrator giving them a user account and their own password. When you create a website on a PC that has a broadband connection, you are giving other people permission to have access to some of your files. This needs to be very limited access to prevent your website being corrupted by strangers, but you still need to allow internet users to read html files and, if you are running an active website as described in the rest of this document, to execute script files. You don’t know who these users are, and you certainly can not trust them not to make unsafe changes to the files, so usually files on a website are set to give read and execute permission to all internet users. </p><p>Establishing Permissions for WWW ‘Guests’</p><p>When IIS (Internet Information Server) is installed on a PC running Windows 2000 or XP, a folder is created to host all of the web sites that it is expected to host. This folder is called ‘wwwroot’, and can be found at the location ‘c:\Inetpub\’ on a normal IIS installation. If you locate this folder on an XP system, right-click on it and select Properties from the pop-up menu, you will get the following dialog box:</p><p>The wwwroot Properties dialog on the Web Sharing page With the settings for wwwroot shown above, IIS has been given access to the files in this folder. This allows IIS to access any files created in this folder, and confers the same access rights to any folders created within it. However, if you were to create a folder at some other location and then move or copy it into this folder, it would retain its original access rights (which in normal circumstances would prevent IIS from gaining access to files in it. If that was the case, you can set the folder to enable IIS access by clicking on the Share this folder option, and selecting appropriate access rights from the following dialog box: Setting access rights to a folder Note that you have the ability to apply tight control over how access rights are applied – IIS can be given permission to read, write, execute scripts or execute any file (including native executables), and to browse the folders within it. For ASP applications, the only permissions normally required are to Read files and execute Scripts. However, if the folder contains a database (e.g. an Access .mdb file), then Write access would also be required if you intended to allow web users to send information to it, although for this reason it is better to place database files in a separate folder to which Read and Write permissions (but not Execute permission) has been applied.</p><p>Summary</p><p>Special permission has to be given to files that make up a website so that Internet Information Server is able to read them (for HTML files) and execute them (for ASP files). Specific permissions are set for files in the c:\Inetpub\wwwroot folder (in a normal IIS installation) as follows:  Files that are created in this folder can by default be accessed by IIS</p><p> Folders that are created in wwwroot provide access permissions to files in them in the same way that it does (and the same for folders created within these folders etc.)</p><p> Files in folders that are created elsewhere and then moved or copied to wwwroot can not by default be accessed by IIS. In this case, right-click on the folder, go to the Web Sharing page of the dialog and click on Share this folder to set permissions</p><p> Database files (such as Access MDB files) need to reside in a folder that has been given web sharing permissions for Read and Write access. Note that client-server databases (such as Oracle or SQL Server) manage their own permissions and so there is no need to do anything to provide the correct level of access to them.</p>

View Full Text

Details

  • File Type
    pdf
  • Upload Time
    -
  • Content Languages
    English
  • Upload User
    Anonymous/Not logged-in
  • File Pages
    79 Page
  • File Size
    -

Download

Channel Download Status
Express Download Enable

Copyright

We respect the copyrights and intellectual property rights of all users. All uploaded documents are either original works of the uploader or authorized works of the rightful owners.

  • Not to be reproduced or distributed without explicit permission.
  • Not used for commercial purposes outside of approved use cases.
  • Not used to infringe on the rights of the original creators.
  • If you believe any content infringes your copyright, please contact us immediately.

Support

For help with questions, suggestions, or problems, please contact us