If you've been following our previous posts on the Aras Innovator RESTful API, you know just about everything you need to implement your own custom integrations, clients, tools, and mobile applications. You can perform basic CRUD operations to create and modify Innovator data, and you can use OAuth tokens to authenticate requests. All that's missing is file handling!
Files behave a little differently than other items in Innovator, so the Aras Innovator RESTful API includes some special actions for uploading files to the vault. In this post, we'll cover these vault-specific endpoints to show how you can add file uploading to your custom projects.
As in our previous posts about the RESTful API, we're using an API development tool to demonstrate the HTTP requests needed to upload files. That way each, the example is language-agnostic and you can follow along without jumping right into code. If you want to follow along with the blog, you can download Postman for free.
If you want to see how you can use these HTTP requests in code, check out the rest-upload-example project on the Aras Labs GitHub. The project provides a simple HTML form that allows the user to enter the credentials for an Aras Innovator instance and select a file to upload. The included submitForm() JavaScript function authenticates the user and uploads the file to the specified vault via the HTTP requests covered below.
Screenshot of the rest-upload-example community project on the Aras Labs GitHub
Authentication
We'll use OAuth to authenticate the HTTP requests in this example, so the first thing we need to do is get a new token from Innovator's OAuth server. We've covered using OAuth tokens with Aras Innovator in a previous blog post, so I won't dive into the details here. Basically, you'll need a POST request with the following information passed in the body:
- grant_type : "password"
- scope : "Innovator"
- client_id : "IOMApp"
- "IOMApp" is the default client id for new Aras Innovator instances. You can find out how to change it or add a new client id here.
- username : <Innovator User>
- Any requests made using the returned token will share the same permissions as this user
- password : <MD5 password hash>
- The rest-upload-example project demonstrates one way to create the hash from the password string entered by the user. Check out getConnectionInput() in my-upload.js.
- database : <database name>
If you're using Postman to test or follow along, your request and reply should look something like this:
Request a new OAuth token from the Aras Innovator OAuth server.
Once you get the request back from the vault server, you can get the token from the JSON object that's returned. To authenticate our subsequent requests, we just need to include this token in the "Authorization" header.
Note: You could use basic authentication to send these requests, but OAuth tokens are the preferred authentication approach moving forward.
Uploading the File
Now that we have our token for authenticating requests, there are just three basic steps to upload a file to Innovator via the RESTful API: get a transaction id, upload the file contents, and commit the transaction.
1. Begin the Upload Transaction
The first step to upload a file to Innovator is to get a transaction id from the vault server. All we need to do to is send a POST request, authenticated with our token, to the vault.BeginTransaction endpoint. The result will be a JSON object with a "transactionId" property.
Get a new transaction id from the vault server with the vault.BeginTransaction endpoint.
We'll include the transaction id in all of the calls to upload the file. This string should be unique to each file that you upload. If you need to upload multiple files, you'll need to request a new transaction id for each. This helps the vault server process each file upload transaction.
Note: Postman does have an "Authorization" tab where you can choose the type of authentication you want to use and enter the token or credentials. That works, but I've opted to explicitly define the Authorization header to show how to use the OAuth token without a specific client feature.
2. Upload the File
Now that we have started a transaction with the vault server, we can start uploading the file. In this example, we're uploading a small, simple text file to Innovator with a single HTTP request. However, you can also upload a file in "chunks" if you have a large file or network constraints. The uploadFile() function in the rest-upload-example community project demonstrates how you can programmatically split your file upload into multiple requests.
In the screenshot below, you can see we're passing 5 headers:
- Content-Disposition: attachment; filename*=utf-8''<filename>
- If your filename includes spaces or special characters, you'll need to encode them. Check out this reference for character codes.
- Be sure to include the two single quotes before the file name (not a single double quote).
- Content-Range: bytes <start index>-<end index>/<length>
- Content-Type: "application/octet-stream"
- transactionid: <transaction id from previous step>
- Authorization: Bearer <OAuth token>
If you're uploading a file in a single HTTP request, setting the Content-Range header is pretty simple. The start index will be 0, end index will be length-1, and length will be the file size in bytes. For a 53 byte file, the Content-Range header will be set to "bytes 0-52/53".
If you're uploading a file with multiple HTTP requests, you'll need to calculate the Content-Range for each request. If I were uploading the 53 byte file in 10 byte chunks, I would use the chunk's original start and end index, as well as the file size. For example, the third 10 byte chunk of a 53 byte file would have the Content-Range header "bytes 20-29/53".
The body of each request should contain the bytes referenced by the Content-Range header. In this case, I'm uploading a whole text file so I can just paste the contents into the body of the Postman request.
Upload one or more file chunks using the vault.UploadFile endpoint.
You'll notice that the vault.FileUpload request above returned an empty body. This is expected. To check whether the upload succeeded or failed, check the status code that is returned. A 200 status code indicates success. If you're getting a 40* or 500 error, double check your server credentials and request headers.
3. Commit the Upload Transaction
Once the contents of the file have been uploaded, we can commit the transaction. Committing the transaction tells the server we're done uploading file content and the file can be moved from the transaction folder to its final destination in the vault. The file doesn't exist in the vault until we get a successful response from the commit transaction request.
The headers of the vault.CommitTransaciton request are pretty straightforward. We'll use the same transactionid and Authorization headers that we used to upload the file contents, however the Content-Type header will look a little different.
- Content-Type: multipart/mixed; boundary=<boundary string>
- transactionid: <transaction id>
- Authorization: Bearer <OAuth token>
We need to define a boundary string to help the vault server parse the contents of the request body. You can use a randomly generated GUID, the item id, etc. Just make sure that the boundary you define in the header and the string you use in the request body match.
Headers for the vault.CommitTransaction endpoint
The body of the vault.CommitTransaction request is composed of specially formatted text. Here's the general format:
--<boundary string>
Content-Type: application/http
POST http://<server>/<web alias>/Server/odata/File HTTP/1.1
Content-Type: application/json
{"id":"<file item id>","filename":"<filename>","file_size":<total size in bytes>,"Located":[{"file_version":1,"related_id":"<vault id>"}]}
--<boundary string>--
A couple things to note about the body format:
- The strings highlighted in yellow will specific to your Innovator instance and the file you're uploading.
- The <boundary string> parameter should be replaced with the boundary string defined in the request's Content-Type header.
- The spacing in the body text is very important. All line endings should be carriage returns (rn). Depending on how you're building the request body, you may or may not need to explicitly enter these line ending characters.
A sample request body and result for vault.CommitTransaction
A successful request will return a 200 status code and some body text about the new File item. You can parse the body text to get the JSON object and access the item's properties.
Confirming the Upload
The commit request will return some metadata about the new File item, but you can perform a GET request if you want to get specific metadata. The screenshot below shows how to request a File item from Innovator, just like any other ItemType. You can also retrieve the contents of a file by adding "/$value" to the end of the request url.
GET request to retrieve a File item from the Innovator server
Now you can include file uploading in your custom projects that use the Aras Innovator RESTful API! What are you going to try first? Share your ideas and questions in the comments below.
Looking for more Aras inspiration?
Subscribe to our blog and follow @ArasLabs on Twitter for more helpful content! You can also find our latest open-source projects and sample code on the Aras Labs GitHub page.