Vous êtes sur la page 1sur 54

Version

1.0
MIDDLE EAST TECHNICAL UNIVERSTIY

Ltfi ilteris NEY/e1305937

Creating bookstore web service project report

METU /GRADUATE SECOND PROGRAM

Creating Bookstore Web Service

METU Middle East Technical University 06531 ANKARA/TRKIYE

Prepared By: L.ilteris NEY Graduate Student E1305937 Date : 04/06/2004 Submitted To: Dr. Semih ETIN

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Table of Contents
DEFINITION OF THE PROBLEM .................................................2 LIST OF REQUIREMENTS..........................................................3 USE CASES OF WHOLESALE BOOK SUPPLIER.........................5 USE CASES OF ONLINE BOOKSTORE .....................................6 WEB CLASS HIERARCHY...........................................................7 ARCHITECTURE OF BOOKSTORE PROJECT .................................8 DATABASE DESIGN ..................................................................9 WEB APPLICATION N-TIER LOGGING & EXCEPTION HANDLING MECHANISM ..........................................................................10 WEB ARCHITECTURE SECURITY..............................................11 CONCLUSION ........................................................................12 ADDENDUM ...........................................................................13

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Definition Of The Problem


In the world of software development there lots of improvement in the area of Architectural design and principles. The philosophies and implementation details are changing as the people guiding the development of the application. In this fantastic and yet sometimes complex world of software development there are some tried and true architecture patterns and software development guidelines employed by most architects. Also your design must have an ability to turn towards innovation instead of lending itself to common practices. Web services are one such area where architects must lean on their creative side and hope that their solutions are still successful. In this report we will explain an exciting voyage down the road of Web services application. From requirements to use cases, to database design, to component frameworks, to user interfaces, we will cover each and every aspect of system design required to build an application with collaborative Web services. The reason why we selected online Bookstore web service is everybody walking down the street has some idea about bookstores. It is easy to imagine common processes that exist in Bookstore. Here is the proposed problem definition that appears in a Bookstore (as an example). The example scenario consists of two companies: Ilteriss Online Books, and Oneys Wholesale Book Supplier. Oneys company is merely a wholesale supplier of books. He has no storefront and no direct cusilteriser sales; he simply provides books at wholesale prices to other book suppliers. Ilteriss company runs an online bookstore that allows internet cusilterisers to purchase books through his shopping cart. Ilteris carries no inventory; instead he accepts cusilteriser orders and passes these orders through to Oney who supplies the books. Because Oney has no storefront his only means of generating revenue is to have other bookstores place orders with him. In recognition of this, Oney has decided to build an ASP.NET application with a Web services layer to allow real-time sales with his vendors. In light of this, Ilteris has decided to increase his productivity and create his own online shopping cart system with ASP.NET and a Web services tie-in to Oneys application. By making this business collaboration agreement, both hope to increase revenue and productivity1.

Ref: Scope Document : This scenario also explained in project scope document

METU

B O O K S T O R E

P R O J E C T

R E P O R T

List of Requirements
R E Q U I R E M E N T S O F W H O L E S A L E B O O K S U P P L I E R

The first client meeting is with Oney. After speaking with Oney about his requirements we establish the following information: Must be able to accept orders from other vendors, specifically Ilteris Oney is the only wholesale supplier in Turkey. Due to this fact he has a requirement of accepting orders from the vendors. For our demonstration he must has a capability of accepting orders from Ilteriss online bookstore. Needs a way to manage his inventory of books Wholesale book supplier also needs a GUI to manage or view his bookstore. Needs to be able to view and process orders Oney expressed that he needs have some functionality to view and process orders. Wants his inventory to be secured from unauthorized vendors Being one of the most famous supplier , Oney is searching a mechanism that is accurate and secure. Wants to provide access to his book inventory to select vendors He wants to add only the vendors that has certificate. On-certified vendors must not able to view his online bookstore.
R E Q U I R E M E N T S O F O N L I N E B O O K S T O R E V E N D O R

The next client meeting is with Ilteris. Ilteris has slightly more involved requirements because of the cusilteriser interaction. We learn the following from Ilteris: Requires a public area for regular surfers and a secured area for authorized users Online bookstore requires his web site visitors not to access every single unit on their own. Due to fact that these mechanism involves view chart and process order mechanisms. Needs to be able to track cusilteriser accounts Cusilteriser account must be tracked for bonus prices or promotions for future enhancements. Must be able to search Oneys database for books Cusilteriser must search Wholesale suppliers available books from bookstores web site Needs a cusilteriser login screen Cusilteriser must have a login screen and authentication mechanism.
3

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Needs a shopping cart mechanism to let cusilterisers add/remove books from their cart In every online shopping site has some shopping cart mechanism. This mechanism is required for cusilteriser to view the books that he is going to purchase. Needs a way to place orders in Oneys application For new orders that is appeared in Ilteriss online Bookstore he needs to put them on the system. Wants to let cusilterisers view the orders theyve placed Needs to have a way to let Oneys application notify his system auilterisatically on order completion Wholesale book supplier must confirm the given order. Wants to notify cusilterisers by email when their order is completed When order is completed cusilterisers must be notified. Wants a user friendly error page Any error happens in the systems must notify the visitor with the proper error page. Wants to display to the cusilteriser the number of items in their cart in an area thats always viewable to the cusilteriser In every single page the cusilteriser must be notified .

METU

B O O K S T O R E

P R O J E C T

R E P O R T

USE CASEs of Wholesale Book Supplier

F U N C T I O N S

E X P L A N A T I O N S

View Books Function : Owner of Wholesale book supplier can view the books that is in the stock. Edit Book Function : This function is for editing the books names , prices etc. View Search Orders : To search the confirmed orders owner can checks the availableness. Confirm Orders : This is for the status of confirmation on orders. Web Service Functions : Search Books service : This is for vendors to search books that are only available Create Order Service: To create order vendor uses this service. So vendor need not to check its availability or have to produce program for stock control Get Orders : This service is for other wholesale suppliers to get orders from any other wholesale book supplier

METU

B O O K S T O R E

P R O J E C T

R E P O R T

USE CASEs of Online Bookstore

F U N C T I O N S

E X P L A N A T I O N S

Search Books : With using this function cusilteriser can search books from vendor Add Book to Chart : Cusilteriser can add his/her available and likely to buy books. Remove Book from Cart:: Cusilteriser can change her selection View Shopping Cart: : Cusilteriser can view her Shopping cart Create Order: Cusilteriser can create order Create Cusilteriser: System uses this function to create Cusilterisers Login Cusilteriser: This is a GUI for cusilteriser for login.

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Web Class Hierarchy

As it is in layered approach 2 We have some classes and these classes are in some Tier. These tiers have some special functionalities. But what we defined here is polymorphism3. Also as we can see some functions are overwritten while inherited.

Layered Approach : Refer to Architecture of bookstore project on page no:8 Polymorphism : Refer to OO Design principles

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Architecture Of Bookstore Project

Bookstore Project has n-Tiered Architecture. Architecture consist of 4 tiers. These are Presentation Layers , Business Logic layer , Data Tier Layer and actual Database procedures and Server layer. This layered approach is for only maintainability of the system. As any case in real life situation things can significantly subject to change. When a business situation appears and this business situation requires some modification on the system it only effects the layer that is the change exist. To give a clear example less assume that our database is changed from SQL Server to Oracle. Especially the data connection strings and data adapters need to be changed. If we did not enforce the system as a layered approach any programmer can put the data connection string on the Forms (which is infect in Presentation Layer and must not be changed) and to make this change he must edit the Forms that exist in the system. But this change can have some side effects due to time pass. In layered approach the programmer only modifies the data Tier layer which is dealing with connection string etc.
8

METU

B O O K S T O R E

P R O J E C T

R E P O R T

DataBase Design

Wholesale Book supplier database is shown in figure. There exist five tables in database. These are Order table , Order Book , Availability , client and Book tables. In Order Table we store order status which can be pending or complete , client_Id , whom submitted the order and creation date to store date information about order. Order book table stores matching of the book and order connection. Book table is for book name, author , book price and availability id which is foravailability table. Availability table is for books which can be in stock or in out of stock. Client table stores bookstore vendors whom can access the wholesale book supplier datas or webservices. Client table has also a field about ws_token. This field is used for security measure. We inform the client about his ws_token and client connects to our webservice with a SOAP message involving this security key.

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Web Application N-Tier Logging & Exception Handling Mechanism


