-
Notifications
You must be signed in to change notification settings - Fork 14
Tutorial 02 16 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.
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 client must
-
include in the body of the HTTP request, the data representing the entity, in JSON format, including valid data for primary properties, and any required fields.
-
include an HTTP request header named
Content-Type
that identifies the type of data being sent in the request body asapplication/json
. -
include an HTTP request 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 add this header automatically 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.
To generate endpoints that allow clients to upsert entities via HTTP PUT
operations, you must enable the ENABLE_PUT
option:
-
Edit
regen.bat
and remove the rem comment from the beginning of the line, like this:set ENABLE_PUT=-define ENABLE_PUT
-
Save your changes to
regen.bat
. -
If you don't already have a command prompt open in the solution folder, use the
Tools > Command Prompt (x64)
menu option to open a Windows command prompt, and type the following command:cd ..
-
Type the following command to regenerate your code:
regen
As the batch file executes, you will see various messages confirming which source files are being generated.
- Look for the word
DONE
to indicate that all code generation tasks completed successfully.
Enabling the ENABLE_PUT
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:
-
A parameter named 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.
- Notice that the parameter is decorated with an attribute
-
A parameter named
aCustomer
which is a customer object containing the data for the customer to be created.- Notice that the parameter is decorated with an attribute
{FromBody}
, indicating that the data must be provided via the body of the HTTP request.
- Notice that the parameter is decorated with an attribute
The little bit of code left at the top of the procedure division does two things:
-
It checks the value of
ModelState.IsValid
, and if false returns an error.- Code that has already 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 anHTTP 400 (bad request)
response, and also that information about the invalid data will be included in the body of the response to the client.
- Code that has already 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
-
It assigns the value of
aCustomerNumber
from the URL to the equivalent property in theaCustomer
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 all your other controllers.
If you are generating Postman Tests then a new PUT request is added to the folder for each entity type, but you will need to re-import the newly generated tests into Postman. The instructions will walk you through doing this later.
-
Select
Build > Rebuild Solution
from the Visual Studio menu. -
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 ==========
- 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.
-
Start
Postman
and close any request tabs that may be open. -
Select
File > Import
from the Postman menu. -
In the
IMPORT
dialog, click theChoose Files
button. -
Browse to your main solution folder, select the
PostMan_ODataTests.postman_collection.json
file, and then click theOpen
button. -
Back in the
IMPORT
dialog, click theImport
button. -
In the
COLLECTION EXISTS
dialog, click theReplace
button.
Postman will now re-load the tests in the Harmony Core Sample API
collection. Notice that the total number of tests increases.
- Open the
Customer Tests
folder and select thePUT Create or update customer
request.
You will notice that:
- The HTTP method is set to
PUT
. - The URL is set to
{{ServerBaseUri}}/{{ODataPath}}/v{{ApiVersion}}/Customers(CustomerNumber=123)
- Click on the
Headers
tab and you will see that theContent-Type
header is set toapplication/json
- Click on the
Body
tab and 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. Correct the value of the CustomerNumber property in the request body if you wish.
- Click the big 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. You should see that the response body looks something like this:
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.
- In the response tab set, click on the
Headers
tab. You should see something like this:
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.
- Click the big blue
Send
button again.
This time the response should look a little different:
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!
- Click on the
GET Read Customer
request and make sure the parameter value in the URL is set to123
before clicking the big blueSend
button.
You should see that the GET endpoint was able to retrieve the entity that you just created and updated.
- When you are done with your testing, stop the self-hosting application.
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
-
Tutorial 2: Building a Service from Scratch
- Creating a Basic Solution
- Enabling OData Support
- Configuring Self Hosting
- Entity Collection Endpoints
- API Documentation
- Single Entity Endpoints
- OData Query Support
- Alternate Key Endpoints
- Expanding Relations
- Postman Tests
- Supporting CRUD Operations
- Adding a Primary Key Factory
- Adding Create Endpoints
- Adding Upsert Endpoints
- Adding Patch Endpoints
- Adding Delete Endpoints
-
Harmony Core Code Generator
-
OData Aware Tools
-
Advanced Topics
- CLI Tool Customization
- Adapters
- API Versioning
- Authentication
- Authorization
- Collection Counts
- Customization File
- Custom Field Types
- Custom File Specs
- Custom Properties
- Customizing Generated Code
- Deploying to Linux
- Dynamic Call Protocol
- Environment Variables
- Field Security
- File I/O
- Improving AppSettings Processing
- Logging
- Optimistic Concurrency
- Multi-Tenancy
- Publishing in IIS
- Repeatable Unit Tests
- Stored Procedure Routing
- Suppressing OData Metadata
- Traditional Bridge
- Unit Testing
- EF Core Optimization
- Updating a Harmony Core Solution
- Updating to 3.1.90
- Creating a new Release
-
Background Information