Home > Articles > Programming > Windows Programming

This chapter is from the book

This chapter is from the book

5.6 Building VB.NET Serviced Components

Now it's time for you to use serviced components to build the supermarket ordering and inventory system outlined in the previous section! You'll work through each step in the process, from designing to coding and then testing the components in an ASP.NET application.

LAB 5-1 An Ordering and Inventory System Made with Serviced Components

STEP 1. Create a new VB.NET class library project.

  1. Open VS.NET and select File'New'Project.

  2. Highlight Class Library and give the new project the name "SupermarketSystem".

STEP 2. Design the components.

  1. Consider the design of your system. First, establish the users of the system and their roles:

    • Supervisors (adding new products, receiving orders, updating inventory)

    • Receiving Clerks (receiving orders)

    • Suppliers (shipping goods to the supermarket and supplying order information)

  2. Organize these functions into a series of classes for your class library. Four classes will handle the business logic of the different duties that each user will perform: Product, ReceiveOrder, UpdateInventory, and CreateOrder. Lab Figure 5-1 shows the classes and the methods of each class.

    Lab Figure 5-1Lab Figure 5-1 The classes and methods of the Supermarket program

    Adding a new product to the system, updating inventory, receiving an order, and creating an order all involve operations on the database tier of the application. For some of these operations, database updates can occur in multiple tables. It's important to keep data integrity across these tables. You also want to implement security features so the users perform only the functions that their roles designate. You'll see this functionality develop as you code the components.

STEP 3. Write code for component functionality.