One of the most important aspects of proper system design is exception handling. The key to understanding exception flow is application layering. Application layering is an object oriented (OO) design principle known as encapsulation. By implementing correct encapsulation between classes and components, your exception handling becomes almost auilterisatic. In our scenario our layering is fairly straightforward. Each layer is represented by a tier in our framework. This means we have a database layer, a business layer, an ASPX layer, and a Web service layer. By implementing a cusilteris exception class for each layer, we can easily identify and trace our exceptions at any point in our application. Below in figure diagrams the exception flow in both of our applications.

10

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Web Architecture Security


Security is probably the biggest technology related buzzword . When dealing with Web services however, our security responsibilities as a developer are quite different. There are two main aspects of application security: authorization/authentication and data transmission. We want to keep out the people who shouldn't be accessing our system to begin with, and also prevent data from being captured in transit. To secure Oney's Web services implemented a cusilteris SOAP header, which is essentially an inner class definition with some public properties that gets wrapped into the SOAP envelope during a Web method call. By defining properties of this SOAP header class we can set properties that will allow the Web service to determine if the consumer is in fact authorized to access the application. The second security measure is the lock-down of specific protocols, thus preventing the Web service from being accessed in certain ways. Below figure depicts the protocol policies for online bookstore's application.

By removing the HttpPost, HttpGet and Documentation protocols, we entirely prevent the invocation of our Web services via Post and Get, and also remove the ability to generate WSDL documents which would allow unauthorized users to generate Web references and proxy classes for our Web services. By using both security measures in conjunction, we accomplish the following: Prevent the discovery and prevent the ability to invoke our Web service through any means other than HttpSoap, which essentially means the consumers must have a valid proxy class prior to the Web service lock-down. This allows the developer to be the only distributor of Web service access.
11

METU

B O O K S T O R E

P R O J E C T

R E P O R T

If somehow a user managed to discover the Web service they would not be able to generate the WSDL file for the service because the Documentation protocol is removed. If somehow an unauthorized user managed to acquire a proxy class or WSDL file by some extraneous means, they would not pass the authentication checks because they wouldn't have valid ClientIds or WSTokens which must be present in our cusilteris SOAP header.

Conclusion
In this project main aim was only implementing the web services and wholesale owner functions. But for to show the real situation in this area we will try to also implement the online bookstore part but to confess it is not easy task to accomplish in a short period. Nevertheless what we gain from this project are Preparing the project scope Preparing requirement involving the scope Preparing a design document with the proper methods. (Use Cases , Class Diagrams , block diagrams and architecture) Programming with a design document ready Testing with Use cases

Because of the well designed, multi tier components implemented in these applications, providing additional features is a fairly easy process. This is the goal of enterprise application design. Were also able to expand the application to any size without affecting other components which reduces code volume. The most critical point to remember about everything thats been implemented is that the application is not defined by its functionality, but rather by the underlying design used to implement the features. What the product actually does is through a solid architecture framework. That is the ultimate goal of an engineer, to create a reusable framework that can be applied to any requirements possibly conceivable. The most common mistake by most developers is to take a set of requirements and design an application that is one dimensional, and has no reusable features or framework. Even our project proposal involves implementing both bookstore and wholesale supplier. We only implemented wholesalebook supplier. This is why implementing fully onlinebookstore project requires many function even it is not revelant with our project. So we restrict our proposal to reflect this fact.4

Ref: Project Proposal

12

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Addendum
Availability.Db
Imports WSB2BUtil Public Class AvailabilityDb

Private Const CLASS_NAME As String = "AvailabilityDb"

'#################################################################### ##################################################### ' DB Configuration Properties '#################################################################### ##################################################### Friend Shared WSB2B_GET_AVAILABILITY As String = "WSB2B_GET_AVAILABILITY"

Friend Shared PARAM_AVAILABILITY_ID_NAME As String = "@paramAvailabilityId" Friend Shared PARAM_AVAILABILITY_ID_TYPE As SqlDbType = SqlDbType.Int Friend Shared PARAM_AVAILABILITY_ID_SIZE As Integer = 4

' Table field names, used for dataset references Public Shared FIELD_AVAILABILITY_ID As String = "AVAILABILITY_ID" Public Shared FIELD_AVAILABILITY_NAME As String = "AVAILABILITY_NAME" Friend Shared AVAILABILITY_TABLE_NAME As String = "AVAILABILITY"

'#################################################################### ##################################################### ' Returns the entire availability table '#################################################################### ##################################################### Public Function GetAllAvailability() As DataSet Const METHOD_NAME As String = "GetAllAvailability"

Dim DbObj As DbAccess = New DbAccess Try


13

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Dim ReturnValue As Int32

Return DbObj.ExecuteDataset(Me.WSB2B_GET_AVAILABILITY, Nothing, Me.AVAILABILITY_TABLE_NAME)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Function

End Class

BookDb.vb
Imports WSB2BUtil Public Class BookDb

Private Const CLASS_NAME As String = "BookDb"

'#################################################################### ##################################################### ' DB Configuration Properties '#################################################################### ##################################################### Friend Shared WSB2B_GET_BOOKS As String = "WSB2B_GET_BOOKS" Friend Shared WSB2B_UPDATE_BOOK As String = "WSB2B_UPDATE_BOOK" Friend Shared WSB2B_GET_BOOKS_DYNAMIC As String = "WSB2B_GET_BOOKS_DYNAMIC"

Friend Shared PARAM_BOOK_ID_NAME As String = "@paramBookId" Friend Shared PARAM_BOOK_ID_TYPE As SqlDbType = SqlDbType.Int Friend Shared PARAM_BOOK_ID_SIZE As Integer = 4

Friend Shared PARAM_AUTHOR_NAME As String = "@paramAuthor" Friend Shared PARAM_AUTHOR_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_AUTHOR_SIZE As Integer = 50

14

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Friend Shared PARAM_BOOK_NAME_NAME As String = "@paramBookName" Friend Shared PARAM_BOOK_NAME_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_BOOK_NAME_SIZE As Integer = 100

Friend Shared PARAM_PRICE_NAME As String = "@paramPrice" Friend Shared PARAM_PRICE_TYPE As SqlDbType = SqlDbType.SmallMoney Friend Shared PARAM_PRICE_SIZE As Integer = 4

Friend Shared PARAM_WHERE_CLAUSE_NAME As String = "@paramWhereClause" Friend Shared PARAM_WHERE_CLAUSE_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_WHERE_CLAUSE_SIZE As Integer = 1000

' Table field Public Shared Public Shared Public Shared Public Shared Friend Shared

names, used for dataset references FIELD_BOOK_ID As String = "BOOK_ID" FIELD_BOOK_NAME As String = "BOOK_NAME" FIELD_AUTHOR As String = "AUTHOR" FIELD_PRICE As String = "PRICE" BOOK_TABLE_NAME As String = "BOOK"

'#################################################################### ##################################################### ' Returns a set of books, filtered by the criteria parameters '#################################################################### ##################################################### Public Function GetBooks(ByVal BookId As Int32, ByVal AuthorName As String, ByVal BookName As String, ByVal AvailabilityId As Int32) As DataSet Const METHOD_NAME As String = "GetBooks"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection

params.Add(DbObj.MakeParam(Me.PARAM_BOOK_ID_NAME, Me.PARAM_BOOK_ID_TYPE, Me.PARAM_BOOK_ID_SIZE, BookId)) params.Add(DbObj.MakeParam(Me.PARAM_AUTHOR_NAME, Me.PARAM_AUTHOR_TYPE, Me.PARAM_AUTHOR_SIZE, AuthorName)) params.Add(DbObj.MakeParam(Me.PARAM_BOOK_NAME_NAME, Me.PARAM_BOOK_NAME_TYPE, Me.PARAM_BOOK_NAME_SIZE, BookName)) params.Add(DbObj.MakeParam(AvailabilityDb.PARAM_AVAILABILITY_ID_NAME, AvailabilityDb.PARAM_AVAILABILITY_ID_TYPE, _
15

METU

B O O K S T O R E

P R O J E C T

R E P O R T

AvailabilityDb.PARAM_AVAILABILITY_ID_SIZE, AvailabilityId))

Return DbObj.ExecuteDataset(Me.WSB2B_GET_BOOKS, params, Me.BOOK_TABLE_NAME)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Function

'#################################################################### ##################################################### ' Updates the BookId specified with the parameter values '#################################################################### ##################################################### Public Sub UpdateBook(ByVal BookId As Int32, ByVal AuthorName As String, ByVal BookName As String, ByVal Price As Double, _ ByVal AvailabilityId As Int32) Const METHOD_NAME As String = "UpdateBook"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection

