Future Directions

The current solution addresses all the requirements identified in the problem analysis. It also provides the opportunity for future enhancements, such as a more detailed security model and a reporting tool that can profile rendering usage. As always, however, there is still room for improvement.

Adding Ticket Caching

One of the reasons for using a ticket-based security system is to replace the costly user ID and password lookup with a faster ticket verification. In the example so far, the ticket verification also entails a database trip and still results in unnecessary overhead on every method call.

A better approach is possible by adding caching. With a caching strategy, a ticket object is added to the cache for every ticket, indexed by GUID. Listing 18-35 shows the rewritten Login method. A sliding expiration policy is used, ensuring that the ticket is retained for up to three hours after the last request.

Listing 18-35 Adding a ticket to the cache
 <WebMethod(), SoapHeader("Ticket", _  Direction:=SoapHeaderDirection.InOut)> _ Public Function Login() As Boolean     Dim UserID As Integer     UserID = Tables.Users.AuthenticateUser(Ticket.UserName, _                                            Ticket.Password)     If UserID <> 0 Then         ' Clear the password for enhanced security.         Ticket.Password = ""         Ticket.UserName = ""         Ticket.UserID = UserID         ' Create the session and assign the GUID to the SOAP header.         Dim GUID As Guid = Tables.Sessions.CreateSession(UserID)         Ticket.SessionGUID = GUID 
         ' Store the ticket in the cache.         Context.Cache.Insert(GUID, Ticket, null, DateTime.MaxValue,                      TimeSpan.FromHours(3))         Return True     Else         Return False     End If End Function 

When authenticating a session, the service first checks the cache and then turns to the database as a last resort, as shown in Listing 18-36.

Listing 18-36 Checking the cache for the ticket
 Private Sub AuthenticateSession(ByVal ticket As TicketHeader)     Dim Success As Boolean = True     Dim CachedTicket As TicketHeader     CachedTicket = Context.Cache(ticket.SessionGUID)     If CachedTicket Is Nothing         ' Attempt database validation.         If Tables.Sessions.GetSession(ticket.SessionGUID, _           ticket.UserID) Is Nothing Then             ' No record was returned.             Success = False         End If     Else         ' Ticket was found in the cache. Validate it.         If CachedTicket.UserID <> ticket.UserID Then             Success = False         End If     End If     If Not Success Then         Throw New Security.SecurityException("Session not valid.")     End If End Sub 

Note

Even with caching, it's still strongly recommended that you store information in a backend database. Otherwise, the session ticket might be removed by ASP.NET if server resources become scarce or the amount of information in the cache increases.


Enhanced Client Cleanup

Currently the client contains a minimum of error-handling code. The problem with this approach is that an unhandled exception could cause the application to terminate without properly logging out. To solve this problem, you can add exception-handling code to the Web method calls and log out if an error occurs. Although this problem is minor (and is addressed by the DeleteOldSessions stored procedure), our approach can be improved. One possibility is to use the AppDomain.UnhandledException event, which fires whenever an exception occurs that your code doesn't catch. You can then use your event handler to log out. This technique is described in detail in Chapter 14.

Another problem is that the client doesn't consider the possibility that a session could time out. Ideally, the server would create a special SoapException class, with some XML information added to the Detail property that indicates a security exception. Upon receiving this exception, the client could then attempt to log in again.

Adding Message Queuing

Another nice addition to this application would be the use of Message Queuing. As explained at the beginning of this chapter, a NotifyQueue field allows the database to record a client-specific queue path. The task processor front end can retrieve this information after a rendering operation completes and mail a message to the client. The client application can be upgraded to include a worker thread that continuously scans the message queue and reports immediately when rendered files are ready. This is similar to the approach developed in the preceding chapter, and it saves the client from needing to requery the XML Web service.

Adding Load Balancing

The current solution assumes that only one server will be rendering tasks. However, what happens if Trey Research is remarkably successful and wants to add another server to the mix to maximize the throughput of the system?

In fact, only a few simple changes are required to enable the application to support multiple instances of the task processor. The most significant change is that the rendering application can no longer retrieve a list of tasks to process. Instead, it must retrieve the information for a single task.

Here's the current code for retrieving available tasks:

 Tasks = Tables.Tasks.GetAvailableTasks() For Each Task In Tasks     ' Mark the record in progress.     Tables.Tasks.UpdateTaskStatus(Task.TaskGUID, _       DBComponent.TaskStatus.InProgress)     ' (Processing code omitted.) Next 

The problem here is that multiple task processors will contain duplicate lists and will probably attempt to process the same task at the same time. Instead, you should replace the GetAvailableTasks method (and stored procedure) with a RequestNextTask method that returns a single available task.

The task-processing code will now look like this:

 Tasks = Tables.Tasks.RequestNextTask() ' (Processing code omitted.) 

Note that the code no longer needs to explicitly set the task record in progress by calling the UpdateTaskStatus method because the RequestNextTask stored procedure performs this step automatically. If you don't use this approach, you might run into a significant concurrency problem when the actions of more than one processor overlap as follows:

  1. The first task processor queries an available task and receives task A.

  2. The second task processor queries an available task. Because task A is not yet in progress, it receives the same task.

  3. The first task processor marks task A in progress.

  4. The second task processor also marks task A in progress.

  5. Both servers are performing the same work.

To avoid this problem, steps 1 and 3 must be collapsed into a single RequestNextTask stored procedure that uses an embedded SQL transaction. Listing 18-37 shows one example that works by holding a lock on the task record.

Listing 18-37 The new RequestNextTask stored procedure
 CREATE Procedure RequestNextTask  AS SET TRANSACTION ISOLATION LEVEL REPEATABLE READ BEGIN TRANSACTION       Find the GUID for the next record.     DECLARE @GUID uniqueidentifier     SELECT TOP 1 @GUID = ID FROM Tasks        WHERE State=0 ORDER BY SubmitDate       Update the status to place it in progress.     UPDATE Tasks SET State = 1 WHERE ID = @GUID       Return the record.     SELECT * FROM Tasks WHERE ID = @GUID COMMIT TRANSACTION 

In addition, you might want to expand the RequestNextTask stored procedure and the Tasks table so that they record information about which server is processing a given task.

Finally, you might need to tighten the rules of some of the other stored procedures. For example, you might want to add a Where clause that makes sure a record status can't be updated to complete unless it is currently marked in progress. Listing 18-38 shows such a change for the CompleteTask stored procedure.

Listing 18-38 The refined CompleteTask stored procedure
 CREATE Procedure CompleteTask  (     @GUID            uniqueidentifier,     @RenderedFileURL nvarchar(100),     @Status          int  )  AS     UPDATE Tasks     SET State = @Status,     RenderedFileURL = @RenderedFileURL,     CompleteDate = GETDATE()     WHERE [ID] = @GUID     A ND State = 1  


Microsoft. NET Distributed Applications(c) Integrating XML Web Services and. NET Remoting
MicrosoftВ® .NET Distributed Applications: Integrating XML Web Services and .NET Remoting (Pro-Developer)
ISBN: 0735619336
EAN: 2147483647
Year: 2005
Pages: 174

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net