- Multiuser Concurrency Problems
- Possible Solutions
- DHO Code Changes for Timestamping
- UI Code Changes for Timestamping
- Testing
- Summary
UI Code Changes for Timestamping
Checking the timestamps during the update process is really the responsibility of the DHO, but there are still a few changes that need to be done in the UI component. Namely, when we retrieve a Publisher and display it to the user, the user has the option of making changes to the data and submitting it back to the server to be saved. This means that whenever a Publisher is displayed on the detail form, we need to save its LastModDateTime property as user state information. Also, the previous version of the application stores the PubID of the Publisher being displayed as a querystring parameter that gets sent back to the application when the form is submitted. Because we have to make changes to clsUserState to hold LastModDateTime, we might as well also change it to hold the PubID.
Change the General Declaration section in clsUserState to include the two new properties:
Public Filter As String Public CurrPubID As Long Public CurrPubLastModDateTime As Date Public Publishers As Collection
Change the SaveState method to write these new values to the user state file:
... ' Save the state data. Write #lFileNbr, Filter Write #lFileNbr, CurrPubID Write #lFileNbr, CurrPubLastModDateTime Write #lFileNbr, Publishers.Count For Each lPubState In Publishers Write #lFileNbr, lPubState.PubID, lPubState.Name, lPubState.Phone Next lPubState ...
And change the GetState method to read them from the user state file:
... ' Get the state data. Input #lFileNbr, Filter Input #lFileNbr, CurrPubID Input #lFileNbr, CurrPubLastModDateTime Input #lFileNbr, lNbrPubs For lIndex = 1 To lNbrPubs Step 1 ...
That's it for the clsUserState changes. The only other changes to make are to make sure that the necessary procedures in clsWebApp use these two new values. These changes are pretty minor. The DetailProc procedure is responsible for displaying a Publisher in a form, so this procedure needs to be modified to populate the clsUserState.CurrPubID and CurrPubLastModDateTime after it has retrieved a Publisher from the database:
Private Sub DetailProc() Dim lHTML As String Dim lPubList As clsPublisherList Dim lPub As clsPublisher ' Instantiate the clsPublisherList object and ask it to return ' the Publisher with the ID found in the query string. mUserState.CurrPubID = gAspReq.QueryString.Item("PubID") Set lPubList = New clsPublisherList Set lPub = lPubList.Fetch(mUserState.CurrPubID) ' Store the LastModDateTime in the state object in case the ' user decides to update it. mUserState.CurrPubLastModDateTime = lPub.LastModDateTime ' Load the HTML file. lHTML = LoadHTML("Detail.htm") ...
The UpdateProc procedure needs to be changed to restore the PubID and LastModDateTime from the clsUserState object before sending the update request to the DHO:
... ' Create a new Publisher object.Set lPub = New clsPublisher ' Restore the values from the user state object. lPub.PubID = mUserState.CurrPubID lPub.LastModDateTime = mUserState.CurrPubLastModDateTime ' Get the new values from the form. lPub.Name = gAspReq.Form("txtName") lPub.CompanyName = gAspReq.Form("txtCompany") lPub.Address = gAspReq.Form("txtAddress") lPub.City = gAspReq.Form("txtCity") lPub.State = gAspReq.Form("txtState") lPub.Zip = gAspReq.Form("txtZip") lPub.Telephone = gAspReq.Form("txtTelephone") lPub.Fax = gAspReq.Form("txtFax") ...
Recall that the clsPublisher.DBUpdate method changes the value of the Publisher's LastModDateTime property, so this new timestamp value must be saved in the user state object in case the user does a second update:
... ' Ask the clsPublisherList object to update this Publisher. Set lPubList = New clsPublisherList Call lPubList.Update(lPub) ' Now resave the new LastModDateTime with the user state in ' case the user does a second update. mUserState.CurrPubLastModDateTime = lPub.LastModDateTime ' Send back a confirmation. lHTML = LoadHTML("Detail.htm") ...
Remember that the DHO will cancel the update and raise an error if the timestamps don't match. This error will be caught by the error handler that clsWebApp has defined in the WebMain procedure. If this is something that happens frequently, however, it might be a good idea to declare an error handler in the UpdateProc so that a nicer error message form is displayed.
To make the sample application more functional, I've also enhanced it so that new Publishers can be inserted into the database, and existing Publishers can be deleted. This gives us a chance to exercise the clsPublisherList.Insert and clsPublisherList.Delete methods in the DHO. The user interface code changes are also fairly minor.
I've added a new ProcInsert procedure to handle the inserts. This procedure is nearly identical to the ProcUpdate procedure, with the exception that it calls the clsPublihserList.Insert method instead of the clsPublisherList.Update method. The Publisher's PubID column is generated by the DBMS whenever a new record is inserted, so in addition to LastModDateTime, ProcInsert must save the PubID of the new publisher to the user state object after it has been inserted into the database:
... ' Ask the clsPublisherList object to Insert this Publisher. Set lPubList = New clsPublisherList Call lPubList.Insert(lPub) ' Now save the new PubID and LastModDateTime into the user state ' object in case the user does a subsequent update. mUserState.CurrPubID = lPub.PubID mUserState.CurrPubLastModDateTime = lPub.LastModDateTime ' Send back a confirmation. lHTML = LoadHTML("Detail.htm") ...
In this sample application, I've kept ProcInsert and ProcUpdate as two separate procedures. In a real application, UpdateProc and InsertProc could easily be combined into a single procedure, or at least the duplicate code in each could be modularized into sub procedures to reduce the amount of redundant code.
Finally, I've added a new ProcDelete procedure to handle the delete requests. This procedure simply creates a new clsPublisher object, restores the PubID and LastModDateTime properties from the user state object, calls the DHO to delete the record, and sends back a confirmation message. One special bit of code to note is that after the Publisher has been deleted, the PubID and LastModDateTime properties of the user state object should be reinitialized:
Private Sub DeleteProc() Dim lHTML As String Dim lPubList As clsPublisherList Dim lPub As clsPublisher ' Create a new Publisher object. Set lPub = New clsPublisher ' Restore the values from the user state object. lPub.PubID = mUserState.CurrPubID lPub.LastModDateTime = mUserState.CurrPubLastModDateTime ' Ask the clsPublisherList object to delete this Publisher. Set lPubList = New clsPublisherList Call lPubList.Update(lPub) ' Clear the current publisher information stored in the ' user state object. mUserState.CurrPubID = 0 mUserState.CurrPubLastModDateTime = 0 ' Send back a confirmation HTML file. mHTMLSendString = LoadHTML("DeleteConfirm.htm") End Sub