params.Add(DbObj.MakeParam(Me.PARAM_BOOK_ID_NAME, Me.PARAM_BOOK_ID_TYPE, Me.PARAM_BOOK_ID_SIZE, BookId)) params.Add(DbObj.MakeParam(Me.PARAM_AUTHOR_NAME, Me.PARAM_AUTHOR_TYPE, Me.PARAM_AUTHOR_SIZE, AuthorName)) params.Add(DbObj.MakeParam(Me.PARAM_BOOK_NAME_NAME, Me.PARAM_BOOK_NAME_TYPE, Me.PARAM_BOOK_NAME_SIZE, BookName)) params.Add(DbObj.MakeParam(Me.PARAM_PRICE_NAME, Me.PARAM_PRICE_TYPE, Me.PARAM_PRICE_SIZE, Price)) params.Add(DbObj.MakeParam(AvailabilityDb.PARAM_AVAILABILITY_ID_NAME, AvailabilityDb.PARAM_AVAILABILITY_ID_TYPE, _ AvailabilityDb.PARAM_AVAILABILITY_ID_SIZE, AvailabilityId))

DbObj.ExecuteProc(Me.WSB2B_UPDATE_BOOK, params)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally
16

METU

B O O K S T O R E

P R O J E C T

R E P O R T

DbObj.Close() DbObj = Nothing End Try End Sub

'#################################################################### ##################################################### ' Returns a set of books, filtered by the ids in the arraylist '#################################################################### ##################################################### Public Function GetBooksByIds(ByVal BookIds As ArrayList) As DataSet Const METHOD_NAME As String = "GetBooksByIds"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection Dim Predicate As New Text.StringBuilder Predicate.Append(" WHERE ").Append(Me.FIELD_BOOK_ID).Append(" IN(").Append(Utils.ConvertCollectionToCSV(BookIds)).Append(")")

params.Add(DbObj.MakeParam(Me.PARAM_WHERE_CLAUSE_NAME, Me.PARAM_WHERE_CLAUSE_TYPE, Me.PARAM_WHERE_CLAUSE_SIZE, Predicate.ToString()))

Return DbObj.ExecuteDataset(Me.WSB2B_GET_BOOKS_DYNAMIC, params, Me.BOOK_TABLE_NAME)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Function

End Class

17

METU

B O O K S T O R E

P R O J E C T

R E P O R T

ClientDb.vb
Imports WSB2BUtil Public Class ClientDb

Private Const CLASS_NAME As String = "ClientDb"

'#################################################################### ##################################################### ' DB Configuration Properties '#################################################################### ##################################################### Friend Shared WSB2B_GET_CLIENTS As String = "WSB2B_GET_CLIENTS"

Friend Shared PARAM_CLIENT_ID_NAME As String = "@paramClientId" Friend Shared PARAM_CLIENT_ID_TYPE As SqlDbType = SqlDbType.Int Friend Shared PARAM_CLIENT_ID_SIZE As Integer = 4

Friend Shared PARAM_CLIENT_NAME_NAME As String = "@paramClientName" Friend Shared PARAM_CLIENT_NAME_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_CLIENT_NAME_SIZE As Integer = 50

Friend Shared PARAM_WS_TOKEN_NAME As String = "@paramWsToken" Friend Shared PARAM_WS_TOKEN_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_WS_TOKEN_SIZE As Integer = 100

' Table field Public Shared Public Shared Public Shared Friend Shared

names, used for dataset references FIELD_CLIENT_ID As String = "CLIENT_ID" FIELD_CLIENT_NAME As String = "CLIENT_NAME" FIELD_WS_TOKEN As String = "WS_TOKEN" CLIENT_TABLE_NAME As String = "CLIENT"

' Static variable for application object storage/retrieval Public Shared CLIENT_DS_REF As String = "CLIENT_DS"

18

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### ##################################################### ' Returns the entire client table '#################################################################### ##################################################### Public Function GetAllClients() As DataSet Const METHOD_NAME As String = "GetAllClients"

Dim DbObj As DbAccess = New DbAccess Try Dim ReturnValue As Int32

Return DbObj.ExecuteDataset(Me.WSB2B_GET_CLIENTS, Nothing, Me.CLIENT_TABLE_NAME)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Function

End Class

DbAccess.vb
Imports System.Data.SqlClient Imports System.Xml Imports System.Configuration.ConfigurationSettings

' The SqlHelper class is intended to encapsulate high performance, scalable best practices for ' common uses of SqlClient. Public NotInheritable Class DbAccess Implements IDisposable

Private conn As SqlConnection

'#################################################################### ##################################################### ' FRIEND METHODS


19

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### #####################################################

' Execute a SqlCommand (that returns a resultset) against the specified SqlConnection ' using the provided parameters. ' e.g.: ' Dim ds as Dataset = ExecuteDataset(CommandType.StoredProcedure, "GetOrders", new SqlParameter("@prodid", 24)) ' Parameters: ' -commandText - the stored procedure name or T-SQL command ' -commandParameters - a collection of SqlParamters used to execute the command ' Returns: a dataset containing the resultset generated by the command Friend Overloads Function ExecuteDataset(ByVal commandText As String, ByVal commandParameters As Collection, ByVal TableName As String) As DataSet

'create a command and prepare it for execution Dim cmd As New SqlCommand Dim ds As New DataSet Dim da As SqlDataAdapter

PrepareCommand(cmd, CType(Nothing, SqlTransaction), CommandType.StoredProcedure, commandText, commandParameters)

'create the DataAdapter & DataSet da = New SqlDataAdapter(cmd)

'fill the DataSet using default values for DataTable names, etc. da.Fill(ds, TableName)

'detach the SqlParameters from the command object, so they can be used again cmd.Parameters.Clear()

'return the dataset Return ds

End Function 'ExecuteDataset

20

METU

B O O K S T O R E

P R O J E C T

R E P O R T

' Execute a SqlCommand that has no return values. Used for create/update/delete functions. ' Parameters: ' -procName - the name of the stored procedure ' -params - a collection of SqlParamters used to execute the command Friend Sub ExecuteProc(ByVal procName As String, ByVal params As Collection) Dim cmd As New SqlCommand PrepareCommand(cmd, CType(Nothing, SqlTransaction), CommandType.StoredProcedure, procName, params) cmd.ExecuteNonQuery() Me.Close() End Sub

' Creates an Sql parameter object Friend Function MakeParam(ByVal ParamName As String, ByVal DbType As SqlDbType, ByVal Size As Int32, ByVal Value As Object) As SqlParameter Dim param As SqlParameter

If Size > 0 Then param = New SqlParameter(ParamName, DbType, Size) Else param = New SqlParameter(ParamName, DbType) End If

param.Direction = ParameterDirection.Input

If TypeOf Value Is DateTime Then If (Not Value Is Nothing AndAlso Convert.ToDateTime(Value).Year <> 1) Then param.Value = Value End If ElseIf TypeOf Value Is Int32 Then If CType(Value, Int32) <> 0 Then param.Value = Value End If Else If (Not Value Is Nothing AndAlso Value.ToString().Length > 0) Then param.Value = Value End If End If

Return param End Function

21

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### ##################################################### ' PRIVATE METHODS '#################################################################### #####################################################

' This method is used to attach array of SqlParameters to a SqlCommand. ' This method will assign a value of DbNull to any parameter with a direction of ' InputOutput and a value of null. ' This behavior will prevent default values from being used, but ' this will be the less common case than an intended pure output parameter (derived as InputOutput) ' where the user provided no input value. ' Parameters: ' -command - The command to which the parameters will be added ' -commandParameters - an array of SqlParameters tho be added to command Private Sub AttachParameters(ByVal command As SqlCommand, ByVal commandParameters As Collection) Dim p As SqlParameter For Each p In commandParameters 'check for derived output value with no value assigned If p.Direction = ParameterDirection.InputOutput And p.Value Is Nothing Then p.Value = Nothing End If command.Parameters.Add(p) Next p End Sub 'AttachParameters

' This method opens (if necessary) and assigns a connection, transaction, command type and parameters ' to the provided command. ' Parameters: ' -command - the SqlCommand to be prepared ' -transaction - a valid SqlTransaction, or 'null' ' -commandType - the CommandType (stored procedure, text, etc.) ' -commandText - the stored procedure name or T-SQL command ' -commandParameters - an array of SqlParameters to be associated with the command or 'null' if no parameters are required Private Sub PrepareCommand(ByVal command As SqlCommand, _ ByVal transaction As SqlTransaction, _ ByVal commandType As CommandType, _ ByVal commandText As String, _
22

METU

B O O K S T O R E

P R O J E C T

R E P O R T

ByVal commandParameters As Collection)

