ExposureRoom Home
  Log in Sign Up
Shiv's Website
Shiv Kumar
United States
Friends: 71
Focused on : 2
 
       
                                                             

WebServices and Complex Types

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 21, 2008 10:52 AM  Views:547   Favorited:0 Comments:0
Filed Under:  Programming, Work
Tags:  Delphi, SOAP, WebServices
 
You saw in the previous tutorial - Building your first WebService how simple it it with Delphi 6 to build WebServices. This tutorial assumes you've read that tutorial or know of the things that were discussed there.

Sending back strings, integers etc. as a result of a WebService method is really simple. You didn't have to do anything special to accomplish this. In this tutorial, we'll see how we can:

  1. Send back custom objects
  2. Define and raise custom Exceptions

As soon as you read the things we're going to learn how to do in this tutorial, you must have thought to yourself:

  • If we send back an Object how will clients written in tools other then Delphi deal with these objects?
  • Riase an Exception? What, on a Remote Client? What if the client is not a Delphi client? How will it know what to do with a Delphi Exception?
  • What if the Client is running on a platform other than Windows?

If you asked any or all of these questions, then you've been paying attention . That's good news. Read on, and you'll have the answers to questions you ask.

WebService Methods Returning Objects

The WebService we will build in this tutorial has two methods. The WSDL file for this WebService can be found at http://webservices.matlus.com/scripts/XMLDataSetWebService.dll/wsdl/IDatasetXML. One of these methods (GetSchemaInfo) returns an array of objects. The object is a custom Delphi object. The class declaration of this object is shown below.

The TFieldInfo object
type
  TFieldInfo = class(TRemotable)
  private
    FIsKeyField: Boolean;
    FFieldSize: Integer;
    FFieldName: string;
    FFieldType: string;
  published
    property FieldName: string read FFieldName write FFieldName;
    property FieldType: string read FFieldType write FFieldType;
    property FieldSize: Integer read FFieldSize write FFieldSize;
    property IsKeyField: Boolean read FIsKeyField write FIsKeyField;
  end;

  TFieldInfoArray =   array of TFieldInfo;
As shown above, TFieldInfoArray is declared as an array of type TFieldInfo. Depending on the number of fields in the table, the method will return those many elements in the array. Take a look at the SOAP Request and Response shown below.

Showing the HTTP Header and Content (SOAP Request) for the GetSchemaInfo method.
POST http://webservices.matlus.com/scripts/xmldatasetwebservice.dll/soap/IDatasetXML HTTP/1.0
Accept: application/octet-stream, text/xml
SOAPAction: "urn:DatasetXMLIntf-IDatasetXML#GetSchemaInfo"
Content-Type: text/xml
User-Agent: Borland SOAP 1.1
Host: webservices.matlus.com
Content-Length: 535
Proxy-Connection: Keep-Alive
Pragma: no-cache

<?xml version="1.0" encoding='UTF-8'?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <NS1:GetSchemaInfo xmlns:NS1="urn:DatasetXMLIntf-IDatasetXML" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
      <NS1:TableName xsi:type="xsd:string">Customer</NS1:TableName>
    </NS1:GetSchemaInfo>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Showing the HTTP Header and Content (SOAP Response) for the GetSchemaInfo method.
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Fri, 20 Jul 2001 04:34:23 GMT
Content-Type: text/xml
Content-Length: 4676
Content:

