26 Oct 2017

Design Automation API using AWS S3

Forge Design Automation with AWS S3 files

Process files with Design Automation require give access to your files, so your app needs to make them available somewhere. It can be on your own server, but in order to make it secure, you need to implement some type of authorization. If the file is unprotected, anyone with the links can download or upload to it. For this sample we'll use a secured AWS S3 Bucket.

The overall architecture will be: from a browser webapp, an end-user can select a DWG file on the machine. The webapp will upload the file to the server (storing on the App_Data folder), create a bucket on AWS S3 and move the file there, erase from server App_Data. Now trigger a Design Automation WorkItem for a built-in Activity: PlotToPDF. This will GET the DWG file from S3 and PUT the resulting PDF back, always using Authorization header provided by the application server (ASP.NET in this sample). When done, the application will get the PDF from S3, send to the client browser, erase local files and S3 bucket. 

Overall architecture

This approach ensures no file is left after the operation and no transfer of files is done without proper authorization. All communication must use SSL (HTTPS) connections.

Here is the main app source code, and below is the webform main code. You'll need the AWS Security token SHA256 generator, see this C# class.

// define the Bucket, DWG and PDF file names
string bucketName = "designautomationsample-" + Guid.NewGuid().ToString();
string dwgFileName = FileUpload1.FileName;
string pdfFileName = dwgFileName.Replace(".dwg", ".pdf");

// upload from client/browser to my server
string fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data"), dwgFileName);
Directory.CreateDirectory(Path.GetDirectoryName(fileSavePath));
FileUpload1.SaveAs(fileSavePath);

IAmazonS3 client = new AmazonS3Client(Amazon.RegionEndpoint.USWest2);

// create AWS Bucket
if (!await client.DoesS3BucketExistAsync(bucketName))
  await client.EnsureBucketExistsAsync(bucketName);

// Upload file from Server to S3 bucket
client.UploadObjectFromFilePath(bucketName, FileUpload1.FileName, fileSavePath, null);

// delete files from server
Directory.Delete(Path.GetDirectoryName(fileSavePath), true);

// OAuht 2-legged on Forge
TwoLeggedApi apiInstance = new TwoLeggedApi();
dynamic bearer = await apiInstance.AuthenticateAsync(Config.FORGE_CLIENT_ID, Config.FORGE_CLIENT_SECRET, Autodesk.Forge.oAuthConstants.CLIENT_CREDENTIALS, Config.FORGE_SCOPE_DESIGN_AUTOMATION);

// generate URLs for Design Automation to access (download & upload) S3 files
Uri downloadFromS3 = new Uri(client.GeneratePreSignedURL(bucketName, dwgFileName, DateTime.Now.AddSeconds(90), null));

Dictionary<string, object> props = new Dictionary<string, object>();
props.Add("Verb", "PUT");
Uri uploadToS3 = new Uri(client.GeneratePreSignedURL(bucketName, pdfFileName, DateTime.Now.AddSeconds(10), props));

// prepare WorkItem (based on the built-in "PlotToPDF" activity")
WorkItemsApi workItemsApi = new WorkItemsApi();
workItemsApi.Configuration.AccessToken = bearer.access_token;
JObject arguments = new JObject
{
  new JProperty(
    "InputArguments", new JArray
    {
      new JObject
      {
        new JProperty("Resource", downloadFromS3.GetLeftPart(UriPartial.Path)),
        new JProperty("Headers", MakeHeaders( WebRequestMethods.Http.Get, downloadFromS3)),
        new JProperty("Name",  "HostDwg")
      }
    }
  ),
  new JProperty(
    "OutputArguments", new JArray
    {
      new JObject
      {
        new JProperty("Name", "Result"),
        new JProperty("HttpVerb", "PUT"),
        new JProperty("Resource", uploadToS3.GetLeftPart(UriPartial.Path)),
        new JProperty("Headers", MakeHeaders(WebRequestMethods.Http.Put, uploadToS3)),
        new JProperty("StorageProvider",  "Generic")
      }
    }
  )
};

// submit the workitem...
dynamic workitem = await workItemsApi.CreateWorkItemAsync(new Autodesk.Forge.Model.WorkItem(string.Empty, arguments, null, null, null, "PlotToPDF"));
// wait...
System.Threading.Thread.Sleep(5000); // wait 5 second, this is not safe, but will do it for now due issue #17
// get the status
string id = workitem.Id;
dynamic status = await workItemsApi.GetWorkItemAsync(id);  // Due an issue with the .NET SDK, this is not working (#17)

// download the PDF from S3 to our server
fileSavePath = fileSavePath.Replace(".dwg", ".pdf");
client.DownloadToFilePath(bucketName, pdfFileName, fileSavePath, null);

// send the PDF file to the client (DO NOT expose a direct URL to S3, but send the bytes)
Response.Clear();
Response.ContentType = "application/pdf";
Response.AddHeader("Content-Disposition", "attachment;filename=\"" + pdfFileName + "\"");
Response.BinaryWrite(File.ReadAllBytes(fileSavePath));
Response.Flush();
Response.End();

// delete files on S3
await client.DeleteObjectAsync(bucketName, dwgFileName);
await client.DeleteObjectAsync(bucketName, pdfFileName);
await client.DeleteBucketAsync(bucketName);

// delete PDF file from server
Directory.Delete(Path.GetDirectoryName(fileSavePath), true);

Related Article