'if the provided connection is not open, we will open it Me.Open()

'associate the connection with the command command.Connection = conn

'set the command text (stored procedure name or SQL statement) command.CommandText = commandText

'if we were provided a transaction, assign it. If Not (transaction Is Nothing) Then command.Transaction = transaction End If

'set the command type command.CommandType = commandType

'attach the command parameters if they are provided If Not (commandParameters Is Nothing) Then AttachParameters(command, commandParameters) End If

Return End Sub 'PrepareCommand

Private Sub Open() ' Open the connection If conn Is Nothing Then conn = New SqlConnection(AppSettings("DatabaseConnString")) conn.Open() End If End Sub

Friend Sub Close() ' Close the connection and cleanup the class with the Dispose() method Me.Dispose() End Sub

Public Sub Dispose() Implements IDisposable.Dispose If Not conn Is Nothing Then conn.Close() conn.Dispose()
23

METU

B O O K S T O R E

P R O J E C T

R E P O R T

conn = Nothing End If GC.SuppressFinalize(Me) End Sub

End Class

Order.Db
Imports WSB2BUtil

Public Class OrderDb

Private Const CLASS_NAME As String = "OrderDb"

'#################################################################### ##################################################### ' DB Configuration Properties '#################################################################### ##################################################### Friend Shared WSB2B_GET_ORDERS As String = "WSB2B_GET_ORDERS" Friend Shared WSB2B_CREATE_ORDER As String = "WSB2B_CREATE_ORDER" Friend Shared WSB2B_CREATE_ORDER_BOOK_REL As String = "WSB2B_CREATE_ORDER_BOOK_REL" Friend Shared WSB2B_CONFIRM_ORDER As String = "WSB2B_CONFIRM_ORDER"

Friend Shared PARAM_ORDER_ID_NAME As String = "@paramOrderId" Friend Shared PARAM_ORDER_ID_TYPE As SqlDbType = SqlDbType.Int Friend Shared PARAM_ORDER_ID_SIZE As Integer = 4

Friend Shared PARAM_ORDER_STATUS_NAME As String = "@paramOrderStatus" Friend Shared PARAM_ORDER_STATUS_TYPE As SqlDbType = SqlDbType.NVarChar Friend Shared PARAM_ORDER_STATUS_SIZE As Integer = 20

' Table field Public Shared Public Shared Public Shared Friend Shared

names, used for dataset references FIELD_ORDER_ID As String = "ORDER_ID" FIELD_ORDER_STATUS As String = "ORDER_STATUS" FIELD_CREATION_DATE As String = "CREATION_DATE" ORDER_TABLE_NAME As String = "ORDER"

24

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### ##################################################### ' Possible order statuses, defined as static constants '#################################################################### ##################################################### Public Shared ORDER_STATUS_PENDING As String = "Pending" Public Shared ORDER_STATUS_COMPLETE As String = "Complete"

'#################################################################### ##################################################### ' Searches the orders table with the parameters specified '#################################################################### ##################################################### Public Function GetOrders(ByVal OrderId As Int32, ByVal OrderStatus As String, ByVal ClientId As Int32) As DataSet Const METHOD_NAME As String = "GetOrders"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection

params.Add(DbObj.MakeParam(Me.PARAM_ORDER_ID_NAME, Me.PARAM_ORDER_ID_TYPE, Me.PARAM_ORDER_ID_SIZE, OrderId)) params.Add(DbObj.MakeParam(Me.PARAM_ORDER_STATUS_NAME, Me.PARAM_ORDER_STATUS_TYPE, Me.PARAM_ORDER_STATUS_SIZE, OrderStatus)) params.Add(DbObj.MakeParam(ClientDb.PARAM_CLIENT_ID_NAME, ClientDb.PARAM_CLIENT_ID_TYPE, ClientDb.PARAM_CLIENT_ID_SIZE, ClientId))

Return DbObj.ExecuteDataset(Me.WSB2B_GET_ORDERS, params, Me.ORDER_TABLE_NAME)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Function

'#################################################################### #####################################################
25

METU

B O O K S T O R E

P R O J E C T

R E P O R T

' Creates a new pending order '#################################################################### ##################################################### Public Sub CreateOrder(ByVal OrderId As Int32, ByVal BookIds As ArrayList, ByVal ClientId As Int32) Const METHOD_NAME As String = "CreateOrder"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection

params.Add(DbObj.MakeParam(Me.PARAM_ORDER_ID_NAME, Me.PARAM_ORDER_ID_TYPE, Me.PARAM_ORDER_ID_SIZE, OrderId)) params.Add(DbObj.MakeParam(Me.PARAM_ORDER_STATUS_NAME, Me.PARAM_ORDER_STATUS_TYPE, Me.PARAM_ORDER_STATUS_SIZE, Me.ORDER_STATUS_PENDING)) params.Add(DbObj.MakeParam(ClientDb.PARAM_CLIENT_ID_NAME, ClientDb.PARAM_CLIENT_ID_TYPE, ClientDb.PARAM_CLIENT_ID_SIZE, ClientId))

' Create the main order entry DbObj.ExecuteProc(Me.WSB2B_CREATE_ORDER, params)

' Now create an association relationship between the order, and each book in the order Dim BookId As Int32 For Each BookId In BookIds params = New Collection params.Add(DbObj.MakeParam(Me.PARAM_ORDER_ID_NAME, Me.PARAM_ORDER_ID_TYPE, Me.PARAM_ORDER_ID_SIZE, OrderId)) params.Add(DbObj.MakeParam(BookDb.PARAM_BOOK_ID_NAME, BookDb.PARAM_BOOK_ID_TYPE, BookDb.PARAM_BOOK_ID_SIZE, BookId)) DbObj.ExecuteProc(Me.WSB2B_CREATE_ORDER_BOOK_REL, params) Next

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Sub

'#################################################################### ##################################################### ' Confirms a pending order by updating its status to Complete

26

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### ##################################################### Public Sub ConfirmOrder(ByVal OrderId As Int32) Const METHOD_NAME As String = "ConfirmOrder"

Dim DbObj As DbAccess = New DbAccess Try Dim params As Collection = New Collection

params.Add(DbObj.MakeParam(Me.PARAM_ORDER_ID_NAME, Me.PARAM_ORDER_ID_TYPE, Me.PARAM_ORDER_ID_SIZE, OrderId)) params.Add(DbObj.MakeParam(Me.PARAM_ORDER_STATUS_NAME, Me.PARAM_ORDER_STATUS_TYPE, Me.PARAM_ORDER_STATUS_SIZE, Me.ORDER_STATUS_COMPLETE))

DbObj.ExecuteProc(Me.WSB2B_CONFIRM_ORDER, params)

Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New DbTierException(ex.Message, ex) Finally DbObj.Close() DbObj = Nothing End Try End Sub

End Class

DbTierException.vb
Public Class DbTierException Inherits System.ApplicationException

Public Sub New(ByVal message As String) MyBase.New(message) End Sub

Public Sub New(ByVal message As String, ByVal innerException As Exception) MyBase.New(message, innerException) End Sub End Class

27

METU

B O O K S T O R E

P R O J E C T

R E P O R T

BookWS.asmx
Imports Imports Imports Imports Imports Imports System.Web.Services System.Web.Security System.Web.Services.Protocols BizTier DbTier WSB2BUtil

' Class defining the SOAP Header security context for this Web Service Public Class BookSecurityContext Inherits SoapHeader Public ClientId As Int32 Public WSToken As String End Class <WebService(Namespace:="http://tempuri.org/")> _ Public Class BookWS Inherits System.Web.Services.WebService #Region " Web Services Designer Generated Code " Public Sub New() MyBase.New() 'This call is required by the Web Services Designer. InitializeComponent() 'Add your own initialization code after the InitializeComponent() call End Sub 'Required by the Web Services Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Web Services Designer 'It can be modified using the Web Services Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 'CODEGEN: This procedure is required by the Web Services Designer 'Do not modify it using the code editor. If disposing Then If Not (components Is Nothing) Then components.Dispose()
28

METU

B O O K S T O R E

P R O J E C T

R E P O R T

End If End If MyBase.Dispose(disposing) End Sub #End Region

