Introduction
FlowWright uses a distributed File System (DFS) Connector to connect to a distributed file system for storage. By default, FlowWright connects to the File system DFS connector, which is backward compatible with previous versions of FlowWright. With distributed servers, a distributed file system is necessary; multiple servers should and can access the same file storage system. FlowWright ships with the following DFS connectors:
- File system connector
- Database connector
- Azure File system connector
- Azure Blog storage connector
The DFS connector is created by simply implementing a Class based on an Interface.
Building & Configuring a DFS connector.
This sample will illustrate how to create a new DFS connector, configure it using the Configuration Manager, and use DFS connectors for file storage of the File system DFS connector.
Here's the example code of the File system DFS connector; it implements the interface IFWDFSStorageConnector.
public FWDFSFileStoreConfig Config { get; set; } /// <summary> /// Gets or sets the user session. /// </summary> /// <value>The o user session.</value> public FWUserSession UserSession { get; set; } /// <summary> /// Gets or sets the s error. /// </summary> /// <value>The s error.</value> public string Error { get; set; } /// <summary> /// Initializes a new instance of the <see cref="clsFileSystemConnector"/> class. /// </summary> /// <param name="oConnConfig">The o connection configuration.</param> /// <param name="oConnUserSession">The o connection user session.</param> public clsFileSystemConnector(FWDFSFileStoreConfig oConnConfig, FWUserSession oConnUserSession) { Config = oConnConfig; UserSession = oConnUserSession; }
/// <summary> /// Creates the file. /// </summary> /// <param name="filePath">The file path.</param> /// <param name="oFileContents">The o file contents.</param> /// <param name="fileID">The file identifier.</param> /// <param name="oFileType">Type of the o file.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool CreateFile(string filePath, byte[] oFileContents, out string fileID, DFSFileStoreFileType oFileType) { try { fileID = filePath; filePath = Path.Combine(Config.Parms["FileRootPath"], filePath); using (FileStream oSourceStream = File.Create(filePath)) { oSourceStream.Seek(0, SeekOrigin.End); oSourceStream.Write(oFileContents, 0, oFileContents.Length); } return (true); } catch (Exception ex) { Error = ex.Message; fileID = string.Empty; } return false; } /// <summary> /// Creates the folder. /// </summary> /// <param name="parentFolderPath">The parent folder path.</param> /// <param name="folderName">Name of the folder.</param> /// <param name="folderID">The folder identifier.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool CreateFolder(string parentFolderPath, string folderName, out string folderID) { try { parentFolderPath = Path.Combine(parentFolderPath, folderName); if (!FolderExists(parentFolderPath)) { parentFolderPath = Path.Combine(Config.Parms["FileRootPath"], parentFolderPath); Directory.CreateDirectory(parentFolderPath); } folderID = parentFolderPath; return (true); } catch (Exception ex) { Error = ex.Message; folderID = string.Empty; } return false; } /// <summary> /// Files the exists. /// </summary> /// <param name="filePath">The file path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool FileExists(string filePath) { filePath = Path.Combine(Config.Parms["FileRootPath"], filePath); return File.Exists(filePath); } /// <summary> /// Folders the exists. /// </summary> /// <param name="folderPath">The folder path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool FolderExists(string folderPath) { folderPath = Path.Combine(Config.Parms["FileRootPath"], folderPath); return Directory.Exists(folderPath); }
/// <summary> /// Gets the file. /// </summary> /// <param name="filepath">The filepath.</param> /// <returns>clsDEFile.</returns> public FWDFSFile GetFile(string filePath) { FWDFSFile oFileObject = null; try { string fileFullPath = Path.Combine(Config.Parms["FileRootPath"], filePath); if (File.Exists(fileFullPath)) { oFileObject = new FWDFSFile(filePath, filePath, this); } else { Error = "File not found"; } } catch (Exception ex) { Error = ex.Message; } return (oFileObject); } /// <summary> /// Gets the files. /// </summary> /// <param name="folderPath">The folder path.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> /// <returns>List<clsDEFile>.</returns> public List<FWDFSFile> GetFiles(string folderPath, bool recursive = false) { List<FWDFSFile> oFilesObject = new List<FWDFSFile>(); try { folderPath = Path.Combine(Config.Parms["FileRootPath"], folderPath); string[] oFiles = { "" }; if (recursive) { oFiles = Directory.GetFiles(folderPath, "*", SearchOption.AllDirectories); } else { oFiles = Directory.GetFiles(folderPath, "*", SearchOption.TopDirectoryOnly); } foreach (var sFilePath in oFiles) { string relativeFilePath = sFilePath.Replace(Config.Parms["FileRootPath"] + "\\", ""); FWDFSFile oFileObject = new FWDFSFile(relativeFilePath, relativeFilePath, this); oFilesObject.Add(oFileObject); } } catch (Exception ex) { Error = ex.Message; } return (oFilesObject); } /// <summary> /// Gets the files using folder identifier. /// </summary> /// <param name="folderID">The folder identifier.</param> /// <returns>List<clsDEFile>.</returns> public List<FWDFSFile> GetFilesUsingFolderID(string folderID) { return GetFiles(folderID); } /// <summary> /// Gets the file using the identifier. /// </summary> /// <param name="fileID">The file identifier.</param> /// <returns>clsDEFile.</returns> public FWDFSFile GetFileUsingID(string fileID) { return GetFile(fileID); } /// <summary> /// Gets the folder. /// </summary> /// <param name="folderPath">The folder path.</param> /// <returns>clsDEFolder.</returns> public FWDFSFolder GetFolder(string folderPath) { if (!FolderExists(folderPath)) return (null); return new FWDFSFolder(folderPath, this); } /// <summary> /// Gets the folder using the identifier. /// </summary> /// <param name="folderID">The folder identifier.</param> /// <returns>clsDEFolder.</returns> public FWDFSFolder GetFolderUsingID(string folderID) { return GetFolder(folderID); } /// <summary> /// Gets the parameters. /// </summary> /// <returns>Dictionary<System.String, System.String>.</returns> public Dictionary<string, string> GetParameters() { Dictionary<string, string> oMyParams = new Dictionary<string, string>(); if (Config.Parms == null || Config.Parms.Count == 0) { oMyParams.Add("FileRootPath", @"C:\inetpub\wwwroot\cDevWorkflow"); } else { oMyParams = Config.Parms; } return (oMyParams); } /// <summary> /// Moves the file. /// </summary> /// <param name="sourceFilePath">The source file path.</param> /// <param name="targetFilePath">The target file path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool MoveFile(string sourceFilePath, string targetFilePath) { sourceFilePath = Path.Combine(Config.Parms["FileRootPath"], sourceFilePath); targetFilePath = Path.Combine(Config.Parms["FileRootPath"], targetFilePath); if (File.Exists(sourceFilePath)) { File.Move(sourceFilePath, targetFilePath); return true; } return false; }
/// <summary> /// Removes the file. /// </summary> /// <param name="filePath">The file path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool RemoveFile(string filePath) { filePath = Path.Combine(Config.Parms["FileRootPath"], filePath); if (File.Exists(filePath)) { try { File.Delete(filePath); return (true); } catch (IOException e) { Error = e.Message; } } return (false); } /// <summary> /// Removes the file using the identifier. /// </summary> /// <param name="fileID">The file identifier.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool RemoveFileUsingID(string fileID) { return RemoveFile(fileID); } /// <summary> /// Removes the folder. /// </summary> /// <param name="folderPath">The folder path.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool RemoveFolder(string folderPath) { if (!FolderExists(folderPath)) return false; folderPath = Path.Combine(Config.Parms["FileRootPath"], folderPath); Directory.Delete(folderPath, true); return true; } /// <summary> /// Removes the folder using the identifier. /// </summary> /// <param name="folderID">The folder identifier.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool RemoveFolderUsingID(string folderID) { return RemoveFolder(folderID); } /// <summary> /// Tests the connector. /// </summary> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool TestConnector() { bool bStatus = false; try { Dictionary<string, string> oFilesystemParms = Config.Parms; string sFilePath = oFilesystemParms["FileRootPath"].Trim(); bStatus = Directory.Exists(sFilePath); if (!bStatus) Error = "File path does not exist"; } catch (Exception ex) { Error = ex.Message; bStatus = false; } return (bStatus); } /// <summary> /// Updates the file. /// </summary> /// <param name="filePath">The file path.</param> /// <param name="oFileContents">The o file contents.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool UpdateFile(string filePath, byte[] oFileContents) { try { filePath = Path.Combine(Config.Parms["FileRootPath"], filePath); using (FileStream SourceStream = File.Open(filePath, FileMode.OpenOrCreate)) { SourceStream.Seek(0, SeekOrigin.End); SourceStream.Write(oFileContents, 0, oFileContents.Length); } return (true); } catch (Exception ex) { Error = ex.Message; } return false; } /// <summary> /// Gets the file contents. /// </summary> /// <param name="filePath">The file path.</param> /// <returns>System.Byte[].</returns> public byte[] GetFileContents(string filePath) { filePath = Path.Combine(Config.Parms["FileRootPath"], filePath); byte[] oFileData = null; using (FileStream oFS = File.OpenRead(filePath)) { using (BinaryReader oBinaryReader = new BinaryReader(oFS)) { oFileData = oBinaryReader.ReadBytes((int)oFS.Length); } } return oFileData; } /// <summary> /// Updates the file using identifier. /// </summary> /// <param name="fileID">The file identifier.</param> /// <param name="oFileContents">The o file contents.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> public bool UpdateFileUsingID(string fileID, byte[] oFileContents) { return UpdateFile(fileID, oFileContents); } /// <summary> /// Gets the sub folders. /// </summary> /// <param name="folderPath">The folder path.</param> /// <param name="recursive">if set to <c>true</c> [recursive].</param> /// <returns>List<clsDEFolder>.</returns> public List<FWDFSFolder> GetSubFolders(string folderPath, bool recursive) { List<FWDFSFolder> folderList = new List<FWDFSFolder>(); folderPath = Path.Combine(Config.Parms["FileRootPath"], folderPath); SearchOption oOption = SearchOption.TopDirectoryOnly; if (recursive) { oOption = SearchOption.AllDirectories; } Directory.GetDirectories(folderPath, "*", oOption).ToList().ForEach(sPath => folderList.Add(GetFolder(sPath))); return folderList; }
Copy the DFS connector DLL file to the FlowWright BIN directory:
“C:\inetpub\wwwroot\cDevWorkflow\bin”.

