Custom DFS Connector

Last published at: April 6th, 2021

Introduction

Distributed File System (DFS) connector is used by FlowWright 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 v9.7 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, how to configure it using the Configuration Manager, and the use of DFS connectors for file storage.

 

 


Parameters


Start Microsoft Visual Studio 201x and select “New Project”.   Select “C#” as the language and select “Class Library” as the project type.  Also, select .Net Framework 4.6.1 from the top dropdown. This example is going to use C# as the development language.  This same example can be also written using Visual Basic .Net.

For the name of the project, provide “Test DFS Connector”.

 

 

Once the project is created, select “Class1.cs” and rename the class to “clsFileSystem.cs”

 

 

 

Add a reference to FlowWright, navigate to the following directory “C:\inetpub\wwwroot\cDevWorkflow\bin”.  Select the file “cDevDecisionEngine.dll”

You can also add the required references using the “FlowWright API” Nuget package using the Nuget package manager.

 

Add a using statement for the namespace “cDevWorkflow.cDevDecisoinEngine”

Next, let’s see the methods and properties we need in order to create a DFS connector

  1. Properties
    • clsDFSFileStoreConfig oConfig
    • deUserSession oUserSession
    • string sError
  2. Methods
    • getParameters
    • testConnector
    • fileExists
    • folderExists
    • createFolder
    • createFile
    • updateFile
    • updateFileUsingID
    • getFile
    • getFileUsingID
    • getFileContents
    • getFiles
    • getFilesUsingFolderID
    • getFolder
    • getFolderUsingID
    • getSubFolders
    • moveFile
    • removeFile
    • removeFileUsingID
    • removeFolder
    • removeFolderUsingID

Let’s start implementing the class by using the inteface “deIDFSStorageConnector”. 

We will be building this DFS connector to store files and folders within the file system of the server.  Interface methods can be implemented for any storage API.

In order to implement the interface select the interface and right-click -> select -> Quick Actions and refactorings -> click on Implement interfaces.

 

MS Visual Studio will automatically create the method stubs within the class based on the implemented Interface.

Next, let’s start writing the required methods.  When configuring the connector, you might want to pass some configuration parameters, for example, store the root directory path. Let’s implement the “getParameters” method.

Created a parameter called “fileRootPath” to capture the file root path for the storage connector. We are setting the path as “C:\inetpub\wwwroot\cDevWorkflow”.   Path value provided will be the default value, but this value can be modified using the UI configuration.

 

 

 

Next, let’s implement a method to test the connector, let’s implement the method  “testConnector”. This method will check if the root file path exists or not.

We are using “Directory.Exists” method to determine whether the file path exists or not.

 

Next, let’s implement the “fileExists” method. First, we combine the “filePath” and “fileRootPath” to get the full file path. Then we are using “File.Exists” to determine file exists or not.

 

 

Next, let’s implement the “folderExists” method. Combine “folderPath” with “fileRootPath” to get the full folder path. Then we are using “Directory.Exists” to determine folder exists or not.

Next, let’s implement the “createFolder” method. Combine “parentFolderPath” and “folderName” to get the relative path. Let’s check if the folder exists or not. If not exists, then we will create a new folder. To create a new folder, we need to combine “parentFolderPath” with “fileRootPath” to form the full folder path for the new folder.

Next, let’s implement the “createFile” method. Combine “filePath” and “fileRootPath” to get the full file path. Then we create a new file using the FileStream API.

 

Next, let’s implement the “updateFile” method. Combine “filePath” and “fileRootPath” to get the full path of the file. Then we overwrite the existing file using the FileStream API.

Next, let’s implement the “updateFileUsingID” method. In this method “fileID” is nothing but “filePath”, in other cases this can be the id representing the file. Since we are creating the file system connector, we are using identifiers as paths. Let’s call the “updateFile” method to perform this functionality.

 

