Creating the EMS Pets Resource

From Appmethod Topics
Jump to: navigation, search

Go Up to Tutorial: Using an EMS Client to Access a Custom EMS Resource

To begin this tutorial, create a new EMS Resource that exposes all the methods available (see EMS Resource Overview for more information about them) and implement them.

The EMS Pets resource of this tutorial stores information about pets (such as name and kind) in a file in the EMS Server. The EMS Pets resource implements the available endpoints and the EMS Pets client application can retrieve the list of pets or modify the stored information about them by calling these EMS Pets resource endpoints.

Creating the EMS Pets Resource

You need to create a new EMS Resource and define the EMS Resource Endpoints that are exposed. For more information, see EMS Resource Overview.

  1. Open the EMS Package Wizard in Appmethod.
    • For Object Pascal: File > New > Other > Object Pascal Projects > EMS > EMS Package.
  2. Select 'Create package with resource'.
    EMSPackageWizard1.png
  3. Add the EMS Resource name and file type:
    • Resource name: Pets
    • File Type: Unit
    EMSPackageWizard2.png
  4. Select the EMS Endpoints that the EMS Resource exposes. For this tutorial, select all endpoints.
    EMSPackageWizard3.png
  5. Click the Finish button.
  6. In the Project Manager, rename the unit to PetsResource.
  7. Save the project.

You can see the created EMS Resource in the Code tab:

  • For Object Pascal:
  [ResourceName('Pets')]
{$METHODINFO ON}

  TPetsResource = class
  private
    FPetsManager: TPetsManager;
    function GetModuleDirectory: string;
    procedure CheckManager;
    function IdFromRequest(const ARequest: TEndpointRequest): Integer;
  public
    destructor Destroy; override;
  published
    [EndpointName('GetPets')]
    procedure Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [EndpointName('GetPet')]
    [ResourceSuffix('{item}')]
    procedure GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [EndpointName('AddPet')]
    procedure Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [EndpointName('UpdatePet')]
    [ResourceSuffix('{item}')]
    procedure PutItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    [EndpointName('DeletePet')]
    [ResourceSuffix('{item}')]
    procedure DeleteItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
  end;
{$METHODINFO OFF}

procedure Register;

Creating the Pet Type

You need to create a user-defined object that will be passed between the EMS Client and EMS Server Endpoints methods using JSON objects.

Note: You need to use this data unit in the EMS Client application to serialize the data.

Create a new Pet type unit for your application, as follows:

  1. In the Project Manager | right-click in the project | Add New > Unit.
  2. Rename the unit to PetType.
  3. Create a new record TPet as follows:
    • For Object Pascal:
     TPet = record
      private
        FId: integer;
        FKind: string;
        FName: string;
      public
        constructor Create(const AId: Integer; const AName, AKind: string);
        property Name: string read FName write FName;
        property Kind: string read FKind write FKind;
        property Id: integer read FId write FId;
      end;
    
  4. Add the following code to your unit:
    • For Object Pascal:
    constructor TPet.Create(const AId: integer; const AName, AKind: string);
    begin
      FName := AName;
      FKind := AKind;
      FId := AId;
    end;
    
  5. To serialize the Pet object, you need to create the the TPetJSON class in your unit. Define it as follows:
    • For Object Pascal:
     TPetJSON = class
      public
       class function JSONToPet(const AJSON: TJSONValue): TPet; static;
       class function JSONToPets(const AJSON: TJSONArray): TArray<TPet>; static;
       class procedure PetsToJSON(const APets: TArray<TPet>; const AJSON: TJSONArray); static;
       class procedure PetToJSON(const APet: TPet; const AJSON: TJSONObject); static;
      end;
    
  6. Add the following uses clauses:
    • For Object Pascal:
    	uses System.JSON, System.Generics.Collections;
    
  7. Implement the methods of your unit.
    • For Object Pascal:
    class function TPetJSON.JSONToPet(const AJSON: TJSONValue): TPet;
    begin
      Result := TPet.Create(AJSON.GetValue<integer>('id'),
        AJSON.GetValue<string>('kind'), AJSON.GetValue<string>('name'));
    end;
    
    class procedure TPetJSON.PetToJSON(const APet: TPet; const AJSON: TJSONObject);
    begin
      AJSON.AddPair('id', TJSONNumber.Create(APet.FId));
      AJSON.AddPair('kind', TJSONString.Create(APet.FKind));
      AJSON.AddPair('name', TJSONString.Create(APet.FName));
    end;
    
    class function TPetJSON.JSONToPets(const AJSON: TJSONArray)
      : TArray<TPet>;
    var
      LValue: TJSONValue;
      LList: TList<TPet>;
    begin
      LList := TList<TPet>.Create;
      try
        for LValue in AJSON do
          LList.Add(TPetJSON.JSONToPet(LValue));
        Result := LList.ToArray;
      finally
        LList.Free;
      end;
    end;
    
    class procedure TPetJSON.PetsToJSON(const APets: TArray<TPet>;
      const AJSON: TJSONArray);
    var
      LPet: TPet;
      LJSONObject: TJSONObject;
    begin
      for LPet in APets do
      begin
        LJSONObject := TJSONObject.Create;
        PetToJSON(LPet, LJSONObject);
        AJSON.Add(LJSONObject);
      end;
    end;
    

