1
votes

This is working sample code calling ResolveNames in EWS that I generated with SoapUI after retrieving the WDSL from Exchange Server:

<?xml version="1.0"?>
<soapenv:Envelope 
  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:typ="http://schemas.microsoft.com/exchange/services/2006/types" 
  xmlns:mes="http://schemas.microsoft.com/exchange/services/2006/messages">
<soapenv:Header><typ:RequestServerVersion Version="Exchange2010"/></soapenv:Header>
<soapenv:Body>
  <mes:ResolveNames ReturnFullContactData="1" SearchScope="ActiveDirectoryContacts">
    <mes:UnresolvedEntry>deve</mes:UnresolvedEntry>
  </mes:ResolveNames>
</soapenv:Body>
</soapenv:Envelope>

This is the bare Delphi XE2 code that I use:

procedure TFrmTestEWS.BtnConnectClick(Sender: TObject);
var
   lESB        : ExchangeServicePortType;
   lResNames   : ResolveNames;
   lReqVersion : RequestServerVersion;
   lResResult  : ResolveNamesResponse;
   lServerVer  : ServerVersionInfo;
   lUnresolved : String;
begin
   lServerVer  := ServerVersionInfo.Create;
   lResNames   := ResolveNames.Create;
   lReqVersion := RequestServerVersion.Create;
   lUnresolved := 'Deve';
   with lResNames do
   begin
      ReturnFullContactData := true;
      SearchScope           := ResolveNamesSearchScopeType.ActiveDirectoryContacts;  // Scoped enums is on!
      ParentFolderIds       := nil;
      UnresolvedEntry       := lUnresolved;
   end;
   lReqVersion.Version := ExchangeVersionType.Exchange2010;
   lESB := (HTTPRIO1 as ExchangeServicePortType);
   lESB.ResolveNames(lResNames,
                     nil,              // Impersonation
                     nil,              // MailboxCulture
                     lReqVersion,
                     lResResult,
                     lServerVer);

It generates:

<?xml version="1.0"?>
<SOAP-ENV:Envelope 
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SOAP-ENV:Body xmlns:NS1="http://schemas.microsoft.com/exchange/services/2006/types">
  <ResolveNames xmlns="http://schemas.microsoft.com/exchange/services/2006/messages" ReturnFullContactData="true" NS1:SearchScope="ActiveDirectoryContacts">
    <UnresolvedEntry>deve</UnresolvedEntry>
  </ResolveNames>
  <MailboxCulture xsi:nil="true"/>
  <ExchangeImpersonation xsi:nil="true"/>
  <NS1:RequestServerVersion Version="Exchange2010"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

The error I get:

The request failed schema validation: The 'http://schemas.microsoft.com/exchange/services/2006/types:SearchScope' attribute is not declared.

SearchScope is an attribute as defined in messages.xsd:

<!-- ResolveNames request -->

<xs:complexType name="ResolveNamesType">
    <xs:complexContent>
        <xs:extension base="m:BaseRequestType">
            <xs:sequence>
                <xs:element name="ParentFolderIds" type="t:NonEmptyArrayOfBaseFolderIdsType" minOccurs="0"/>
                <xs:element name="UnresolvedEntry" type="t:NonEmptyStringType"/>
            </xs:sequence>
            <xs:attribute name="ReturnFullContactData" type="xs:boolean" use="required"/>
            <xs:attribute name="SearchScope" type="t:ResolveNamesSearchScopeType" default="ActiveDirectoryContacts"/>
        </xs:extension>
    </xs:complexContent>
</xs:complexType>

<xs:element name="ResolveNames" type="m:ResolveNamesType"/>

with in types.xsd:

<!-- ResolveNames request -->

<xs:simpleType name="ResolveNamesSearchScopeType">
  <xs:restriction base="xs:string">
    <xs:enumeration value="ActiveDirectory"/>
    <xs:enumeration value="ActiveDirectoryContacts"/>
    <xs:enumeration value="Contacts"/>
    <xs:enumeration value="ContactsActiveDirectory"/>
  </xs:restriction>
</xs:simpleType>