<?xml version="1.0" encoding='UTF-8'?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <NS1:GetSchemaInfoResponse
      xmlns:NS1="urn:DatasetXMLIntf-IDatasetXML"
      SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
      xmlns:NS2="http://www.w3.org/2001/XMLSchema">
      <NS1:return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="NS2:TFieldInfo[13]">
        <NS1:item href="#1"/>
        <NS1:item href="#2"/>
        <NS1:item href="#3"/>
        <NS1:item href="#4"/>
        <NS1:item href="#5"/>
        <NS1:item href="#6"/>
        <NS1:item href="#7"/>
        <NS1:item href="#8"/>
        <NS1:item href="#9"/>
        <NS1:item href="#10"/>
        <NS1:item href="#11"/>
        <NS1:item href="#12"/>
        <NS1:item href="#13"/>
      </NS1:return>
      <NS2:item id="1" xsi:type="NS2:TFieldInfo">
        <NS2:FieldName xsi:type="xsd:string">CustNo</NS2:FieldName>
        <NS2:FieldType xsi:type="xsd:string">ftFloat</NS2:FieldType>
        <NS2:FieldSize xsi:type="xsd:int">0</NS2:FieldSize>
        <NS2:IsKeyField xsi:type="xsd:boolean">False</NS2:IsKeyField>
      </NS2:item>
      <NS2:item id="2" xsi:type="NS2:TFieldInfo">
        <NS2:FieldName xsi:type="xsd:string">Company</NS2:FieldName>
        <NS2:FieldType xsi:type="xsd:string">ftWideString</NS2:FieldType>
        <NS2:FieldSize xsi:type="xsd:int">30</NS2:FieldSize>
        <NS2:IsKeyField xsi:type="xsd:boolean">False</NS2:IsKeyField>
      </NS2:item>
      <NS2:item id="3" xsi:type="NS2:TFieldInfo">
        <NS2:FieldName xsi:type="xsd:string">Addr1</NS2:FieldName>
        <NS2:FieldType xsi:type="xsd:string">ftWideString</NS2:FieldType>
        <NS2:FieldSize xsi:type="xsd:int">30</NS2:FieldSize>
        <NS2:IsKeyField xsi:type="xsd:boolean">False</NS2:IsKeyField>
      </NS2:item>
    </NS1:GetSchemaInfoResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>


Figure 1: Showing part of the WSDL file that defines the complexType

The SOAP request is a standard request (for this WebService) calling the GetSchemaInfo method and sending it the TableName, Customer. The response by the WebService is what we need to take a closer look at. As I mentioned earlier, this method returns an array of objects. In XML terms, as you can see from the WSDL file or Figure 1, this is a complexType that is defined as an array. So you see, that the object is really defined like a Delphi record or C/C struct. When a Delphi client receives this kind of data it gets converted into an object instance (more about this later). The Customer table in this case has 13 fields. You can see this from the HTTP response shown earlier. I've shown only the first 3 fields to save space, but the line:

Showing part of the HTTP Header and Content (SOAP Response) that tells us to expect 13 elements in the TfieldInfoArray.

<NS1:return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="NS2:TFieldInfo[13]">
tells us to expect 13 elements in the array TFieldInfoArray. If the client is a Java Client, then it will handle this response the way it needs to. It may or may not treat this response as an array of objects. That depends on the implementation of the framework used. Delphi 6's SOAP framework works with interfaces and objects. When you do SOAP with Delphi 6, you are really doing Remote Object Invocation. (That's where TRIO got it's name from. This is similar to RMI in Java with the advantage that the protocol is a standard.


Figure 2: SOAP is just a Wire Protocol

Remember that SOAP is a wire protocol. What that means is that the actual communication between two end points is taking place using the SOAP protocol. What happens before the message leaves one end and what happens once the messages gets to the other end is left totally up the implementation specific details of the two end points. What is key is that everyone should be on the same page as far as their interpretation of the SOAP specification is concerned. Once the messages gets across the wire it's up to that end point how you deal with it. Even though SOAP has the word Object in it, is not necessary that there are any objects involved. When a Delphi built WebService server receives a SOAP Request, it instantiates an instance of an object. It doesn't matter what kind of client made a request or what platform the client is running on. Similarly, if a WebService responds with a SOAP Response as shown earlier, a Delphi WebService Client, will instantiate an instance of an array of objects with 13 elements in it. It doesn't matter what language the WebService was written in or what platform it is running on. That's just the way Delphi implements SOAP.