In the process of implementing the components, we'll discuss these topics:

  • How to use the System.EnterpriseServices namespace and one particular class: the ServicedComponent class

  • How to use class attributes to specify the type of COM+ support you want your components to have

  • How to specify an interface from which to call the components

  • How to control the transaction inside the components

  1. Create the classes outlined in the specifications in Step 2: Product, ReceiveOrder, UpdateInventory, and CreateOrder. For each of these classes, right-click the project in the Solution Explorer and select Add Class...from the menu. Name the classes as listed above. VS.NET will "stub out" an empty class definition for each class.

  2. Building serviced components requires support from COM+ Component Services. The .NET Framework implements this support through classes in the System.EnterpriseServices namespace. VS.NET doesn't include this reference by default, so you'll need to add it yourself. Select Project'Add Reference from the menu and select System.EnterpriseServices from the list. Click the Select button and then click OK.

  3. Now you're ready to add component functionality. Start by adding the following code for the Product class (Product.vb).

    Lab Code Sample 5-1

    Imports System
    Imports System.Reflection
    Imports System.EnterpriseServices
    Imports System.Data
    Imports System.Data.SqlClient
    
     <Assembly: ApplicationName("Supermarket")>
     <Assembly: ApplicationActivation(ActivationOption.Library)>
     <Assembly: AssemblyKeyFile("KeyFile.snk")>
     Namespace Supermarket
    
     Public Interface IProduct
       Function Create(ByVal SKU As String, _
                     ByVal Description As String, _
                     ByVal UnitPrice As Decimal, _
                     ByVal StockingBinNumber As String) _
                     As Boolean
       End Interface
    
     <ConstructionEnabled( _
       [Default]:="Default Construction String"), _
       Transaction(TransactionOption.Required)> _
       Public Class Product
           Inherits ServicedComponent
           Implements Supermarket.IProduct
    
         Public Sub New()
    
         End Sub
    
         Protected Overrides Sub Construct( _
               ByVal constructString As String)
    
         End Sub
    
         Function Create(ByVal SKU As String, _
                     ByVal Description As String, _
                     ByVal UnitPrice As Decimal, _
                     ByVal StockingBinNumber As String) _
                     As Boolean _
                     Implements Supermarket.IProduct.Create
    
           Dim objCnn As SqlConnection
           Dim objCmd As SqlCommand
           Dim objParam As SqlParameter
           Dim intRowsReturned As Integer
    
           Try
             objCnn = New SqlConnection()
             objCnn.ConnectionString = _
    "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
             objCnn.Open()
             objCmd = objCnn.CreateCommand()
             objCmd.CommandText = _
       "INSERT INTO Product " & _
       "( SKU, Description, UnitPrice, StockingBinNumber ) " & _
       "VALUES ( @sku, @description, @unitprice, @stockingbinnumber )"
    
             objParam = New SqlParameter()
             With objParam
                .ParameterName = "@sku"
                .SqlDbType = SqlDbType.VarChar
                .Direction = ParameterDirection.Input
                .Value = SKU
             End With
             objCmd.Parameters.Add(objParam)
    
             objParam = New SqlParameter()
             With objParam
                .ParameterName = "@description"
                .SqlDbType = SqlDbType.VarChar
                .Direction = ParameterDirection.Input
                .Value = Description
             End With
    
             objCmd.Parameters.Add(objParam)
             objParam = New SqlParameter()
             With objParam
                .ParameterName = "@unitprice"
                .SqlDbType = SqlDbType.Decimal
                .Direction = ParameterDirection.Input
                .Value = UnitPrice
             End With
             objCmd.Parameters.Add(objParam)
    
             objParam = New SqlParameter()
             With objParam
                .ParameterName = "@stockingbinnumber"
                .SqlDbType = SqlDbType.VarChar
                .Direction = ParameterDirection.Input
                .Value = StockingBinNumber
             End With
             objCmd.Parameters.Add(objParam)
    
             intRowsReturned = _
                objCmd.ExecuteNonQuery()
             Create = True
               ContextUtil.SetComplete()
    
           Catch E As Exception
             Create = False
           ContextUtil.SetAbort()
           Finally
             objCnn.Close()
           End Try
    
         End Function
    
       End Class
    
    End Namespace

    This code implements a serviced component. As mentioned before, a serviced component is a .NET class that uses COM+ Component Services. The class becomes a serviced component when it derives from the System.EnterpriseServices.ServicedComponent class. Before we talk more about the ServicedComponent class, let's first investigate the beginning of the code where some assembly-level attributes are declared.

    Making a serviced component requires you to provide information to COM+ Component Services about the component's configuration. First you designate to which package, or COM+ application, the component will belong. That designation is made with the ApplicationName assembly-level attribute shown in line . The name for the order and inventory application is "Supermarket". COM+ applications are listed in the Component Services Console.

    COM+ Component Services provides a runtime environment for assembly components. You can also control where the components are activated—in the same process as the creator of the object (IIS) or in a separate system process (dllhost.exe). You can control application activation by specifying the ApplicationActivation assembly attribute shown in line . This code specifies ActivationOption.Library, which causes components to be activated in the creator's process. So, if you were running these components inside ASP.NET Web Forms, the creating process would be the Web server, IIS. The ActivationOption.Server option provides faster performance; this option, which runs the component in a system process, provides more isolation, so the component's execution won't adversely affect the execution of IIS. One advantage to using ActivationOption.Library is to make debugging easier.

    The final assembly-level attribute, AssemblyKeyFile, specifies a shared name for the assembly (see line ). The shared name is sometimes referred to as a strong name. A shared name ensures that a name assigned to a component is unique. This is accomplished by using a public/private cryptographic key pair to sign each shared component in the assembly. The public key is then published with the assembly. Besides specifying this attribute in the code, you'll need to actually generate the key file specified for the assembly-level attribute. To do this, use sn.exe, the Strong Name Utility. Lab Figure 5-2 shows how this is done.

    Lab Figure 5-2Lab Figure 5-2 Specifying a strong name for the assembly using sn.exe

    The usage of sn.exe, as shown in Lab Figure 5-2, outputs a .snk file. This is the file name that you specify in the AssemblyKeyFile attribute in the code (change the path to the location of your generated key file). You must complete this step before you compile your project. These assembly-level attributes appear once in the project's code. The class file in which they appear is irrelevant, but they must be declared once and only once in the assembly code.

    Warning

    Never let anybody else have access to your .snk file. It contains a private key that is for your use only.

    Let's move on to the main part of the component code. Line defines the Supermarket namespace, which will contain all the components for the Supermarket application. Line declares an interface you'll use in the client application (the ASP.NET Web Form) to call the component. The interface has one function, Create(), which you'll use to set up a new product in the Product database table.

    The component's class definition comes next. The code beginning in line shows two class-level attributes for the Product class. The first one, ConstructionEnabled, specifies that the class will be able to use COM+ constructor strings. A constructor string is a string passed into the activation procedure of the component. This string can be specified by a system administrator inside the Component Services Console. A constructor string can contain any information, but typically you'll use it for passing initialization information to the component. If no constructor string is given for the component in the Component Services Console (see Lab Figure 5-3, which shows the Component Services Console dialog box for setting a constructor string), a default constructor string is used by specifying it in the attribute as shown in the continuation of line . The other class-level attribute specifies how the class will participate in transactions. Use the Transaction attribute to specify the participation level. In this code, Transaction uses TransactionOption.Required. This indicates that the Product component should participate in an existing transaction if one already exists. If no transaction exists, a new transaction will begin and the Product component will execute within the boundaries of that transaction.

    Lab Figure 5-3Lab Figure 5-3 Specifying a constructor string in Component Services

    The class definition continues with the specification of an inherited class and an Implements statement. Since this will be a serviced component, it needs to derive from the ServicedComponent class, as shown in line . In line the class implements the IProduct interface specified earlier.

    Line provides implementation support for constructor strings. Since the code specified that the component will have support for constructor strings with the class-level attribute ConstructionEnabled, here there is an override method for the Construct() sub. This method will be called upon object construction, and the constructor string assigned to the component will be available in the constructString variable.

    Now follows the implementation of the Create() method, which accepts a new product's SKU number, product description, unit price, and stocking bin number. The Create() method then executes the appropriate ADO.NET code to insert a new row into the Product database table. (ADO.NET code will be discussed in Chapter 7. For now, just be aware that the Product component contains this method that will be callable from an ASP.NET Web Form.)

    Although the discussion of the ADO.NET code details is deferred, it's important to point out two details in the Create() method. These are two methods of the ContextUtil object, SetComplete() in line and SetAbort() in line . These methods cast a vote in the current transaction. Typically, when you have verified that all of the code inside a particular component method has executed successfully, a call to SetComplete() is made. This tells COM+ Component Services that a unit of work has succeeded and that database integrity and consistency is assured for updates made by the unit of work. It's a signal that the transaction can continue running. Conversely, if a failure occurs (an exception or other user-defined error or condition), the program needs to cast a "fail" vote for the transaction by calling SetAbort(). This will cause COM+ Component Services to stop the transaction and roll back any changes made to the database by previous steps in the transaction.

  4. Now that you understand the basic pieces of the Product class code, add the code for the remaining classes (CreateOrder, UpdateInventory, and ReceiveOrder). The implementations are all different, of course, but they follow pretty much the same conventions as the Product component.

    Lab Code Sample 5-2

    Imports System
    Imports System.Reflection
    Imports System.EnterpriseServices
    Imports System.Data
    Imports System.Data.SqlClient
    
    
    Namespace Supermarket
    
       Public Interface ICreateOrder
          Function Create(ByVal OrderNumber As String, _
                          ByVal SupplierName As String) _
                          As Boolean
          Function AddItems(ByVal OrderNumber As String, _
                          ByVal SKU As String, _
                          ByVal Quantity As Integer) _
                          As Boolean
       End Interface
    
       <ConstructionEnabled( _
       [Default]:="Default Construction String"), _
       Transaction(TransactionOption.Required)> _
       Public Class CreateOrder
          Inherits ServicedComponent
          Implements ICreateOrder
    
          Public Sub New()
    
          End Sub
    
          Public Function Create(ByVal OrderNumber As String, _
                          ByVal SupplierName As String) _
                          As Boolean _
                          Implements ICreateOrder.Create
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim intRowsAffected As Integer
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
       "INSERT INTO Orders " & _
       "( OrderNumber, SupplierName, OrderReceived ) " & _
       "VALUES ( @OrderNumber, @SupplierName, @OrderReceived )"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = OrderNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SupplierName"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SupplierName
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderReceived"
                   .SqlDbType = SqlDbType.Bit
                   .Direction = ParameterDirection.Input
                   .Value = False
                End With
                objCmd.Parameters.Add(objParam)
    
                intRowsAffected = objCmd.ExecuteNonQuery()
    
                Create = True
                ContextUtil.SetComplete()
             Catch E As Exception
                Create = False
                ContextUtil.SetAbort()
             End Try
          End Function
    
          Public Function AddItems(ByVal OrderNumber As String, _
                          ByVal SKU As String, _
                          ByVal Quantity As Integer) _
                          As Boolean _
                          Implements ICreateOrder.AddItems
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim intMaxLineNumber As Integer
             Dim intRowsAffected As Integer
             Dim objTemp As Object
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
                   "SELECT MAX( LineItemNumber ) " & _
                   "FROM OrderDetails " & _
                   "WHERE OrderNumber = @OrderNumber"
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = OrderNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objTemp = objCmd.ExecuteScalar()
                If TypeOf objTemp Is DBNull Then
                   intMaxLineNumber = 1
                Else
                   intMaxLineNumber = CType(objTemp, Integer)
                   intMaxLineNumber += 1
                End If
    
                objCmd = objCnn.CreateCommand()
                objCmd.CommandText = _
                   "INSERT INTO OrderDetails " & _
                   "( OrderNumber, LineItemNumber, SKU, " & _
                   "QuantityReceived, Quantity ) VALUES " & _
                   "( @OrderNumber, @LineNumber, @SKU, " & _
                   "@QuantityReceived, @Quantity )"
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = OrderNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@LineNumber"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = intMaxLineNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SKU"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SKU
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@QuantityReceived"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = 0
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@Quantity"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = Quantity
                End With
                objCmd.Parameters.Add(objParam)
    
                intRowsAffected = objCmd.ExecuteNonQuery()
    
                AddItems = True
                ContextUtil.SetComplete()
    
             Catch E As Exception
                AddItems = False
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
    
          End Function
    
       End Class
    End Namespace

    Lab Code Sample 5-3

    Imports System
    Imports System.Reflection
    Imports System.EnterpriseServices
    Imports System.Data
    Imports System.Data.SqlClient
    
    Namespace Supermarket
    
       Public Interface IUpdateInventory
          Function Update(ByVal BinNumber As String, _
                          ByVal SKU As String, _
                          ByVal Quantity As Integer) _
                          As Boolean
          Function GetStockingLocation(ByVal SKU As String) _
                          As String
       End Interface
    
       <ConstructionEnabled( _
       [Default]:="Default Construction String"), _
       Transaction(TransactionOption.Required)> _
       Public Class UpdateInventory
          Inherits ServicedComponent
          Implements IUpdateInventory
    
          Public Sub New()
    
          End Sub
    
          Public Function GetStockingLocation( _
                  ByVal SKU As String) As String _
                  Implements IUpdateInventory.GetStockingLocation
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim objTemp As Object
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
                      "SELECT StockingBinNumber " & _
                      "FROM Product WHERE SKU = @SKU"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SKU"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SKU
                End With
                objCmd.Parameters.Add(objParam)
    
                objTemp = objCmd.ExecuteScalar()
                If TypeOf objTemp Is DBNull Then
                   GetStockingLocation = ""
                Else
                   GetStockingLocation = _
                      CType(objCmd.ExecuteScalar(), String)
                End If
                ContextUtil.SetComplete()
    
             Catch E As Exception
                ContextUtil.SetAbort()
                GetStockingLocation = ""
             Finally
                objCnn.Close()
             End Try
    
          End Function
    
          Private Function InventoryRecExists( _
                  ByVal SKU As String, _
                  ByVal StockingBinNumber As String) _
                  As Boolean
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim intRowCount As Integer
             Dim objTemp As Object
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
                        "SELECT COUNT(*) FROM Inventory " & _
                        "WHERE SKU = @SKU AND " & _
                        "BinNumber = @StockingBinNumber"
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SKU"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SKU
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@StockingBinNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = StockingBinNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objTemp = objCmd.ExecuteScalar()
                If TypeOf objTemp Is DBNull Then
                   intRowCount = 0
                Else
                   intRowCount = CType(objTemp, Integer)
                End If
    
                If intRowCount > 0 Then
                   InventoryRecExists = True
                Else
                   InventoryRecExists = False
                End If
    
                ContextUtil.SetComplete()
    
             Catch E As Exception
                InventoryRecExists = False
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
    
          End Function
    
          Private Sub UpdateInventoryRecord( _
                      ByVal BinNumber As String, _
                      ByVal SKU As String, _
                      ByVal Quantity As Integer)
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim intRowCount As Integer
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = "UPDATE Inventory " & _
                     "SET Quantity = Quantity + @Quantity " & _
                     "WHERE BinNumber = @BinNumber AND SKU = @SKU"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@Quantity"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = Quantity
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@BinNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = BinNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SKU"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SKU
                End With
                objCmd.Parameters.Add(objParam)
    
                intRowCount = objCmd.ExecuteNonQuery()
                ContextUtil.SetComplete()
    
             Catch E As Exception
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
          End Sub
    
          Private Sub InsertInventoryRecord( _
                  ByVal BinNumber As String, _
                  ByVal SKU As String, _
                  ByVal Quantity As Integer)
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim intRowCount As Integer
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
                     "INSERT INTO Inventory " & _
                     "( BinNumber, SKU, Quantity ) VALUES " & _
                     "( @BinNumber, @SKU,  @Quantity )"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@BinNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = BinNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@SKU"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = SKU
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@Quantity"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = Quantity
                End With
                objCmd.Parameters.Add(objParam)
    
                intRowCount = objCmd.ExecuteNonQuery()
                ContextUtil.SetComplete()
    
             Catch E As Exception
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
          End Sub
    
          Public Function Update(ByVal BinNumber As String, _
                  ByVal SKU As String, _
                  ByVal Quantity As Integer) _
                  As Boolean _
                  Implements IUpdateInventory.Update
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim strStockingLocation As String
             Dim intRowsAffected As Integer
    
             Try
                If InventoryRecExists(SKU, BinNumber) Then
                   UpdateInventoryRecord( _
                        BinNumber, _
                        SKU, _
                        Quantity)
                Else
                   InsertInventoryRecord( _
                        BinNumber, _
                        SKU, _
                        Quantity)
                End If
    
                Update = True
                ContextUtil.SetComplete()
    
             Catch E As Exception
                Update = False
                ContextUtil.SetAbort()
             End Try
    
          End Function
       End Class
    End Namespace

    Lab Code Sample 5-4

    Imports System
    Imports System.Reflection
    Imports System.EnterpriseServices
    Imports System.Data
    Imports System.Data.SqlClient
    
    Namespace Supermarket
    
       Public Interface IReceiveOrder
          Function GetNextLineItem(ByVal OrderNumber As String, _
                          ByRef SKU As String) As Integer
          Function Receive(ByVal OrderNumber As String, _
                          ByVal SKU As String, _
                          ByVal LineNumber As Integer, _
                          ByVal QuantityReceived As Integer) _
                          As Boolean
       End Interface
    
       <ConstructionEnabled( _
       [Default]:="Default Construction String"), _
       Transaction(TransactionOption.Required)> _
       Public Class ReceiveOrder
          Inherits ServicedComponent
          Implements IReceiveOrder
    
          Public Sub New()
    
          End Sub
    
          Private Sub UpdateOrderDeatils( _
                      ByVal OrderNumber As String, _
                      ByVal LineNumber As Integer, _
                      ByVal QuantityReceived As Integer)
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim objSQLDr As SqlDataReader
             Dim intRowsAffected As Integer
    
             Try
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
      "UPDATE OrderDetails " & _
      "SET QuantityReceived = " & _
      "QuantityReceived + @QuantityReceived " & _
      "WHERE OrderNumber = " & _
      "@OrderNumber AND LineItemNumber = @LineNumber"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@QuantityReceived"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = QuantityReceived
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = OrderNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@LineNumber"
                   .SqlDbType = SqlDbType.Int
                   .Direction = ParameterDirection.Input
                   .Value = LineNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                intRowsAffected = objCmd.ExecuteNonQuery()
    
                ContextUtil.SetComplete()
    
             Catch E As Exception
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
    
          End Sub
    
          Public Function GetNextLineItem( _
                    ByVal OrderNumber As String, _
                    ByRef SKU As String) _
                    As Integer Implements _
                    IReceiveOrder.GetNextLineItem
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim objSQLDr As SqlDataReader
    
             Try
    
                objCnn = New SqlConnection()
                objCnn.ConnectionString = _
      "Initial Catalog=Supermarket;Data Source=localhost;uid=sa;pwd="
                objCnn.Open()
                objCmd = objCnn.CreateCommand()
    
                objCmd.CommandText = _
                    "SELECT MAX(LineItemNumber), " & _
                    "SKU FROM OrderDetails od, Orders o " & _
                    "WHERE od.OrderNumber = @OrderNumber AND " & _
                    "o.OrderReceived = 0 AND " & _
                    "o.OrderNumber = od.OrderNumber GROUP BY SKU"
    
                objParam = New SqlParameter()
                With objParam
                   .ParameterName = "@OrderNumber"
                   .SqlDbType = SqlDbType.VarChar
                   .Direction = ParameterDirection.Input
                   .Value = OrderNumber
                End With
                objCmd.Parameters.Add(objParam)
    
                objSQLDr = objCmd.ExecuteReader()
                objSQLDr.Read()
    
                If Not objSQLDr.IsDBNull(0) Then
                   GetNextLineItem = objSQLDr.GetInt32(0)
                   SKU = objSQLDr.GetString(1)
                Else
                   GetNextLineItem = -1
                   SKU = ""
                End If
    
                ContextUtil.SetComplete()
                objSQLDr.Close()
    
             Catch E As Exception
                GetNextLineItem = -1
                SKU = ""
                ContextUtil.SetAbort()
             Finally
                objCnn.Close()
             End Try
    
          End Function
    
          Public Function Receive(ByVal OrderNumber As String, _
                          ByVal SKU As String, _
                          ByVal LineNumber As Integer, _
                          ByVal QuantityReceived As Integer) _
                          As Boolean _
                          Implements IReceiveOrder.Receive
    
             Dim objCnn As SqlConnection
             Dim objCmd As SqlCommand
             Dim objParam As SqlParameter
             Dim objInvUpdate As IUpdateInventory
             Dim strBinNumber As String
    
             Try
                UpdateOrderDeatils(OrderNumber, _
                        LineNumber, _
                        QuantityReceived)
    
                objInvUpdate = New UpdateInventory()
                strBinNumber = _
                   objInvUpdate.GetStockingLocation(SKU)
                If objInvUpdate.Update(strBinNumber, _
                        SKU, QuantityReceived) Then
                   Receive = True
                   ContextUtil.SetComplete()
                Else
                   Receive = False
                   ContextUtil.SetAbort()
                End If
    
             Catch E As Exception
                Receive = False
                ContextUtil.SetAbort()
             End Try
          End Function
    
       End Class
    End Namespace
  5. Now that you've entered the main code for the Supermarket COM+ application, it's time to compile it. Choose Build'Build Solution from the menu to create the assembly file. In the next step, you'll create a project that references that assembly.