Next, let’s implement the “getFile” method. First, check if the file exists or not. If the file exists, then we get the “clsDEFile” object.

 

 

Next, let’s implement the “getFileUsingID” method. In this case, the “fileID” is nothing but the relative path of the file.  Since we already have a method that does the work, let’s call the “getFile” method.

 

 

Next, let’s implement the “getFileContents” method.  Combine the “filePath” with “fileRootPath” to get the full file path. Then using the “FileStream” and “BinaryReader” we get the file contents in the format of a   byte array.

 

Next, let’s implement the “getFiles” method. Combine the “folderPath” with “fileRootPath” to get the full folder path. Then get all files under the folder and its subfolders if the recursive parameter is set to ’true’, otherwise get files from the given folder path only.  This method creates a bunch of objects using the “clsDEFile” class and returns them in a list.

 

Next, let’s implement the “getFilesUsingFolderID” method. FolderID is nothing but the relative path of the file, let’s use the existing method “getFiles”.

 

Next, let’s implement the “getFolder” method. First, let’s check if the folder exists or not. If the folder exists, then we are returning a “clsDEFolder” object.

 

Next, let’s implement the “getFolderUsingID” method. folderID is the relative path of the folder, let’s call the existing method “getFolder”.

 

 

Next, let’s implement the “getSubFolders” method. Combine the “folderPath” with “fileRootpath” to get the full folder path. If the “recursive” parameter is set to “true”, then get all the folders and their sub-folders.

 

 

Next, let’s implement the “moveFile” method. Check if the source file exists or not. If the file exists, then combine the “sourceFilePath” and “targetFilePath” with “fileRootpath” to get the full source file path of the file. Use the File.Move method to move the file.

 

 

Next, let’s implement the “removeFile” method. First, let’s check if the file exists or not. If the file exists, then combine the “filePath” with “fileRootPath” to get the full file path, and then write the code to remove the file.

 

Next, let’s implement the “removeFileUsingID” method. fileID is nothing but the relative path to the file, let’s use the existing “removeFile” method to remove the file.

 

 

 

Next, let’s implement the “removeFolder” method. First, let’s check if the folder exists or not. If the folder exists, then combine the “folderPath” with “fileRootPath” to get a full folder path, then use the code to remove the folder.

 

Next, let’s implement the “removeFileUsingID” method. folderID is nothing but the relative path to the folder, let’s use the existing “removeFolder” method to remove the folder.

 

 

 

 

Here’s the full source code for the File DFS connector:

using cDevWorkflow.cDevDecisionEngine;

using System;

using System.Collections.Generic;

using System.IO;

using System.Linq;

 

namespace Test_DFS_Connector

{

    public class clsFileSystem: deIDFSStorageConnector

    {

        public clsDFSFileStoreConfig oConfig { get; set; }

        public deUserSession oUserSession { get; set; }

        public string sError { get; set; }

 

        public clsFileSystem (clsDFSFileStoreConfig oConnConfig, deUserSession oConnUserSession)

        {

            oConfig = oConnConfig;

            oUserSession = oConnUserSession;

        }

 

        public Dictionary<string, string> getParameters()

        {

            Dictionary<string, string> oMyParams = new Dictionary<string, string>();

 

            if (oConfig.oParms == null || oConfig.oParms.Count == 0)

            {

                oMyParams.Add("fileRootPath", @"C:\inetpub\wwwroot\cDevWorkflow");

            }

            else

            {

                oMyParams = oConfig.oParms;

            }

 

            return (oMyParams);

        }

 

        public bool testConnector()

        {

            bool bStatus = false;

 

            try

            {

                Dictionary<string, string> oFilesystemParms = oConfig.oParms;

                string sFileRootPath = oFilesystemParms["fileRootPath"].Trim();

 

                bStatus = Directory.Exists(sFileRootPath);

 

                if (!bStatus) sError = "File root path does not exist";

            }

            catch (Exception ex)

            {

                sError = ex.Message;

                bStatus = false;

            }

 

            return (bStatus);

        }

 

