Having examined the basics of exposing
Native XML Web Services, let’s look at a sample application. For our
example, we will use the fictitious Adventure Works Cycles, a small
bicycle company that is the basis for the samples that ship with SQL
Server 2005. In our scenario, Adventure Works Cycles has just hired a
new sales manager who is responsible for increasing revenue. He has
decided that the sales representatives need mobile access to customer
information while they are on the road visiting client stores. He has
named this application Adventure Sales and has written up a list of what the application must allow sales representatives to do:
Search
for client stores by name or location (to help minimize travel time by
scheduling visits to multiple stores in a locality).
View address and contact information for stores (to facilitate making appointments and getting directions to the stores).
View the status of orders recently placed by the store (to be able to field questions about orders during a visit).
Our detail-oriented sales manager ordered
smartphone PDAs for the sales force and announced his new initiative to
the senior executives and the sales team before consulting with the IT
manager.
The IT department at Adventure Works Cycles is
small and extremely busy. The only person available to work on the
project is a junior developer who does not have the time or expertise to
develop a full-blown n-tier system. However, after analyzing the
requirements, the savvy IT manager realizes that because the company’s
data is stored on SQL Server 2005, they can complete this project in a
few days using a simple Windows Forms application to run under Windows
Mobile 5.0 and to access the data via Native XML Web Services.
Note
Although our scenario
demands a Windows Mobile solution, the sample code is implemented as a
standard C# Windows Forms project. This allows you to open the project
and work with the code without needing additional setup to support
Windows Mobile 5.0. Because Windows Mobile 5.0 supports invoking Web
services, this application can quite easily be rewritten as a mobile
application. |
1. Creating the SQL Server Functionality
We’ll be creating three new stored procedures
and a UDF to implement the required SQL Server functionality for our
application. The SQL scripts for creating these are in the SQL Server
Management Studio sample project .
You can open this project and run the scripts in the order they are
discussed in this section. This way, you’ll be ready to expose them as
Web methods in the Native XML Web Services endpoint we will be creating
later in the section.
To demonstrate returning data as XML and consuming it on the client side, the first stored procedure, GetStoreInfo, shown in Listing 1, takes a store CustomerID and returns the store name and address as XML.
Listing 1. GetStoreInfo from GetStoreInfo.sql
USE AdventureWorks;
GO
IF OBJECT_ID ( 'dbo.GetStoreInfo', 'P' ) IS NOT NULL
DROP PROCEDURE [dbo].[GetStoreInfo];
GO
CREATE PROCEDURE [dbo].[GetStoreInfo]
@CustomerID [int]
AS
BEGIN
SELECT Store.Name, Address.AddressLine1, Address.City, Address.PostalCode,
StateProvince.StateProvinceCode as State
FROM Sales.Store
INNER JOIN Sales.CustomerAddress
ON Store.CustomerID = CustomerAddress.CustomerID
INNER JOIN Person.Address
ON CustomerAddress.AddressID = Address.AddressID
INNER JOIN Person.StateProvince
ON Address.StateProvinceID = StateProvince.StateProvinceID
AND Address.StateProvinceID = StateProvince.StateProvinceID
WHEREStore.CustomerID=@CustomerID
FOR XML AUTO, ELEMENTS
END;
GO
|
The other two stored procedures simply return the results of an SQL query without using the FOR XML functionality. We will use the DEFAULT choice for the WSDL option when we create the endpoint so that the data will be returned to the client application as a DataSet.
Note
We can safely return a DataSet
in our example because we know the client application is using the .NET
Framework 2.0. If you need to support non-Microsoft clients, you should
use the simple WSDL when setting up the client proxy to ensure
compatibility, and you should avoid .NET-specific types. |
The GetStoreContacts stored procedure shown in Listing 2 returns all of the contacts associated with the store identified by the passed CustomerID.
Listing 2. GetStoreContacts from GetStoreContacts.sql
USE AdventureWorks;
GO
IF OBJECT_ID ( 'dbo.GetStoreContacts', 'P' ) IS NOT NULL
DROP PROCEDURE [dbo].[GetStoreContacts];
GO
CREATE PROCEDURE [dbo].[GetStoreContacts]
@CustomerID [int]
AS
BEGIN
SET NOCOUNT ON;
SELECT Contact.FirstName, Contact.LastName,
Contact.EmailAddress, Contact.Phone
FROM Person.Contact
INNER JOIN Sales.StoreContact
ON Contact.ContactID = StoreContact.ContactID
INNER JOIN Sales.Store
ON StoreContact.CustomerID = Store.CustomerID
WHERE (Store.CustomerID = @CustomerID)
END;
GO
|
The GetStoreRecentOrders stored procedure is shown in Listing 3. It returns orders that are less than two months old or are still open.
Listing 3. GetStoreRecentOrders from GetStoreRecentOrders.sql
USE AdventureWorks;
GO
IF OBJECT_ID ( 'dbo.GetStoreRecentOrders', 'P' ) IS NOT NULL
DROP PROCEDURE [dbo].[GetStoreRecentOrders];
GO
CREATE PROCEDURE [dbo].[GetStoreRecentOrders]
@CustomerID [int]
AS
BEGIN
DECLARE @TwoMonthsAgo DATETIME
SELECT @TwoMonthsAgo = DATEADD(Month, -26, GETDATE())
SELECT SalesOrderHeader.OrderDate, SalesOrderHeader.DueDate, SalesOrderHeader
.ShipDate, dbo.ufnGetSalesOrderStatusText(Sales.SalesOrderHeader.Status)
AS OrderStatus
FROM Sales.Store
INNER JOIN Sales.SalesOrderHeader
ON Sales.Store.CustomerID = Sales.SalesOrderHeader.CustomerID
WHERE((Sales.SalesOrderHeader.OrderDate>@TwoMonthsAgo
OR Sales.SalesOrderHeader.DueDate>@TwoMonthsAgo)
OR (Sales.SalesOrderHeader.Status<>5 AND Sales.SalesOrderHeader.Status<>6)
)
AND (Sales.Store.CustomerID = @CustomerID)
ORDER BY Sales.Store.CustomerID, OrderDate DESC
END;
GO
|
Note
Because all the sample data in the AdventureWorks database is dated before 2005, we cheated a bit in the GetStoreRecentOrders stored procedure in Listing 10-4
and subtracted 26 months from the current date instead of 2 months, as
the requirements for the scenario demand. Depending on when you run the
example, you might need to tweak this value or add new data to the AdventureWorks database to get recent orders to show up in the application. |
The ufnGetStoreSalesYTD user-defined function is shown in Listing 4. It returns the sum of the sales orders placed by the store in the current year.
Listing 4. ufnGetStoreSalesYTD from ufnGetStoreSalesYTD.sql
USE AdventureWorks;
GO
IF OBJECT_ID ( 'dbo.ufnGetStoreSalesYTD', 'FN' ) IS NOT NULL
DROP FUNCTION [dbo].[ufnGetStoreSalesYTD];
GO
CREATE FUNCTION [dbo].[ufnGetStoreSalesYTD](@CustomerID Int)
RETURNS [decimal]
AS
BEGIN
DECLARE @ThisYear Int
SELECT @ThisYear = DATEPART(year, GETDATE())-2
DECLARE @YTDSale decimal
SELECT @YTDSale = SUM(SubTotal)
FROM Sales.SalesOrderHeader
WHERE CustomerID=@CustomerID AND DATEPART(year, OrderDate)=@ThisYear
RETURN @YTDSale
END;
GO
|
Note
As with the GetStoreRecentOrders stored procedure, we had to cheat a bit in the ufnGetStoreSalesYTD user-defined function shown in Listing 10-5
to get non-zero values. We did this by subtracting two years from the
current year to get back to where AdventureWorks data exists. Depending
on when you run the example, you might need to tweak this value or add
new data to the AdventureWorks database to get recent orders to show up in the application. |