STEP 4. Create the ASP.NET application.

  1. Create a new VB ASP.NET project called "SupermarketWeb".

  2. Add a reference to System.EnterpriseServices to the project by right-clicking the project icon and selecting Add Reference... from the pop-up menu.

  3. Add four new Web Forms (.aspx files) and their code-behind files (.vb files) to the project, named as follows:

    Lcs5-5.aspx, Lcs5-5.aspx.vb
    Lcs5-6.aspx, Lcs5-6.aspx.vb
    Lcs5-7.aspx, Lcs5-7.aspx.vb
    Lcs5-8.aspx, Lcs5-8.aspx.vb

    These Web Forms will test the different components and their functionality.

    Product Component Test Web Form

    The HTML for the Lcs5-5.aspx file appears below.

    Lab Code Sample 5-5

    <%@ Page Language="vb"
       AutoEventWireup="false"
       src="Lcs5-05.aspx.vb"
       Inherits="WebForm1"
       Transaction="RequiresNew"%>
    <html>
    <head>
    <title>Create New Product</title>
    </head>
    <body>
    
    <form id="Form1"
       method="post"
       runat="server">
    
    <p>SKU:
    <asp:TextBox
       id=txtSKU
       runat="server">
    </asp:TextBox></p>
    
    <p>Description:
    <asp:TextBox
       id=txtDescription
       runat="server">
    </asp:TextBox></p>
    
    <p>Unit Price:
    <asp:TextBox
       id=txtUnitPrice
       runat="server">
    </asp:TextBox></p>
    
    <p>Stocking Location:
    <asp:TextBox
       id=txtStockLoc
       runat="server">
    </asp:TextBox></p>
    
    <p>
    <asp:Button
       id=cmdAddProduct
       runat="server"
       Text="Add Product">
    </asp:Button>
    
    <asp:CompareValidator
       id=CompareValidator1
       runat="server"
       ErrorMessage="You must enter a price (number)"
       Type="Double"
       ControlToValidate="txtUnitPrice"
       Operator="DataTypeCheck">
    </asp:CompareValidator></p>
    
    </form>
    </body>
    </html>

    ASP.NET Web Forms can run inside the context of a COM+ Component Services transaction. In this ASP.NET Web application, the Web Forms call the serviced components in response to events raised by Web Controls (buttons clicked and so on). Since the ASP.NET Web Form is the initiator of calls made into a COM+ application, the Web Form is considered the root of the transaction. It has the "final vote" as to whether or not the transaction succeeds or fails.

    In order to designate that the Web Form will participate in a transaction, you need to set the Transaction page attribute (highlighted in bold above). This code specifies the level as RequiresNew. This means that the page will always begin a new transaction for any units of work executed during the lifetime of the page.

    Here is the code-behind file, Lcs5-5.aspx.vb, for the preceding Web Form.

    Imports SupermarketSystem.Supermarket
    Imports System.EnterpriseServices
    Imports System.Reflection
    Imports System.ComponentModel
    
    Public Class WebForm1
          Inherits System.Web.UI.Page
          Protected WithEvents cmdAddProduct As _
       System.Web.UI.WebControls.Button
          Protected WithEvents txtSKU As _
       System.Web.UI.WebControls.TextBox
          Protected WithEvents txtDescription As _
       System.Web.UI.WebControls.TextBox
          Protected WithEvents txtUnitPrice As _
       System.Web.UI.WebControls.TextBox
          Protected WithEvents txtStockLoc As _
       System.Web.UI.WebControls.TextBox
          Protected WithEvents CompareValidator1 As_
       System.Web.UI.WebControls.CompareValidator
    
          Private Sub Page_Load(ByVal sender As System.Object, _
                 ByVal e As System.EventArgs) Handles MyBase.Load
    
          End Sub
    
          Private Sub cmdAddProduct_Click( _
             ByVal sender As System.Object, _
             ByVal e As System.EventArgs) _
             Handles cmdAddProduct.Click
    
             Dim objProduct As IProduct
             Dim bln As Boolean
    
             objProduct = New Product()
             bln = objProduct.Create(txtSKU.Text, _
                      txtDescription.Text, _
                      CDec(txtUnitPrice.Text), _
                      txtStockLoc.Text)
    
             If bln Then
                    ContextUtil.SetComplete()
             Else
                    ContextUtil.SetAbort()
             End If
    
       End Sub
    End Class

    The Click event for the cmdAddProduct button calls the Product component to add a new product to the database. The code creates a new Product object and obtains a reference to the IProduct interface. It then calls the Create() method. If the call was successful (returned True), SetComplete() is called to indicate to COM+ that this unit of work in the transaction was successful. If not, SetAbort()stops the transaction immediately.

    CreateOrder Component Test Web Form

    The code for the Lcs5-6.aspx file follows below. Note that again the Transaction page attribute is specified and set to RequiresNew.

    Lab Code Sample 5-6

    <%@ Page Language="vb"
       AutoEventWireup="false"
       src="Lcs5-06.aspx.vb"
       Inherits="SupermarketCreateOrder"
       Transaction="RequiresNew"%>
    <html>
    <head>
    <title>Create New Order</title>
    </head>
    <body>
    
    <form id="Form1"
       method="post"
       runat="server">
    
    <p>Order number:
    <asp:TextBox
       id=txtOrderNumber
       runat="server">
    </asp:TextBox></p>
    
    <p>Supplier Name:
    <asp:TextBox
       id=txtSupplierName
       runat="server">
    </asp:TextBox></p>
    
    <p>
    <asp:Button
       id=cmdCreateOrder
       runat="server"
       Text="Add">
    </asp:Button></p>
    
    </form>
    </body>
    </html>

    The code-behind file, Lcs5-6.aspx.vb, contains the following code.

    Imports System.EnterpriseServices
    Imports System.Reflection
    Imports SupermarketSystem.Supermarket
    
    Public Class SupermarketCreateOrder
       Inherits System.Web.UI.Page
       Protected WithEvents txtOrderNumber As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents txtSupplierName As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents cmdCreateOrder As _
          System.Web.UI.WebControls.Button
    
       Private Sub Page_Load(ByVal sender As System.Object, _
          ByVal e As System.EventArgs) Handles MyBase.Load
    
       End Sub
    
       Private Sub cmdCreateOrder_Click( _
          ByVal sender As System.Object, _
          ByVal e As System.EventArgs) _
          Handles cmdCreateOrder.Click
    
          Dim objCreateOrder As ICreateOrder
          objCreateOrder = New CreateOrder()
    
          If objCreateOrder.Create(txtOrderNumber.Text, _
                                txtSupplierName.Text) Then
             ContextUtil.SetComplete()
          Else
             ContextUtil.SetAbort()
          End If
       End Sub
    End Class

    Similar to the test Web Form for the Product component, this code creates a CreateOrder object using New. The program calls the Create() method and then calls SetComplete() or SetAbort() upon success or failure, respectively.

    AddToOrder Test Web Form

    The HTML code for the AddToOrder Web Form (Lcs5-7.aspx) appears below.

    Lab Code Sample 5-7

    <%@ Page Language="vb"
       AutoEventWireup="false"
       Codebehind="SupermarketAddToOrder.aspx.vb"
       Inherits="SupermarketWeb.SupermarketAddToOrder"
       Transaction="RequiresNew"%>
    <html>
    <head>
    <title>Add To Order</title>
    </head>
    <body>
    
    <form id="Form1"
       method="post"
       runat="server">
    
    <p>Order Number:
    <asp:TextBox
       id=txtOrderNumber
       runat="server">
    </asp:TextBox></p>
    
    <p>SKU:
    <asp:TextBox
       id=txtSKU
       runat="server">
    </asp:TextBox></p>
    
    <p>Quantity:
    <asp:TextBox
       id=txtQuantity
       runat="server">
    </asp:TextBox></p>
    
    <p>
    <asp:CompareValidator
       id=CompareValidator1
       runat="server"
       ErrorMessage="Quantity must be a whole number!"
       ControlToValidate="txtQuantity"
       Type="Integer"
       Operator="DataTypeCheck">
    </asp:CompareValidator></p>
    
    <p>
    <asp:Button
       id=cmdAddToOrder
       runat="server"
       Text="Add To Order">
    </asp:Button></p>
    
    </form>
    </body>
    </html>

    Here is the associated code-behind file (Lcs5-7.aspx.vb). AddItems() is a method of the CreateOrder component, so the code is very similar to the CreateOrder Web Form.

    Imports System.EnterpriseServices
    Imports System.Reflection
    Imports SupermarketSystem.Supermarket
    
    Public Class SupermarketAddToOrder
       Inherits System.Web.UI.Page
       Protected WithEvents txtOrderNumber As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents txtSKU As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents txtQuantity As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents CompareValidator1 As _
          System.Web.UI.WebControls.CompareValidator
       Protected WithEvents cmdAddToOrder As _
          System.Web.UI.WebControls.Button
    
       Private Sub Page_Load(ByVal sender As System.Object, _
            ByVal e As System.EventArgs) Handles MyBase.Load
    
       End Sub
    
       Private Sub cmdAddToOrder_Click( _
            ByVal sender As System.Object, _
            ByVal e As System.EventArgs) _
            Handles cmdAddToOrder.Click
    
          Dim objCreateOrder As ICreateOrder
          objCreateOrder = New CreateOrder()
    
          If objCreateOrder.AddItems(txtOrderNumber.Text, _
             txtSKU.Text, CInt(txtQuantity.Text)) Then
    
             ContextUtil.SetComplete()
          Else
             ContextUtil.SetAbort()
          End If
       End Sub
    End Class

    ReceiveOrder Component Test Web Form

    Finally, here is the code (Lcs5-8.aspx) for the Web Form that will receive items for an order.

    Lab Code Sample 5-8

    <%@ Page Language="vb"
       AutoEventWireup="false"
       src="SupermarketReceiveOrder.aspx.vb"
       Inherits="SupermarketReceiveOrder"
       Transaction="RequiresNew"%>
    <html>
    <head>
    <title>Receive Order</title>
    </head>
    <body>
    
    <form id="Form1"
       method="post"
       runat="server">
    
    <p>Order Number to Receive:
    
    <asp:TextBox
       id=txtOrderToReceive
       runat="server">
    </asp:TextBox>
    
    <asp:Button
       id=cmdGetOrder
       runat="server"
       Text="Get Order">
    </asp:Button></p>
    
    <p>
    <asp:Panel
       id=Panel1
       runat="server"
       Width="399px"
       Height="144px"
       Enabled="False">
    
       <p>
       <asp:Label
       id=lblOrderNumber
       runat="server"
       Width="184px"
       Height="19px">
       </asp:Label></p>
    
       <p></p><p>
       <asp:Label
       id=lblReceiveSKU
       runat="server"
       Width="183px"
       Height="19px">
       </asp:Label></p>
    
       <p>
       <asp:Label
       id=lblLineNumberReceive
       runat="server"
       Width="188px"
       Height="19px">
       </asp:Label></p>
    
       <p>
       <asp:Label
       id=Label1
       runat="server"
       Width="128px"
       Height="19px">
       Quantity To Receive:
       </asp:Label>
    
       <asp:TextBox
       id=txtQuantityToReceive
       runat="server">
       </asp:TextBox>
    
       <asp:Button
       id=cmdReceive
       runat="server"
       Text="Receive">
       </asp:Button>
    
    </asp:Panel></p>
    
    </form>
    </body>
    </html>

    This Web Form wraps page elements in a Panel Web Control. The panel is initially disabled to avoid displaying or enabling the order information until a valid order number is keyed into the Web Form. Here's the code-behind file (Lcs5-8.aspx.vb).

    Imports System.EnterpriseServices
    Imports System.Reflection
    Imports SupermarketSystem.Supermarket
    
    Public Class SupermarketReceiveOrder
       Inherits System.Web.UI.Page
       Protected WithEvents cmdGetOrder As _
          System.Web.UI.WebControls.Button
       Protected WithEvents Panel1 As _
          System.Web.UI.WebControls.Panel
       Protected WithEvents lblOrderNumber As _
          System.Web.UI.WebControls.Label
       Protected WithEvents Label1 As _
          System.Web.UI.WebControls.Label
       Protected WithEvents txtOrderToReceive As _
          System.Web.UI.WebControls.TextBox
       Protected WithEvents cmdReceive As _
          System.Web.UI.WebControls.Button
       Protected WithEvents lblReceiveSKU As _
          System.Web.UI.WebControls.Label
       Protected WithEvents lblLineNumberReceive As _
          System.Web.UI.WebControls.Label
       Protected WithEvents txtQuantityToReceive As _
          System.Web.UI.WebControls.TextBox
    
       Private Sub Page_Load(ByVal sender As System.Object, _
          ByVal e As System.EventArgs) Handles MyBase.Load
    
       End Sub
    
       Private Sub cmdGetOrder_Click( _
          ByVal sender As System.Object, _
          ByVal e As System.EventArgs) _
          Handles cmdGetOrder.Click
    
          Dim objReceiveOrder As IReceiveOrder
          Dim intLineNumber As Integer
          Dim strSKUToReceive As String
    
          objReceiveOrder = New ReceiveOrder()
    
          intLineNumber = _
               objReceiveOrder.GetNextLineItem( _
               txtOrderToReceive.Text, _
               strSKUToReceive)
    
          If intLineNumber <> -1 Then
             ViewState("OrderToReceive") = txtOrderToReceive.Text
             ViewState("SKUToReceive") = strSKUToReceive
             ViewState("LineNumber") = intLineNumber
             Panel1.Enabled = True
             lblLineNumberReceive.Text = _
                  "Line Number: " & intLineNumber
             lblOrderNumber.Text = _
                  "Order Number: " & txtOrderToReceive.Text
             lblReceiveSKU.Text = "SKU: " & strSKUToReceive
          Else
             Panel1.Enabled = False
          End If
       End Sub
    
       Private Sub cmdReceive_Click( _
          ByVal sender As System.Object, _
          ByVal e As System.EventArgs) _
          Handles cmdReceive.Click
    
          Dim objReceiveOrder As IReceiveOrder
          objReceiveOrder = New ReceiveOrder()
    
          If objReceiveOrder.Receive( _
                   ViewState("OrderToReceive"), _
                   ViewState("SKUToReceive"), _
                   ViewState("LineNumber"), _
                   CInt(txtQuantityToReceive.Text)) Then
    
             ContextUtil.SetComplete()
          Else
             ContextUtil.SetAbort()
          End If
       End Sub
    End Class

    This form uses two Button Web Controls, cmdGetOrder and cmdReceive. This makes a two-step process for receiving items for an order. First, the event handler for cmdGetOrder calls GetNextLineItem(), taking as input the order number the user entered. If there is a line item to receive for the order, the program displays the line-item information in the Label Web Control contained within the Panel Web Control. The Panel Web Control is then enabled, making the information visible to the user. The line-item information is also copied to the ViewState StateBag because the program will need this information on the subsequent post-back that will occur when items are received.

    The event handler for cmdReceive calls the Receive() method. The Receive() method updates the OrderDetails table as well as the Inventory table. Using a transaction in this situation helps point out any discrepancies between quantities in inventory and quantities ordered. The Receive() method returns True on success and False on failure, and the program makes an appropriate call to either SetComplete() or SetAbort() as a result.

  4. Now you need to add a reference to the assembly DLL for the components. Right-click the References folder in the Solution Explorer, select Add Reference, browse to the compiled DLL, and select it. Click OK to add the reference to the selected assembly.