Public BookSecurityCtx As BookSecurityContext Private Const CLASS_NAME As String = "BookWS" Private WebUtil As New WSUtil

'==================================================================== ============================================================ ' This method uses the SOAP Header parameters to determine the web service consumer ' If authenticated, the method searches the books in the Db based on the parameters '==================================================================== ============================================================ <WebMethod(), SoapHeader("BookSecurityCtx")> _ Public Function SearchBooks(ByVal BookId As Int32, ByVal AuthorName As String, ByVal BookName As String, ByVal AvailabilityId As Int32) As DataSet Const METHOD_NAME As String = "SearchBooks" Try If WebUtil.Authenticate(BookSecurityCtx) = False Then Throw New System.Security.SecurityException("Unauthorized access") Dim BookObj As New BookServices Return BookObj.GetBooks(BookId, AuthorName, BookName, AvailabilityId) Catch BizEx As BizTierException 'Exception has already been logged, just throw it to the consumer Throw BizEx Catch DbEx As DbTierException 'Exception has already been logged, just throw it to the consumer Throw DbEx Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New WSException(ex.Message, ex) End Try End Function

'==================================================================== ============================================================ ' This method uses the SOAP Header parameters to determine the web service consumer ' If authenticated, the method returns a dataset of all the books in the array list
29

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'==================================================================== ============================================================ <WebMethod(), SoapHeader("BookSecurityCtx")> _ Public Function GetBooksByIds(ByVal BookIds As ArrayList) As DataSet Const METHOD_NAME As String = "GetBooksByIds" Try If WebUtil.Authenticate(BookSecurityCtx) = False Then Throw New System.Security.SecurityException("Unauthorized access") Dim BookObj As New BookServices Return BookObj.GetBooksByIds(BookIds) Catch BizEx As BizTierException 'Exception has already been logged, just throw it to the consumer Throw BizEx Catch DbEx As DbTierException 'Exception has already been logged, just throw it to the consumer Throw DbEx Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New WSException(ex.Message, ex) End Try End Function

'==================================================================== ============================================================ ' This method uses the SOAP Header parameters to determine the web service consumer ' If authenticated, the method returns all of the possible availabilities '==================================================================== ============================================================ <WebMethod(), SoapHeader("BookSecurityCtx")> _ Public Function GetAvailabilityDs() As DataSet Const METHOD_NAME As String = "GetAvailabilityDs" Try If WebUtil.Authenticate(BookSecurityCtx) = False Then Throw New System.Security.SecurityException("Unauthorized access") Dim AvailObj As New AvailabilityServices Return AvailObj.GetAllAvailability() Catch BizEx As BizTierException 'Exception has already been logged, just throw it to the consumer Throw BizEx Catch DbEx As DbTierException 'Exception has already been logged, just throw it to the consumer Throw DbEx Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME)
30

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Throw New WSException(ex.Message, ex) End Try End Function

<WebMethod(), SoapHeader("BookSecurityCtx")> _ Public Function GetAnani(ByVal BookId As String, ByVal AuthorName As String, ByVal BookName As String, ByVal Price As Double, ByVal AvailabilityId As Double) As String Const METHOD_NAME As String = "GetAnani" Dim BizObj As New BookServices BizObj.UpdateBook(BookId, AuthorName, BookName, Price, AvailabilityId) Return "12" End Function

End Class

OrderWS.asmx
Imports Imports Imports Imports Imports Imports System.Web.Services System.Web.Security System.Web.Services.Protocols BizTier DbTier WSB2BUtil

' Class defining the SOAP Header security context for this Web Service Public Class OrderSecurityContext Inherits SoapHeader Public ClientId As Int32 Public WSToken As String End Class

<WebService(Namespace:="http://tempuri.org/")> _ Public Class OrderWS Inherits System.Web.Services.WebService #Region " Web Services Designer Generated Code " Public Sub New() MyBase.New() 'This call is required by the Web Services Designer. InitializeComponent()

31

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'Add your own initialization code after the InitializeComponent() call End Sub 'Required by the Web Services Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Web Services Designer 'It can be modified using the Web Services Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container End Sub Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean) 'CODEGEN: This procedure is required by the Web Services Designer 'Do not modify it using the code editor. If disposing Then If Not (components Is Nothing) Then components.Dispose() End If End If MyBase.Dispose(disposing) End Sub #End Region

Public OrderSecurityCtx As OrderSecurityContext Private Const CLASS_NAME As String = "OrderWS" Private WebUtil As New WSUtil

'==================================================================== ============================================================ ' This method uses the SOAP Header parameters to determine the web service consumer ' If authenticated, the method creates a new cusilteriser order '==================================================================== ============================================================ <WebMethod(), SoapHeader("OrderSecurityCtx")> _ Public Function CreateOrder(ByVal OrderId As Int32, ByVal BookIds As ArrayList) As Boolean Const METHOD_NAME As String = "CreateOrder" Try If WebUtil.Authenticate(OrderSecurityCtx) = False Then Throw New System.Security.SecurityException("Unauthorized access") Dim OrderObj As New OrderServices OrderObj.CreateOrder(OrderId, BookIds, OrderSecurityCtx.ClientId)
32

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Return True Catch BizEx As BizTierException 'Exception has already been logged, just throw it to the consumer Throw BizEx Catch DbEx As DbTierException 'Exception has already been logged, just throw it to the consumer Throw DbEx Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New WSException(ex.Message, ex) End Try End Function

'==================================================================== ============================================================ ' This method uses the SOAP Header parameters to determine the web service consumer ' If authenticated, the method searches orders based on the parameter criteria '==================================================================== ============================================================ <WebMethod(), SoapHeader("OrderSecurityCtx")> _ Public Function GetOrders(ByVal OrderId As Int32, ByVal OrderStatus As String, ByVal ClientId As Int32) As DataSet Const METHOD_NAME As String = "CreateOrder" Try If WebUtil.Authenticate(OrderSecurityCtx) = False Then Throw New System.Security.SecurityException("Unauthorized access") Dim OrderObj As New OrderServices Return OrderObj.GetOrders(OrderId, OrderStatus, ClientId) Catch BizEx As BizTierException 'Exception has already been logged, just throw it to the consumer Throw BizEx Catch DbEx As DbTierException 'Exception has already been logged, just throw it to the consumer Throw DbEx Catch ex As Exception Log.WriteLogEntry(ex, Me.CLASS_NAME, METHOD_NAME) Throw New WSException(ex.Message, ex) End Try End Function

End Class

33

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Editbook.aspx.vb
Imports DbTier Imports BizTier Public Class EditBook Inherits System.Web.UI.Page Protected WithEvents Label2 As System.Web.UI.WebControls.Label Protected WithEvents Label3 As System.Web.UI.WebControls.Label Protected WithEvents Label4 As System.Web.UI.WebControls.Label Protected WithEvents Label5 As System.Web.UI.WebControls.Label Protected WithEvents btnUpdate As System.Web.UI.WebControls.Button Protected WithEvents txtPrice As System.Web.UI.WebControls.TextBox Protected WithEvents txtAuthor As System.Web.UI.WebControls.TextBox Protected WithEvents txtBookName As System.Web.UI.WebControls.TextBox Protected WithEvents cboAvailability As System.Web.UI.WebControls.DropDownList Protected WithEvents btnCancel As System.Web.UI.WebControls.Button Protected WithEvents rfvBookName As System.Web.UI.WebControls.RequiredFieldValidator Protected WithEvents RequiredFieldValidator1 As System.Web.UI.WebControls.RequiredFieldValidator Protected WithEvents lblMsg As System.Web.UI.WebControls.Label Protected WithEvents Label1 As System.Web.UI.WebControls.Label #Region " Web Form Designer Generated Code " 'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: This method call is required by the Web Form Designer 'Do not modify it using the code editor. InitializeComponent() End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Put user code to initialize the page here ' Ensure the msg label is invisible on load Me.lblMsg.Visible = False If Not Page.IsPostBack Then ' Get a reference to the viewbooks page which invokes this webform
34

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Dim ViewBookRef As ViewBooks = CType(Context.Handler, ViewBooks) ' Grab the BookId from the form Dim BookId As String = ViewBookRef.BookId ' Instantiate our business tier objects required Dim BookObj As New BookServices Dim AvailObj As New AvailabilityServices ' Retrieve the Book being edited Dim BookDs As DataSet = BookObj.GetBooks(Convert.ToInt32(BookId), Nothing, Nothing, Nothing) ' Populate our form with the book info If Not BookDs Is Nothing Then With BookDs.Tables(0).Rows(0) Me.txtAuthor.Text = .Item(BookDb.FIELD_AUTHOR) Me.txtBookName.Text = .Item(BookDb.FIELD_BOOK_NAME) Me.txtPrice.Text = .Item(BookDb.FIELD_PRICE) ' Populate the availability drop down Dim AvailDs As DataSet = AvailObj.GetAllAvailability() Me.cboAvailability.DataSource = AvailDs.Tables(0) Me.cboAvailability.DataTextField = AvailabilityDb.FIELD_AVAILABILITY_NAME Me.cboAvailability.DataValueField = AvailabilityDb.FIELD_AVAILABILITY_ID Me.DataBind() ' Once the form is databound we can select the availability Id of the book in the dropdown Me.cboAvailability.Items.FindByValue(.Item(AvailabilityDb.FIELD_AVAIL ABILITY_ID)).Selected = True ' Add the BookId to the viewstate so we can retrieve it on the next postback Me.ViewState.Add(BookDb.FIELD_BOOK_ID, BookId) End With End If End If End Sub Private Sub btnCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCancel.Click Server.Transfer(WebPageNames.VIEW_BOOKS) End Sub