Adding the Pets Manager

This tutorial stores the Pets data in a file in the EMS Server (pets.ini). To access and manage the data of this file, a new unit is created in the EMS Pets Resource, called PetsManager.

  1. In the Project Manager | right-click in the project | Add New > Unit
  2. Rename the unit to PetsManager.
  3. Create the new TPetsManager class as follows:
    • For Object Pascal:
    type
      TPetsManager = class
        FUserID: string;
        FIniFile: TIniFile;
      private
        function EncodePet(const AName: string; const AKind: string): string;
        function DecodePetLine(const ALine: string; const AField: integer): string;
      public
        constructor Create(const ADirectory: string);
        destructor Destroy; override;
        function GetPets: TArray<TPet>;
        function GetPet(const AId: Integer; out APet: TPet): Boolean;
        procedure UpdatePet(const AId: Integer; const APet: TPet);
        procedure AddPet(const APet: TPet);
        function DeletePet(const AId: Integer): Boolean;
        function PetExists(const AId: Integer): Boolean;
      end;
    
  4. Add the following uses clauses:
    • For Object Pascal:
    uses
      System.SysUtils, System.IniFiles, System.Generics.Collections, PetType,
      System.Classes, System.StrUtils, System.Types;
    
  5. Add the following code to your unit:
    • For Object Pascal:
    constructor TPetsManager.Create(const ADirectory: string);
    var
      LPath: string;
    begin
      LPath := IncludeTrailingPathDelimiter(ExpandFileName(ADirectory)) + 'pets.ini';
      FIniFile := TIniFile.Create(LPath);
      FUserID := '0000';
    end;
    
    // Procedure to add a pet to the pets.ini file
    procedure TPetsManager.AddPet(const APet: TPet);
    begin
      FIniFile.WriteString(FUserID, APet.Id.ToString, EncodePet(APet.Name, APet.Kind));
    end;
    
    // Function to check if a pet exists in the pets.ini file
    function TPetsManager.PetExists(const AId: Integer): Boolean;
    begin
      Result := FIniFile.ValueExists(FUserID, AId.ToString);
    end;
    
    // Function to delete a pet from the pets.ini file
    function TPetsManager.DeletePet(const AId: Integer): Boolean;
    begin
      Result := PetExists(AId);
      if Result then
        FIniFile.DeleteKey(FUserID, AId.ToString);
    end;
    
    // Destructor
    destructor TPetsManager.Destroy;
    begin
      FIniFile.Free;
      inherited;
    end;
    
    // Function to get a specific pet from the pets.ini file
    function TPetsManager.GetPet(const AId: Integer; out APet: TPet): Boolean;
    var
      LName: string;
      LKind: string;
    begin
      Result := PetExists(AId);
      if Result then
      begin
        LName := DecodePetLine(FIniFile.ReadString(FUserID, AId.ToString, ''), 1);
        LKind := DecodePetLine(FIniFile.ReadString(FUserID, AId.ToString, ''), 0);
        APet := TPet.Create(AId, LName, LKind);
      end;
    end;
    
    // Function to get all pets from the pets.ini file
    function TPetsManager.GetPets: TArray<TPet>;
    var
      LList: TList<TPet>;
      LPet: TPet;
      LSection: TStrings;
      I: integer;
    begin
      LSection := nil;
      LList := nil;
      try
        LSection := TStringList.Create;
        LList := TList<TPet>.Create;
        FIniFile.ReadSectionValues(FUserID, LSection);
        for I := 0 to LSection.Count - 1 do
        begin
          LPet := TPet.Create(LSection.Names[I].ToInteger,
            DecodePetLine(LSection.ValueFromIndex[I], 1),
            DecodePetLine(LSection.ValueFromIndex[I], 0));
          LList.Add(LPet);
        end;
        Result := LList.ToArray;
      finally
        LList.Free;
        LSection.Free;
      end;
    end;
    
    // Function to update a pet from the pets.ini file
    procedure TPetsManager.UpdatePet(const AId: Integer; const APet: TPet);
    begin
      if PetExists(AId) then
        FIniFile.DeleteKey(FUserID, AId.ToString);
      FIniFile.WriteString(FUserID, AId.ToString, EncodePet(APet.Name, APet.Kind));
    end;
    
    // Function to encode the data (to be saved in the pets.ini file)
    function TPetsManager.EncodePet(const AName: string;
      const AKind: string): string;
    var
      LPet: string;
    begin
      LPet := AName + ',' + AKind;
      Result := LPet;
    end;
    // Function to decode a line from the pets.ini file
    function TPetsManager.DecodePetLine(const ALine: string;
      const AField: integer): string;
    var
      LPetField: TStringDynArray;
    begin
      LPetField := SplitString(ALine, ',');
      Result := LPetField[AField];
    end;
    

