Delphi生成的EWS SOAP请求中的“未声明属性”错误

时间:2012-10-30 13:25:29

标签: delphi soap delphi-xe2 exchangewebservices

这是在EWS中调用ResolveNames的工作示例代码,该代码是我从Exchange Server检索WDSL后使用SoapUI生成的:

<?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>

这是我使用的裸Delphi XE2代码:

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);

它生成:

<?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>

我得到的错误:

请求未通过架构验证:未声明“http://schemas.microsoft.com/exchange/services/2006/types:SearchScope”属性。

SearchScope是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 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>

我认为NS1:SearchScope =“ActiveDirectoryContacts”不正确但离开NS1:out会出现同样的错误。
也许将类型和消息的Exchange xmlns规范推迟到SOAP-ENV:Body中是出错的原因?
此外,NS1:RequestServerVersion Version =“Exchange2010”不在SOAP_ENV:Header中  看起来很可疑。

我查看了some Google results,但无法让它发挥作用。

基本上我的问题是:
如何在生成的代码中移动标记或xmlns属性直到它工作,而不必自己构建整个SOAP? 如果那是不可能的,哪种方法最好,这样我仍然可以从导入的类型库中受益? (像this这样的东西?)

感谢

1 个答案:

答案 0 :(得分:1)

我决定不尝试修复特定的XML标记,而是完全控制出来的SOAP内容:
我只是在TStringStream中构建XML,然后在HTTPRIO.BeforeExecute中将TStringStream内容放入SOAPStream中。
安装SOAPUI后,我可以导入W​​DSL,然后“手动”生成并测试所需的SOAP调用。完成后,我将它们移到Delphi代码中 这给了我一个优势,即仍然可以访问WDSL生成的代码来解析结果(无需深入研究返回的XML)。

这是代码,它显示了旧方法(BtnConnectClick)和新方法(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.