To clarify a few things, the methodology I used required pulling all Fortis documents out and placing them in a directory, along with the dwcontrol
files containing their indexes. That was one part of this, and it was written in Foxpro against the Fortis COM object. This process also created a Foxpro table with all document information, including file names, Fortis database locations, Fortis document types, and the Fortis document ID.
Once all the documents and their indexes were in the right place (which you would have to do anyway if you wanted to use Import Jobs), the C#/.NET application put them in the right file cabinets with their indexes. All cabinets involved in the migration had three fields in them so I could back-link them to Fortis:
Fortis DB Location
Fortis Document Type
Fortis Document ID
In that sense, the process was self-logging because at any point I could query DocuWare's SQL Server database and see how many documents were migrated. But the C# program also logged progress, in the GUI and with summary results at the end. Since this involved creating my own code, I could do whatever I wanted with regard to logging and progress-reporting.
As for whether or not the code is straightforward, I would say the actual calls to DocuWare were the easier part. More work was put into deciding how to organize the documents and getting the multi-threading working. The calls to DocuWare followed examples already listed in DocuWare online help. Here are examples of the main routines that stored documents to DocuWare and indexed them (same name, UploadFileToFileCabinet
, overloaded static methods):
public static Document UploadFileToFileCabinet(FileCabinet fileCabinet, string uploadFile, string indexFieldNames, dynamic indexValues, bool timestampsAlreadyUTC = false)
Document indexInfo = new Document();
indexInfo.Fields = new List();
int numIndexFields = Math.Min(indexFieldNames.Length, indexValues.Length);
string fieldName = "";
dynamic value = null;
Type valueType = null;
for (int i = 0; i < numIndexFields; i++)
fieldName = indexFieldNames[i];
value = indexValues[i];
valueType = ((ObjectHandle)value).Unwrap().GetType();
if (valueType.Name.ToUpper() == "STRING")
else if (valueType.Name.ToUpper() == "INT")
else if (valueType.Name.ToUpper() == "DECIMAL")
else if (valueType.Name.ToUpper() == "DATETIME")
DateTime timestamp = (DateTime)value;
// We need to make timestamps UTC (Universal Coordinated Time) for storage in DocuWare, as that is how
// all date/time fields are stored in the database.
timestamp = timestamp.ToUniversalTime();
return UploadFileToFileCabinet(fileCabinet, uploadFile, indexInfo);
public static Document UploadFileToFileCabinet(FileCabinet fileCabinet, string uploadFile, Document indexInfo = null)
LastError = "";
// We will use a standard upload method for smaller files, but the "Easy" version for larger files, otherwise
// large file uploads might fail (the "Easy" version is meant for huge file uploads, according to the documentation).
int largeFileThresholdInBytes = 10 * 1024 * 1024; // 10 MB
Document uploadedDoc = null;
FileInfo fileInfo = new FileInfo(uploadFile);
if (fileInfo.Length < largeFileThresholdInBytes)
// Smaller file.
uploadedDoc = fileCabinet.UploadDocument(indexInfo, fileInfo);
// Larger file.
uploadedDoc = fileCabinet.EasyUploadSingleDocument(fileInfo, indexInfo);
catch (Exception ex)
LastError = ex.Message;
You need to have the DocuWare API NuGet packages installed for the above code to truly make sense. And of course these routines require helper routines to do things like get a FileCabinet
object by file cabinet name, etc. These routines were a mix of using DocuWare API classes and hitting SQL Server directly to gather various GUIDs and such. I can't really post all that without posting the whole code base.
So, maybe not quite as straightforward as I said. *smile* But it all originated from DocuWare Platform (also known as the "SDK" or "REST API") examples. That documentation can be found here:
That's how I came up with most of my code base, and it took a while. Some of that was due to writing routines in both Foxpro and C#, though. If you go with C#/.NET for all of it, and don't worry about multi-threading, it should come together more quickly.
Let me know if you have specific questions or if the overall outline of how I did things remains unclear!