'#################################################################### ##################################################### ' Updates the book information to the database

35

METU

B O O K S T O R E

P R O J E C T

R E P O R T

'#################################################################### ##################################################### Private Sub btnUpdate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnUpdate.Click ' Our validation controls have already ensured the correct fields are populated Try Dim BizObj As New BookServices BizObj.UpdateBook(Me.ViewState.Item(BookDb.FIELD_BOOK_ID), Me.txtAuthor.Text, _ Me.txtBookName.Text, Me.txtPrice.Text, Me.cboAvailability.SelectedItem.Value) Me.lblMsg.Visible = True Me.lblMsg.Text = "The book was updated successfully" Catch ex As Exception Me.lblMsg.Visible = True Me.lblMsg.Text = "There was an error updating the book" End Try End Sub End Class

EditBook.aspx <%@ Register TagPrefix="uc1" TagName="NavBar" Src="NavBar.ascx" %> <%@ Page Language="vb" AutoEventWireup="false" Codebehind="EditBook.aspx.vb" Inherits="WSB2BOney.EditBook"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>EditBook</title> 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <link rel="stylesheet" href="Styles.css">
36

<meta name="GENERATOR" content="Microsoft Visual Studio.NET

METU

B O O K S T O R E

P R O J E C T

R E P O R T

</HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <P><uc1:NavBar runat="server"></uc1:NavBar></P> id="NavBar1"

<asp:Label id="Label1" style="Z-INDEX: 102; LEFT: 24px; POSITION: absolute; TOP: 80px" runat="server" Book</asp:Label> <DIV class="FORM_CONTAINER" style="Z-INDEX: 101; LEFT: 16px; WIDTH: 492px; POSITION: absolute; TOP: 72px; HEIGHT: 208px" ms_positioning="GridLayout"> <asp:Button id="btnUpdate" style="Z-INDEX: 100; LEFT: 340px; POSITION: absolute; TOP: 168px" tabIndex="5"></asp:Button> <asp:Button CausesValidation="False" id="btnCancel" style="Z-INDEX: 101; LEFT: 416px; POSITION: absolute; TOP: 168px" tabIndex="6"></asp:Button> <asp:RequiredFieldValidator id="rfvBookName" style="ZINDEX: 102; LEFT: 96px; POSITION: absolute; TOP: 48px" runat="server" ErrorMessage="Book name is required" ControlToValidate="txtBookName"></asp:RequiredFieldValidator> <asp:RequiredFieldValidator id="RequiredFieldValidator1" style="Z-INDEX: 103; LEFT: 56px; POSITION: absolute; TOP: 100px" runat="server" ErrorMessage="Price is required" ControlToValidate="txtPrice"></asp:RequiredFieldValidator> <asp:Label id="lblMsg" style="Z-INDEX: 111; LEFT: 12px; POSITION: absolute; TOP: 172px" runat="server" Height="16px" Width="309px" Visible="False"></asp:Label></DIV> CssClass="ASP_TEXT" runat="server" CssClass="BUTTON" Text="Back" runat="server" CssClass="BUTTON" Text="Save" Width="456px" CssClass="TITLE" Height="28px">Edit

<asp:TextBox id="txtPrice" style="Z-INDEX: 109; LEFT: 24px; POSITION: absolute; TOP: 192px" runat="server"
37

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Width="132px" tabIndex="2"></asp:TextBox>

CssClass="ASP_TEXT"

<asp:TextBox id="txtAuthor" style="Z-INDEX: 108; LEFT: 268px; POSITION: absolute; TOP: 140px" runat="server" Width="216px" CssClass="ASP_TEXT" MaxLength="50" tabIndex="3"></asp:TextBox> <asp:Label id="Label5" style="Z-INDEX: 106; LEFT: 268px; POSITION: absolute; TOP: 172px" runat="server" CssClass="ASP_LABEL">Availability</asp:Label> <asp:Label id="Label4" style="Z-INDEX: 105; LEFT: 24px; POSITION: absolute; TOP: 172px" runat="server" CssClass="ASP_LABEL">Price</asp:Label> <asp:Label id="Label3" style="Z-INDEX: 104; LEFT: 268px; POSITION: absolute; TOP: 120px" runat="server" CssClass="ASP_LABEL">Author</asp:Label> <asp:Label id="Label2" style="Z-INDEX: 103; LEFT: 24px; POSITION: absolute; TOP: 120px" runat="server" CssClass="ASP_LABEL">Book Name</asp:Label> <asp:TextBox id="txtBookName" style="Z-INDEX: 107; LEFT: 24px; POSITION: absolute; TOP: 140px" runat="server" Width="216px" CssClass="ASP_TEXT" MaxLength="100" tabIndex="1"></asp:TextBox> <asp:DropDownList id="cboAvailability" style="Z-INDEX: 110; LEFT: 268px; POSITION: absolute; TOP: 192px" runat="server" Width="216px" CssClass="ASP_TEXT" tabIndex="4"></asp:DropDownList> </form> </body> </HTML>

Homepage.aspx
38

METU

B O O K S T O R E

P R O J E C T

R E P O R T

<%@ Register TagPrefix="uc1" TagName="NavBar" Src="NavBar.ascx" %> <%@ Page Language="vb" AutoEventWireup="false" Codebehind="HomePage.aspx.vb" Inherits="WSB2BOney.HomePage"%> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>HomePage</title> 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <link rel="stylesheet" href="Styles.css"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <P> <asp:Label id="Label3" style="Z-INDEX: 105; LEFT: 32px; POSITION: absolute; TOP: 224px" runat="server" of Pending orders</asp:Label> <asp:Label id="lblOrders" style="Z-INDEX: 103; LEFT: 32px; POSITION: absolute; TOP: 244px" runat="server" Width="748px"></asp:Label><uc1:NavBar runat="server"></uc1:NavBar> id="NavBar1" CssClass="ASP_TEXT_BLUE_BOLD">Number <meta name="GENERATOR" content="Microsoft Visual Studio.NET

<asp:Label id="lblTitle" style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute; TOP: 116px" runat="server" CssClass="ASP_LABEL" Welcome to Wholesale Bookstore Administration</asp:Label>
39

Width="924px">

METU

B O O K S T O R E

P R O J E C T

R E P O R T

<asp:Label id="lblDate" style="Z-INDEX: 102; LEFT: 32px; POSITION: absolute; TOP: 184px" runat="server" Width="752px"></asp:Label> <asp:Label id="Label1" style="Z-INDEX: 104; LEFT: 32px; POSITION: absolute; TOP: 164px" runat="server" Date</asp:Label></P> <DIV class="FORM_CONTAINER" style="LEFT: 16px; WIDTH: 920px; POSITION: absolute; TOP: 152px; HEIGHT: 180px" ms_positioning="GridLayout"></DIV> </form> </body> Homepage.aspx.vb
Imports DbTier Imports BizTier Public Class HomePage Inherits System.Web.UI.Page Protected WithEvents lblDate As System.Web.UI.WebControls.Label Protected WithEvents Label1 As System.Web.UI.WebControls.Label Protected WithEvents lblOrders As System.Web.UI.WebControls.Label Protected WithEvents Label3 As System.Web.UI.WebControls.Label Protected WithEvents lblTitle As System.Web.UI.WebControls.Label #Region " Web Form Designer Generated Code " 'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: This method call is required by the Web Form Designer 'Do not modify it using the code editor. InitializeComponent() End Sub #End Region Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Put user code to initialize the page here

