The Internal OrderFill Client

The Internal OrderFill Client

The internal client provides the features needed to process orders after they've been submitted. It also has the ability to dynamically receive a message and update the interface accordingly, thanks to some careful threading code. Figure 17-9 shows the interface that appears when the internal client is started.

Figure 17-9. The OrderFill client at startup

graphics/f17dp09.jpg

The first step is to register the current queue and retrieve the IDs for all the available orders. The database connection and queue path are drawn from the configuration file shown here:

 <?xml version="1.0" encoding="utf-8" ?> <configuration>   <appSettings>     <add key="TransactConnection"          value="Data Source=localhost;Initial                 Catalog=Transact;Integrated Security=SSPI" />     <add key="MessageQueue"          value="FARIAMAT\private$\TransactNotify" />   </appSettings> </configuration> 

Listing 17-21 shows the code that runs when the internal client starts. It registers the queue, retrieves the order IDs, and starts a separate thread that monitors the queue for new messages. (We'll deal with the threading code that's involved a little later.)

Listing 17-21 Configuring the internal client on startup
 ' The remote OrderService. Private Proxy As New localhost.OrderService() ' The local database component. Private DB As New DBComponent.TransactTables() ' The custom threaded object that monitors the queue. Private WithEvents Monitor As New QueueMonitor() ' The thread where the QueueMonitor performs its work. Private MonitorThread As New Thread(AddressOf Monitor.DoWatch) Private Sub OrderFillClient_Load(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles MyBase.Load     ' Register queue.     Proxy.RegisterNotifyQueue( _       ConfigurationSettings.AppSettings("MessageQueue"))     Dim ID As Integer     For Each ID In DB.Order.GetAvailableOrderIDs()         lstAvailable.Items.Add(ID)     Next     ' Start the queue monitor.     MonitorThread.Priority = ThreadPriority.BelowNormal     MonitorThread.Start() End Sub 

The two arrow buttons enable users to place orders in progress or return them to the available pool. The client can place multiple orders in progress at the same time, which can be useful as different teams in the production center work to assemble the parts for different orders. Often, while one order is underway but waiting (for example, while special software is being installed), another order is filled.

When an order is checked out, a full structure is returned with all the order information. This object is placed directly in the in-progress list control (as shown in Listing 17-22). The list control calls the object's ToString method to determine what text it should display. In the case of the Order object, this method is overridden to return some useful information, including the order ID and the total number of stations.

Listing 17-22 Putting an order in progress
 Private Sub cmdCheckOut_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles cmdCheckOut.Click     ' Ensure an item is currently selected.     If lstAvailable.SelectedIndex <> -1 Then         Dim Ord As Order         Try             ' Put the record in progress.             ' This step allows you to run multiple OrderFillClient             ' instances at the same time without conflict.             Ord = DB.Order.SetOrderInProgress(Val(lstAvailable.Text))             ' Move the item from one listbox to the next.             ' Note that the item in lstInProgress is a full-fledged             ' Order object, with all the order information.             lstInProgress.Items.Add(Ord)             lstAvailable.Items.Remove(lstAvailable.SelectedItem)         Catch Err As Exception             MessageBox.Show(Err.ToString, "Error")         End Try     End If End Sub 

Whenever an item is selected from the in-progress list control, the corresponding order information is displayed (as shown in Figure 17-10). Listing 17-23 shows the event-handling code that displays the information.

Listing 17-23 Displaying order information
 Private Sub lstInProgress_SelectedIndexChanged( _   ByVal sender As System.Object, ByVal e As System.EventArgs) _   Handles lstInProgress.SelectedIndexChanged     If lstInProgress.SelectedIndex <> -1 Then         Dim Ord As Order = CType(lstInProgress.SelectedItem, Order)         Dim Display As String         Display = "Order ID: " & Ord.OrderID.ToString()         Display &= Environment.NewLine         Display &= "Customer ID: " & Ord.CustomerID & _         Display &= Environment.NewLine         Dim Station As Station, Item As OrderItem         For Each Station In Ord.Stations             Display &= Environment.NewLine             Display &= " **** STATION ****" & Environment.NewLine             For Each Item In Station.OrderItems                 Display &= "     * ITEM *       "                 Display &= Item.ProductName & ": "                 Display &= Item.PriceOrderAt.ToString("C")                 Display &= Environment.NewLine             Next         Next         txtOrderInfo.Text = Display     Else         ' This automatically clears the order display when the list         ' is emptied.         txtOrderInfo.Text = ""     End If End Sub 
Figure 17-10. Displaying information for an in-progress order

graphics/f17dp10.jpg

Orders can be returned to the pool just as easily as they are retrieved, as shown in Listing 17-24.

Listing 17-24 Returning an in-progress order
 Private Sub cmdReject_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles cmdReject.Click     If lstInProgress.SelectedIndex <> -1 Then         Try             Dim ID As Integer             ID = CType(lstInProgress.SelectedItem, Order).OrderID             DB.Order.SetOrderAvailable(ID)             ' Move the item from one listbox to the next.             lstAvailable.Items.Add(ID)             lstInProgress.Items.Remove(lstInProgress.SelectedItem)         Catch Err As Exception 
             MessageBox.Show(Err.ToString, "Error")         End Try     End If End Sub 

To complete an order, the ID is retrieved from the currently selected item and submitted to the database component (as shown in Listing 17-25). The unique ID for the shipping record is displayed in a message box so it can be noted in the production paperwork.

Listing 17-25 Completing an order
 Private Sub cmdComplete_Click(ByVal sender As System.Object, _   ByVal e As System.EventArgs) Handles cmdComplete.Click     Try         ' Complete the order.         Dim OrderID, ShipID As Integer         OrderID = CType(lstInProgress.SelectedItem, Order).OrderID         ShipID = DB.Order.CompleteOrder(OrderID)         ' Update the list.         lstInProgress.Items.Remove(lstInProgress.SelectedItem)         ' Notify the user.         MessageBox.Show("Confirmation for this shipment is: " & _           ShipID.ToString, "Shipment Recorded")     Catch         MessageBox.Show(Err.ToString, "Error")     End Try End Sub 

Note that error-handling code is always used when the application performs a database operation. Otherwise, an unhandled error could cause the application to end and jeopardize any other orders that are currently in progress.

When the application closes, it automatically returns all in-progress orders and terminates the queue-monitoring thread (as shown in Listing 17-26). It also unregisters the queue to inform the XML Web service that notification messages are no longer required.

Listing 17-26 Returning orders on shutdown
 Private Sub OrderFillClient_Closing(ByVal sender As Object, _   ByVal e As System.ComponentModel.CancelEventArgs) _   Handles MyBase.Closing     ' Unregister the queue.     Proxy.RegisterNotifyQueue("")     ' You must use this method to reject all the currently in-progress      ' orders. Otherwise, they will not be available in the future.     Dim Ord As Order     For Each Ord In lstInProgress.Items         DB.Order.SetOrderAvailable(Ord.OrderID)     Next     ' It is safe to abort this thread while it is sleeping     ' or waiting for messages.     MonitorThread.Abort()     MonitorThread.Join() End Sub 

The Queue Monitor

The final ingredient in the order-filling client is the custom queue-monitoring class. It follows the threading guidelines set out in Chapter 6. The process can be divided into five steps:

  1. Check the queue for messages. If messages aren't found, pause the thread for a few seconds.

  2. If a message is found, fire an event to the client. The custom EventArgs object for transmitting the message is shown here:

     Public Class MessageEventArgs     Inherits EventArgs     Public Message As Integer     Public Sub New(ByVal message As Integer)         Me.Message = message     End Sub End Class 
  3. Handle the event and add the message information to an ArrayList member variable. (A collection is used in case several messages are received in quick succession, before the interface can be updated. This approach allows the client to use locking to protect against concurrency errors.)

  4. Invoke a method to refresh the user interface display.

  5. Read the message information from the ArrayList. For each new order ID found, add an entry to the available list.

Listing 17-27 shows the custom object for monitoring the queue. The actual monitoring is performed in the DoWatch method. It loops continuously (so long as the RequestStop flag is not set). Each time it attempts to retrieve a message for 30 seconds. If no message is found, an exception is thrown and the thread is temporarily suspended.

Listing 17-27 Monitoring the queue
 Public Class QueueMonitor     Public Event MessageReceived(ByVal sender As Object, _       ByVal e As MessageEventArgs)     Public RequestStop As Boolean = False     Public Sub DoWatch()         Dim Queue As New MessageQueue( _           ConfigurationSettings.AppSettings("MessageQueue"))         Dim Message As System.Messaging.Message         Do Until RequestStop             Dim AllowedTypes() As Type = {GetType(Integer)}             Queue.Formatter = New XmlMessageFormatter(AllowedTypes)             Try                 Message = Queue.Receive(TimeSpan.FromSeconds(30))                 If Not Message Is Nothing Then                     RaiseEvent MessageReceived(Me, _                       New MessageEventArgs(Message.Body))                 End If 
             Catch e As MessageQueueException                 ' No message was received after 30 seconds.                 If Not RequestStop Then                     Thread.Sleep(TimeSpan.FromSeconds(30))                 End If             End Try         Loop     End Sub End Class 

You could use the MessageQueue.Receive method without specifying a timeout, but this would tie up the thread indefinitely. By using the looping approach, the thread can still respond (within no more than 30 seconds) if the RequestStop variable is set to True. Incidentally, the code performs equally well if you loop more aggressively, reducing the timeout and thread sleep to 5 or fewer seconds. However, A. Datum considers a notification time of 30 seconds to be satisfactory. Optionally, you can retrieve the wait time from a configuration file and set it programmatically.

When the event is fired, the update takes two steps. First, the event is handled and the message information is recorded. Second, the AddNewFromQueue method is invoked to update the user interface (as shown in Listing 17-28).

Listing 17-28 Handling the MessageReceived event
 Private Messages As New ArrayList() Private Sub Monitor_MessageReceived(ByVal sender As Object, _   ByVal e As MessageEventArgs) Handles Monitor.MessageReceived     SyncLock Messages         Messages.Add(e.Message)     End SyncLock     Me.Invoke(New MethodInvoker(AddressOf AddNewFromQueue)) End Sub 

Remember that the MessageReceived event takes places on the QueueMonitor thread, so it can't update the user interface directly. Instead, the Control.Invoke method marshals this code to the user interface thread, where it can perform its work safely. The private AddNewFromQueue function then updates the interface (as shown in Listing 17-29).

Listing 17-29 Updating the interface
 Private Sub AddNewFromQueue()     SyncLock Messages         Dim Message As String         For Each Message In Messages             lstAvailable.Items.Add(Message)         Next         Messages.Clear()     End SyncLock End Sub 

No additional order information is required. The full order is retrieved if the order is selected and placed in progress.



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