<article id="aspforms-sessionless">
<artheader>
	<title>Sessionless ASP With ASPForms</title>
	<subtitle>Using ASPForms to manage sessions in a sessionless environment</subtitle>

<author>
<firstname>Joel</firstname>
<surname>Hainley</surname>
<affiliation>
<address><email>jhainley-at-myndkryme-dot-com</email></address>
</affiliation>
</author>

<edition>Draft Edition</edition>
<pubdate>March 04, 2004</pubdate>
<copyright>
<year>2004</year>
<holder>myndKryme Laboratories</holder>
</copyright>
</artheader>
	<section id="article-preface">
		<title>
			Background
		</title>
		<para>
		I was talking with a colleague the other day about some of the challenges of developing web applications in ASP. One of the areas that we both agreed was difficult to manage was the problems around sessionless web environments. The advantages of load balanced network architectures does come at a cost to ASP developers who have to code around the constraints that such topologies impose. 
		</para>
		<para>
		The session object that is provided with IIS is not sophisticated enough to deal with sessionless environments so developers have to build their own session systems so that user specific data can be persisted throughout the lifetime of a "sessionless session". The problem with this is that sometimes the mechanisms created are more sophisticated than they need to be, are so cumbersome as to be difficult to work with, or just don't work for all situations. The goal of this article is to provide an approach to quick and easy "sessionless sessions" that are robust and intuitive to work with.
		</para>
        <sect2>
        <title>
            Enter ASPForms
        </title>
        <para>
		ASPForms [<ulink url="http://www.myndkryme.com/products/aspforms">www.myndkryme.com/products/aspforms.html</ulink>] provides mechanisms to define, retrieve, validate and persist data, form-based and otherwise. The component allows a developer to specify information that it should expect to retrieve from a request, once this information is specified it allows the developer to load the data from the request with a single method call. This is convenient because having to constantly call request.form("somefield") gets tedious as data capture requirements in a project grow. Once this data has been captured it is possible to persist this data, as well as the configuration information, to am XML string or the session.
		</para>
		<para>
		ASPForms provides the ability for you to create multiple rulesets for your data, this allows a developer to capture the data from a request and then call a single method to see if the retrieved data is valid for rules specified, a list of errors can be retrieved from the form. Each of the error objects will contain developer specified information that can be used to provided feedback to the user. This validation information ( rulesets, rules, error messages, etc ) can be persisted to an XML string, or directly to the session just like the previous configuration information. 
		</para>
		<para>
		Once your data has passed validation ASPForms then also provides the ability to define how the data should be persisted to a datastore through the use of what we call Adapters. At the moment the only Adapter that is in the ASPForms component is a SQLAdapter, once configured this adapter will spit out sql statements for you to run against your sql server for easy creation, updating, and retrieval of your data.
		</para>
		<para>
		Given the abilities of ASPForms it is apparent that a lot of the challenges of sessionless environments would be of little concern for a developer using this product. The ability to persist all of the data to a xml string simplifies a lot of the operations and provides a session-like interface for managing the data. Realizing how much this component could ease sessionless development for ASP developers we decided to create a sample application. While creating this sample application we added a few features to ASPForms that make it even more useful in sessionless environments. ASPForms is a tool that is powerfully suited to aid you in all of your data manipulation, validation, persistence needs, whether you work in session-aware or session-less environments.
		</para>
        </sect2>
        </section>
        <section id="article-bcos">
        <title>
            The BCOS ( Business Card Ordering System ) Wizard
        </title>        
        <para>
