Overriding standard REST procedures

Hi Wagner, you wrote ...

"Well, since you mentioned Sparkle, I thought you were considering using XData service operations, which are more flexible and allow you to implement any server-side code, even non-database related.
If you want to intercept the standard XData rest operations, there is are undocumented (because it's subject to change) virtual methods in TXDataServerModule that you can override to add custom code to the standard request handlers. For example, there are methods OnEntityRetrieve and OnEntitySetRetrieve that you can override and throw a 403 exception if user is not authorized."

I can only find the OnEntity* methods in TXDataRequestHandler,
So I figured I need to override those.
Which leads me to the problem that I also need to inherit TXDataServerModule and override     function CreateHandler(const C: THttpServerContext): TXDataRequestHandler; because I cannot get my Handler working with the XDataServer in any other way - or am I missing something?

If I do so - I could almost achieve what I want - with two drawbacks though.

1. The entities get queried before the OnEntity* procedure gets called. Which is in my case not necessary and unefficient because they won't be deliverd to the client.
2. I cannot override the standard message processing when the root url is called - so all my entity types can be seen when the root url is requested and I have no way to prevent that.

I think I need to implement my own TXDataBaseModule descendent because I won't be able to override the CreateBaseHandler function in TXDataServerModule since it is declared as "final". Which is a pitty in my situation, because I would need to override "procedure ProcessRequest;" of TXDataBaseRequestHandler to achieve what I want without changing the code in XData.Module.Base which I prefer not to do.

If I should have not gotten something wrong it is IMO too difficult to override the standard rest response behaviour of a XDataServer - is there anything cooking to change that?

Best regards

Roland

Hi Wagner,

is there anymwhere a sample for the service operations?
I wonder what the RegisterServiceType is for? The procedure in XData.Service.Common does nothing in my version - should be 1.5 - is looks like this ...
procedure RegisterServiceType(const TypeInfo: Pointer);
begin
end;

procedure RegisterServiceType(const AClass: TClass); overload;
begin
end;

Could this be the reason why I do not get any call to a service operation working?

You mean sample code? There is a chapter in manual with some step-by-step examples: http://www.tmssoftware.biz/business/xdata/doc/web/service_operations_tutorial.htm and http://www.tmssoftware.com/site/blog.asp?post=295 . Do you have any specific issue you are experiencing that I can help with?


About the RegisterServiceType, it's just that in indeed. The point is that sometimes the class or interface is not used at all in your server code and then the linker removed it from the application. So calling RegisterServiceType, even if it's dummy, prevents the linker from removing it.

How is your service operation implemented, how are you trying call it, and what is the error message you get?

Hi Wagner,

sorry - did not see your answer earlier.
Ok - I understand so the  RegisterServiceType is empty on purpose.

My Service Interface was declared as follows ...
  [ServiceContract]
  IMemexService = interface(IInvokable)
  ['{B395D718-0A27-4DEF-BEA9-AF556E291441}']
    function InitSession(AnIP: String): String;
    function IsValidSession(const AnIP: String; ASessionID: String): Boolean;
    function Test: String;
  end;

This is the declaration of the implementation class ...
  [ServiceImplementation]
  TMemexService = class(TInterfacedObject, IMemexService)
    function InitSession(AnIP: String): String;
    function IsValidSession(const AnIP: String; ASessionID: String): Boolean;
    function Test: String;
  end;

... with ...

function TMemexService.InitSession(AnIP: String): String;
var
  s: TSession;
begin
  s := TXDataOperationContext.Current.GetManager.Find<TSession>.Where
    (TExpression.Eq('Ip', AnIP)).UniqueResult;
  if Assigned(s) then
    TXDataOperationContext.Current.GetManager.Remove(s);
  s := TSession.Create;
  s.Id := doubleguid;
  s.Ip := AnIP;
  TXDataOperationContext.Current.GetManager.Save(s);
  Result := s.Id;
end;

function TMemexService.IsValidSession(const AnIP: String;
  ASessionID: String): Boolean;
begin
  Result := true;
end;

function TMemexService.Test: String;
begin
  Result := 'Test';
end;

and 

initialization
RegisterServiceType(TypeInfo(IMemexService));

I had overridden the On* delegates ... so this might have caused the problem that my service procedures did not work?
  TctriXDataRequestHandler = class(TXDataRequestHandler)
  private
    procedure DoForbidden;
  strict protected
    procedure OnBeforeEntityMerge(Manager: TObjectManager;
      Entity: TObject); override;
    procedure OnBeforeEntityUpdate(Manager: TObjectManager; AClass: TClass;
      IdValue: Variant); override;
    procedure OnBeforeEntityUpsert(Manager: TObjectManager; AClass: TClass;
      IdValue: Variant); override;
    procedure OnCollectionRetrieve(Manager: TObjectManager; Entity: TObject;
      Collection: IObjectList); override;
    procedure OnEntityCreate(Manager: TObjectManager; Entity: TObject);
      override;
    procedure OnEntityDelete(Manager: TObjectManager; AClass: TClass;
      IdValue: Variant); override;
    procedure OnEntityMerge(Manager: TObjectManager; Entity: TObject); override;
    procedure OnEntityRetrieve(Manager: TObjectManager;
      Entity: TObject); override;
    procedure OnEntitySetRetrieve(Manager: TObjectManager;
      Criteria: TCriteria); override;
    procedure OnEntityUpdate(Manager: TObjectManager; Entity: TObject);
      override;
    procedure OnEntityUpsert(Manager: TObjectManager; Entity: TObject);
      override;
    procedure OnPrimitivePropertyRetrieve(Manager: TObjectManager;
      Entity: TObject; Prop: TXDataSimpleProperty); override;
    procedure OnStreamPropertyRetrieve(Manager: TObjectManager; Entity: TObject;
      Prop: TXDataSimpleProperty); override;
    procedure OnStreamPropertyUpdate(Manager: TObjectManager; Entity: TObject;
      Prop: TXDataSimpleProperty; var Blob: TBlob); override;
    procedure OnValueRetrieve(Manager: TObjectManager; Entity: TObject;
      Prop: TXDataSimpleProperty; var Value: TValue); override;
  end;

... with ...

procedure TctriXDataRequestHandler.OnEntityUpsert(Manager: TObjectManager;
  Entity: TObject);
begin
  DoForbidden;
end;

and so on for the other OnXY-events,

Besides of the service operations -  which I could not get to work in the example above - my problem with XData - even though I would really like to use it - is, that I do not know how to implement authorization beyond Username-Password and grant all access or deny all access by that means. So at the moment I could only see good use for it in private api settings not in public api once. Either I am missing something or the way to deal with authorizations/authentifications and the missing capabilities of customization of the exportet standard REST interface are still improvable.
But do not understand this to be a harsh criticism - overall it is an amazing product - and all I mention here is IMO just a problem if you want to share an XDATA-based API with third parties beyond your organization or if you want to leverage it in a public website without developing an intermediary service.

At the moment - until XData might support what I need in this situation - I achieve what I want using Sparkle with a Connection Pool according to this article http://edn.embarcadero.com/article/30027 .
I first tried to use the ConnectionPool of your source - but was not successful because I wanted to access a whole datamodule with firedac components from within each threaded module instance. This would not have been possible - as far as I did see it. Would it? It would be cool if 
TDBConnectionFactory could be given a datamodule type and IDBConnectionPool could give me not only a connection, but also a whole datamodule instance in that case. But I am not sure if that would be possible.
Well for me it works at the moment with the approach shown in the Jensen tutorial and I really like the minimalism and performance Sparkle offers me. IMO it is the best component set for microrservice development with Delphi. Thanks to you guys..

Hi Roland, thank you for your nice words.


About connection pool, why do you need the data module? Anyway, there is an undocumented class in Sparkle in unit Sparkle.Sys.ObjectPool which is a generic object pool you can use (for example, TObjectPool<TMyDataModule> will implement an object pool for your TMyDataModule instances).

About authentication/authorization: that's the purpose of the OnXY-events, why didn't you use them to implement those?

About your issue with service not being called: I'm still not able to guess what's wrong. Are you able to pack it into a compilable project that reproduces the problem? This way it's easier for me to debug and fix it to make it work for you. One hint I can think of: pay attention to compiler warnings, if you forget to add an "uses" clause, the [ServiceImplementation] and [ServiceContract] attributes might not be the XData ones and just dummy ones, thus it will not be able to find those.