Navigate to the FlowWright Configuration Manager, expand the “Distributed File Storage” Menu, and select the “DFS Storage Connectors” menu item.

Let’s use the auto-detect feature of FlowWright to auto-detect and configure the DFS connector:

Once you click on the “Auto Detect” button on the toolbar, the auto-detect UI should display the custom DFS connector as shown below:

Select the “clsFileSystem” DFS connector and select the “Manage—Configure” menu item. The DFS connector will be automatically configured within FlowWright.
Select the DFS connector and edit the “fileRootPath” parameter to match the path you want for storage.

Now that the DFS connector is fully configured, let’s use the new DFS connector for FlowWright application storage.
You can navigate to the status page. Status - Distributed - Storage Connector

Then, save the connector and verify the details.

We will see how it works once we have configured the “clsFileSystem” as the default storage connector. Let’s create a new Process definition.

Open the Process Designer and drag and drop a “task” step to the Process definition.

Select the task step and fill out its properties. Route the task to the current user and your account. Save the Process definition and click the “Create Instance” menu item. Click on the “Generate” button to generate a new name for the Instance. Then click the “Create & Execute” button to create the Process instance. Close the Process Designer and go to the Process Instances page. The newly created instance will be displayed on the list.

Navigate to the Process - Instance page. Select and render the process instance as below.

Navigate to the Engage - Tasks page. Select the process instance to view the tasks.

Select the task from the list and render it. Then click the “Choose Files” button to select any file. Then click the “Upload” button to upload the files for the task.

Now go to the file system, open the folder: “C:\FlowWright\wwwroot\TempFiles\” and verify whether the uploaded files exist. Using DFS connectors, user-uploaded files can be stored centrally and accessed by multiple distributed servers.