To illustrate the features of ASPForms can be leveraged to aid developers in sessionless environments we will put together a very simple sessionless business card ordering wizard. This application will take information from the user, validate the 
information, prompting the user to fix any invalid data, and move them through the steps necessary to order business 
cards. The final step will display the entire set of information to the user for confirmation before placing the order, similar to how a typical order entry system would work.
        </para>
        <para>
		The information we will be gathering will be broken up into the following categories :
		<itemizedlist>
			<listitem>Business Card Address Information - the information that the users wants displayed on the business card</listitem>
			<listitem>Business Card Style - we have identified multiple business card styles, and the user can select from the version they feel is most appropriate.</listitem>
			<listitem>Quantity Requirements - the number of cards that the user wishes to order at this time</listitem>
			<listitem>Shipping Address - the name/address information to ship the business cards to</listitem>
			<listitem>Billing Address - the name/address to ship the bill to.</listitem>
			<listitem>Additional Instructions - any comments the user would like to make about the order</listitem>
		</itemizedlist>
		</para>
		<para>
		The details of what information we will be capturing on each page is found in the accompanying source for this article. Throughout the rest of this article we will often show snippets of code from this sample source code so that we can talk about various points, but you will need to grab the source to have the complete sample application.
		</para>
        <para>
            The steps/decisions involved in creating this wizard :
            <itemizedlist>
                <listitem>
                1. Initializing an ASPForm object.
                </listitem>
                <listitem>
                2. Add details for the information that we will gather
                </listitem>
                <listitem>
                3. Add the Validation Rules for the information we gather
                </listitem>   
                <listitem>
                4. Persisting the ASPForm object and its data 
throughout all of the steps of the wizard.
                </listitem>
            </itemizedlist>
        </para>
        <sect2>
            <title>
                Initializing an ASPForms object.
            </title>
        <para>
To initialize an ASPForm object in this example we created a simple 
include file called frmInitialize.asp, within this file we created a 
function getConfiguredForm() which returns an aspforms object with the 
configuration information that we will be using throughout the rest of this 
application. The function is not necessary, but it does help keep all of the initial configuration information for an ASPForm object in one place.
        </para>
        <example><title>Creating and Initializing an ASPForm 
object</title>
        <programlisting>
            function getConfiguredForm()
           	    set frm = Server.CreateObject("ASPForms.ASPFormV1")            
           	    
           	    set getConfiguredForm = frm
            end function
        </programlisting>
        </example>        
        <para>
        The getConfiguredForm function performs several tasks to create 
the object for use throughout the rest of the system. The first step is 
for it to create a new instance of ASPFormV1, this object will be 
passed back to the code calling this function upon completing the 
configuration of this new object. It should be noted that this is the only time 
that this function will be called, once the form has been configured we 
will be using some of it's features to persist the configuration, and 
its data, throughout the rest of the session.
        </para>
        <para>
We made the decision to  create a single ASPForm object for all of the information that we will be gathering in this wizard. You could just as easily have created a ASPForm object for each of the pages of the wizard. However, in the interest of keeping things simple for this example we opted for the single ASPForm approach. It is left to the reader to determine if this is the right decision for their solution.        
        </para>
        <para>
Now with a handle ( the variable named 'frm' ) to the newly created 
object we can begin to add the elements that will be needed throughout this 
wizard. We create each element giving it a name, this name will be the name that aspforms uses to load the information from the request, as well as the handle that the developer uses to retrieve data. 
		</para>
		<para>
		The next argument is a boolean value that indicates whether the value is required.  For all of the elements within this application we will not set any of the fields to be required fields. This is because all of the required fields would have to have data in them before the isvalid method would return true. Since we are storing multiple pages of information into this single object we would have not way of knowing if the current page of data we gathered was valid or not. We will be using the functionality of our Rulesets to help us determine if required information has been filled out on any one individual page.
        </para>        
        <example><title>Adding The Necessary Elements To Our 