We saw in the - Building your First WebService tutorial (from here on in reffered to as "the previous tutorial), how Delphi built WebServices and clients figure out what these objects are and how to instantiate them and which objects to instanciate. In this case, the difference is that the WebService is returning an object. How the client figures out what this object is and how to instanciate which object, we'll see later in this tutorial (hint - the object needs to be registered with the Invocation Registry and needs to be derived from a specific class).

Building the Server

I won't go into the step by step instructions on how to start with building a WebService application with Delphi 6 Enterprise. If you've not built WebServices with Delphi before, I suggest you read the previous Tutorial.

The first thing we're going to need is to define our Interface. You can use the Wizard (available to registered users) or code the unit by hand. The method GetDatasetAsXML expects to see and SQL SELECT statement as a parameter and it returns the result of the query in the form of an XML document. The second method, GetSchemaInfo expects to see the name of a table (in this case a table that exists in the dbdemos.mdb Access database) and returns the schema of the table in the form of an array of TFieldInfo objects (one element per field).

exclamation.gif Returning an XML document as a result of a WebService method is not recommended. This is because the WSDL file indicates that this method will return a string. Someone building a client for this service simply looking at the WSDL file (which is really how WebServices are supposed to work.) will have no indication that this "string" is in fact going to be an XML document.

Suggestion.gif When designing WebServices it is important to keep the spirit of WebServices.
  • WSDL - always think, "Will just the WSDL file be enough for anyone else to use my WebService?"
  • Interoperability - People using other development tools and/or platforms should be able to access your WebService.
If you feel you don't need to comply with these two points. You probably don't need to use WebServices. That is, if you're building the client and server for personal consumption, as a result you can ignore the above "requirements", there are probably better solutions for your needs. Remember, XML is a very verbose protocol and as a result tends to be very slow. The number of bytes being transferred between client and server is very large and there is a fair amount of processing on both the client and server in constructing the XML document and parsing it. There are solutions (I'll present one of them soon if I haven't already by the time you read this) that are much lighter than XML and can use HTTP and so still gain the benefits that SOAP provides but require a lot less bandwidth and processing power and as a result are more scalable. If you need to expose and API to a system you're building, to the out side world then WebServices is a good choice. If you're using WebServices for a Client/Server or n-tier system then WebServices is not the answer. You might want to take a look at this article on ZDNet Fat protocols slow Web services.

The IDataSetXML Interface Unit.
unit DatasetXMLIntf;

interface

uses
  Types, XSBuiltIns, uFieldInfo;

type
  IDatasetXML = interface(IInvokable)
    ['{C7785514-F56F-40AD-B943-950A162296BE}']
    function GetDatasetAsXML(const SelectSQL: WideString): WideString; stdcall;
    function GetSchemaInfo(const TableName: WideString): TFieldInfoArray; stdcall;
  end;

implementation

uses
  InvokeRegistry;

initialization
  InvRegistry.RegisterInterface(TypeInfo(IDatasetXML), '', '');

end.
You should notice that there is an additional unit (uFieldInfo) in the Uses clause of this unit. This is the unit in which the TFieldInfo class is declared.

The uFieldInfo Unit.
unit uFieldInfo;

interface

uses
  InvokeRegistry, XSBuiltIns;

type
  TFieldInfo = class(TRemotable)
  private
    FIsKeyField: Boolean;
    FFieldSize: Integer;
    FFieldName: string;
    FFieldType: string;
  published
    property FieldName: string read FFieldName write FFieldName;
    property FieldType: string read FFieldType write FFieldType;
    property FieldSize: Integer read FFieldSize write FFieldSize;
    property IsKeyField: Boolean read FIsKeyField write FIsKeyField;
  end;

  TFieldInfoArray =   array of TFieldInfo;


implementation

initialization
  RemClassRegistry.RegisterXSClass(TFieldInfo, '', 'TFieldInfo', '');
  RemTypeRegistry.RegisterXSInfo(TypeInfo(TFieldInfoArray), '', 'TFieldInfoArray');

finalization
  RemClassRegistry.UnRegisterXSClass(TFieldInfo);
  RemTypeRegistry.UnRegisterXSInfo(TypeInfo(TFieldInfoArray));
end.
The things to notice here are the initialization and finalization sections, and the fact that the class TFieldInfo is derived from TRemotable. TRemotable is the base class for classes that can be passed as parameters or return values in a Web Service application. Again, this class is compiled with RTTI and has a virtual constructor that is used by the TPascalInvoker as explained in the previous tutorial.

exclamation.gif For ComplexTypes to work, you need to apply a fix to the WebServExp.pas source code file. This is an unofficial fix that I have made and is not supported. So far I have not seen any repercussions to implementing this fix. When Borland releases a patch or fix for this, please be sure to implement their fix rather than this. The WSDL generated without this fix is not correct as it does not include the definition for the complexTypes.
The following code listing shows the unofficial fix for the WebServExp.pas source code File.
function TWebServExp.FindSchema(const ObjectTypeInfo: PTypeinfo; const
TnsURI: string): Boolean;
var
  Index: Integer;
begin
  Result := False;
  //Do not register Empty TnsURI or tkSet or any predefined type from XMLSchema
{ TODO -oShiv Kumar -cBUG :
Complex Types are not generated if this code is NOT commented.
Don't know the implications of doing this, but so far have not had issues. }

{  if ((TnsURI = '') or (ObjectTypeInfo.Kind = tkSet) or (TnsURI =
SXMLSchemaURI_1999) or  (TnsURI = SXMLSchemaURI_2000_10) or
    (TnsURI = SXMLSchemaURI_2001))  then
  begin
    Result := True;
    Exit;
  end;
}
  for Index := 0 to Length(SchemaArray) -1 do
  begin
    if SchemaArray[Index].TypeInfo = ObjectTypeInfo then
    begin
      Result := True;
      Exit;
    end;
  end;

  //Add new type
  Index := Length(SchemaArray);
  SetLength(SchemaArray, Index 1);
  SchemaArray[Index].TypeName := ObjectTypeInfo.Name;
  SchemaArray[Index].NameSpace := TnsURI;
  SchemaArray[Index].TypeInfo := ObjectTypeInfo;
  SchemaArray[Index].XSGenerated := False;
end;

More on TRemotable

Some of you might have wondered after seeing the Interface declaration, especially the method:

function GetSchemaInfo(const TableName: WideString): TFieldInfoArray; stdcall;
Who is responsible for freeing the instance of TFieldInfoArray that is being returned? If this method were a regular method, then the calling method is responsible. In this case however, the calling method is a remote application, and what's more, it doesn't have to be a Delphi client that calls on this WebService. The answer is that when TRemotable descendants are created in a method that was called remotely using an Invokable interface, the instance is automatically freed after the value of the TRemotable descendant is marshaled for transport back to the client. So you don't need to worry about this aspect of it.

What if a TRemotable type is a parameter to a method instead of being returned as a result? On the Client end (Delphi Clients), the client is responsible for creating and instance of it before making the method call and freeing the instance after it is finished using it. On the server end, the instance is once again automatically created and freed. Of course, for all of this to work, both the client and server need to register the class with the Invocation Registry. This is very important. Take a look at the on-line help for the methods RegisterXSClass and RegisterXSInfo.

The unit DatasetXMLImpl that implements the IDataSetXML Interface.
unit DatasetXMLImpl;

interface

uses
  Classes, DatasetXMLIntf, InvokeRegistry, uFieldInfo, DB, ADODB, msxmldom,
  xmldom, XMLIntf, XMLDoc;

type
  ENotSelectSQL = class(ERemotableException)
  private
    FSQLStatememnt: string;
    FNotes: string;
    FReason: string;
  public
    constructor Create(IllegalSQLStatement, AReason: string);
  published
    property SQLStatement: string read FSQLStatememnt write FSQLStatememnt;
    property Notes: string read FNotes write FNotes;
    property Reason: string read FReason write FReason;
  end;

  TDatasetXML = class(TInvokableClass, IDatasetXML)
  private
    XMLDocument1: TXMLDocument;
    ADOConnection1: TADOConnection;
    ADODataset1: TADODataset;
    DocumentElement: IXMLNode;
    procedure CreateDataAccessObjects;
    procedure FreeDataAccessObjects;
    procedure GenerateDataSection(Dataset: TDataSet);
    procedure GenerateSchemaSection(Dataset: TDataSet);
    function GenerateDataAsXML: string;
  public
    { IDatasetXML }
    function GetDatasetAsXML(const SelectSQL: WideString): WideString; stdcall;
    function GetSchemaInfo(const TableName: WideString): TFieldInfoArray; stdcall;
  end;

implementation

uses
  TypInfo, SysUtils;

{ TDatasetXML }

procedure TDatasetXML.CreateDataAccessObjects;
begin
  ADOConnection1 := TADOConnection.Create(nil);
  ADODataset1 := TADODataset.Create(nil);
  XMLDocument1 := TXMLDocument.Create(nil);
  ADOConnection1.Provider := 'Microsoft.Jet.OLEDB.4.0';
  ADOConnection1.ConnectionString := 'Provider=Microsoft.Jet.OLEDB.4.0;'  
    'Data Source=C:\Program Files\Common Files\Borland Shared\Data\dbdemos.mdb;'  
    'Persist Security Info=False';
  ADOConnection1.LoginPrompt := False;
  ADOConnection1.Connected := True;
  ADODataset1.Connection := ADOConnection1;
end;

procedure TDatasetXML.FreeDataAccessObjects;
begin
  ADODataset1.Free;
  ADOConnection1.Free;
  XMLDocument1.Free;
end;

function TDatasetXML.GetDatasetAsXML(const SelectSQL: WideString): WideString;
begin
  if AnsiCompareText(Copy(SelectSQL, 1, 6), 'SELECT') <> 0 then
  begin
    raise ENotSelectSQL.Create(SelectSQL, 'Only SELECT SQL Statements are Allowed');
  end;
  CreateDataAccessObjects;
  try
    ADODataset1.CommandText := SelectSQL;
    ADODataset1.Open;
    Result := GenerateDataAsXML;
  finally
    FreeDataAccessObjects;
  end;
end;

function TDatasetXML.GetSchemaInfo(
  const TableName: WideString): TFieldInfoArray;
var
  i: Integer;
begin
  CreateDataAccessObjects;
  try
    with ADODataset1 do
    begin
      CommandText := 'SELECT * FROM '   TableName;
      Open;
      FieldDefs.Update;
      SetLength(Result, FieldCount);
      for i := 0 to FieldCount -1 do
      begin
        Result[i] := TFieldInfo.Create;
        Result[i].FieldName := Fields[i].FieldName;
        Result[i].FieldType := GetEnumName(TypeInfo(TFieldType),Integer(Fields[i].DataType));
        Result[i].FieldSize := Fields[i].Size;
        Result[i].IsKeyField := Fields[i].IsIndexField;
      end; { for i := 0 to Dataset.FieldCount -1 do }
    end; { with Dataset do }
  finally
    FreeDataAccessObjects;
  end;
end;

function TDatasetXML.GenerateDataAsXML: string;
begin
  XMLDocument1.Active := True;
  DocumentElement := XMLDocument1.CreateElement('dataset', '');
  { Set this element as the Document Element }
  XMLDocument1.DocumentElement := DocumentElement;
  GenerateSchemaSection(ADODataSet1);
  GenerateDataSection(ADODataSet1);
  Result := XMLDocument1.XML.Text;
end;

procedure TDatasetXML.GenerateSchemaSection(Dataset: TDataSet);
var
  i: Integer;
  SchemaElement: IXMLNode;
  AttributeElement: IXMLNode;
begin
  SchemaElement := DocumentElement.AddChild('schema');
  with Dataset do
  begin
    FieldDefs.Update;
    for i := 0 to FieldCount -1 do
    begin
      AttributeElement := SchemaElement.AddChild('attributeType');
      AttributeElement.SetAttribute('fieldName', Fields[i].FieldName);
      AttributeElement.SetAttribute('fieldType', GetEnumName(TypeInfo(TFieldType),Integer(Fields[i].DataType)));
      AttributeElement.SetAttribute('fieldSize', IntToStr(Fields[i].Size));
    end; { for i := 0 to Dataset.FieldCount -1 do }
  end; { with Dataset do }
end;

procedure TDatasetXML.GenerateDataSection(Dataset: TDataSet);
var
  i: Integer;
  DataElement: IXMLNode;
  RowElement: IXMLNode;
begin
  DataElement := DocumentElement.AddChild('data');
  with Dataset do
  begin
    First;
    while not Eof do
    begin
      RowElement := DataElement.AddChild('row');
      for i := 0 to Dataset.FieldCount -1 do
      begin
         RowElement.SetAttribute(Dataset.Fields[i].FieldName, Dataset.Fields[i].AsString);
      end; { for i := 0 to Table1.FieldCount -1 do }
      Next;
    end; { while not Eof do }
  end; { with Table do }
end;


{ ENotSelectSQL }

constructor ENotSelectSQL.Create(IllegalSQLStatement, AReason: string);
begin
  FSQLStatememnt := IllegalSQLStatement;
  FNotes := 'Due to Security Reasons. Only SQL SELECT Statments are allowed.'   #13#10 
   'Further, the WebService can Access only the DBDEMOS.mdb database';
  FReason := AReason;
  inherited Create(Reason   #13#10 Notes);
end;

initialization
  InvRegistry.RegisterInvokableClass(TDatasetXML);
  RemClassRegistry.RegisterXSClass(ENotSelectSQL, '', 'ENotSelectSQL', '');
end.

The implementation of the IDataSetXML Interface is quite straightforward. We use the TXMLDocument component (New to Delphi 6 - read about it on the on-line help) to generate the XML being returned as a result of the GetDataSetXML method. Of course we could have just as easily used ADO and it's inherent capability to produce XML documents of the recordset it holds. But I did it this way for those that may not want to use ADO and secondly, since we have this new component in D6.

ERemotableException

First, let me say, that it is not required that you define a custom exception in your WebService projects. If an Exception (EException) is raised at the server end while executing a SOAP Request, the exception will be automatically encoded as a SOAP Fault packet and returned to the client. The client, on seeing this fault packet will raise an exception. So why then would we want to create custom remotable exceptions? Either when you need to convey more information than a simple message (published properties etc. will be encoded into the SOAP Fault packet) and/or if you need different kinds of exceptions in your application for better handling (more granular) of Exceptions.

The base class for Remotable Exceptions is ERemotableException. You need to derive your custom exceptions from this class and register them using the RegisterXSClass method. If the client of this WebService is a Delphi client, then your custom exceptions need to be register at the client side as well (this is automatically handled for you when you import the WSDL File using the expert.

The HTTP Header and content shown below is a sample SOAP Fault Packet of an exception of type ENotSelectSQL raised by the server.

HTTP Header and Content showing a SOAP Fault Packet.
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Fri, 20 Jul 2001 10:48:43 GMT
Content-Type: text/xml
Content-Length: 1066
Content:

<?xml version="1.0" encoding='UTF-8'?>
<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/1999/XMLSchema"
  xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <SOAP-ENV:Fault>
      <SOAP-ENV:faultcode>SOAP-ENV:Server</SOAP-ENV:faultcode>
      <SOAP-ENV:faultstring>Only SELECT SQL Statements are Allowed Due to Security Reasons.
                            Only SQL SELECT Statments are allowed.
                            Further, the WebService can Access only the DBDEMOS.mdb database</SOAP-ENV:faultstring>
      <NS1:detail xmlns:NS1="http://www.w3.org/2001/XMLSchema" xsi:type="NS1:ENotSelectSQL">
        <NS1:SQLStatement xsi:type="xsd:string">SELEC * FROM CUSTOMER</NS1:SQLStatement>
        <NS1:Notes xsi:type="xsd:string">Due to Security Reasons. Only SQL SELECT Statments are allowed.
                                         Further, the WebService can Access only the DBDEMOS.mdb database</NS1:Notes>
        <NS1:Reason xsi:type="xsd:string">Only SELECT SQL Statements are Allowed</NS1:Reason>
      </NS1:detail>
    </SOAP-ENV:Fault>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

This finishes the WebService Server. Next we'll tackle the WebService client.

exclamation.gif The client is really very simple in this case and so I don't go into it here. But it is available for download with the rest of this project.

Comments have been Disabled for this post





Menus

Theme

Privacy Policy  |  Terms Of Service  |  Contact Us  |  Support  |  Help/FAQs  |  News