Reputation: 9096
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
Upvotes: 1
Views: 1742
Reputation: 9096
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.
Upvotes: 1