ASPForm</title>
        <programlisting>
            function getConfiguredForm()
           	    set frm = Server.CreateObject("ASPForms.ASPFormV1")            

                'add all of the desired elements to the asp form
                frm.Elements.CreateElement "name", false
                frm.Elements.CreateElement "title", false
                frm.Elements.CreateElement "address1", false
                frm.Elements.CreateElement "address2", false
                frm.Elements.CreateElement "city", false
                frm.Elements.CreateElement "state", false
                frm.Elements.CreateElement "zipcode", false           	    
				
           	    set getConfiguredForm = frm
            end function
        </programlisting>
        </example>        
        </sect2>
        <sect2>
            <title>Creating The Wizard's Rulesets And Rules</title>
        <para>
		Now that we have all of the elements that we will be needing for our wizard it is time to create a series of rulesets that we will utilize to validate the information within our application. The idea behind a ruleset is that you may have times when you need to validate a subset of the all data within an ASPForm object. To facilitate this you create a named ruleset, and add rules to the ruleset appropriate to the information you would like to validate. When you want to validate the data based upon the rulesets you call the validate method and pass it the name of the ruleset to use for validation. If you want to validate your data against all of your rulesets you call the validate method without passing it a ruleset.
		</para>
        <example><title>Creating The Ruleset In Our ASPForm</title>
        <programlisting>
            function getConfiguredForm()
           	    set frm = Server.CreateObject("ASPForms.ASPFormV1")            
           	    
				'add all of the desired elements to the asp form
				frm.Elements.CreateElement "name", false
				....
				....
				           	    
           	    'create a new ruleset named cardInfo
				frm.Rulesets.CreateRuleset( "cardInfo" )           	    
				
           	    set getConfiguredForm = frm
            end function
        </programlisting>
        </example>        
        </sect2>
		
		<para>Once we have a ruleset for our ASPForm object, we can begin to add rules to this ruleset. Rules in ASPForms are very configurable, you can add rules for datatypes, lengths ( both min and max ), and even regular expressions. The rule also contains the text that should be added to the error object if validation against that rule fails. You will notice that the rules are also named, this allows you to modify the rules that data is validated against at runtime. Below we are adding all of the rules for the first ruleset, once again this ruleset only pertains to the first screen of the wizard.
		</para>
        <example><title>Adding Rules To Our Ruleset</title>
        <programlisting>
            function getConfiguredForm()
           	    set frm = Server.CreateObject("ASPForms.ASPFormV1")            
           	    
				'add all of the desired elements to the asp form
				....
				....
           	    'create a new ruleset named cardInfo
                ....
                ....
                'add rules to the cardInfo ruleset
                frm.Rulesets("cardInfo").CreateRule "name_maxlength", "name", ASPFORMS_MAXLENGTH, _
                                                    "The name must be no greater than 25 characters", 25
                                                    
                frm.Rulesets("cardInfo").CreateRule "name_minlength", "name", ASPFORMS_MINLENGTH, _
                                                    "You Must Enter A Name", 0
                frm.Rulesets("cardInfo").CreateRule "title_maxlength", "title", ASPFORMS_MAXLENGTH, _
                                                    "The title must be no greater than 25 characters", 25
                                                     
                frm.Rulesets("cardInfo").CreateRule "title_minlength", "title", ASPFORMS_MINLENGTH, _
                                                    "You Must Enter A Name", 0
                frm.Rulesets("cardInfo").CreateRule "address1_maxlength", "address1", ASPFORMS_MAXLENGTH, _
                                                    "Address 1 must be no greater than 25 characters", 25
                                                    
                frm.Rulesets("cardInfo").CreateRule "address1_minlength", "address1", ASPFORMS_MINLENGTH, _
                                                    "You must enter a value for Address 1", 0
                                                    
                frm.Rulesets("cardInfo").CreateRule "address2_maxlength", "address2", ASPFORMS_MAXLENGTH, _
                                                    "Address 2 must be no greater than 25 characters", 25
                
                frm.Rulesets("cardInfo").CreateRule "city_minlength", "city", ASPFORMS_MINLENGTH, _
                                                    "You must enter a value for city", 0
                                                    
                frm.Rulesets("cardInfo").CreateRule "city_maxlength", "city", ASPFORMS_MAXLENGTH, _
                                                    "City must be no greater than 25 characters", 25
                                                    
                frm.Rulesets("cardInfo").CreateRule "state_minlength", "state", ASPFORMS_MINLENGTH, _
                                                    "You must enter a value for state", 0
                                                    
                frm.Rulesets("cardInfo").CreateRule "state_maxlength", "state", ASPFORMS_MAXLENGTH, _
                                                    "State must be no greater than 2 characters", 2
                                                    
                frm.Rulesets("cardInfo").CreateRule "zipcode_minlength", "zipcode", ASPFORMS_MINLENGTH, _
                                                    "You must enter a value for zipcode", 0
                                                    
                frm.Rulesets("cardInfo").CreateRule "zipcode_maxlength", "zipcode", ASPFORMS_MAXLENGTH, _
                                                    "Zipcode must be no greater than 5 characters", 5
                                                    
                frm.Rulesets("cardInfo").CreateRule "zipcode_numeric", "zipcode", ASPFORMS_NUMERIC, _
                                                    "Zipcode can contain only numbers", ""
				
           	    set getConfiguredForm = frm
            end function
        </programlisting>
        </example>		
        <para>
		With the ASPForm configured with its elements, rulesets, and rules we can continue with the creation of our wizard. The first thiing we need to do is look at how we will structure our html forms so that the ASPForms can retrieve their data from the request. Below we have included the form specific html for the Business Card Information part of the wizard.         
        </para>
        <example><title>A Closer Look At the HTML</title>
        <programlisting>
        'this comes from the cardInfo.asp page in the sample application
        
		&lt;form name="form1" method="post" action="cardInfo.asp"&gt;
			&lt;input type="hidden" name="pagepost" size="25" value="cardInfo.asp"&gt;
			&lt;input type="hidden" name="currentState" value="&lt;%= replace(mFrm.getXML, chr(34), "!") %&gt;"&gt;	

			Name:&lt;br&gt;
			&lt;input type="text" name="name" value="&lt;%= mFrm.Elements("name").value %&gt;"&gt;&lt;br&gt;
			
			Title:&lt;br&gt;
			&lt;input type="text" name="title" value="&lt;%= mFrm.Elements("title").value %&gt;"&gt;&lt;br&gt;
			
			Address 1:&lt;br&gt;
			&lt;input type="text" name="address1" value="&lt;%= mFrm.Elements("address1").value %&gt;"&gt;&lt;br&gt;
			
			Address 2:&lt;br&gt;
			&lt;input type="text" name="address2" value="&lt;%= mFrm.Elements("address2").value %&gt;"&gt;&lt;br&gt;
			
			City:&lt;br&gt;
			&lt;input type="text" name="city" value="&lt;%= mFrm.Elements("city").value %&gt;"&gt;&lt;br&gt;			
			
			State:&lt;br&gt;
			&lt;input type="text" name="state" value="&lt;%= mFrm.Elements("state").value %&gt;"&gt;&lt;br&gt;
			
			Zipcode:&lt;br&gt;
			&lt;input type="text" name="zipcode" value="&lt;%= mFrm.Elements("zipcode").value %&gt;"&gt;&lt;br&gt;
			
			&lt;br&gt;
			&lt;input type="submit" value="next"&gt;&lt;br&gt;
		&lt;/form&gt;
        </programlisting>
        </example>		
        <para>There is a lot of stuff going on with this code example so we'll take it one step at a time. The first thing to note is that the form posts back to itself, this is a stylistic choice and is not necessary to take advantage of ASPForms. This is the approach that is taken by the Talon-ASP web development framework, and since it is an approach that I am comfortable with I opted to replicate it here.</para>
        
		<para>There are two hidden fields for in this form, the first is a hidden field ( pagepost )that we use higher up in the ASP to determine if the page is being painted for the first time, or it is being reposted. If it is the first time that this page is being painted the hidden field will not have the name of this page in the page post field, it will be blank or have another pagename in it. If the page has been displayed to the user and this is a post of that page, then the hidden field will have the name of this page in it.
		</para>
        <para>
        	We're going to skip the second hidden form for a moment and talk about the rest of the form. Notice that the names of the text fields for the application correspond to the elements that we created in the form, ASPForms uses it's element name to retrieve the data from a request when load is called. Once you have these synched up ASPForms takes over grabbing the information. There is one other thing to note with this page, notice that the value for the text boxes are filled out from the aspForm object. This allows you to set default values, or to paint all of the data that the user just submitted, so if the validation fails then the textboxes will display all of the information from the user. When you couple this approach with the error objects, you can be very specific about which fields have failed, and why, when you display the information to the user.
        </para>
        <para>
