Skip to content

Tutorial 02 16 Adding Upsert Endpoints

mattl91 edited this page Mar 3, 2023 · 13 revisions

Harmony Core Logo

Adding Upsert Endpoints

The term "upsert" is used to describe an operation where an entity is "UPdated" if it already exists but is "inSERTed" if it does not already exist.

VIDEO: Creating a Basic Solution

In Harmony Core, as in many other RESTful web service implementations, an HTTP PUT operation is used to perform an upsert operation for an entity.

When performing an HTTP PUT, there are several requirements that the client must comply with in order for an entity to be upserted:

  • The body of the HTTP request must include JSON data for the entity. This must include valid data for primary properties and any required fields.

  • The HTTP request must have a header named Content-Type that identifies the type of data being sent in the request body as application/json.

  • The HTTP request must have a header named Content-Length that specifies the length of the data (in bytes) being sent in the request body. Note that some client tools, including Postman, will automatically add this header based on the data you pass.

If you need to create a new entity but have the primary key value for the new entity generated on the server, use a create operation instead.

Enabling Upsert Endpoints

To generate endpoints that allow clients to upsert entities via HTTP PUT operations, you must set the Enable PUT endpoints option in the Harmony Core CLI tool:

  1. At a Windows prompt, move to the directory with your Harmony Core solution, and type harmonycore gui. Then, when the GUI for the Harmony Core CLI tool opens, go to the OData screen, scroll down to the Enable PUT endpoints option, and double-click it.

  2. In the "Enter new value" screen, click the diamond icon to the right of Enable PUT endpoints. (The diamond will change to a checkmark.) Then click OK.

Generating the Code

  1. Save the settings you made by selecting File > Save from the menu.

  2. Generate code for your solution by selecting Codegen > Regen from the menu. When code generation is finished, a message lists the files that were generated. Click OK to close the message.

What Changed

Setting the Enable PUT endpoints option causes an additional endpoint method to be added to each of the generated OData controller classes. The new method (with most of the code removed from the procedure division) looks something like this:

{HttpPut("Customers(CustomerNumber={aCustomerNumber})")}
{Produces("application/json")}
{ProducesResponseType(StatusCodes.Status201Created)}
{ProducesResponseType(StatusCodes.Status400BadRequest)}
{ProducesResponseType(StatusCodes.Status404NotFound)}
;;; <summary>
;;; Create (with a client-supplied primary key) or replace a customer.
;;; </summary>
;;; <param name="aCustomerNumber">Customer number</param>
;;; <returns>Returns an IActionResult indicating the status of the operation and containing any data that was returned.</returns>
public method PutCustomer, @IActionResult
    {FromODataUri}
    required in aCustomerNumber, int
    {FromBody}
    required in aCustomer, @Customer
 proc

     ;; Validate inbound data
     if (!ModelState.IsValid)
        mreturn ValidationHelper.ReturnValidationError(ModelState)

     ;;Ensure that the key values in the URI win over any data that may be in the model object
     aCustomer.CustomerNumber = aCustomerNumber


endmethod

The sample code above was taken from CustomersController.dbl, and as you can see, the code accepts two parameters:

  • aCustomerNumber, which is the primary key value for the entity to be upserted. Notice that the parameter is decorated with an attribute {FromODataUri}, indicating that the data must be provided via a URL parameter of the HTTP request.

  • aCustomer, which is a Customer object containing the data for the customer to be created. Notice that this parameter is decorated with an attribute {FromBody}, indicating that the data must be provided via the body of the HTTP request.

The little bit of code at the top of the procedure division shown above does two things:

  • It checks the value of ModelState.IsValid, and if it's false, it returns an error.

    • Code that has already been executed in the ASP.NET Core pipeline has already inspected the inbound data from the client, and if that data is determined to be invalid for any reason, then ModelState.IsValid is set to false. By returning ValidationHelper.ReturnValidationError(ModelState), we ensure that the client will receive an HTTP 400 (bad request) response, and that information about the invalid data will be included in the body of the response to the client.
  • It assigns the value of aCustomerNumber from the URL to the equivalent property in the aCustomer data object.

    • This ensures that if the client passes different customer numbers in the URL and request body, the value in the URL is used.