        public bool fileExists(string filePath)

        {

            filePath = Path.Combine(oConfig.oParms["fileRootPath"], filePath);

            return File.Exists(filePath);

        }

 

        public bool folderExists(string folderPath)

        {

            folderPath = Path.Combine(oConfig.oParms["fileRootPath"], folderPath);

            return Directory.Exists(folderPath);

        }

 

        public bool createFolder(string parentFolderPath, string folderName, out string folderID)

        {

            try

            {

                parentFolderPath = Path.Combine(parentFolderPath, folderName);

 

                if (!folderExists(parentFolderPath))

                {

                    parentFolderPath = Path.Combine(oConfig.oParms["fileRootPath"], parentFolderPath);

 

                     Directory.CreateDirectory(parentFolderPath);

                    folderID = parentFolderPath;

                }

                else

                {

                    folderID = string.Empty;

                }

 

                return true;

            }

            catch (Exception ex)

            {

                sError = ex.Message; folderID = string.Empty;

            }

 

            return false;

        }

 

        public bool createFile(string filePath, byte[] oFileContents, out string fileID, DFSFileStoreFileType oFileType)

        {

            try

            {

                fileID = filePath;

 

                filePath = Path.Combine(oConfig.oParms["fileRootPath"], filePath);

 

                using (FileStream oSourceStream = File.Create(filePath))

                {

                    oSourceStream.Seek(0, SeekOrigin.End);

                     oSourceStream.Write(oFileContents, 0, oFileContents.Length);

                }

 

                return (true);

            }

            catch (Exception ex)

            {

                sError = ex.Message;

                fileID = string.Empty;

            }

 

            return false;

        }

 

        public bool updateFile(string filePath, byte[] oFileContents)

