使用RTTI创建的SOAP请求未完全解析为XML

时间:2014-05-06 14:52:15

标签: delphi soap delphi-xe5 rtti

在我们正在创建的新应用程序中,我们有很多SOAP requests(现在很容易超过50个不同的应用程序)。为了尽可能抽象地创建请求,我们添加了一个名为TRequestMessageParser的抽象类来委托构造soap请求。

此抽象类接收参数列表,并使用SetParameterValues方法通过使用新SOAP request填充相应的RTTI。它创建并填充给定请求的对象参数,数组参数和其他复杂结构。然后,我们创建与derived classes生成的特定request type相关联的WSDL importer。这些派生类只做两件事:

  1. 实例化请求。
  2. 致电SetParameterValues
  3. 现在,这很好(或似乎)。创建请求,如果您调试它,您可以看到参数中指定的所有属性都已设置,无论它们是序数类型,实例还是动态数组。

    将请求解析为XML text时会出现问题。当发生这种情况时,dynamic array properties are never set。我们可以通过使用我们分配给服务包装器的OnBeforeExecute的{​​{1}}事件处理程序来确认这一点。不会抛出任何错误或异常。简单地忽略动态数组属性。

    如果我们手动创建请求,即专门创建和设置每个对象,数组和属性,那么请求(看起来与RTTI相同)会被正确解析为XML文本。

    很明显,当我们使用RTTI创建请求时,我们必须做错事,尽管我们无法通过调试和谷歌搜索转换错误来找到它。

    您将找到THTTPRIO类的相关代码:

    TRequestMessageParser

    TRequestMessageParser<REQ: TRemotable> = class protected FRequest : REQ; <snip rest of declaration> procedure TRequestMessageParser<REQ>.SetParameterValues(Parameters: TObjectList<TRequestParameter>); begin SetParameterValues(FRequest, Parameters); end; procedure TRequestMessageParser<REQ>.SetParameterValues(parentObject: TObject; ParameterList : TObjectList<TRequestParameter>); var parameter : TRequestParameter; requestPropertyRttiType : TRttiType; requestProperty : TRttiProperty; booleanValue : boolean; begin //context is initialized in constructor for parameter in ParameterList do begin if parameter.IsComplexType then //true if it has > 1 subparameter (object or array) begin requestProperty := context.GetType(parentObject.ClassType).GetProperty(parameter.Code); requestPropertyRttiType := requestProperty.PropertyType; case requestPropertyRttiType.TypeKind of tkClass: ManageObjectProperty(parentObject, requestPropertyRttiType, parameter); tkDynArray: ManageDynamicArrayProperty(parentObject, parameter); else raise Exception.Create('Unsupported type for requests.'); end; end else //ordinal types begin requestProperty := context.GetType(parentObject.ClassType).GetProperty(parameter.Code); if requestProperty.PropertyType.TypeKind = tkEnumeration then begin if (requestProperty.PropertyType as TRttiEnumerationType).UnderlyingType.Handle = System.TypeInfo(Boolean) then begin booleanValue := parameter.Value; requestProperty.SetValue(parentObject, TValue.From(booleanValue)); end //TODO: probably not necessary as SOAP request have no enumerations so far else requestProperty.SetValue(parentObject, TValue.FromVariant(parameter.Value)); end else requestProperty.SetValue(parentObject, TValue.FromVariant(parameter.Value)); end; end; end; procedure TRequestMessageParser<REQ>.ManageObjectProperty(parentObject: TObject; requestPropertyRttiType : TRttiType; parameter : TRequestParameter); var requestPropertyInstance : TObject; requestProperty : TRttiProperty; begin requestPropertyInstance := requestPropertyRttiType.AsInstance.MetaclassType.Create; //we add the instance to the parent object requestProperty := context.GetType(parentObject.ClassType).GetProperty(parameter.Code); requestProperty.SetValue(parentObject, requestPropertyInstance); //we assign the parameters corresponding to the instance SetParameterValues(requestPropertyInstance, parameter.Subparameters); end; procedure TRequestMessageParser<REQ>.ManageDynamicArrayProperty(parentObject: TObject; parameter : TRequestParameter); var parentType : trttiType; objectProperty : TRttiProperty; DynArrayType: TRttiDynamicArrayType; DynArrElementType: TRttiType; newArrayValue : TValue; parentObjectArrayValue : TValue; ArrayLength : LongInt; i : integer; begin //we retrive rtti information for the property parentType := context.GetType(parentObject.ClassInfo); objectProperty := parentType.GetProperty(parameter.Code); DynArrayType := (objectProperty.PropertyType as TRttiDynamicArrayType); //we retrieve a reference to the property as TValue newArrayValue := objectProperty.GetValue(parentObject); //we get and set the dynamic array length arrayLength := parameter.Subparameters.Count; DynArraySetLength(PPointer(newArrayValue.GetReferenceToRawData)^, newArrayValue.TypeInfo, 1, @arrayLength); //we retrieve the array element type DynArrElementType := DynArrayType.ElementType; //if it is an object we create the corresponding instances if DynArrElementType.IsInstance then begin for i := 0 to ArrayLength - 1 do AddObjectElementToDynamicArray(newArrayValue, i, DynArrElementType, parameter.Subparameters[i]); end //if it is an ordinal element we assign the value else if DynArrElementType.IsOrdinal then begin for i := 0 to ArrayLength - 1 do newArrayValue.SetArrayElement(i, TValue.FromVariant(parameter.Subparameters[i].Value)); end else raise Exception.Create('Unsupported'); //until now we have a copy of the dynamic array so we reassign it to the property TValue.MakeWithoutCopy(newArrayValue.GetReferenceToRawData, DynArrayType.Handle, parentObjectArrayValue); objectProperty.SetValue(parentObject, parentObjectArrayValue); end; procedure TRequestMessageParser<REQ>.AddObjectElementToDynamicArray(DynamicArray : TValue; position: integer; DynamicArrayElementType: TRttiType; objectElementParameter: TRequestParameter); var ElementValue : TValue; objectSubparameter : TRequestParameter; begin ElementValue := DynamicArrayElementType.GetMethod('Create').Invoke(DynamicArrayElementType.AsInstance.MetaclassType, []); SetParameterValues(ElementValue.AsObject, objectElementParameter.Subparameters); DynamicArray.SetArrayElement(position, ElementValue); end; 是一个简单的类,其中包含TRequestParameterCodeValue(通用list of subparameters),所有这些都可通过{{ 1}}属性。我需要看到它,我也可以添加它的代码。

    我们正在使用Delphi XE5来创建应用程序。如果有人能给我们至少一个关于我们做错事的领导,那就太好了!

1 个答案:

答案 0 :(得分:1)

正如@J ...建议问题是(某种程度上)动态数组在发送请求时超出了范围。

为了解决这个问题,我们在创建请求之后但在将其发送到服务之前,为请求的每个动态数组属性分配了相同动态数组的副本。复制在发送请求之前完成,如下所示:

Foo := TFooRequestMessageParser.getRequest;
//for each dynamic array property get a copy. This applies also to subproperties
Foo.DynArrayProperty := Copy(Foo.DynArrayProperty);
fooService.SendRequest(foo);

另一种可能性是@J ...也建议,即手动递增引用计数以避免动态数组被释放。这可能是一种更明智,更快捷的方法,但现在我们将坚持我们的解决方案。