ptg 1944 CHAPTER 48 SQL Server Web Services LISTING 48.4 Calling a SQL Server Web Method from C# if (txtEmployeeId.Text != string.Empty) { EPT_SQL2008UnleashedExamples SQLEndpointProxy = new EPT_SQL2008UnleashedExamples(); SQLEndpointProxy.Credentials = System.Net.CredentialCache.DefaultCredentials; int EmployeeId = int.Parse(txtEmployeeId.Text); object[] ReturnArray = SQLEndpointProxy.WM_GetEmployeeBasics(EmployeeId); foreach (object Obj in ReturnArray) { if (Obj is DataSet) { DataSet ds = (DataSet)Obj; if (ds.Tables.Count == 0) { lblResults.Text = “(No Results Found)”; } else { lblResults.Text = “(“ + ((SqlRowCount)ReturnArray[1]).Count + “ Result(s) Found)”; gvData.DataSource = ds; gvData.DataBind(); } } } } After you test whether the text box txtEmployeeId is nonempty, you instantiate the WSDL-based stub class opensql.EPT_SQL2008UnleashedExamples and name it SQLEndpointProxy. Next, you set the credentials used by the web service to those of the currently logged-on user. NOTE When using SQL Server (not Windows) Authentication, instead of assuming the default credentials, you need to add web services security (WS-Security) username and pass- word headers to the SOAP request. Note that the password will be sent in clear text, so SSL is required to be installed and turned on for your web service. Anonymous web access is completely disabled for SQL Server web services, and Visual Studio turns on NTLM authentication by default for the sample web application’s virtual directory. ptg 1945 Examples: A C# Client Application 48 Depending on your system’s security policy configuration, the following line might be required in the configuration section of your web.config (or machine.config) file: <identity impersonate=”true” userName=”SQLWebServicesClient” password=”wsdl”/> This line tells the CLR to run the web application under the credentials of the user SQLWebServicesClient (created earlier and also specified after the AUTHORIZATION keyword in the DDL). The client application thus impersonates SQLWebServicesClient in its requests to the web service, regardless of the credentials of the logged-in Windows user. When btnGetValue is clicked in the running browser window, the text typed into txtEmployeeId is typecast to an integer value. This value represents the EmployeeId of the employee about whom the web method’s stored procedure returns data. You pass this value into the call to the SQL Server web method with the code: opensql.WM_GetEmployeeBasics(EmployeeId) Notice that WM_ GetEmployeeBasics is exactly the same name you specified in WEBMETHOD (minus the namespace prefix). EmployeeId corresponds to the input parameter of the stored procedure @EmployeeId. The next line of Listing 48.4 illustrates how the SOAP results returned from SQL Server are deserialized from XML into .NET Framework objects. As mentioned earlier in this chapter, when ALL_RESULTS is specified for the FORMAT state- ment of the web method, you get back the array object[] ReturnArray that has two or more elements: . The result set (if any) of the stored procedure, deserialized by the CLR into System.Data.DataSet. SELECT queries on relational data (as opposed to XML data) are always returned as .NET DataSets. . An object of type SqlRowCount, representing the number of records in the result set. . Any errors or warnings (or the value 0, if none occur), typed as SqlMessage objects. Also possible in the object array are the following objects (not returned in this example): . The results of SELECT FOR XML statements are deserialized into System.Xml. XmlElement objects. . Output parameters of a SQL Server web method–bound stored procedure are deserial- ized as SqlParameter objects. Because you don’t always know at runtime which objects are in which position in an array, it is best to iterate through the objects, testing to see which class they are before using them. This is the purpose of the foreach loop in the code example. At this point, you need to run the web application by clicking the IDE’s Run toolbar button or by pressing F5. When the browser is up and running, you enter a number in txtEmployeeId and click btnGetValue. If any tables are returned in the DataSet (for ptg 1946 CHAPTER 48 SQL Server Web Services FIGURE 48.3 Calling a SQL Server web service from a C# web application. example, if (ds.Tables.Count == 0)) the DataSet is bound to GridView, and the result- ing data is displayed. With a little visual sprucing up, your webpage should look a lot like Figure 48.3. Listing 48.5 contains the HTML built in default.aspx so far. (You add to this code as you continue through the examples.) LISTING 48.5 ASP.NET HTML Code in default.aspx <%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”Default.aspx.cs” Inherits=”_Default” %> <!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.1//EN” “http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd”> <%@ Page Language=”C#” AutoEventWireup=”true” CodeFile=”Default.aspx.cs” Inherits=”_Default” %> <html xmlns=”http://www.w3.org/1999/xhtml” > <head runat=”server”> <title>SQL Server 2008 Web Services - Called From Our ASP.NET Web Site</title> </head> <body> <form id=”form1” runat=”server”> <div> <h4>SQL Server 2008 Web Services - Called From Our ASP.NET Web Site</h4> <asp:Label ID=”lblResults” runat=”server” Text=”” Font-Italic=true></asp:Label> <br /> <br /> <asp:GridView ID=”gvData” runat=server BackColor=”LightGoldenrodYellow” BorderColor=”Tan” BorderWidth=”1px” ptg 1947 Examples: A C# Client Application 48 CellPadding=”2” ForeColor=”Black”> <FooterStyle BackColor=”Tan” /> <PagerStyle BackColor=”PaleGoldenrod” ForeColor=”DarkSlateBlue” HorizontalAlign=”Center” /> <SelectedRowStyle BackColor=”DarkSlateBlue” ForeColor=”GhostWhite” /> <HeaderStyle BackColor=”Tan” Font-Bold=”True” /> <AlternatingRowStyle BackColor=”PaleGoldenrod” /> </asp:GridView> <br /> <h6>Run Stored Procedure:</h6> EmployeeID: <asp:TextBox ID=”txtEmployeeId” runat=”server”>1</asp:TextBox> <asp:Button ID=”btnGetValue” runat=”server” OnClick=”btnGetValue_Click” Text=”Get Employee” /><br /> Example 2: Running Ad Hoc T-SQL Batches from a SQL Server Web Service For this example, you need to execute a batch of T-SQL statements by adding a new web method to the endpoint and changing it to accept query batches. The syntax for making changes to SOAP endpoints is similar to that for CREATE ENDPOINT. The differences are shown in Listing 48.6. LISTING 48.6 ALTER ENDPOINT T-SQL Syntax ALTER ENDPOINT endpointname <same as above> AS HTTP ( <same as above> <except> ADD EXCEPT_IP = (ip-address) DROP EXCEPT_IP = ( { <4-part-ip> | <4-part-ip>:<mask> } [ , n ] ) ADD WEBMETHOD webmethodname ALTER WEBMETHOD webmethodname DROP WEBMETHOD webmethodname ) FOR SOAP ( <same as above> ) ptg 1948 CHAPTER 48 SQL Server Web Services The following bullets explain the keywords used with ALTER ENDPOINT: . ADD and DROP EXCEPT_IP allow you to update the list of IP addresses allowed to connect to the web service. . ADD WEBMETHOD allows a new web method to be added to the web service. . ALTER WEBMETHOD permits changes in the attributes of an existing web method. . DROP WEBMETHOD permanently drops the named web method from the endpoint. Now you can change your endpoint and set BATCHES to ENABLED so you can run ad hoc queries on the web service: ALTER ENDPOINT EPT_SQL2008UnleashedExamples FOR SOAP ( BATCHES = ENABLED ) At this point, you need to return to Visual Studio and right-click the App_WebReferences node under the project name in the Solution Explorer. Then you select Update Web References from the context menu. This causes the IDE to re-request the WSDL from SQL Server to check for changes to the service description. The .NET IDE recognizes that batching has been turned on by adding the sqlbatch() method to the proxy class. All the behind-the-scenes work for SQL batching is done via this magical .NET method. When BATCHES is enabled, SQL Server adds some special elements to the WSDL XML to make this happen. A peek at the WSDL in opensql.wsdl (found under the App_WebReference node in Solution Explorer) serves to illustrate some of the special batching XML nodes: <wsdl:message name=”sqlbatchSoapIn”> <wsdl:part name=”parameters” element=”s0:sqlbatch” /> </wsdl:message> <wsdl:message name=”sqlbatchSoapOut”> <wsdl:part name=”parameters” element=”s0:sqlbatchResponse” /> </wsdl:message> You should now open default.aspx once again in design mode and add an additional TextBox control to the form named txtSQLBatch and set its TextMode property to MultiLine. Then you need to add a second Button control named btnRunBatch. Next, you double-click btnRunBatch and add the code in Listing 48.7 to the empty event handler ( btnRunBatch_Click()) generated by the IDE. ptg 1949 Examples: A C# Client Application 48 LISTING 48.7 Calling Ad Hoc T-SQL Batches from C# EPT_SQL2008UnleashedExamples SQLEndpointProxy = new EPT_SQL2008UnleashedExamples(); SQLEndpointProxy.Credentials = System.Net.CredentialCache.DefaultCredentials; opensql.SqlParameter[] sqlParams = new opensql.SqlParameter[1]; // note: using opensql.SqlParameter avoids namespace collisions // with System.Data.SqlClient.SqlParameter sqlParams[0] = new opensql.SqlParameter(); sqlParams[0].name = “EmployeeId”; sqlParams[0].Value = int.Parse(txtEmployeeId.Text); object[] ReturnArray = SQLEndpointProxy.sqlbatch(txtSQLBatch.Text, ref sqlParams); if (ReturnArray.Length > 0) { foreach (Object Obj in ReturnArray) { if (Obj is DataSet) { DataSet ds = (DataSet)Obj; if (ds.Tables.Count == 0) { lblResults.Text = “(No Results Found)”; } else { gvData.DataSource = ds; gvData.DataBind(); } } } } else { lblResults.Text = “(No Results)”; } As in the first example, the first and second code lines in Listing 48.7 create the proxy object and set its credentials. Next, an array of opensql.SqlParameter objects of length 1 is created, and its single parameter (EmployeeID) is assigned the value of txtEmployeeId.Text, typecast to an integer. You use this value as a declared parameter to your SQL batch. Instead of calling any web method by name, you instead call SQLEndpointProxy.sqlbatch(), passing in the text of the T-SQL statement and the value of txtEmployeeId.Text. With a little visual sprucing up, the running webpage should look something like the one in Figure 48.4. ptg 1950 CHAPTER 48 SQL Server Web Services Now you need to append the HTML code in Listing 48.8 to default.aspx just below the last line entered. LISTING 48.8 Additional ASP.NET HTML Code for default.aspx <br /> <h6>Run Sql Batches:</h6> <asp:TextBox ID=”txtSQLBatch” runat=”server” Height=”280px” TextMode=”MultiLine” Width=”437px”>SELECT LoginId, BusinessEntityID FROM HumanResources.Employee WHERE BusinessEntityID = @EmployeeID </asp:TextBox> <asp:Button ID=”btnRunBatch” runat=”server” OnClick=”btnRunBatch_Click” Text=”Run Batch” /> When btnRunBatch is clicked at runtime, the text of the query stored in txtSQLBatch.Text is executed. The parameter @EmployeeId is populated from txtEmployeeId.Text (in this case, typecast to the integer value 1), and the batch is run. The web service responds with SOAP flowing over HTTP, and the envelope is deserialized into an array of objects. FIGURE 48.4 Running T-SQL batches on a web service by using sqlbatch(). ptg 1951 Examples: A C# Client Application 48 The resulting DataSet is again bound to the GridView gvData. Any SqlMessages in the ReturnArray are appended to the text of lblResults for viewing on the page. This type of querying just touches the tip of what can be accomplished via ad hoc web services queries using sqlbatch(). You can use your imagination to take it as far as you want. Example 3: Calling a Web Method–Bound Stored Procedure That Returns XML For your third and final web method, you create a stored procedure that returns XML from an xml column, using the new FOR XML PATH syntax. To do this, you need to create the stored procedure in Listing 48.9 in the context of the AdventureWorks2008 database. LISTING 48.9 A Stored Procedure That Returns XML use AdventureWorks2008 GO CREATE PROC dbo.GetJobCandidateResumeXml ( @JobCandidateId int ) AS SELECT Resume.query(‘ declare namespace ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”; /ns:Resume/ns:Name ‘) as “Name”, Resume.query(‘ declare namespace ns=”http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/Resume”; /ns:Resume/ns:Skills ‘) as “Skills” FROM HumanResources.JobCandidate WHERE JobCandidateId = @JobCandidateId FOR XML PATH(‘CandidateQuickView’) The Resume column of HumanResources.JobCandidate is of the new SQL data type xml. The .query() syntax used in GetJobCandidateResumeXml is part of the XQuery language, which is newly supported in SQL Server 2008. Chapter 47, “Using XML in SQL Server 2008,” describes these features in detail. Now you need to bind the stored procedure to the existing endpoint, using the T-SQL in Listing 48.10. ptg 1952 CHAPTER 48 SQL Server Web Services LISTING 48.10 ALTER ENDPOINT Syntax for Adding a Web Method That Returns XML ALTER ENDPOINT EPT_SQL2008UnleashedExamples FOR SOAP ( ADD WEBMETHOD ‘urn:www-samspublishing-com:examples’.’WM_GetJobCandidateResumeXml’ ( NAME = ‘AdventureWorks2008.dbo.GetJobCandidateResumeXml’, FORMAT = ALL_RESULTS, SCHEMA = STANDARD ) ) You need to return to Visual Studio and update the web reference of the project, as described in Example 1. Then you switch default.aspx to design mode and add another Button control, named btnGetXml, to the bottom of the page. In the HTML Source view of default.aspx, you append to the page the lines of ASP.NET code found in Listing 48.11. LISTING 48.11 The Final ASP.NET HTML Code in default.aspx <h6>Run Xml-Based Stored Procedure:</h6> Job Candidate Id: <asp:TextBox ID=”txtJobCandidateId” runat=”server” Text=”1”></asp:TextBox> <asp:Button ID=”btnGetXml” runat=”server” Text=”Get Xml” OnClick=”btnGetXml_Click” /> <hr /> <textarea style=”font-size:11px;” rows=”10” cols=”80” ID=”textareaXml” runat=”server”></textarea> <hr /> <h6>SqlRowCount=<asp:Label ID=”lblRowCount” runat=”server”/></h6> <h6>SqlResultCode=<asp:Label ID=”lblResultCode” runat=”server”/></h6> At this point you should double-click btnGetXml. In btnGetXml_Click(), you type or copy the C# code found in Listing 48.12. LISTING 48.12 Consuming a Web Method That Returns XML in C# opensql.EPT_SQL2008UnleashedExamples SQLEndpointProxy = new opensql.EPT_SQL2008UnleashedExamples(); SQLEndpointProxy.Credentials = System.Net.CredentialCache.DefaultCredentials; ptg 1953 Examples: A C# Client Application 48 object[] XmlResult = SQLEndpointProxy.WM_GetJobCandidateResumeXml(int.Parse(txtJobCandidateId.Text)); if (XmlResult.Length == 3) { lblRowCount.Text = ((SqlRowCount)XmlResult[1]).Count.ToString(); lblResultCode.Text = XmlResult[2].ToString(); XmlElement CandidateQuickViewXmlElement = (XmlElement)XmlResult[0]; textareaXml.Value = CandidateQuickViewXmlElement.OuterXml; } For this example to compile, you must add the following C# using statement to the top of default.aspx.cs: using System.Xml; The final output should display the XML result of the GetJobCandidateResumeXml stored procedure in an HTML text area, as shown in Figure 48.5. FIGURE 48.5 Returning XML from a stored procedure to a SQL Server web service C# client. . System.Net.CredentialCache.DefaultCredentials; opensql.SqlParameter[] sqlParams = new opensql.SqlParameter[1]; // note: using opensql.SqlParameter avoids namespace collisions // with System.Data.SqlClient.SqlParameter sqlParams[0] = new opensql.SqlParameter(); sqlParams[0].name. T -SQL Batches from C# EPT _SQL2 008UnleashedExamples SQLEndpointProxy = new EPT _SQL2 008UnleashedExamples(); SQLEndpointProxy.Credentials = System.Net.CredentialCache.DefaultCredentials; opensql.SqlParameter[]. ptg 1944 CHAPTER 48 SQL Server Web Services LISTING 48.4 Calling a SQL Server Web Method from C# if (txtEmployeeId.Text != string.Empty) { EPT _SQL2 008UnleashedExamples SQLEndpointProxy = new EPT _SQL2 008UnleashedExamples(); SQLEndpointProxy.Credentials