I thought that NS1:SearchScope="ActiveDirectoryContacts" is incorrect but leaving the NS1: out gives the same error.
Maybe postponing the Exchange xmlns specs for types and messages to within the SOAP-ENV:Body is the reason for the error?
Also, the NS1:RequestServerVersion Version="Exchange2010" not being in the SOAP_ENV:Header looks suspect.

I have looked at some Google results but could not get it to work.

Basically my question is:
How can I move around the tags or xmlns attributes in the generated code until it works, without having to construct the entire SOAP myself?
And if that is not possible what approach is best so that I can still benefit from the imported type library? (Stuff like this?)

Thanks
Jan

1

1 Answers

1
votes

I have decided to not try to fix the specific XML tags but take full control over the SOAP contents going out:
I just build up the XML in a TStringStream, then in the HTTPRIO.BeforeExecute I put the TStringStream contents into the SOAPStream.
With SOAPUI installed, I can import the WDSL, then generate and test the desired SOAP calls 'by hand'. Once I have those complete, I move them into the Delphi code.
This gives me the advantage that still have access to the WDSL generated code for parsing the results (without having to dive into the returned XML).

Here's the code, it shows the old approach (BtnConnectClick) and the new one (BtnAlternateClick).

unit uTestEWS;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls, Vcl.ExtCtrls,TypInfo, WinAPI.WinInet,
  Soap.Rio, Soap.InvokeRegistry, Soap.SOAPHTTPClient, Soap.SOAPHTTPTrans,
  services { = The file generated from the WDSL };

type
  TFrmTestEWS = class(TForm)
    HTTPRIO: THTTPRIO;
    Panel1: TPanel;
    MmoLog: TMemo;
    TV: TTreeView;
    MmoResult: TMemo;
    BtnConnect: TButton;
    BtnAlternate: TButton;
    Memo1: TMemo;
    Label1: TLabel;
    procedure HTTPRIOBeforeExecute(const MethodName: string;
      SOAPRequest: TStream);
    procedure HTTPRIOAfterExecute(const MethodName: string;
      SOAPResponse: TStream);
    procedure BtnConnectClick(Sender: TObject);
    procedure HTTPRIO1HTTPWebNode1BeforePost(const HTTPReqResp: THTTPReqResp;
      Data: Pointer);
    procedure BtnAlternateClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FSoapData: TStringStream;
    procedure Res(Msg: String);
    procedure InitializeSoapData;
    procedure FinalizeSoapData;
  public
  end;

var
  FrmTestEWS: TFrmTestEWS;

implementation

{$R *.dfm}

procedure TFrmTestEWS.BtnConnectClick(Sender: TObject);
var
   lESB        : ExchangeServicePortType;
   lResNames   : ResolveNames;
   lReqVersion : RequestServerVersion;
   lResResult  : ResolveNamesResponse;
   lServerVer  : ServerVersionInfo;
begin
   lServerVer  := ServerVersionInfo.Create;
   lResNames   := ResolveNames.Create;
   lReqVersion := RequestServerVersion.Create;
   lResResult  := ResolveNamesResponse.Create;
   try
      try
         // 1. Setup
(* Replaced by alternate, see BtnAlternateClick
         lUnresolved := 'deve';
         with lResNames do
         begin
            ReturnFullContactData := true;
            SearchScope           := ResolveNamesSearchScopeType.ActiveDirectoryContacts;  // Scoped enums is on!
            ParentFolderIds       := nil;
            UnresolvedEntry       := lUnresolved;
         end;
         lReqVersion.Version := ExchangeVersionType.Exchange2010;
*)
         // 2. Execute
         lESB := (HTTPRIO as ExchangeServicePortType);
         lESB.ResolveNames(lResNames,
                           nil,              // Impersonation
                           nil,              // MailboxCulture
                           lReqVersion,
                           lResResult,
                           lServerVer);
         // 3. Report
         MmoResult.Clear;
         Res('Server version:');
         with lServerVer do
         begin
            Res('  MajorVersion: ' + IntToStr(MajorVersion));
            Res('  MinorVersion: ' + IntToStr(MinorVersion));
            Res('  MajorBuildNumber: ' + IntToStr(MajorBuildNumber));
            Res('  MinorBuildNumber: ' + IntToStr(MinorBuildNumber));
            Res('  Version: ' + Version);
         end;
         // [ snip rest of code not relevant for this example]
      except
         on E:Exception do MmoResult.Text := E.Message;
      end;
   finally
      // 4. Clean up
      lResResult.Free;
      lServerVer.Free;
      lReqVersion.Free;
      lResNames.free;
      LESB := nil;
   end;