STEP 5. Run the application.

  1. Now you're ready to build the application. Select Build'Build Solution from the menu. Select a start page for the application (like Lcs5-5.aspx) by right-clicking on a Web Form in the Solution Explorer and selecting Set As Start Page from the menu.

  2. Run the application by selecting Debug'Start Without Debugging.

  3. Test the application by entering some products. Then create a new order, add some items to the order, and run an order-receive process. This should complete a full test.

Something important happened when you first called a component's method inside the assembly. The system performed what is known as a lazy registration. A lazy registration automatically places the components in the assembly into a new COM+ application in the COM+ Component Services Console. It does this based on the assembly-level attributes specified in the component assembly code. Lab Figure 5–4 shows the Supermarket COM+ application in the COM+ Component Services Console.

Lab Figure 5-4Lab Figure 5-4 The Supermarket application within the COM+ Component Services Console

STEP 6. Add role-based security.

  1. One of the requirements for the application is that only certain categories of users should be allowed to run certain components. To enable role-based security for the Supermarket COM+ application, right-click on the Supermarket COM+ application icon and select Properties. Click the Security tab. Check the boxes and radio buttons as shown in Lab Figure 5-5.

    Lab Figure 5-5Lab Figure 5-5 Enabling role-based security for the Supermarket application

  2. Now you can set up the user roles (Receiving Clerks, Supervisors, and Suppliers) inside the COM+ Component Services Console. For each role, right-click the Roles folder under the Supermarket application (see Lab Figure 5-6), select New'Role from the menu, and assign a name for the role.

    Lab Figure 5-6Lab Figure 5-6 Adding roles for the Supermarket application

  3. Assign users to each role by right-clicking the User folder under the role and selecting New'User from the menu. Pick a Windows account name(s) or group(s) to assign to the role. Lab Figure 5-7 shows users assigned to the various roles of the Supermarket application.

    Lab Figure 5-7Lab Figure 5-7 Assigning users to roles

  4. Now you need to assign each role to a component. Right-click a component in the COM+ application and select Properties from the menu. Click the Security tab. Check Enforce component level access checks, and then check the roles you wish to assign to the component, as shown in Lab Figure 5-8.

    Lab Figure 5-8Lab Figure 5-8 Assigning roles to components

STEP 7. Test the role-based security.

  1. A convenient way to test the role-based security is to call the components from a console application and run the console application in the security context of a specific user. Normally, when you run an application from the console, Windows uses the security context of the currently logged-on user. By using the runas command, you can run an executable program using any account name (provided, of course, you have the password of that account!). Here's a simple console application you can run to test role-based security.

    Imports System
    Imports SupermarketSystem.Supermarket
    Imports System.EnterpriseServices
    
    Module Module1
    
       Sub Main()
          Dim objCreateOrder As ICreateOrder
    
          objCreateOrder = New CreateOrder()
    
          objCreateOrder.Create("834957239-1", "My Supplier")
    
       End Sub
    
    End Module

    Compile this small testing console application and run it using the runas command below:

    runas /user:rmdevbiz01\matt roletester.exe

    Given the role configuration of the CreateOrder component, the username "matt" should be the only user allowed to run the application. Assuming that the user account "matt" has appropriate Windows permissions set to run the program, it should execute without errors.

  2. You can also perform a negative test by rerunning the program with an account name not included in the Supplier role.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020