        {

            try

            {

                filePath = Path.Combine(oConfig.oParms["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)

            {

                sError = ex.Message;

            }

 

            return false;

        }

 

        public bool updateFileUsingID(string fileID, byte[] oFileContents)

        {

            return updateFile(fileID, oFileContents);

        }

 

        public clsDEFile getFile(string filepath)

        {

            clsDEFile oFileObject = null;

 

            try

            {

                if (fileExists(filepath))

                {

                    oFileObject = new clsDEFile(filepath, filepath, this);

                }

                else

                {

                    sError = "File not found";

                }

            }

            catch (Exception ex)

            {

                sError = ex.Message;

            }

 

            return (oFileObject);

        }

 

        public clsDEFile getFileUsingID(string fileID)

        {

            return getFile(fileID);

        }

 

        public byte[] getFileContents(string filePath)

        {

            filePath = Path.Combine(oConfig.oParms["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;

        }

 

        public List<clsDEFile> getFiles(string folderPath, bool recursive = false)

        {

            List<clsDEFile> oFilesList = new List<clsDEFile>();

 

            try

            {

                folderPath = Path.Combine(oConfig.oParms["fileRootPath"], folderPath);

                string[] oFiles = Directory.GetFiles(folderPath, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

 

                foreach (var sFilePath in oFiles)

                {

                    string relativeFilePath = sFilePath.Replace(oConfig.oParms["fileRootPath"] + "\\", "");

 

                    clsDEFile oFileObject = new clsDEFile(relativeFilePath, relativeFilePath, this);

 

                     oFilesList.Add(oFileObject);

                }

            }

            catch (Exception ex)

            {

                sError = ex.Message;

            }

 

            return (oFilesList);

        }

 

        public List<clsDEFile> getFilesUsingFolderID(string folderID)

        {

            return getFiles(folderID);

        }

        public clsDEFolder getFolder(string folderPath)

        {

            if (!folderExists(folderPath)) return (null);

 

            return new clsDEFolder(folderPath, this);

        }

 

        public clsDEFolder getFolderUsingID(string folderID)

        {

            return getFolder(folderID);

        }

 

        public List<clsDEFolder> getSubFolders(string folderPath, bool recursive)

        {

            List<clsDEFolder> folderList = new List<clsDEFolder>();

 

            folderPath = Path.Combine(oConfig.oParms["fileRootPath"], folderPath);

 

            SearchOption oOption = SearchOption.TopDirectoryOnly;

            if (recursive)

            {

                oOption = SearchOption.AllDirectories;

            }

 

             Directory.GetDirectories(folderPath, "*", oOption).ToList().ForEach(sPath => folderList.Add(getFolder(sPath)));

 

            return folderList;

        }

 

        public bool moveFile(string sourceFilePath, string targetFilePath)

        {

            if (fileExists(sourceFilePath))

            {

                sourceFilePath = Path.Combine(oConfig.oParms["fileRootPath"], sourceFilePath);

                targetFilePath = Path.Combine(oConfig.oParms["fileRootPath"], targetFilePath);

 

                File.Move(sourceFilePath, targetFilePath);

                return true;

            }

 

            return false;

        }

 

        public bool removeFile(string filePath)

        {

            if (fileExists(filePath))

            {

                try

                {

                    filePath = Path.Combine(oConfig.oParms["fileRootPath"], filePath);

                    File.Delete(filePath);

                    return (true);

                }

                catch (IOException e)

                {

                    sError = e.Message;

                }

            }

 

            return (false);

         }

 

        public bool removeFileUsingID(string fileID)

        {

            return removeFile(fileID);

        }

 

        public bool removeFolder(string folderPath)

        {

            if (!folderExists(folderPath)) return false;

 

            folderPath = Path.Combine(oConfig.oParms["fileRootPath"], folderPath);

 

            Directory.Delete(folderPath, true);

 

            return true;

        }

 

        public bool removeFolderUsingID(string folderID)

        {

            return removeFolder(folderID);

        }

    }

}

 

 

Now that the DFS connector code is completed, compile the project to create the DLL.  Navigate to the directory where the DLL was compiled to and select the DLL: “Test DFS Connector.dll”.  Right-click on the file and select “Copy” from the context menu.

 

Copy the DFS connector DLL file to the FlowWright BIN directory:

 “C:\inetpub\wwwroot\cDevWorkflow\bin”.

 

 

Navigate to the FlowWright Configuration Manager and 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: 

Now Select the “clsFileSystem” DFS connector and select “ManageConfigure” menu item, the DFS connector will be automatically configured within FlowWright.

Select the DFS connector and edit the “fileRootPath” parameter as what path you want for storage.

Now that the DFS connector is fully configured, let’s use the new DFS connector for FlowWright application storage. 

Go to the status page. Status Distributed-> Storage Connector 

 

Then save the connector and verify the details.

 

 

 

 

 

 

 

 

 

 

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

 

Open the Workflow designer and drag and drop a “task” step to the workflow definition.

 

 

 

 

 

Select the task step and fill the properties of the step. Route the task to the current user – to your account. Save Workflow 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 Workflow instance. Close the Workflow designer and go to the Workflow instances page. The newly created instance will be displayed on the list.

Click Execute->Run Engine. The instance will be in “sleeping” status.

 

 

 

Open the task menu to view the list of tasks.

 

Select the task from the list and render the task.

 

Click on the “Choose Files” button and select any file. Then click the “Upload” button to upload the files for the task.

 

 

Now go to the file system and open the folder: “C:\DFSStorage” and verify that the uploaded files exist or not.

 

Using DFS connectors, user uploaded files can be stored centrally and accessed by multiple distributed servers.  FlowWright also provides a “Storage Explorer”, navigate to the Distributed File System menu and select “Storage Explorer” menu item.

You can also download any given file using the context menu for the file.

There are more helpful articles are available within the FlowWright knowledgebase, you can view the knowledgebase using the following link:

https://flowwright.atlassian.net/projects/CSR/knowledge