CssClass="ASP_TEXT_BLUE_BOLD">Today's

40

METU

B O O K S T O R E

P R O J E C T

R E P O R T

If Not Page.IsPostBack Then ' Retrieve the number of pending orders Dim OrderObj As New OrderServices Dim OrderDs As DataSet = OrderObj.GetOrders(Nothing, OrderDb.ORDER_STATUS_PENDING, Nothing) If Not OrderDs Is Nothing Then Me.lblOrders.Text = OrderDs.Tables(0).Rows.Count Else Me.lblOrders.Text = "No pending orders" End If Me.lblDate.Text = Now.ToLongDateString End If End Sub End Class

NavBar.ascx <%@ Control Language="vb" AutoEventWireup="false" Codebehind="NavBar.ascx.vb" Inherits="WSB2BOney.NavBar" TargetSchema="http://schemas.microsoft.com/intellisense/ie5" %> <link rel="stylesheet" href="Styles.css"> <div align="center"> <asp:Button CausesValidation="False" runat="server" CssClass="BUTTON" id="btnHome" Text="Home"

tabIndex="-1"></asp:Button>&nbsp; <asp:Button CausesValidation="False" id="btnViewBooks" Text="View Books" runat="server" CssClass="BUTTON" tabIndex="-1"></asp:Button>&nbsp; <asp:Button CausesValidation="False" id="btnViewOrders" Text="View Orders" runat="server" CssClass="BUTTON" tabIndex="-1"></asp:Button>&nbsp; <hr width="100%" color="#000099"> </div>

41

METU

B O O K S T O R E

P R O J E C T

R E P O R T

SearchOrders.aspx <%@ Page Language="vb" AutoEventWireup="false" Codebehind="SearchOrders.aspx.vb" Inherits="WSB2BOney.SearchOrders"%> <%@ Register TagPrefix="uc1" TagName="NavBar" Src="NavBar.ascx" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <title>EditBook</title> 7.0"> <meta name="CODE_LANGUAGE" content="Visual Basic 7.0"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <link rel="stylesheet" href="Styles.css"> </HEAD> <body MS_POSITIONING="GridLayout"> <form id="Form1" method="post" runat="server"> <P> <asp:Label id="lblMsg" style="Z-INDEX: 107; LEFT: 24px; POSITION: absolute; TOP: 260px" runat="server" Height="28px" CssClass="TITLE" Width="456px" Visible="False"></asp:Label><uc1:NavBar id="NavBar1" runat="server"></uc1:NavBar></P> <asp:Label id="Label1" style="Z-INDEX: 102; LEFT: 24px; POSITION: absolute; TOP: 76px" runat="server" Orders</asp:Label> <asp:DataGrid OnItemCommand="Confirm_Order" id="dgResults" style="Z-INDEX: 106; LEFT: 20px; POSITION: absolute; TOP: 212px"
42

<meta name="GENERATOR" content="Microsoft Visual Studio.NET

Width="456px" CssClass="TITLE" Height="28px">Search

METU

B O O K S T O R E

P R O J E C T

R E P O R T

CellPadding="3" runat="server" CssClass="DG_NORMAL" Visible="False" AutoGenerateColumns="False"> <HeaderStyle CssClass="DG_HEADER"></HeaderStyle> <Columns> <asp:BoundColumn HeaderText="Order Id"></asp:BoundColumn> DataField="ORDER_ID"

<asp:BoundColumn DataField="BOOK_NAME" HeaderText="Book Name"></asp:BoundColumn> <asp:BoundColumn DataField="CLIENT_NAME" HeaderText="Client"></asp:BoundColumn> <asp:BoundColumn DataField="ORDER_STATUS" HeaderText="Status"></asp:BoundColumn> <asp:BoundColumn DataField="CREATION_DATE" HeaderText="Order Date"></asp:BoundColumn> <asp:ButtonColumn ButtonType="PushButton" Text="Confirm" Visible="False"></asp:ButtonColumn> </Columns> </asp:DataGrid> <DIV class="FORM_CONTAINER" style="Z-INDEX: 100; LEFT: 16px; WIDTH: 492px; POSITION: absolute; TOP: 68px; HEIGHT: 136px" ms_positioning="GridLayout"> <asp:Button id="btnSearch" style="Z-INDEX: 100; LEFT: 396px; POSITION: absolute; TOP: 96px" runat="server" Text="Search" tabIndex="3"></asp:Button> CssClass="BUTTON"

<asp:DropDownList id="cboClients" style="Z-INDEX: 110; LEFT: 4px; POSITION: absolute; TOP: 64px" tabIndex="1" runat="server" Width="216px"></asp:DropDownList></DIV> CssClass="ASP_TEXT"

<asp:Label id="Label3" style="Z-INDEX: 104; LEFT: 268px; POSITION: absolute; TOP: 116px" runat="server" CssClass="ASP_LABEL">Order Status</asp:Label>
43

METU

B O O K S T O R E

P R O J E C T

R E P O R T

<asp:Label id="Label2" style="Z-INDEX: 103; LEFT: 24px; POSITION: absolute; TOP: 116px" runat="server" CssClass="ASP_LABEL"> Client</asp:Label> <asp:DropDownList id="cboStatus" style="Z-INDEX: 105; LEFT: 268px; POSITION: absolute; TOP: 136px" runat="server" Width="216px" CssClass="ASP_TEXT" tabIndex="2"></asp:DropDownList> </form> </body> </HTML> SearchOrders.aspx.vb
Imports DbTier Imports BizTier Imports WSB2BUtil Public Class SearchOrders Inherits System.Web.UI.Page Protected WithEvents Label1 As System.Web.UI.WebControls.Label Protected WithEvents Label3 As System.Web.UI.WebControls.Label Protected WithEvents Label2 As System.Web.UI.WebControls.Label Protected WithEvents cboStatus As System.Web.UI.WebControls.DropDownList Protected WithEvents cboClients As System.Web.UI.WebControls.DropDownList Protected WithEvents btnSearch As System.Web.UI.WebControls.Button Protected WithEvents lblMsg As System.Web.UI.WebControls.Label Protected WithEvents dgResults As System.Web.UI.WebControls.DataGrid #Region " Web Form Designer Generated Code " 'This call is required by the Web Form Designer. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() End Sub Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Init 'CODEGEN: This method call is required by the Web Form Designer 'Do not modify it using the code editor. InitializeComponent() End Sub #End Region
44

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 'Put user code to initialize the page here If Not Page.IsPostBack Then Dim ClientObj As New ClientServices Dim ClientDs As DataSet = ClientObj.GetAllClients() Me.cboClients.DataSource = ClientDs.Tables(0) Me.cboClients.DataTextField = ClientDb.FIELD_CLIENT_NAME Me.cboClients.DataValueField = ClientDb.FIELD_CLIENT_ID Me.cboStatus.Items.Add("") 'blank entry Me.cboStatus.Items.Add(OrderDb.ORDER_STATUS_PENDING) Me.cboStatus.Items.Add(OrderDb.ORDER_STATUS_COMPLETE) Me.DataBind() Me.cboClients.Items.Insert(0, "") Me.lblMsg.Visible = False Me.dgResults.Visible = True End If End Sub

'#################################################################### ##################################################### ' This method searches the database for orders that have been placed. If filtered by status=Pending, we provide a button ' allowing the user to confirm and complete the order '#################################################################### ##################################################### Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click Dim OrderObj As New OrderServices Dim ClientId As Int32 = Nothing If Not Utils.IsNull(Me.cboClients.SelectedItem.Value) Then ClientId = Convert.ToInt32(Me.cboClients.SelectedItem.Value) End If Dim OrderDs As DataSet = OrderObj.GetOrders(Nothing, Me.cboStatus.SelectedItem.Value, ClientId) If Not OrderDs Is Nothing Then If Me.cboStatus.SelectedItem.Text = OrderDb.ORDER_STATUS_PENDING Then Me.dgResults.Columns.Item(5).Visible = True Else Me.dgResults.Columns.Item(5).Visible = False End If Me.dgResults.DataSource = OrderDs.Tables(0) Me.dgResults.Visible = True Me.DataBind() Me.lblMsg.Visible = False Me.dgResults.Visible = True End If
45

METU

B O O K S T O R E

P R O J E C T

R E P O R T

End Sub