The second hidden field is used to persist the ASPForms data, and configuration through various requests, this is the backbone of our persistence mechanism for this sample. We simply create a blank ASPForms object on each post and load the data from this field into the ASPForm object, then we have the ASPForm object retrieve the rest of the data for the request by calling the loadfromrequest method of the object. In the first cardInfo page we do things a bit differently as outlined below, the pages in the wizard following things do this one a bit different as outline in the second code snippet in this example.
        </para>
        <example><title>Loading configuration and data from the request</title>
        <programlisting>
        	'sample #1
            if( request("pagepost") = "cardInfo.asp") then
                set mFrm = Server.CreateObject("ASPForms.AspFormV1")
                mFrm.LoadXML( replace(request("currentState"), "!", chr(34) ) )		
                mFrm.LoadFromRequest
            else
                'this is the first time that we have 
                set mFrm = getConfiguredForm
            end if
			
            '------------------------------------------------------------------------------
			
			'sample #2
            set mFrm = Server.CreateObject("ASPForms.AspFormV1")
	
            if( request("pagepost") = "billingInfo.asp") then
                mFrm.LoadXML( replace(request("currentState"), "!", chr(34) ) )		
                mFrm.LoadFromRequest()

                if( mFrm.Validate("billInfo") = true ) then
                    server.Transfer("shippingInfo.asp")
                end if
            else
               mFrm.LoadXML( replace(request("currentState"), "!", chr(34) ) )
               mFrm.LoadFromRequest(true)
            end if
        </programlisting>
        </example>		
	    <para>
		In first sample when the user submits the form we determine that it is a post of the form back to itself, we then create a blank instance of an ASPFormV1 object and use the serialized data to load the object back to it's previous state. Once that is done, we grab data from the request, and the validate the information. If the data appears to be valid we forward the request on to the next page, if it is not valid then we display the errors to the user. We also dump all of the submitted back into the form giving the user a chance to change it. The second sample is essentially the same thing but the order is a bit different.
        </para>
        <para>