end;

procedure TFrmTestEWS.FormCreate(Sender: TObject);
begin
   FSoapData := TStringStream.Create('',TEncoding.UTF8);
   FSoapData.Position := 0;
end;

procedure TFrmTestEWS.FormDestroy(Sender: TObject);
begin
  FSoapData.Free;
end;

procedure TFrmTestEWS.HTTPRIOAfterExecute(const MethodName: string;
  SOAPResponse: TStream);
var
   TS: TStringStream;
   S : String;
begin
   S := MmoLog.Text + #13#10#13#10 + 'Response:' + #13#10#13#10;
   TS := TStringStream.Create(S);
   TS.Position := Length(S);
   SOAPResponse.Position := 0;
   TS.CopyFrom(SOAPResponse,SOAPResponse.Size);
   TS.Position := 0;
   MmoLog.Lines.LoadFromStream(TS);
   TS.Free;
end;

procedure TFrmTestEWS.HTTPRIOBeforeExecute(const MethodName: string;
  SOAPRequest: TStream);
begin
   // 1. Alternate approach
   SOAPRequest.Position := 0;
   FSoapData.Position := 0;
   SOAPRequest.CopyFrom(FSoapData,FSoapData.Size);
   SOAPRequest.Size := FSoapData.Size;
   // 2. Logging
   MmoLog.Clear;
   MmoLog.Lines.Add('Request:' + #13#10#13#10);
   FSoapData.Position := 0;
   MmoLog.Lines.LoadFromStream(FSoapData);
end;

procedure TFrmTestEWS.InitializeSoapData;
begin
   FSoapData.Clear;
   FSoapData.WriteString('<soapenv:Envelope');
   FSoapData.WriteString('  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"');
   FSoapData.WriteString('  xmlns:typ="http://schemas.microsoft.com/exchange/services/2006/types"');
   FSoapData.WriteString('  xmlns:mes="http://schemas.microsoft.com/exchange/services/2006/messages">');
   FSoapData.WriteString('<soapenv:Header><typ:RequestServerVersion Version="Exchange2010"/></soapenv:Header>');
   FSoapData.WriteString('<soapenv:Body>');
end;

procedure TFrmTestEWS.FinalizeSoapData;
begin
   FSoapData.WriteString('</soapenv:Body>');
   FSoapData.WriteString('</soapenv:Envelope>');
end;

procedure TFrmTestEWS.BtnAlternateClick(Sender: TObject);
begin
   InitializeSoapData;
   FSoapData.WriteString('  <mes:ResolveNames ReturnFullContactData="1" SearchScope="ActiveDirectoryContacts">');
   FSoapData.WriteString('    <mes:UnresolvedEntry>deve</mes:UnresolvedEntry>');
   FSoapData.WriteString('  </mes:ResolveNames>');
   FinalizeSoapData;
   // Pick up from first attempt 'execute': 
   BtnConnectClick(Sender);
end;

procedure TFrmTestEWS.HTTPRIO1HTTPWebNode1BeforePost(
  const HTTPReqResp: THTTPReqResp; Data: Pointer);
const
   CONTENT_HEADER_EX2010 = 'Content-Type: text/xml; charset=utf-8';
begin
   // http://forum.delphi-treff.de/archive/index.php/t-31817.html
   // Need to exchange the Content-Type Header, because Exchange 2010 expects
   // 'Content-Type: text/xml; charset=utf-8' instead of
   // 'Content-Type: text/xml; charset="utf-8"' which is RFC conform and used by XE2
   HttpAddRequestHeaders(Data, PChar(CONTENT_HEADER_EX2010), Length(CONTENT_HEADER_EX2010), HTTP_ADDREQ_FLAG_REPLACE);
end;

procedure TFrmTestEWS.Res(Msg: String);
begin
   MmoResult.Lines.Add(Msg);
end;

end.