SOAP request created with RTTI not fully parsed in XML

In the new application that we are creating now, we have a lot SOAP requests(now more than 50 different). To abstract away the creation of queries as much as possible, we have added an abstract class named TRequestMessageParserto delegate the construction of soap queries.

This abstract class receives a list of parameters and has a method SetParameterValuesto populate the corresponding one SOAP requestwith a new one RTTI. It creates and populates object parameters, array parameters and other complex structures of a given request. Then we create derived classes, attached to the specific request type, generated WSDL importer. These derived classes do only two things:

  • fulfill the request.
  • a challenge SetParameterValues.

Now it works great (or seems). The request is created, and if you debug it, you can see that all the properties specified in the parameters are set regardless of whether they are ordinal types, instances or dynamic arrays.

The problem occurs when the request is parsed on XML text. When will this happen dynamic array properties are never set. We could confirm this using the event handler OnBeforeExecute THTTPRIOthat we assigned to the service shell. An error or exception is not thrown. The properties of a dynamic array are simply ignored.

If we create the request manually, i.e. we create and install each object, array and property specifically, then the request (which seems to be the same as RTTI) is correctly processed by XML-text.

, - , RTTI, , Google , , .

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;

TRequestParameter - , Code, a Value a list of subparameters ( TObjectList), read. Id, , .

Delphi XE5 . - , , !

+4
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... , , . , , , .

+1

Source: https://habr.com/ru/post/1539457/


All Articles