I continued implementing the numerous improvements suggested by Philippe Leefsma to my REST API node.js web server, mongo database and C# REST API client:
- CompHound domain, organisation web site and rerouting
- Implementing a DoorData container class
- Updating Get to return a list of deserialised DoorData instances
- Passing a DoorData instance to the Put method
- Implementing a REST API router DoorService class
This finally completes the list improvements to the FireRating in the cloud sample:
- Implement a door data container object
- Convert GET and PUT to use arrays of door objects
- Use the RestSharp templated object transfer and deserialiser
- Set up the web server to use the
{upsert:true}
option
After I finish writing this, I can switch back and continue work on the new CompHound component tracker in preparation for my upcoming conference presentations at RTC Europe in Budapest end of October and Autodesk University in Las Vegas in December.
CompHound Domain, Organisation Web Site and Rerouting
By the way, I already reserved the comphound.net domain and set up a skeleton web site for the CompHound organisation, with a lot of help from my colleague Kean Walmsley.
By the way, I am happy to say that Kean is here with us this week at the Prague Autodesk Cloud Accelerator, mainly providing support for the AutoCAD I/O web service, e.g., demonstrating his Jigsawify app.
Anyway, following Kean's lead, I implemented a normal GitHub Pages web site for the CompHound organisation, accessible through comphound.github.io.
I paid $12 to buy the top-level domain name comphound.net
from
Google domains and set up the domain properties to route the comphound.net
domain to the GitHub Pages web site:
Implementing a DoorData Container Class
Back to the firerating sample, I continued the seemingly never-ending process of perfectioning the firerating mongo database node.js web server and its FireRatingCloud C# REST client.
I'm getting there, though!
The steps today define an explicit helper class for RestSharp to serialise and deserialise the door data between the C# client and the REST API calls:
class DoorData { public string _id { get; set; } public string project_id { get; set; } public string level { get; set; } public string tag { get; set; } public double firerating { get; set; } /// <summary> /// Constructor to populate instance by /// deserialising the REST GET response. /// </summary> public DoorData() { } /// <summary> /// Constructor from BIM to serialise for /// the REST POST or PUT request. /// </summary> /// <param name="door"></param> /// <param name="project_id"></param> /// <param name="paramGuid"></param> public DoorData( Element door, string project_id_arg, Guid paramGuid ) { Document doc = door.Document; _id = door.UniqueId; project_id = project_id_arg; level = doc.GetElement( door.LevelId ).Name; tag = door.get_Parameter( BuiltInParameter.ALL_MODEL_MARK ).AsString(); firerating = door.get_Parameter( paramGuid ) .AsDouble(); } }
Updating Get to Return a List of Deserialised DoorData Instances
With the DoorData class in place, I updated the RestSharp REST API GET method to return a list of deserialised DoorData instances like this:
/// <summary> /// GET JSON document data from /// the specified mongoDB collection. /// </summary> public static List<DoorData> Get( string collection_name_and_id ) { var client = new RestClient( RestApiBaseUrl ); var request = new RestRequest( _api_version + "/" + collection_name_and_id, Method.GET ); IRestResponse<List<DoorData>> response = client.Execute<List<DoorData>>( request ); return response.Data; }
It's getting shorter and simpler all the time, isn't it?
The mainline call to make use of this is shorter, simpler and more maintainable too:
// Determine custom project identifier. string project_id = Util.GetProjectIdentifier( doc ); // Get all doors referencing this project. string query = "doors/project/" + project_id; List<DoorData> doors = Util.Get( query ); if( null != doors && 0 < doors.Count ) { using( Transaction t = new Transaction( doc ) ) { t.Start( "Import Fire Rating Values" ); // Retrieve element unique id and // FireRating parameter values. foreach( DoorData d in doors ) { string uid = d._id; Element e = doc.GetElement( uid ); if( null == e ) { message = string.Format( "Error retrieving element for " + "unique id {0}.", uid ); return Result.Failed; } Parameter p = e.get_Parameter( paramGuid ); if( null == p ) { message = string.Format( "Error retrieving shared parameter on " + " element with unique id {0}.", uid ); return Result.Failed; } object fire_rating = d.firerating; p.Set( (double) fire_rating ); } t.Commit(); } }
These enhancements are captured in FireRatingCloud release 2016.0.0.11.
Now that the RestSharp JSON deserialiser is generating the C# DoorData instances, I can remove the JsonParser.cs module that was previously needed to achieve this.
Passing a DoorData Instance to the Put Method
With that in place, let's clean up the PUT call and pass in a DoorData instance to that as well, instead of a generic object:
/// <summary> /// PUT JSON document data into /// the specified mongoDB collection. /// </summary> public static string Put( string collection_name_and_id, DoorData doorData ) { var client = new RestClient( RestApiBaseUrl ); var request = new RestRequest( _api_version + "/" + collection_name_and_id, Method.PUT ); request.RequestFormat = DataFormat.Json; request.AddBody( doorData ); // uses JsonSerializer IRestResponse response = client.Execute( request ); var content = response.Content; // raw content as string return content; }
Again, it is now shorter and simpler than before, and the same applies to the mainline call:
// Loop through all elements of the given target // category and export the shared parameter value // specified by paramGuid for each. FilteredElementCollector collector = Util.GetTargetInstances( doc, Cmd_1_CreateAndBindSharedParameter.Target ); int n = collector.Count<Element>(); DoorData doorData; string jsonResponse; foreach( Element e in collector ) { Debug.Print( e.Id.IntegerValue.ToString() ); doorData = new DoorData( e, project_id, paramGuid ); jsonResponse = Util.Put( "doors/" + e.UniqueId, doorData ); Debug.Print( jsonResponse ); }
This update was captured in FireRatingCloud release 2016.0.0.12 – commented out JsonParser code and pass DoorData instance to Put method.
With that, we are finally all done cleaning up the C# REST API client, as far as I know.
Implementing a REST API Router DoorService Class
...Instead of individual separate module.exports
functions.
With the C# REST API client utterly perfected, there are one little hiccup to iron out in the node.js web server.
Philippe suggests:
About the web project: I would rename instances.js into
instanceService
for example; that's how such a component is generally called. I would also write it as an object with methods and export the whole object rather thanexport.eachMethod = ...
as you do know.
The REST API functionality that he is referring to is implemented in the two JavaScript modules routes.js and doors_v1.js.
As suggested by Philippe, I modified the latter to define an explicit JavaScript object encapsulating all the function definitions and export that:
DoorService = { findAll : function(req, res){ Door.find({},function(err, results) { return res.send(results); }); }, . . . }; module.exports = DoorService;
On my first attempt, this did not work.
Probably I forgot to add the module.
prefix in front of exports
.
I fixed that after reading the illuminating little discussion on node.js, require and exports by Karl Seguin.
Now the router can simply import the DoorService object and route to its methods one by one like this:
module.exports = function(app) { var DoorService = require('./controller/doors_v1'); app.get('/api/v1/doors', DoorService.findAll); app.get('/api/v1/doors/:id', DoorService.findById); app.post('/api/v1/doors', DoorService.add); app.put('/api/v1/doors/:id', DoorService.update3); // added {upsert:true} option app.delete('/api/v1/doors/:id', DoorService.delete); app.get('/api/v1/doors/project/:pid', DoorService.findAllForProject); }
The updated implementation is captured in fireratingdb 0.0.14 – implemented DoorService class to replace individual separate module.exports functions.
To wrap it up, I also added a version number to the various 'hello' messages and published fireratingdb 0.0.15.
As always, I redeployed the web server to heroku for global access, where it continues running at fireratingdb.herokuapp.com.
Thanks again to Philippe for his helpful and illuminating suggestions!