Adding the Implementation to the EMS Resource Endpoints

You need to add the implementation of the EMS Endpoints methods.

  1. Add the following uses clauses:
    • For Object Pascal:
       uses PetType, PetsManager;
    
  2. Create the following private variable in your EMS Resource:
    • For Object Pascal:
    private
        FPetsManager: TPetsManager;
    
  3. Add the following code to your unit:
    • For Object Pascal:
    procedure TPetsResource.Get(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    var
      LPets: TArray<TPet>;
      LJSON: TJSONArray;
    begin
      LJSON := nil;
      try
        CheckManager;
        LPets := FPetsManager.GetPets;
        LJSON := TJSONArray.Create;
        TPetJSON.PetsToJSON(LPets, LJSON);
        AResponse.Body.SetValue(LJSON, True)
      except
        LJSON.Free;
        raise;
      end;
    end;
    
    
    procedure TPetsResource.GetItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    var
      LId: Integer;
      LPet: TPet;
      LJSON: TJSONObject;
    begin
      LId := IdFromRequest(ARequest);
      CheckManager;
      if FPetsManager.GetPet(LId, LPet) then
      begin
        LJSON := TJSONObject.Create;
        try
          TPetJSON.PetToJSON(LPet, LJSON);
          AResponse.Body.SetValue(LJSON, True);
        except
          LJSON.Free;
          raise;
        end;
      end
      else
        AResponse.RaiseNotFound('', Format('Id not found: %d', [LId]));
    end;
    
    procedure TPetsResource.Post(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    var
      LJSON: TJSONObject;
      LPet: TPet;
    begin
      if ARequest.Body.TryGetObject(LJSON) then
      begin
        CheckManager;
        LPet := TPetJSON.JSONToPet(LJSON);
        if FPetsManager.PetExists(LPet.Id) then
          AResponse.RaiseDuplicate('', Format('Can not add duplicate Id: %d', [LPet.Id]));
        FPetsManager.AddPet(LPet);
      end
      else
        AResponse.RaiseBadRequest('', 'JSON expected');
    end;
    
    procedure TPetsResource.PutItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    var
      LId: Integer;
      LPet: TPet;
      LJSON: TJSONObject;
    begin
      LId := IdFromRequest(ARequest);
      if ARequest.Body.TryGetObject(LJSON) then
      begin
        CheckManager;
        LPet := TPetJSON.JSONToPet(LJSON);
        if not FPetsManager.PetExists(LId) then
          AResponse.RaiseNotFound('', Format('Can not update unknown Id: %d', [LId]));
        FPetsManager.UpdatePet(LId, LPet);
      end
      else
        AResponse.RaiseBadRequest('', 'JSON expected');
    end;
    
    procedure TPetsResource.CheckManager;
    begin
      if FPetsManager = nil then
        FPetsManager := TPetsManager.Create(GetModuleDirectory);
    end;
    
    procedure TPetsResource.DeleteItem(const AContext: TEndpointContext; const ARequest: TEndpointRequest; const AResponse: TEndpointResponse);
    var
      LId: Integer;
    begin
      LId := IdFromRequest(ARequest);
      CheckManager;
      if not FPetsManager.DeletePet(LId) then
        AResponse.RaiseNotFound('', Format('Can not delete unknown Id: %d', [LId]));
    end;
    
    destructor TPetsResource.Destroy;
    begin
      FPetsManager.Free;
      inherited;
    end;
    
    function TPetsResource.GetModuleDirectory: string;
    begin
      Result := ExtractFilePath(StringReplace(GetModuleName(HInstance), '\\?\', '', [rfReplaceAll]));
    end;
    
    function TPetsResource.IdFromRequest(const ARequest: TEndpointRequest): Integer;
    var
      LItem: string;
    begin
      LItem := ARequest.Params.Values['item'];
      if not TryStrToInt(LItem, Result) then
        EEMSHTTPError.RaiseBadRequest('', Format('Id is not an integer: "%s"', [LItem]));
    end;
    
    procedure Register;
    begin
      RegisterResource(TypeInfo(TPetsResource));
    end;
    

Loading Your EMS Resource in the EMS Server

The EMS Server loads the EMS Pets Resource and exposes the EMS Pets Resource Endpoints to the EMS Client applications.

  1. Check that the EMS Server is set up in your system.
  2. To load the EMS Pets Resource, press F9 or Run > Run.

The EMS Development Server Window is now open. In the log pane you can see the EMS Pets Resource being loaded.

{"Load":{"Filename":"C:\Users\Public\Documents\Embarcadero\Studio\15.0\Bpl\PetsResourcePackage.bpl","Thread":4072}}
{"RegUnit":{"Filename":"C:\Users\Public\Documents\Embarcadero\Studio\15.0\Bpl\PetsResourcePackage.bpl","Filename":"PetsResource","Thread":4072}}
{"RegResource":{"Resource":"Pets","Endpoints":["GetPets","GetPet","AddPet","UpdatePet","DeletePet"],"Thread":4072}}
EMSServerPetsResource.png

See Also