In a new application we are creating now, we have a lot of SOAP requests
(easily over 50 different ones for now). In order to abstract the creation of the requests as much as possible, we added an abstract class named TRequestMessageParser
to delegate construction of the soap requests.
This abstract class receives a parameter list and has a SetParameterValues
method to fill the corresponding SOAP request
by using the new RTTI
. It creates and fills object parameters, array parameters and other complex structures of a given request. We then create derived classes
tied to a specific request type
generated by the WSDL importer
. These derived classes only do two things:
- instantiate the request.
- call
SetParameterValues
.
Now, this works fine (or seems to). The request is created and if you debug it you can see all properties specified in the parameters are set, regardless of if they are ordinal types, instances or dynamic arrays.
The problem comes when the request is parsed to XML text
. When that happens, the dynamic array properties are never set
. We could confirm this by using the OnBeforeExecute
event handler of the THTTPRIO
we assigned to the service wrapper. No error or exception is thrown. The dynamic array properties are simply ignored.
If we create the request manually, i.e., creating and setting specifically each object, array and property, then the request (which seems the same as the RTTI one) gets correctly parsed to XML text.
So clearly we must be doing something wrong when we create the request by using RTTI, although despite debugging and googling for conversion errors we could not find what it is.
Following you will find the relevant code for the TRequestMessageParser
class:
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;
The TRequestParameter
is a simple class that holds a Code
, a Value
and a list of subparameters
( a generic TObjectList
), all accessible through read
properties. Id you need to see it I can add its code as well.
We are using Delphi XE5 to create the app. If anyone can give us at least a lead on what we are doing wrong it would be great!
GetReferenceToRawData
's documentation warns :The obtained pointer is not always safe to use, because the reference counting may have actually disposed of the value.
It seems a likely scenario without seeing the rest of your code. docwiki.embarcadero.com/Libraries/XE2/en/… – J...service wrapper
? At that point we can see the dynamic arrays if we debug the request. Not sure what happens later when theservice wrapper
parses the object to SOAP text. Could it be that at that point the arrays are getting cleaned up? RegardingGetReferenceToRawData
, we would love to get rid of it, but we would need an alternative way to set the array's length and to assign the array copy to the property, which we haven't found so far. :-( – Guillem Vicensdynamic array property
of the request a copy of the same dynamic arrayafter creating the request but before sending it to the service
. Not the best solution but it saves us from the problem. Please put your comment as an answer so I can accept it. Thanks a lot! :-) – Guillem VicensRTTI
withdynamic arrays
. – Guillem Vicens