You will find similar new code in the other controllers.

If you are generating Postman tests, a new PUT request is added to the folder for each entity type, but you will need to import the newly generated tests into Postman. You'll do this in a subsequent step.

Building the Code

  1. In Visual Studio, select Build > Rebuild Solution from the menu.

  2. Check the Output window. You should see something like this:

    1>------ Rebuild All started: Project: Repository, Configuration: Debug Any CPU ------
    2>------ Rebuild All started: Project: Services.Models, Configuration: Debug Any CPU ------
    3>------ Rebuild All started: Project: Services.Controllers, Configuration: Debug Any CPU ------
    4>------ Rebuild All started: Project: Services.Isolated, Configuration: Debug Any CPU ------
    5>------ Rebuild All started: Project: Services, Configuration: Debug Any CPU ------
    6>------ Rebuild All started: Project: Services.Host, Configuration: Debug Any CPU ------
    ========== Rebuild All: 6 succeeded, 0 failed, 0 skipped ==========
    

Testing the New Functionality

  1. In Visual Studio, press F5 (Start Debugging) to start the self-hosting application. Once again, you should see the console window appear with the messages confirming that your service is running.

It is not possible to test the functionality of the new endpoints using a web browser because the functionality to be tested involves issuing an HTTP PUT request. Browsers can issue PUT requests, but only via the use of an HTML form or some client-side JavaScript code. So you will need to use some other tool, and our tool of choice is Postman.

  1. Start Postman and close any request tabs that may be open.

  2. Select File > Import from the Postman menu, or click the Import button.

  3. In the Import dialog, click the Choose Files button, which opens the Open dialog.

  4. Browse to your main solution folder, select the PostMan_ODataTests.postman_collection.json file, and then click Open.

  5. On the Import Elements tab, click the Import button.

  6. In the COLLECTION EXISTS dialog, click the Replace button.

Postman will now re-load the tests in the "Harmony Core Sample API" collection. Notice that the total number of tests increases.

  1. Open the Customer Tests folder and select the PUT Create or update customer request.

Note the following:

  • The HTTP method is set to PUT.
  • The URL is set to {{ServerBaseUri}}/{{ODataPath}}/v{{ApiVersion}}/Customers(CustomerNumber=123).
  • If you click on the Headers tab, you will see that the Content-Type header is set to application/json.
  • If you click on the Body tab, you will see that the request contains JSON data for a sample customer.

The JSON data does include a CustomerNumber property, and the value disagrees with the value in the URL. But as mentioned earlier, the value in the URL will be used. If you wish, you can correct the value of the CustomerNumber property in the request body.

  1. Click the blue Send button.

Postman will again execute the request, wait for a response, parse the response, and display the details of the response in the UI. The response body should look something like this:

Post Response Body

Notice that the primary key in the entity's body was set to the value passed in the request URL. Also notice that the HTTP response was 201 (Created), indicating that on this occasion the PUT operation resulted in a new entity being created.

  1. In the response tab set, click on the Headers tab. You should see something like this:

Post Response Headers

As with a POST operation, you will notice that the service returned a header named Location, the value of which is the URL that can be used to retrieve the newly created entity. This behavior is defined as part of the REST pattern, but only when new entities are created. If the PUT operation had resulted in an entity update, the Location header would not have been included in the response.

  1. Click the blue Send button again. This time the response should look a little different:

Post Response Headers

You just re-executed the PUT operation for the same customer, so this time the operation resulted in an existing entity (the one you just created) being updated. Notice that the HTTP response this time is a 204 (No content). This response indicates a successful update to an existing entity. It is not necessary to return the data for the updated entity to the client, because the client just provided it to the server in the request!

  1. Click the GET Read Customer request and make sure the parameter value in the URL is set to 123 before clicking the blue Send button.

You should see that the GET endpoint was able to retrieve the entity that you just created and updated.

Stop the Service

  1. When you are done testing, stop the self-hosting application.

Suppressing Upsert Endpoints

Enabling upsert endpoints adds endpoints to all your code-generated OData Controllers, but it is possible to prevent the generation of these endpoints for certain structures. This capability is documented in Structure-Specific Endpoint Control.


Next topic: Adding Patch Endpoints


Clone this wiki locally