Question

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:

  1. instantiate the request.
  2. 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!

Was it helpful?

Solution

As @J... suggested the problem was that (somehow) the dynamic arrays were falling out of scope when sending the request.

To solve the problem we are assigning to each dynamic array property of the request a copy of the same dynamic array after creating the request but before sending it to the service. The copying is done just before sending the request as following:

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

Another possibility would be doing as @J... also suggested, i.e., manually increment the reference count to avoid the dynamic array being freed. This is probably a more sensible and faster way to accomplish it, but for now we will stick to our solution.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top