'#################################################################### ##################################################### ' Confirms a pending order by invoking Ilteris's web service and updating the order in his application first. ' The call is made asynchronously to allow for fast user response '#################################################################### ##################################################### Public Sub Confirm_Order(ByVal sender As Object, ByVal e As DataGridCommandEventArgs) ' Grab the Id of the order being confirmed Dim OrderId As Int32 = Convert.ToInt32(e.Item.Cells(0).Text) Dim OrderObj As New OrderServices 'Get the information regarding the order Dim OrderDs As DataSet = OrderObj.GetOrders(OrderId, Nothing, Nothing) '## NOTE ## ' If we add additional clients, we need to check the client Id and invoke ' the correct web service method for that particular client. Since ' Ilteris is the only client in our example, we invoke his web service directly Dim OrderProxy As New OrderWS 'OrderProxy.OrderSecurityContextValue = WSUtil.GetOrderSecurityContext() 'OrderProxy.BeginGetOrders(OrderId, OrderDs, New AsyncCallback(AddressOf CompleteOrder), OrderProxy) Me.lblMsg.Text = "The order has been sent for completion processing" Me.lblMsg.Visible = True Me.dgResults.Visible = False End Sub

'#################################################################### ##################################################### ' Callback function for when Ilteris's application completes the confirm order method. At this point everything is ok ' and we can update our own database's reference of the order to completed '#################################################################### ##################################################### Private Sub CompleteOrder(ByVal AsyncResult As IAsyncResult) Dim ProxyObj As OrderWS = CType(AsyncResult.AsyncState, OrderWS) 'Dim OrderId As Int32 = ProxyObj.EndConfirmOrder(AsyncResult)
46

METU

B O O K S T O R E

P R O J E C T

R E P O R T

' We can be guaranteed that Ilteris's app has completed the order, so update the order in Oney's app also Dim OrderObj As New OrderServices 'OrderObj.ConfirmOrder(OrderId) End Sub End Class

Web.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <appSettings> <add key="DatabaseConnString" value="server=ILTERIS; database=OneyDb; uid=tiki; pwd=tikibiriki;"/> <add key="LogFileName" value="c:\inetpub\wwwroot\Oney\logs\Log.log" /> </appSettings> <system.web> DYNAMIC DEBUG COMPILATION Set compilation debug="true" to insert debugging symbols (.pdb information) into the compiled page. Because this creates a larger file that executes more slowly, you should set this value to true only when debugging and to false at all other times. For more information, refer to the documentation about debugging ASP.NET files. --> <compilation defaultLanguage="vb" debug="true" /> CUSILTERIS ERROR MESSAGES Set cusilterisErrors mode="On" or "RemoteOnly" to enable cusilteris error messages, "Off" to disable. Add <error> tags for each of the errors you want to handle. "On" Always display cusilteris (friendly) messages. "Off" Always display detailed ASP.NET error information. "RemoteOnly" Display cusilteris (friendly) messages only to users not running on the local Web server. This setting is recommended for security purposes, so that you do not display application detail information to remote clients. --> <cusilterisErrors mode="RemoteOnly" /> AUTHENTICATION This section sets the authentication policies of the application. Possible modes are "Windows", "Forms", "Passport" and "None" "None" No authentication is performed. "Windows" IIS performs authentication (Basic, Digest, or Integrated Windows) according to
47

<!--

<!--

<!--

METU

B O O K S T O R E

P R O J E C T

R E P O R T

its settings for the application. Anonymous access must be disabled in IIS. "Forms" You provide a cusilteris form (Web page) for users to enter their credentials, and then you authenticate them in your application. A user credential token is stored in a cookie. "Passport" Authentication is performed via a centralized authentication service provided by Microsoft that offers a single logon and core profile services for member sites. --> <authentication mode="Windows" />

AUTHORIZATION This section sets the authorization policies of the application. You can allow or deny access to application resources by user or role. Wildcards: "*" mean everyone, "?" means anonymous (unauthenticated) users. --> <authorization> <allow users="*" /> <!-- Allow all users --> <!-roles]"/> <deny roles]"/> --> </authorization> APPLICATION-LEVEL TRACE LOGGING Application-level tracing enables trace log output for every page within an application. Set trace enabled="true" to enable application trace logging. If pageOutput="true", the trace information will be displayed at the botilteris of each page. Otherwise, you can view the application trace log by browsing the "trace.axd" page from your web application root. --> <trace enabled="false" requestLimit="10" pageOutput="false" traceMode="SortByTime" localOnly="true" /> <!-users="[comma separated list of users]" roles="[comma separated list of <allow users="[comma separated list of users]" roles="[comma separated list of

<!--

SESSION STATE SETTINGS By default ASP.NET uses cookies to identify which requests belong to a particular session. If cookies are not available, a session can be tracked by adding a session identifier to the URL. To disable cookies, set sessionState cookieless="true". --> <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"
48

<!--

METU

B O O K S T O R E

P R O J E C T

R E P O R T

cookieless="false" timeout="20" /> GLOBALIZATION This section sets the globalization settings of the application. --> <globalization requestEncoding="utf-8" responseEncoding="utf-8" /> </system.web> </configuration> <!--

Global.asax
Imports System.Web Imports System.Web.SessionState Imports BizTier Imports DbTier Imports WSB2BUtil Public Class Global Inherits System.Web.HttpApplication #Region " Component Designer Generated Code " Public Sub New() MyBase.New() 'This call is required by the Component Designer. InitializeComponent() 'Add any initialization after the InitializeComponent() call End Sub 'Required by the Component Designer Private components As System.ComponentModel.IContainer 'NOTE: The following procedure is required by the Component Designer 'It can be modified using the Component Designer. 'Do not modify it using the code editor. <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent() components = New System.ComponentModel.Container() End Sub #End Region Sub Application_Start(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the application is started Dim ClientObj As New ClientServices
49

METU

B O O K S T O R E

P R O J E C T

R E P O R T

Application.Add(ClientDb.CLIENT_DS_REF, ClientObj.GetAllClients()) End Sub

Sub Session_Start(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the session is started End Sub Sub Application_BeginRequest(ByVal sender As Object, ByVal e As EventArgs) ' Fires at the beginning of each request End Sub Sub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs) ' Fires upon attempting to authenticate the use End Sub Sub Application_Error(ByVal sender As Object, ByVal e As EventArgs) ' Only log errors which did not originate from our components If Not TypeOf Server.GetLastError().InnerException() Is BizTierException And Not TypeOf Server.GetLastError().InnerException() Is DbTierException Then Log.WriteLogEntry(Server.GetLastError.GetBaseException, "Global.asax", "Application_Error") End If End Sub

Sub Session_End(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the session ends End Sub Sub Application_End(ByVal sender As Object, ByVal e As EventArgs) ' Fires when the application ends End Sub End Class

Styless.css
/* Default CSS Stylesheet for a new Web Application project */ BODY { font-weight: normal; font-size: 0.8em; word-spacing: normal; text-transform: none; font-family: Verdana, Helvetica, sans-serif; letter-spacing: normal; background-color: whitesmoke; }
50

METU

B O O K S T O R E

P R O J E C T

R E P O R T

.DG_SELECTED { font-weight: bold; font-size: 12px; color: #000099; font-family: Tahoma; background-color: #33ffff; text-decoration: none; } .DG_NORMAL { border-right: lightgrey thin solid; border-top: lightgrey thin solid; font-size: 10pt; border-left: lightgrey thin solid; border-botilteris: lightgrey thin solid; font-family: Tahoma; background-color: white; } .DG_HEADER { font-weight: bold; font-size: 12px; color: #000099; font-family: Tahoma; background-color: #ccffff; text-decoration: none; } .BUTTON { font-weight: bold; font-size: 10pt; color: #000000; font-family: Tahoma; background-color: #ccffff; text-decoration: none; } .ASP_TEXT { font-size: 9pt; color: black; font-family: Tahoma; } .ASP_TEXT_BLUE_BOLD { font-size: 9pt; color:darkblue; font-family: Tahoma; font-weight:bold; } .ASP_LABEL { font-weight: bold; font-size: 11pt;
51

METU

B O O K S T O R E

P R O J E C T

R E P O R T

font-family: Tahoma; text-decoration: none; } .TITLE { font-weight: bold; font-size: 12pt; color: #000099; font-family: Tahoma; } .FORM_CONTAINER { BORDER-TOP-STYLE: outset; BORDER-RIGHT-STYLE: outset; BORDER-LEFT-STYLE: outset; POSITION: absolute; BACKGROUND-COLOR: #f5f5f5; BORDER-BOTILTERIS-STYLE: outset; }

52

Vous aimerez peut-être aussi