Data Browsing
In this step we'll extend our server so that we can browse the content of 3rd party applications built with Forge such as BIM 360 Docs or Autodesk Docs. We will basically follow the Data Management service's hierarchy of hubs, projects, folders, items, and versions:
Browsing hubs
First, let's add a couple of helper methods for browsing through the hubs, projects, folders, items, and versions:
- Node.js & VSCode
- .NET 6 & VSCode
- .NET 6 & VS2022
Create another file under the services/forge
subfolder, and call it hubs.js
. This is where
will implement the logic for browsing through the individual hubs, projects, folders, items,
and versions. Populate the new file with the following code:
const { HubsApi, ProjectsApi, FoldersApi, ItemsApi } = require('forge-apis');
const { internalAuthClient } = require('./auth.js');
async function getHubs(token) {
const resp = await new HubsApi().getHubs(null, internalAuthClient, token);
return resp.body.data;
}
async function getProjects(hubId, token) {
const resp = await new ProjectsApi().getHubProjects(hubId, null, internalAuthClient, token);
return resp.body.data;
}
async function getProjectContents(hubId, projectId, folderId, token) {
if (!folderId) {
const resp = await new ProjectsApi().getProjectTopFolders(hubId, projectId, internalAuthClient, token);
return resp.body.data;
} else {
const resp = await new FoldersApi().getFolderContents(projectId, folderId, null, internalAuthClient, token);
return resp.body.data;
}
}
async function getItemVersions(projectId, itemId, token) {
const resp = await new ItemsApi().getItemVersions(projectId, itemId, null, internalAuthClient, token);
return resp.body.data;
}
module.exports = {
getHubs,
getProjects,
getProjectContents,
getItemVersions
};
Create a ForgeService.Hubs.cs
under the Models
subfolder with the following content:
using System.Collections.Generic;
using System.Threading.Tasks;
using Autodesk.Forge;
using Autodesk.Forge.Model;
public partial class ForgeService
{
public async Task<IEnumerable<dynamic>> GetHubs(Tokens tokens)
{
var hubs = new List<dynamic>();
var api = new HubsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetHubsAsync();
foreach (KeyValuePair<string, dynamic> hub in new DynamicDictionaryItems(response.data))
{
hubs.Add(hub.Value);
}
return hubs;
}
public async Task<IEnumerable<dynamic>> GetProjects(string hubId, Tokens tokens)
{
var projects = new List<dynamic>();
var api = new ProjectsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetHubProjectsAsync(hubId);
foreach (KeyValuePair<string, dynamic> project in new DynamicDictionaryItems(response.data))
{
projects.Add(project.Value);
}
return projects;
}
public async Task<IEnumerable<dynamic>> GetContents(string hubId, string projectId, string folderId, Tokens tokens)
{
var contents = new List<dynamic>();
if (string.IsNullOrEmpty(folderId))
{
var api = new ProjectsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetProjectTopFoldersAsync(hubId, projectId);
foreach (KeyValuePair<string, dynamic> folders in new DynamicDictionaryItems(response.data))
{
contents.Add(folders.Value);
}
}
else
{
var api = new FoldersApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetFolderContentsAsync(projectId, folderId); // TODO: add paging
foreach (KeyValuePair<string, dynamic> item in new DynamicDictionaryItems(response.data))
{
contents.Add(item.Value);
}
}
return contents;
}
public async Task<IEnumerable<dynamic>> GetVersions(string hubId, string projectId, string itemId, Tokens tokens)
{
var versions = new List<dynamic>();
var api = new ItemsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetItemVersionsAsync(projectId, itemId);
foreach (KeyValuePair<string, dynamic> version in new DynamicDictionaryItems(response.data))
{
versions.Add(version.Value);
}
return versions;
}
}
Create a ForgeService.Hubs.cs
under the Models
subfolder with the following content:
using System.Collections.Generic;
using System.Threading.Tasks;
using Autodesk.Forge;
using Autodesk.Forge.Model;
public partial class ForgeService
{
public async Task<IEnumerable<dynamic>> GetHubs(Tokens tokens)
{
var hubs = new List<dynamic>();
var api = new HubsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetHubsAsync();
foreach (KeyValuePair<string, dynamic> hub in new DynamicDictionaryItems(response.data))
{
hubs.Add(hub.Value);
}
return hubs;
}
public async Task<IEnumerable<dynamic>> GetProjects(string hubId, Tokens tokens)
{
var projects = new List<dynamic>();
var api = new ProjectsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetHubProjectsAsync(hubId);
foreach (KeyValuePair<string, dynamic> project in new DynamicDictionaryItems(response.data))
{
projects.Add(project.Value);
}
return projects;
}
public async Task<IEnumerable<dynamic>> GetContents(string hubId, string projectId, string folderId, Tokens tokens)
{
var contents = new List<dynamic>();
if (string.IsNullOrEmpty(folderId))
{
var api = new ProjectsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetProjectTopFoldersAsync(hubId, projectId);
foreach (KeyValuePair<string, dynamic> folders in new DynamicDictionaryItems(response.data))
{
contents.Add(folders.Value);
}
}
else
{
var api = new FoldersApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetFolderContentsAsync(projectId, folderId); // TODO: add paging
foreach (KeyValuePair<string, dynamic> item in new DynamicDictionaryItems(response.data))
{
contents.Add(item.Value);
}
}
return contents;
}
public async Task<IEnumerable<dynamic>> GetVersions(string hubId, string projectId, string itemId, Tokens tokens)
{
var versions = new List<dynamic>();
var api = new ItemsApi();
api.Configuration.AccessToken = tokens.InternalToken;
var response = await api.GetItemVersionsAsync(projectId, itemId);
foreach (KeyValuePair<string, dynamic> version in new DynamicDictionaryItems(response.data))
{
versions.Add(version.Value);
}
return versions;
}
}
Server endpoints
Next, let's expose the new functionality to the client-side code through another set of endpoints.
- Node.js & VSCode
- .NET 6 & VSCode
- .NET 6 & VS2022
Create a hubs.js
file under the routes
subfolder with the following content:
const express = require('express');
const { authRefreshMiddleware } = require('../services/forge/auth.js');
const { getHubs, getProjects, getProjectContents, getItemVersions } = require('../services/forge/hubs.js');
let router = express.Router();
router.use(authRefreshMiddleware);
router.get('/', async function (req, res, next) {
try {
const hubs = await getHubs(req.internalOAuthToken);
res.json(hubs);
} catch (err) {
next(err);
}
});
router.get('/:hub_id/projects', async function (req, res, next) {
try {
const projects = await getProjects(req.params.hub_id, req.internalOAuthToken);
res.json(projects);
} catch (err) {
next(err);
}
});
router.get('/:hub_id/projects/:project_id/contents', async function (req, res, next) {
try {
const contents = await getProjectContents(req.params.hub_id, req.params.project_id, req.query.folder_id, req.internalOAuthToken);
res.json(contents);
} catch (err) {
next(err);
}
});
router.get('/:hub_id/projects/:project_id/contents/:item_id/versions', async function (req, res, next) {
try {
const versions = await getItemVersions(req.params.project_id, req.params.item_id, req.internalOAuthToken);
res.json(versions);
} catch (err) {
next(err);
}
});
module.exports = router;
And mount the router to our server application by modifying server.js
:
const express = require('express');
const session = require('cookie-session');
const { PORT, SERVER_SESSION_SECRET } = require('./config.js');
let app = express();
app.use(express.static('wwwroot'));
app.use(session({ secret: SERVER_SESSION_SECRET, maxAge: 24 * 60 * 60 * 1000 }));
app.use('/api/auth', require('./routes/auth.js'));
app.use('/api/hubs', require('./routes/hubs.js'));
app.listen(PORT, () => console.log(`Server listening on port ${PORT}...`));
Create a HubsController.cs
file under the Controllers
subfolder with the following content:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
[ApiController]
[Route("api/[controller]")]
public class HubsController : ControllerBase
{
private readonly ILogger<HubsController> _logger;
private readonly ForgeService _forgeService;
public HubsController(ILogger<HubsController> logger, ForgeService forgeService)
{
_logger = logger;
_forgeService = forgeService;
}
[HttpGet()]
public async Task<ActionResult<string>> ListHubs()
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var hubs = await _forgeService.GetHubs(tokens);
return JsonConvert.SerializeObject(hubs);
}
[HttpGet("{hub}/projects")]
public async Task<ActionResult<string>> ListProjects(string hub)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _forgeService.GetProjects(hub, tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpGet("{hub}/projects/{project}/contents")]
public async Task<ActionResult<string>> ListItems(string hub, string project, [FromQuery] string? folder_id)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var contents = await _forgeService.GetContents(hub, project, folder_id, tokens);
return JsonConvert.SerializeObject(contents);
}
[HttpGet("{hub}/projects/{project}/contents/{item}/versions")]
public async Task<ActionResult<string>> ListVersions(string hub, string project, string item)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var versions = await _forgeService.GetVersions(hub, project, item, tokens);
return JsonConvert.SerializeObject(versions);
}
}
The controller handles several endpoints for browsing the content in 3rd party Forge applications such as BIM 360 Docs and ACC. We will make use of these endpoints when building the UI part of the application.
Create a HubsController.cs
file under the Controllers
subfolder with the following content:
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
[ApiController]
[Route("api/[controller]")]
public class HubsController : ControllerBase
{
private readonly ILogger<HubsController> _logger;
private readonly ForgeService _forgeService;
public HubsController(ILogger<HubsController> logger, ForgeService forgeService)
{
_logger = logger;
_forgeService = forgeService;
}
[HttpGet()]
public async Task<ActionResult<string>> ListHubs()
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var hubs = await _forgeService.GetHubs(tokens);
return JsonConvert.SerializeObject(hubs);
}
[HttpGet("{hub}/projects")]
public async Task<ActionResult<string>> ListProjects(string hub)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var projects = await _forgeService.GetProjects(hub, tokens);
return JsonConvert.SerializeObject(projects);
}
[HttpGet("{hub}/projects/{project}/contents")]
public async Task<ActionResult<string>> ListItems(string hub, string project, [FromQuery] string? folder_id)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var contents = await _forgeService.GetContents(hub, project, folder_id, tokens);
return JsonConvert.SerializeObject(contents);
}
[HttpGet("{hub}/projects/{project}/contents/{item}/versions")]
public async Task<ActionResult<string>> ListVersions(string hub, string project, string item)
{
var tokens = await AuthController.PrepareTokens(Request, Response, _forgeService);
if (tokens == null)
{
return Unauthorized();
}
var versions = await _forgeService.GetVersions(hub, project, item, tokens);
return JsonConvert.SerializeObject(versions);
}
}
The controller handles several endpoints for browsing the content in 3rd party Forge applications such as BIM 360 Docs and ACC. We will make use of these endpoints when building the UI part of the application.
Try it out
And that's it for the server side. Time to try it out!
Start (or restart) the app from Visual Studio Code as usual, and navigate to http://localhost:8080/api/hubs in the browser. The server should respond with a JSON list of all the hubs you have access to. Try copying the ID of one of the hubs, and use it in another address: http://localhost:8080/api/hubs/your-hub-id/projects. In this case the server application should respond with a JSON list of all projects available under the specified hub.
If you skipped the login procedure in the previous step, or restarted your server application,
you may need to go to http://localhost:8080/api/auth/login
again to make sure that all the authentication data is available in cookies before testing
the /api/hubs
endpoint.
If you are using Google Chrome, consider installing JSON Formatter or a similar extension to automatically format JSON responses.