Forge & ASP.NET: from zero to hero in 30 minutes

February 8, 2017

By Augusto Goncalves (@augustomaia)

Visual Studio can integrate directly with Git compatible hosts, like Appharbor. That way is very easy to develop and test locally, then just commit and push to the host. The video on this article explains, step by step, how to create your app with Forge to upload, translate and show a model. 

The steps are:

  1. Sign up for an Appharbor account and create an application
  2. Get the repository URL (on Appharbor) and clone locally using Visual Studio Git
  3. Sign up for a Forge account and create an app (Forge Client ID & Secret)
  4. Implement your code locally, debug and test (that's the longest piece). Code is provided below.
    1. WebForm that will upload files, translate and show them
    2. WebAPI endpoint for token (required for Viewer)
    3. JavaScript that implements the Viewer
  5. Commit and push to Appharbor
  6. Adjust setting and variables
  7. Ready!

Already have an ASP.NET app running? These steps can also be used to integrate Forge Viewer into existing ASP.NET apps as there is no prerequisite configuration and show how to setup WebForm and WebAPI that are required, just ignore the clone and push from/to Appharbor. 

Better than writing a big text, the following video demonstrate the process, from zero. To keep it short, I'm not explaining all Forge related parts on this video, but we have lots of tutorials. No need to type the code, everything is right at the bottom of this article. 

Here is the Button_click code showed on the video:

protected async void Button1_Click(object sender, EventArgs e)
{
    // create a randomg bucket name (fixed prefix + randomg guid)
    string bucketKey = "forgeapp" + Guid.NewGuid().ToString("N").ToLower();

    // upload the file (to your server)
    string fileSavePath = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data"), bucketKey, FileUpload1.FileName);
    Directory.CreateDirectory(Path.GetDirectoryName(fileSavePath));
    FileUpload1.SaveAs(fileSavePath);

    // get a write enabled token
    TwoLeggedApi oauthApi = new TwoLeggedApi();
    dynamic bearer = await oauthApi.AuthenticateAsync(
        WebConfigurationManager.AppSettings["FORGE_CLIENT_ID"],
        WebConfigurationManager.AppSettings["FORGE_CLIENT_SECRET"],
        "client_credentials",
        new Scope[] { Scope.BucketCreate, Scope.DataCreate, Scope.DataWrite, Scope.DataRead });

    // create the Forge bucket
    PostBucketsPayload postBucket = new PostBucketsPayload(bucketKey, null, PostBucketsPayload.PolicyKeyEnum.Transient /* erase after 24h*/ );
    BucketsApi bucketsApi = new BucketsApi();
    bucketsApi.Configuration.AccessToken = bearer.access_token;
    dynamic newBucket = await bucketsApi.CreateBucketAsync(postBucket);

    // upload file (a.k.a. Objects)
    ObjectsApi objectsApi = new ObjectsApi();
    oauthApi.Configuration.AccessToken = bearer.access_token;
    dynamic newObject;
    using (StreamReader fileStream = new StreamReader(fileSavePath))
    {
        newObject = await objectsApi.UploadObjectAsync(bucketKey, FileUpload1.FileName,
            (int)fileStream.BaseStream.Length, fileStream.BaseStream,
            "application/octet-stream");
    }

    // translate file
    string objectIdBase64 = ToBase64(newObject.objectId);
    List<JobPayloadItem> postTranslationOutput = new List<JobPayloadItem>()
    {
        new JobPayloadItem(
        JobPayloadItem.TypeEnum.Svf /* Viewer*/,
        new List<JobPayloadItem.ViewsEnum>()
        {
           JobPayloadItem.ViewsEnum._3d,
           JobPayloadItem.ViewsEnum._2d
        })
    };
    JobPayload postTranslation = new JobPayload(
        new JobPayloadInput(objectIdBase64),
        new JobPayloadOutput(postTranslationOutput));
    DerivativesApi derivativeApi = new DerivativesApi();
    derivativeApi.Configuration.AccessToken = bearer.access_token;
    dynamic translation = await derivativeApi.TranslateAsync(postTranslation);

    // check if is ready
    int progress = 0;
    do
    {
        System.Threading.Thread.Sleep(1000); // wait 1 second
        try
        {
            dynamic manifest = await derivativeApi.GetManifestAsync(objectIdBase64);
            progress = (string.IsNullOrWhiteSpace(Regex.Match(manifest.progress, @"\d+").Value) ? 100 : Int32.Parse(Regex.Match(manifest.progress, @"\d+").Value));
        }
        catch (Exception ex)
        {

        }
    } while (progress < 100);

    // ready!!!!

    // register a client-side script to show this model
    Page.ClientScript.RegisterStartupScript(this.GetType(), "ShowModel", string.Format("<script>showModel('{0}');</script>", objectIdBase64));

    // clean up
    Directory.Delete(Path.GetDirectoryName(fileSavePath), true);

}

/// <summary>
/// Convert a string into Base64 (source http://stackoverflow.com/a/11743162)
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public string ToBase64(string input)
{
    var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(input);
    return System.Convert.ToBase64String(plainTextBytes);
}

And the C# GetToken WebAPI implementation:

[HttpGet]
[Route("api/forge/token")]
public async Task<HttpResponseMessage> GetToken()
{
    // get a read-only token
    // NEVER return write-enabled token to client
    TwoLeggedApi oauthApi = new TwoLeggedApi();
    dynamic bearer = await oauthApi.AuthenticateAsync(
        WebConfigurationManager.AppSettings["FORGE_CLIENT_ID"],
        WebConfigurationManager.AppSettings["FORGE_CLIENT_SECRET"],
        "client_credentials",
        new Scope[] { Scope.DataRead });

    // return a plain text token (for simplicity)
    var response = new HttpResponseMessage(HttpStatusCode.OK);
    response.Content = new StringContent(bearer.access_token, System.Text.Encoding.UTF8, "text/plain");
    return response;
}

And, finally, the JavaScript getAccessToken on client-side. The Viewer related code is available as a tutorial

function getAccessToken() {
    var xmlHttp = null;
    xmlHttp = new XMLHttpRequest();
    xmlHttp.open("GET", '/api/forge/token', false /*forge viewer requires SYNC*/);
    xmlHttp.send(null);
    return xmlHttp.responseText;
}

And remember to set the maxRequestLength="2097151" (upload big files) and keep your keys secret on web.config (or better with web.keys.config with .gitignore).

Have questions? Let us know!

Posts by author

Augusto Goncalves
Developer Advocate, Autodesk

Developer Advocate at Autodesk since 2008, working with both desktop and web/cloud apps using top technologies, like C#, JavaScript, NodeJS and any other that can solve problems and improve workflo