Loading... <p>Webservices offer a rich world of functionality. This world is available to the Delphi programmer with the introduction of the Webservice importer introduced in Delphi 6, with version 6.02 also available in Delphi pro. A webservice can work with pretty complex data, with .NET it is a snap to return and receive complete XML datasets. Delphi does not know how to work with these datasets natively. In this paper I will show how to work with .NET data using the GekkoDotNetDataset componenet.</p><h3>The .NET webservice</h3><p>The webservice I am going to build will be a web-wrapper round a database. The database is an Access database local on the web-server. It contains customers, invoices and invoice details.</p><p><img height="394" alt="" width="400" border="0" src="http://www.gekko-software.nl/DotNet/WDN0801.jpg" /></p><p>Methods of the webservice will expose this data to the web as strongly typed XML datasets. Other methods will accept XML datasets to update the database. Importing these tables in a .NET application will result in a nice XSD <a name="schema">schema</a></p><p><img height="627" alt="" width="773" border="0" src="http://www.gekko-software.nl/DotNet/WDN0802.jpg" /></p><p>For a step by step story how to build such a service you can read <a href="https://www.samool.com/go/aHR0cDovL3d3dy5kb3RuZXRqdW5raWVzLmNvbS90dXRvcmlhbHMuYXNweD90dXRvcmlhbGlkPTQ3NQ==" target="_blank" >one of my dotnetjunkies stories</a>, for the moment I will concentrate on the actual webmethods.</p><p>The Customers method returns a dataset containing all customers. It does so by creating a new typed dataset object : <strong>DataSetCustomers</strong>. This object is filled by the oleDbDataAdapter, the internals of this component do the real access to the database.</p><p> </p><p> </p><p class="listing"><font color="#0000ff" size="3">public</font><font size="3"> DataSetCustomers Customers()<br />{<br /> DataSetCustomers ds = </font><font color="#0000ff" size="3">new</font><font size="3"> DataSetCustomers();<br /> oleDbDataAdapterCustomers.Fill(ds);<br /> </font><font color="#0000ff" size="3">return</font><font size="3"> ds;<br />}</font></p><p>The entire resulting XML dataset is returned. One of the many nice things of an XML dataset is that it can be serialized, it can be represented as one long string of characters. Which is something which is very easy to transport over the standard HTTP protocol.</p><p>To fill the contents of the invoices dataset, as described by the <a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDguaHRtI3NjaGVtYQ==" target="_blank" >schema</a>, takes a little more effort. The three tables can live together in one dataset but for every table another oleDbDataAdapter is needed.</p><p><font size="3"><p class="listing">[WebMethod]</p></font><font color="#0000ff" size="3">public</font><font size="3"> DataSetInvoices Invoices()<br />{<br /> DataSetInvoices ds = </font><font color="#0000ff" size="3">new</font><font size="3"> DataSetInvoices();<br /> oleDbDataAdapterCustomers.Fill(ds.Customers);<br /> oleDbDataAdapterInvoices.Fill(ds.Invoices);<br /> oleDbDataAdapterInvoiceDetails.Fill(ds.InvoiceDetails);<br /> </font><font color="#0000ff" size="3">return</font><font size="3"> ds;<br />}</font></p><p>Using dataAdapters all query possibilities of the database can be used. For an example you are still invited at the dotnetjunkies. DataAdapters can also be used to write to a database. The updates to be written are passed as a typed XML dataset. Which make the implementation of of the webmethod a one-liner:</p><p><font size="3"><p class="listing">[WebMethod]</p></font><font color="#0000ff" size="3">public</font><font size="3"> </font><font color="#0000ff" size="3">void</font><font size="3"> UpDateCustomers(DataSetCustomers ds)<br />{<br /> oleDbDataAdapterCustomers.Update(ds.Customers);<br />}</font></p><p>Multiple tables can be updated in one go in the UpdateInvoices method. The order in these updates will be performed is important:</p><p><font size="3"><p class="listing">[WebMethod]</p></font><font color="#0000ff" size="3">public</font><font size="3"> </font><font color="#0000ff" size="3">void</font><font size="3"> UpdateInvoices(DataSetInvoices ds)<br />{<br /> oleDbDataAdapterCustomers.Update(ds.Customers);<br /> oleDbDataAdapterInvoices.Update(ds.Invoices);<br /> oleDbDataAdapterInvoiceDetails.Update(ds.InvoiceDetails);<br />}</font></p><p>You cannot enter a new invoice if you don't know the customer yet. These integrity checks are also performed in an .NET XML dataset object. But in there they can be switched off by setting the EnforceConstraints property to false.</p><h3>A .NET webservice consumer at work</h3><p>In .NET you can build a windows client application which imports the webservice and it will work perfect with all functionality of the webservice. Which means that a windows application can update an Access database somewhere on a webserver on the other side of the globe using plain HTTP.</p><p>The client reads that data from the webserver like this</p><p><font size="3"><p class="listing">localhost.DataSetWebService ws =</p></font><font color="#0000ff" size="3">new</font><font size="3"> localhost.DataSetWebService();<br />dataSetCustomers1.Clear();<br />dataSetCustomers1.Merge(ws.Customers());</font></p><p>The dataset is bound to a grid. Here the user can do some editing after which the update is invoked</p><p><font size="3"><p class="listing">localhost.DataSetWebService ws =</p></font><font color="#0000ff" size="3">new</font><font size="3"> localhost.DataSetWebService();<br />ws.UpDateCustomers(dataSetCustomers1);</font></p><p>See this consumer at work in the dotnetjunkies story.</p><h3>Importing the webservice in Delphi</h3><p>It would be very nice to work with this webservice in Delphi. I will use Delphi 6.02 pro which has full support for webservices clients. Delphi has a webservice importer which is found under the file | New | Other | Webservices menu. After entering the URL of the webservice Delphi will generate a unit describing the webservice.</p><p>The webservice has two types, being the Customers and the Invoices XSD schema. The service has four methods who use these types in their parameters or as result-type. And this is what the Delphi makes out of it :</p><p><img height="442" alt="" width="725" border="0" src="http://www.gekko-software.nl/DotNet/WDN0804.jpg" /></p><p>Which looks pretty disappointing. The Return types of Customers and Invoices webmethods are recognized as composite types Customers and Invoices. Alas, these types contain no members at all. Things get worse with the update methods. Both have a parameter named <strong>ds</strong> of type Invoices or Customers. The importer generates two methods with a parameter named ds of a type named ds as well. This ds type is declared and does not have any members either. That's not going to work. Many webservices work very well with Delphi but in this case it will need some extra help.</p><h3>Introducing the GekkoDotNetDataSet component </h3><p>I have built the GekkoDotNetDataSet component. This component is based on the HTTPrio component and can be found in the demo code. It takes the following approach to the problem:</p><ul> <li>It wraps up one XML dataset.</li> <li>It provides the data to other Delphi components as (client-)datasets.</li> <li>The webservice has to have a function member which returns the typed dataset.</li> <li>The webservice has to have an update member which accepts the typed XML dataset in a parameter.</li></ul><p>The component introduces two new published events and one new published property, visible in the object inspector.</p><ul> <li><strong>OnRequestGetInvocation</strong>, an event which is fired when the component requests the XMLdataset from the service.</li> <li><strong>Paramname</strong>. A string to store the name of the parameter of the updatemethod.</li> <li><strong>OnRequestUpdateInvocation</strong>, an event which is fired when the component requests the webservice to update the data.</li></ul><p>The component has two public methods and two public properties to read and write data</p><ul> <li>The <strong>Get</strong> method reads the data into the componennt.</li> <li>The <strong>DataTable</strong> publishes all data in an array of Delphi (Client)datasets.</li> <li><strong>TableCount</strong> counts the number of Delphi datasets.</li> <li>The <strong>Update</strong> method sends all updates to the webservice</li></ul><p>This componenet is part of the <strong>GekkoDotNetPackage</strong>, it will install itself on the webservices page.</p><h3>Reading data with GekkoDotNetDataSet component </h3><p>I drop two of these components on the form. One for the Web Services' <em>Customers</em> dataset and the other for the<em> Invoices</em> dataset. Despite it's emptiness I can use the imported Service1.pas. The GekkoDotNetDataSet component is a HTTPrio descendent so I have to set the WSDLlocation in both components , it will be <em><a href="https://www.samool.com/go/aHR0cDovL2xvY2FsaG9zdC9XZWJTZXJ2aWNlcy9EYXRhU2VydmljZS9TZXJ2aWNlMS5hc214P3dzZGw=" target="_blank" >http://localhost/WebServices/DataService/Service1.asmx?wsdl</a></em>. The component's only new property is <strong>ParamName</strong>, it is the name of the parameter of the Update methode, <em>ds</em> for both components.</p><p>The real new stuff is in two new events. These get fired when the component <strong>Get</strong>'s or <strong>Update</strong>'s data. As the component does not know which member of the webservice to invoke to read or write data it will make a callback to the component's user. Requesting the user to do the actual invoke.</p><p class="listing">procedure TForm1.DataSetCustomersRequestGetInvocation(Sender: TObject);<br />var Iservice : DataSetWEbServiceSoap;<br /> begin<br /> Iservice:= DataSetCustomers as DataSetWebServiceSoap;<br /> Iservice.Customers;<br /> end;</p><p>You have to get to the actual webservice by typecasting the component to the interface of the service, which can be done because the component is a HTTPrio wrapping up the webservice. The declaration of <strong>DataSetWebServiceSoap </strong>is in the imported Service1.pas. On this interface you call the function which will return the intended XML dataset. In the <em>requestGetInvocation</em>-eventhandler of the other componenent the <em>Invoices</em> method will be invoked.</p><p>The click of a button will fill the form with a dataset, which dataset depends on a radiogroup</p><p class="listing">procedure TForm1.ButtonGetClick(Sender: TObject);<br />var i : integer;<br /> DNdataSet : TGekkoDotNetDataSet;<br /><br /> begin<br /> case RadioGroup1.ItemIndex of<br /> 0 : DNdataSet:= DataSetCustomers;<br /> 1 : DNdataSet:= DataSetInvoices;<br /> else DNdataSet:= nil;<br /> end;<br /> if Assigned(DNdataSet) then<br /> begin<br /> DNdataSet.Get;<br /> CreateDataGrids(DNdataSet);<br /> end;<br /> end;</p><p>By calling <strong>get</strong> on the customers dataset the <strong>DataSetCustomersRequestGetInvocation</strong> eventhandler will be executed. Which will make the right invocation.</p><p>Now the tables of the are filled I will show what's in them. The form has an empty pagecontrol. For every dataset a page is added to the pagecontrol : </p><p class="listing">procedure TForm1.CreateDataGrids(Sender: TObject);<br /> var tP : tTabSheet;<br /> ds : tDataSource;<br /> dn : tDBNavigator;<br /> dg : tDBGrid;<br /> i : integer;<br /><br /> DNdataSet : TGekkoDotNetDataSet;<br /><br /> begin<br /><br /> DNdataSet:= sender as TGekkoDotNetDataSet;<br /> if DNdataSet <> nil then<br /> begin<br /> for i:= 0 to DNdataSet.TableCount - 1 do<br /> begin<br /> tP:=tTabSheet.Create(self);<br /> tP.PageControl:= PageControl1;<br /> tP.Caption:= DNdataSet.DataTable[i].Name;<br /><br /> ds:= tDataSource.Create(Self);<br /><br /> dn:= tDBnavigator.Create(self);<br /> dn.Align:= alTop;<br /> dn.Parent:= tP;<br /> dn.DataSource:= ds;<br /><br /> dg:= tDBGrid.Create(self);<br /> dg.Align:= alClient;<br /> dg.Parent:= tP;<br /> dg.DataSource:= ds;<br /><br /> ds.DataSet:= DNdataSet.DataTable[i];<br /> end;<br /> end;<br /><br /> end;</p><p>For every (client-)dataset in the GekkoDotNetDataSet I create a new page with a datagrid and a dbNavigator.</p><p>When running this application I can browse and update the data in my Delphi form.</p><h3>Updating data with GekkoDotNetDataSet component </h3><p>To return all updates to the webservice the component uses another event</p><p class="listing">procedure TForm1.DataSetInvoicesRequestUpdateInvocation(Sender: TObject);<br />var Iservice : DataSetWEbServiceSoap;<br /> begin<br /> Iservice:= DataSetInvoices as DataSetWebServiceSoap;<br /> Iservice.UpDateInvoices(ds.Create);<br /> end;</p><p>This event is fired by the componenet when it's <strong>Update </strong>method<strong> </strong>is called. The component does not know which member to invoke, in this eventhandler the component' user is requested to invoke the desired method. The<strong> ds</strong> class is declared in the imported unit. It has no members but it will do to invoke the method.</p><p>The form sends the updates by the click of a button</p><p class="listing">procedure TForm1.ButtonSaveClick(Sender: TObject);<br />var DNdataSet : TGekkoDotNetDataSet;<br /> begin<br /> case RadioGroup1.ItemIndex of<br /> 0 : DNdataSet:= DataSetCustomers;<br /> 1 : DNdataSet:= DataSetInvoices;<br /> else DNdataSet:= nil;<br /> end;<br /> if Assigned(DnDataSet) then<br /> DNdataSet.Update;<br /> end;</p><p>Now we have a Delphi application which works with a .NET webservice and can read or write XML dataset data.</p><p><img height="665" alt="" width="700" border="0" src="http://www.gekko-software.nl/DotNet/WDN0805.jpg" /></p><h3>Inpecting the webservice's request and response</h3><p>To get an idea what is going on I have set the GekkoDotNetDataSet componenet's before- and after- execution event to show the full SOAP request and response in explorer windows.</p><p>The request is passed to the eventhandler as a string and the response as a stream. The <strong>ShowXML </strong>method will send the stream to a browser, it does so by saving the xml as a temporary file and pointing a browser to it. In the BeforeExcute event the string request has to be written to a stream before being sent to the ShowXML method.</p><p class="listing">procedure TForm1.HTTPRIO1BeforeExecute(const MethodName: String; var SOAPRequest: WideString);<br /> var ts : tStringStream;<br /> buffer : string;<br />begin<br /> buffer:= SOAPrequest;<br /> ts:= tStringStream.Create(buffer);<br /> Showxml(ts, Send);<br />end;</p><p class="listing">procedure TForm1.HTTPRIO1AfterExecute(const MethodName: String; SOAPResponse: TStream);<br />begin<br /> ShowXML(SOAPResponse, Receive);<br />end;</p><p>Now you can see the full requests as they are sent to the .NET webservice and the response.</p><h3>(Don't) try this ay home !</h3><p>The GekkoDotNetDataSet component relies on the tDNdataSet, whose internals are described in <a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDcuaHRt" target="_blank" >another paper</a>. The class has been created by trial and error in investigating the results of .NET built webservices. If you want to use the class in your own code please consider the following points (this is a disclaimer !)</p><ul> <li>The dataset does (by long) not support all possible field-types.</li> <li>The functionality is based on the diffgram structure as found, this structure is not (as far as I know) backed up by some kind of formal specification. Special testing deserve the roworder attribute and the localization settings.</li> <li>New updated versions of the component and these papers will appear on this website.</li></ul><p>You are welcome to experiment with the component and any suggestions, remarks, comments, or other feedback will be greatly appreciated. It will all be used in the updates.</p><h3>Where are we ?</h3><p>In this paper we have seen how a Delphi webservice client can work with a webservice which works with XML (diffgram) datasets. All functionality is stored in the GekkoDotNetDataSet component, which is based on Delphi's HTTPrio component.</p><h3>What's next ?</h3><ul> <li><a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvRG90TmV0RGF0YVNldEluRGVscGhpLnppcA==" target="_blank" >Download the code</a>.</li> <li>Inside <a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDkuaHRt" target="_blank" >the component</a>.</li> <li>Inside <a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDcuaHRt" target="_blank" >tDNdataset</a> class.</li> <li><a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDYuaHRt" target="_blank" >Data in .NET</a>.</li></ul><p> </p><p>文章来源: <a href="https://www.samool.com/go/aHR0cDovL3d3dy5nZWtrby1zb2Z0d2FyZS5ubC9Eb3ROZXQvQXJ0MDYuaHRt" target="_blank" >http://www.gekko-software.nl/DotNet/Art06.htm</a></p> 相关文章 .NET技术在中国为什么老被人嫌弃 Delphi7调用C#的webservice,在windows2008下不能运行?? Delphi ListView排序 Delphi中获取Unix时间戳及注意事项 恢复delphi7 文件关联 delphi Format格式化函数 如何将Bitmap位图与base64字符串相互转换 通过MAP文件尝试解决Access violation at address错误 Delphi获得与设置系统时间格式 c#的DateTime.Now函数详解 Last modification:August 16th, 2009 at 12:30 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments Pay by AliPay Pay by WeChat
The quality of your superb release about this good post can be compared with the research essay at buy essay service only. Hence, you do this in a good way. Thank you a lot for your work!