When the user has submitted information that is deemed valid by the cardInfo ruleset we use server.transfer to transfer the request to the next page in the sequence. Remember, that we're working in a sessionless environment which means that we aren't able to store the information we just loaded from the request into memory, we could drop the information into the querystring and do a response.redirect on the information, then pick this up on the next page and drop it back into the hidden form.
        </para>
        <para>
While the previous approach would work, it seemed ugly, so we decided to go another route. We simply transfer the post to the next page, we then create an ASPForm object and load the configuration/current state from the hidden field in the post, then we load the information from the posted form. We already know that the data is valid, because we validated it on the page before. We then take this new state, and drop that into the hidden field on the new page, so that when the user submits this form, we can load the new state of the form and validate the form post.
        </para>
	</section>
	<section id="article-info">
		<title>Article Information And Resources</title>
		<para>
		    The current version of this document can be found at <ulink 
url="http://www.myndkryme.com/library/articles/aspforms-sessionless.html">http://www.myndkryme.com/library/articles/aspforms-sessionless.html</ulink>. 
		</para>
		<para>
		This document was authored in docbook, the docbook source can be found at <ulink url="http://www.myndkryme.com/library/articles/aspforms-sessionless-2.dbk.xml">http://www.myndkryme.com/library/articles/aspforms-sessionless-2.dbk.xml.</ulink>
		</para>
		<para>
		The sample code, a self-extracting exe, for this application can be found at <ulink url="http://www.myndkryme.com/library/articles/aspforms-sessionless-src.zip">http://www.myndkryme.com/library/articles/aspforms-sessionless-src.zip</ulink>
		</para>
		<para>
			Please send corrections/suggestions to Joel Hainley at : 
<email>jhainley@myndkryme.com</email>.
		</para>
	</section>
	<section id="author-about">
		<title>About The Author</title>
		<para>
		    Joel Hainley.
		</para>
	</section>	
</article>