Jump to content

Andrea Magni

Members
  • Content Count

    146
  • Joined

  • Last visited

  • Days Won

    3

Posts posted by Andrea Magni


  1. Hi @Stuart Clennett,

    I am happy you found a way to solve by yourself while I was in vacation 🙂

     

    Your solution works but has a couple of things to consider:

    1) that BeforeHandleRequest code gets executed for each incoming request (performance)

    2) manipulating Parameters is not suggested as App.Parameters is like a global object (IIRC).

     

    You have an other opportunity through (yet another) undocumented MARS feature:

     

        [Context, Connection('Dynamic_MyConnection', True)] FD: TMARSFireDAC;

     

    The Connection attribute specifies the name of the connection definition to be used with the TMARSFireDAC instance injected.

    The name supports macros (the second argument enables macro expansion).

     

    You can rely on built-in macros (i.e. using some part of the token, params, username or similar things. A list is available here: https://github.com/andrea-magni/MARS/blob/a8d323558bd591589ef667eac326324302d167a9/Source/MARS.Data.FireDAC.pas#L528 ). Last chance of the macro-expander is looking for custom providers.

    You can register your own in the initialization of one of your units or in the Server.Ignition.pas file:

     

      TMARSFireDAC.AddContextValueProvider(
        procedure (const AActivation: IMARSActivation;
          const AName: string; const ADesiredType: TFieldType; out AValue: TValue)
        begin
          if SameText(AName, 'Dynamic_MyConnection') then
          begin
            if Odd(SecondOf(Now))then
              AValue := 'OddConnection_' + AActivation.Request.HostName
            else
              AValue := 'EvenConnection_' + AActivation.Request.HostName;
          end;
        end
      );

     

    Beware: the connection name has to be this format: [PREFIX]_[SUFFIX](_[OTHER_SUFFIX])

    Using 'MyConnection' won't work for instance...

     

    Pros of this approach:

    1) code is executed only when needed (when the TMARSFireDAC instance gets created to be injected in your resources), all others REST endpoints won't be affected.

    2) it seems more isolated to me (but this can be a personal evaluation).

     

    If you need to fix some TFDConnection instances on your datamodules, try having a TMARSFireDAC instance injected in the datamodule itself and use the FD.ConnectionDefName (already expanded once injected) to fix the ConnectionDefName of your components (OnCreate event of the datamodule should be fine, or AfterConstruction method as well).

    If you encounter troubles doing this, write here again: there's another hidden feature for you to fix this (when registering the datamodule as resource you can specify an anonymous method to initialize it or to call a custom constructor instead of TDatamodule.Create(AOwner)).

     

    Sincerely,

    Andrea

     

     

     

     

    • Thanks 2

  2. Hi @Yaron,

    you can register an InvokeError procedure to deal with non-matching URLs, there's a template in Server.Ignition file, line 107 (https://github.com/andrea-magni/MARS/blob/daef64912b04036917cb336c4eee34438fa993a8/Demos/MARSTemplate/Server.Ignition.pas#L107).

     

    If you uncomment that registration, you can implement whatever policy using the AActivation argument to access Request and Response objects (you can set the StatusCode, Content and ContentType of the Response object as you like).

     

    Sincerely,

    Andrea

    • Like 1

  3. Unfortunately not...

    The TFrameStand functionalities should be the same, there are some changes due to refactoring (i.e. if you had event handlers for TFrameStand events, some argument changed type like TFrameInfo<TFrame> now being TSubjecTInfo, so you may need to adjust them on your side).

    TFormStand is the same concept but supporting TForm instead of TFrame.

     

    If you already have projects using TFrameStand, it would be helpful if you could confirm I broke nothing.

     

    Sincerely,

    Andrea


  4. 17 hours ago, Ruslan said:

    Would like to test new features and versions.

    Do use it in one of my products.

     

    Great, please check out the new version from https://github.com/andrea-magni/TFrameStand/tree/TSubjectStand (you can use Git or download a zip file if you are not confident with Git). Just beware to uninstall any previous version of TFrameStand before proceeding.

     

    Some changes in your code may be needed especially if you used TFrameStand events. For example where you had TFrameInfo<TFrame> as event handler argument, now you'll find TSubjectInfo (that is a new superclass of TFrameInfo<T>).

     

    If you encounter issues, just drop me a line.

    Thanks.

    Sincerely,

    Andrea


  5. 19 hours ago, dummzeuch said:

    Btw: Your github page says:

     

    ##Get started

    Unfortunately that link seems to be broken.

    http://www.andreamagni.eu/wp/tag/tframestand/

     

    It should probably be

    https://blog.andreamagni.eu/tag/tframestand/

    instead.

     

    Thanks, I relocated the blog some time ago and I set up a rewrite rule that for some reason is not working well.

    I changed the links and updated also the demo list. That document will change again soon however, I'll add section for TFormStand soon.

     

    Sincerely,

    Andrea


  6. I'm about to release TFormStand as a form-oriented twin of TFrameStand.

    To add support for TForm I had to refactor quite heavily TFrameStand code (now there is a common ancestor that is TSubjectStand and two inherited components TFrameStand and TFormStand).

     

    I am looking for:

    - people willing to test TFrameStand functionalities (regression tests) are still functioning;

    - people willing to test TFormStand

     

    Please reply to this message or contact me directly if you like to be involved.

     

    The new version is available in the TSubjectStand branch of TFrameStand GitHub repository: https://github.com/andrea-magni/TFrameStand/tree/TSubjectStand

    (Beware: uninstall any previous version of TFrameStand before installing the new one)

     

    Thanks

     

    Sincerely,

    Andrea

    • Like 1

  7. 12 hours ago, Yaron said:

    Is there a way to shorten the URL required to access the MARS server?

     

    What I did so far was to modify FEngine.AddApplication('DefaultApp', '/v', [ 'Server.Resources.*']); in "Server.Ignition".

     

    Ideally, I would like to eliminate the "/rest" and application name entirely, so the entry point would be whatever I define in [Path('')].

     

     

    Hi @Yaron,

    you can definitely get rid of the "/rest" prefix by setting the Engine BasePath property to empty string (by default the engine has 'DefaultEngine' as Name and '/rest/ as BasePath):

     

    class constructor TServerEngine.CreateEngine;
    begin
      FEngine := TMARSEngine.Create;
      try
        // Engine configuration
        FEngine.BasePath := ''; // <==============
        FEngine.Parameters.LoadFromIniFile;
    
        // Application configuration
        FEngine.AddApplication('DefaultApp', '/default', [ 'Server.Resources.*']);

    If you want to push this thing futher, you can even think about inheriting your own TMARSActivation class and shortcut request matching by overriding FindMethodToInvoke method.

     

    HTH

     

    Sincerely,

    Andrea

    • Like 1

  8. Sorry, it's my fault: MARS supports two http layer implementor on the client side: Indy and TNetHttpClient.

    Handling errors has some differences across the two, so it depends if you are using a TMARSClient (Indy) or TMARSNetClient (TNetHttpClient).

     

    Either ways, you can define an OnError handler for your TMARSApplication:

     

    uses FMX.Dialogs
    , MARS.Core.Utils, MARS.Client.Utils
    , IdHttp;
    
    var
      LException: EMARSClientHttpException;
      LIdException: EIdHTTPProtocolException;
    begin
      if AException is EMARSClientHttpException then
      begin
        LException := EMARSClientHttpException(AException);
        ShowMEssage('Error' + LException.Message + ' Details: ' + StreamToString(LException.Content));
        AHandled := True;
      end;
      if AException is EIdHttpProtocolException then
      begin
        LIdException := EIdHttpProtocolException(AException);
        ShowMEssage('Error' + LIdException.Message);
        AHandled := True;
      end;
    
    end;

     

    IIRC I was not able to find a way to collect error details (that are sent back from the server to the client using the body of the response) when using Indy.

    I am open to discuss how to improve the overall mechanism, please express your thoughts (everybody!).

    I'll try to update the old demos (such as HelloWorld) to include error handling at some point.

     

    Sincerely,

    Andrea

     


  9. Change TDataSet to TFDDataSet and you'll be able to produce both TMediaType.APPLICATION_JSON and TMediaType.APPLICATION_JSON_FireDAC (MARS will match what client prefer, looking for the Accept header's value):

      [POST]
      function GetData([BodyParam] AStatement: TStatement): TFDDataSet;
    function THelloWorldResource.GetData(AStatement: TStatement): TFDDataSet;
    begin
      Result := FD.SetName<TFDDataSet>(FD.Query(AStatement.SQL), AStatement.Name);
    end;

     

    Beware to remove the explicit Produces attribute you had.

    You may want to use a REST debugger (I like Postman) to make a call using Accept: application/json-firedac or Accept: application/json to see the two serializations.

     

    Now you can add a TMARSClientResourceJSON to your client application, note the SpecificAccept header where I am instructing to set Accept header to application/json-firedac media type:

    object MARSClientResourceJSON1: TMARSClientResourceJSON
      Application = MARSApplication
      SpecificAccept = 'application/json-firedac'
      SpecificContentType = 'application/json'
      Resource = 'helloworld'
    end

    Then use the following code (TMainDataModule is a datamodule containing the REST components, TMARSClient, TMARSApplication, TMARSClientResourceJSON):

     

    uses MARS.Core.JSON, MARS.Core.Utils, MARS.Data.FireDAC.Utils;
    
    procedure TMainDataModule.RetrieveData(const ASQL, AName: string;
      const AOnCompleted: TProc<TFDMemTable>; const AOnError: TProc<Exception> = nil);
    begin
      MARSClientResourceJSON1.POSTAsync(
        procedure (ABody: TMemoryStream)
        var
          LJSON: TJSONObject;
        begin
          LJSON := TJSONObject.Create;
          try
            LJSON.WriteStringValue('SQL', ASQL);
            LJSON.WriteStringValue('name', AName);
    
            JSONValueToStream(LJSON, ABody);
          finally
            LJSON.Free;
          end;
        end
      , procedure (ARes: TMARSClientCustomResource)
        var
          LData: TArray<TFDMemTable>;
        begin
          LData := TFDDataSets.FromJSON((ARes as TMARSClientResourceJSON).Response as TJSONObject);
          try
            if (Length(LData)>0) and Assigned(AOnCompleted) then
              AOnCompleted(LData[0]);
          finally
            TFDDataSets.FreeAll(LData);
          end;
        end
      , AOnError
      );
    end;

     

    Then you can have a call like:

     

      MainDataModule.RetrieveData('select * from EMPLOYEE', 'Employee'
      , procedure (ADataSet: TFDMemTable)
        begin
          FDMemTable1.Data := ADataSet.Data; // cloning dataset
        end
      , procedure (AError: Exception)
        begin
          ShowMessage(AError.ToString);
        end
      );

     

    FDMemTable1 is a TFDMemTable to store data on the client side.

     

    Please consider:

    1) code could have been more simple than this but I wanted to include the fact this call is actually made asynchronously (so if you need to transfer a large dataset or connection is not very fast, you are not going to hang your client application);

    2) this is a good example how to retrieve a dataset and then have a single (synchronized) point where the resulting dataset is then cloned to a local TFDMemTable that can be easily involved in databinding (LiveBindings) without being affected by the async call.

     

    Please let me know if this helps.

    Sincerely,

    Andrea

     

     


  10. TMARSFDResource is tightly coupled with a server side resource that has this interface:

     

        [GET, IsReference]
        function Retrieve: TArray<TFDDataSet>;
        [POST]
        function Update([BodyParam] const ADeltas: TArray<TFDMemTable>): TArray<TMARSFDApplyUpdatesRes>;

     

    One example of such resource is TMARSFDDataModuleResource (MARS.Data.FireDAC.DataModule.pas) that is the base class of the datamodule in FireDAC Basic demo.

    The logic of this resource does not allow the client to push statements to server (the Retrieve method is marked with GET verb so you don't have body available to send statements to be executed to sever side). Also, the Update method needs to know the structure (statements) of dataset in order to proprerly apply deltas to them.

     

    You can build a different resource on the same idea but adding statements as input to both methods. The Retrieve part we already covered in the previous message (using POST verb). You may want to have a PUT verb to apply updates.

    On the client side, you can use a TMARSClientResourceJSON and the TFDDataSets utility class (MARS.Data.FireDAC.Utils unit) to manage persistence of an array of datasets and/or an array of deltas (have a look at MARS.Client.FireDAC unit for some examples).

     

    As said, it is a bit unusual to send statements to be executed from the client to the server so there's no specific integration in MARS for this.

    It should not be hard to implement and all building blocks are already there. However if you find difficulties just write on this forum and I'll be happy to help.

     

    Sincerely,

    Andrea

     


  11. Hi @Michal S.,

    for security reasons, I decided to avoid error messaging to automatically flow to the client. Error messages may contain sensitive data and by default MARS reply with an 'Internal Server Error' message if an Exception is raised in the server code.

     

    However:

    1) for debug purposes, you can switch the Build Configuration from Release to Debug and have the error message appended after the Internal Server Error string.

    2) in real code, if you want the message to flow to the client use a EMARSHttpException object:

     

    function THelloWorldResource.MyMethodWithException: TJSONObject;
    begin
      Result := TJSONObject.Create;
      try
        Result.WriteStringValue('msg', 'This method will raise an exception');
      //  raise Exception.Create('This is my custom error message'); // this will be seen as 'Internal server error'
        raise EMARSHttpException.Create('This is my custom error message'); // this message will flow to the client
      except
        Result.Free;
        raise;
      end;
    end;

     

    HTH.

    Sincerely,

    Andrea

     


  12. Hi @Yaron,

    if you are using MARS develop branch, you can do as the following example:


     

    uses MARS.Core.RequestAndResponse.Interfaces
    
    THelloWorldResource = class
      //...
    protected
        [Context] Response: IMARSResponse;
    public
        [GET, Path('cookietest')]
        function MyMethod(): TJSONObject;
    end;
    
    function THelloWorldResource.MyMethod: TJSONObject;
    begin
      Result := TJSONObject.Create;
      Result.WriteStringValue('msg', 'This method will also set MyCookie value to current time');
      Response.SetCookie('MyCookie', TimeToStr(Now), 'localhost', '/', Now+1, True);
    end;

     

    If you are using MARS master branch, it should be very similar but you need to use TMARSResponse instead of IMARSResponse (and the MARS.Core.RequestAndResponse.Interfaces unit is not available).

    But the SetCookie method call should be the same.

    This example sets the MyCookie value to the current time (string) for 'localhost' domain (you may want to read the actual host from the incoming request) and for every path matching '/'. Expiration is set 1 day after the request and the Secure flag is set to True.

     

    Sincerely,

    Andrea

     

     


  13. @Arnaud Bouchez great to see you chiming in here :-)

     

    @ertank I agree with Arnaud that having the client sending the SQL statements to be executed on the server is basically breaking the separation between layers in the multi-tier system and this has consequences (changes in client and/or db structure are probably deeply tied).

    It is easy though to implement what you are asking for:

    1) find a way to send SQL statements to the server (I would probably use a JSON structure in the body of a POST request)

    2) grab the list (or the single) of SQL statements from the body

    3) build an array of corresponding datasets

    4) return that array to the client

    Extra: if you need to modify data and you are building a Delphi-To-Delphi system (Delphi client, Delphi server), you can even consider then posting back delta changes to the server and have these applied as in a C/S scenario (MARS has FireDAC integration for this task, just ask if it's your case).

     

    Simple example:

    0) bootstrap a new MARS server application (using MARSCmd) or use an existing one

    1) include these units:   Data.DB, MARS.Data.FireDAC, FireDAC.Phys.FB (or whatever physical driver is needed for your DB)

    2) define data structures:

      TStatement = record
        SQL: string;
        name: string;
      end;
      TStatements = TArray<TStatement>;


     

    3) define a POST method having TStatements as input and an array of datasets as output:

     

      [Path('helloworld')]
      THelloWorldResource = class
      protected
        [Context] FD: TMARSFireDAC;
      public
        [POST, Path('data'), Produces(TMediaType.APPLICATION_JSON)]
        function GetData([BodyParam] AStatements: TStatements): TArray<TDataSet>;
      end;

    implement it:

     

    function THelloWorldResource.GetData(AStatements: TStatements): TArray<TDataSet>;
    var
      LStatement: TStatement;
    begin
      Result := [];
    
      for LStatement in AStatements do
        Result := Result + [
          FD.SetName<TDataSet>(FD.Query(LStatement.SQL), LStatement.name)
        ];
    end;

    4) make the POST call from the client side (using MARS Client or whatever other http library available):

     

    POST /rest/default/helloworld/data HTTP/1.1
    Host: localhost:8080
    Accept: application/json
    Content-Type: application/json
    Host: localhost:8080
    
    [
      {
     	"SQL": "select * from EMPLOYEE order by EMP_NO",
     	"name": "Employees"
      }
    , {
     	"SQL": "select * from COUNTRY where COUNTRY like 'I%'",
     	"name": "Country"
      } 
    ]

     

    5) this is what the server response will look like:

    {
        "Employees": [
            {
                "EMP_NO": 2,
                "FIRST_NAME": "Robert",
                "LAST_NAME": "Nelson",
                "PHONE_EXT": "250",
                "HIRE_DATE": "1988-12-28T00:00:00.000+01:00",
                "DEPT_NO": "600",
                "JOB_CODE": "VP",
                "JOB_GRADE": 2,
                "JOB_COUNTRY": "USA",
                "SALARY": 105900,
                "FULL_NAME": "Nelson, Robert"
            },
            {
                "EMP_NO": 4,
                "FIRST_NAME": "Robert",
                "LAST_NAME": "Vecchio",
                "PHONE_EXT": "233",
                "HIRE_DATE": "1988-12-28T00:00:00.000+01:00",
                "DEPT_NO": "621",
                "JOB_CODE": "Eng",
                "JOB_GRADE": 2,
                "JOB_COUNTRY": "USA",
                "SALARY": 97500,
                "FULL_NAME": "Vecchio, Robert"
            }
        ],
        "Country": [
            {
                "COUNTRY": "Italy",
                "CURRENCY": "Lira"
            }
        ]
    }

     

    To materialize back data into datasets on the client side you can use several strategies (including the Delphi REST Client library TRESTResponseDataSetAdapter, either using REST Client library components or MARS Client library components to perform the request). If you are using FireDAC on the client side and you can afford relying on having the same FireDAC version on client and server, consider also using the TMARSFDResource component (see MARS' FireDAC Basic demo).

     

    Sincerely,

    Andrea

     


  14. Hi @ertank,

    latest stable version is 1.3.2 (that currently is even with master branch).

    The develop branch is where development occurs and is a hundred commits ahead of the master branch currently.

     

    I would suggest you to use 1.3.2 in production and eventually check this page: https://github.com/andrea-magni/MARS/releases to see if it is still the latest from time to time. You also have there a kind-of what's changed information.

    If you are looking for latest features, then develop branch is there for you but beware things may change unexpectedly and bugs may be there.

     

    Sincerely,

    Andrea


  15. Hi @Yaron,

    web applications may authenticate following several different schemas.

     

    One of them is handling authentication and authorization through a token (Authentication Bearer schema) that for convenience may also be embedded into requests through the use of a cookie.

    This kind of authorization/authentication schema is built-in with MARS and relies on JWT (https://jwt.io/) technology.

    This means I've made some work to integrate JWT in MARS and it is provided as an example of authentication/authorization mechanism but it does not mean it is mandatory to use it, you can consider it as a working example. It is also production-ready BTW.

     

    The typical use case:

    1) the client (JS or whatever) should get a token from the server (make a POST request to the included TTokenResource class)

    2) store the token somewhere on the client-side (or rely on the cookie that the server instructs to keep when successful authentication is done)

    3) endorse the token within each client request through the Authentication header ("Bearer <TOKEN>") or rely on browser sending the cookie back to the server

     

    On the server side, you may ask MARS to provide you an instance of TMARSToken through the Dependency Injection mechanism built-in with MARS. Just add a "[Context] TheToken: TMARSToken;"
    field to your resource class and you'll get a valid instance (check TheToken.Verified, TheToken.UserName and other properties).

     

    Also check the Authentication demo and try to open a browser at the following URL: http://localhost:8080/rest/default/token/html

    You can use Chrome Developer Tools to see requests and cookies.

     

    Let me know if this helps, otherwise I may add a demo of a simple webapp using Bootstrap and performing authentication.

     

    Sincerely,

    Andrea


  16. Hi @Rafael Mattos,

    MARS server applications can be deployed in several ways. 

    You can go for the ones relying on Webbroker (standalone indy server) and configure SSL there. Otherwise you can go for apache module/iis ISAPI and let the web server handle encryption. 

    Last, if you are brave enough, you may try new Delphi Cross Socket integration (develop branch) and have SSL implemented that way. 

     

    I tend to leave the REST server http only and ads a reverse-proxy apache instance in front of it, implementing https there (easy, well documented, reliable). 

     

    Sincerely, 

    Andrea


  17. Do you have a chance to debug the server (run the server application with the debugger)?

    Or at least recompile the server in Debug configuration (that should give us some more error details, more than "Internal server error").

     

    I think the problem may be that Accept value...

     

    Sincerely,

    Andrea 


  18. The MARS-FireDAC integration is nice to have but not strictly necessary.

    I would like to add other integrations to MARS with respect to other DAC libraries (Devart on top of course).

    Basically you can see these files:

    These is more or less all the implementation of MARS-FireDAC integration and it should be easy enough to replicate for UniDAC (I am not very experienced with UniDAC but I used it some time ago in a customer's project).

     

    I would surely help if you decide to add UniDAC support to MARS.

    Keep in mind that MARS has a basic TDataSet support, so even if you do nothing you'll always be able to use UniDAC in the server and expose datasets as JSON through the standard (basic) default serialization as a JSON array.

     

    Sincerely,

    Andrea


  19. Hi @mikak,

    if you are looking to actually differentiate applications (i.e. different JWT secrets, different resources [apart from some shared ones], ...) this is the right approach.

     

    If you are looking to expose the same API but target different databases according to the connected user, there may be easier approaches:

    1) detect the user (enforce authentication and find a way to associate user X with database Y, store the association in the authentication token)

    2) have a parameter (PathParam) in your resources (you can implement this in a base class and inherit your resources from that base class)

      [Path('data/{name}')]

      TDataResource = class

        protected

          [PathParam('name)'] FName: string;

          property Name: string read FName;

      end;

     

    Then you can have your TMARSFireDAC for the proper connection name (you will define a connection each database in your configuration file).

     

    I can be more specific but I am a bit in a hurry these days (more time next week I hope).

     

    Sincerely,

    Andrea

     


  20. Hi @Jean Vandromme,

    I am sorry to have so little time to help you these days. Hope will get some more next week.

    In the meantime, please try to examine the XHR request made by Angular in the Chrome development tools.

    In the "Network" pane of Chrome's Development tools you should see the http call that fails.

    Right click on the request (in the left panel) and select "Copy -> Copy as cURL") then paste it here. It should look like:

    curl 'https://en.delphipraxis.net/topic/1091-mars-angular-firedac-json/' ' -H 'Origin: https://en.delphipraxis.net' -H 'Accept-Encoding: gzip, deflate, br' -H 'Accept-Language: it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36' -H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' -H 'Accept: application/json, text/javascript, */*; q=0.01' -H 'Referer: https://en.delphipraxis.net/topic/1091-mars-angular-firedac-json/' -H 'X-Requested-With: XMLHttpRequest' -H 'Connection: keep-alive' --compressed

     

     

    This may help as we can check together if there's some particularities in the actual request.

    Another thing you should try is to debug the REST server in Delphi. Remember to select the "Debug" configuration for your Delphi project (this should also add some more specific informations to the error message you are getting).

     

    Hope to hear from you soon.

     

    Sincerely,

    Andrea


  21. Hi @Jean Vandromme,

    it is a regression as I changed the argument types of that anonymous method.

    I fixed the same issue on other demos but I probably missed that one.

     

    Please check https://github.com/andrea-magni/MARS/commit/91a3a286d0e789375ec7fa0c8e98b4c7b59b5586

    The changes for Server.Ignition.pas are the same you are looking for.

     

    Let me know if you can solve this (and if you like, please open a pull request with the fixes!)

     

    Thanks


  22. TRecord<TItem>.ToDataset (and FromDataset as well) is a MARS feature.

    The TRecord<T> class (defined in MARS.Rtti.Utils unit, https://github.com/andrea-magni/MARS/blob/master/Source/MARS.Rtti.Utils.pas) is there to provide some utility functions around records.

     

    If you don't want to declare record types for large tables, you can skip (Delphi) records and go for a more raw level approach using JSON objects (and then some how map each JSON object to the corresponding DB record). Your declaration would become something like:

     

        [POST, Path('/allItems'), Consumes(TMediaType.APPLICATION_JSON)]
        function postAllItems([BodyParam] const AChanges: TJSONArray): TJSONObject;

     

    This way, if the client sends changes like the following JSON array:

    [
    
      { "id": 123,
        "name": "Andrea",
        ... 
      },
    
      { "id": 456,
        "name": "Jean",
        ...
      }, 
      
      ... 
    ]

     

    You have a chance to iterate over array's elements and then find a decent strategy to update data in the DB (consider client may or not send all fields back, depending on the client-side technology/strategy used).

     

    If you find yourself in the need of some specific MARS utility/feature, just let me know (here or on GitHub).

     

    Sincerely,

    Andrea

×