About
This project presents a simple File Manager Service and Client Application demonstration. The File Manager is a self-hosted (service host) WCF application launched and managed with a simple console interface. The client βtesterβ has a simplified GUI user interface to quickly demo and test the service (Windows Form Application).
Architecture
The demo project consists of these component topics:
- Shared Class Library Project βSharedLibraryβ
- IFileManagerService (Interface for Service)
- FileInformation (Class Describing a File Object)
- Service Class Library Project βFileManagerServiceLibraryβ
- FileManagerService (Code that Implements the Service Interface)
- application configuration (Configuration Reference for Service Host)
- Reference to the Shared Class Library
- WCF Service (Host) Application Project βFileManagerServerHostβ
- Program (Starts, Manages, Stops the Service)
- application configuration (Configuration for Service Host)
- Reference to the FileManagerServiceLibrary
- Client βTester to Serviceβ Windows Form Application Project βClientβ
- Reference to the Shared Class Library
- Main Form GUI User Interface
- Form Code β Processes GUI User Interface
The service interface is defined not in the service application but in a Shared Library. This library defines the interface contracts for the file manager services (ex: Get File) and is referenced by both the client and service host projects.Β The SharedLibrary also has a class definition that defines a file object with a name and size property.
The FileManagerServiceLibrary implements the File Manager service and contracts as defined in the SharedLibrary. The FileManagerServerHost is a simple console application that is responsible for starting the File Manager service, hosting, and managing the service (self-hosted).
The service behaviors were designed to allow multiple threads (concurrency) while each client call to the service spins up a new service instance (PerCall). Although this project is not meant to demo concurrency behaviors, I would recommend referencing βThe Money Pot Problemβ [link] to read my discussion on service behaviors and concurrency.
A client βtesterβ windows form application tests the service and provides output to the user in a simple GUI.
In addition, there will be a short discussion of streaming and chunking techniques used by the project to download and upload files. Also, the client application implements asynchronous programming techniques in creating separate threads while allowing for cancellation.
Shared Class Library
A Class Library project (SharedLibrary) was added to my Visual Studio solution. This library is shared amongst the client and the service. The code is available on GitHub [here].
IFileManagerService (Interface for Service)
The ServiceContract for the File Manager service contains five operation contracts. The code is available on GitHub [here].
- List<FileInformation> GetFiles();
- Returns a list of files in the server directory
- Stream GetFile(string fileName);
- Stream downloads a specified file from the server to the client
- int AddFile(string fileName);
- Uses the chunk process and style of uploading a file from a client to a server.
- void AddFileChunk(string fileName, int chunk, byte[] data);
- This method takes care of adding file chunks on the server for the client to upload/add their file
- void CompleteAddFile(string fileName, int chunks);
- Call from the client to the server to indicate that all chunks are completed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
/// <summary> /// IFileManagerService /// Service interface for the File Manager /// </summary> [ServiceContract] public interface IFileManagerService { /// <summary> /// GetFiles() /// Returns a list of files in the server directory /// </summary> /// <returns>Returns a list of files (List<FileInfo>) in the server directory /// with each list item being a FileInfo object</returns> [OperationContract] List<FileInformation> GetFiles(); /// <summary> /// GetFile(string fileName) /// Stream downloads a specified file from the server to the client /// </summary> /// <param name="fileName">the file name (string) to download</param> /// <returns>the file (Stream)</returns> [OperationContract] Stream GetFile(string fileName); /// <summary> /// AddFile /// Uses the chunk process and style of uploading a file from a client /// to a server. /// </summary> /// <param name="fileName">file name (string) to add from client to server</param> /// <returns>configured size (int) of chunk</returns> [OperationContract] int AddFile(string fileName); /// <summary> /// AddFileChunk /// This method takes care of adding file chunks on the server for /// the client to upload/add their file /// </summary> /// <param name="fileName">the file name (string) of the chunk</param> /// <param name="chunk">the chunk identifier (int)</param> /// <param name="data">the chunk data byte array (byte[])</param> [OperationContract] void AddFileChunk(string fileName, int chunk, byte[] data); /// <summary> /// CompleteAddFile /// Call from the client to the server to indicate that all /// chunks are completed /// </summary> /// <param name="fileName">file name (string)</param> /// <param name="chunks">number of chunk segments (int)</param> [OperationContract] void CompleteAddFile(string fileName, int chunks); } // end of interface |
FileInformation (Class for File Object)
The FileInformation class represents a file object in the application with both name (string) and size (bytes). In addition, there is a custom override of its ToString() object representation for display in the client form application list widow of both remote and local files. The object string representation formats the file size to either bytes, KB, MB, GB, etc. The code is available on GitHub [here].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
/// <summary> /// FileInfo /// Represents a file object with the /// name and size with the intent of common /// OOP class between server and client /// </summary> [DataContract] public class FileInformation { #region Properties // Name of File [DataMember] public string FileName { get; set; } // Size of File (Bytes) [DataMember] public long FileSize { get; set; } #endregion Properties #region constructors /// <summary> /// Default Constructor /// </summary> public FileInformation() { } /// <summary> /// Parameterized Constructor /// </summary> /// <param name="fileName">name of file (string)</param> /// <param name="fileSize">size of file (bytes)</param> public FileInformation(string fileName, long fileSize) { FileName = fileName; FileSize = fileSize; } #endregion constructors #region methods /// <summary> /// String Override of Object /// </summary> /// <returns>Overrides the Object ToString Representation</returns> public override string ToString() { return string.Format("{0} {1}", FileName.PadRight(20, ' '), FormatSize(FileSize).PadLeft(10, ' ')); } /// <summary> /// FormatSize /// Returns the formatted string representation of the data /// input (bytes) to a more concise value (ex: GB) /// </summary> /// <param name="value">value of filesize (bytes)</param> /// <returns>formatted string of the filesize by category</returns> public static string FormatSize(decimal value) { int groups = 0; string suffix = string.Empty; while (value > 1024M) { value /= 1024M; groups++; } switch (groups) { case 0: suffix = "Bytes"; break; case 1: suffix = "KB"; break; case 2: suffix = "MB"; break; case 3: suffix = "GB"; break; case 4: suffix = "TB"; break; default: suffix = "NOPE"; break; } if (groups == 0) { return string.Format("{0} {1}", value, suffix); } else { return string.Format("{0:0.0} {1}", value, suffix); } } // end of method #endregion methods } // end of class |
FileManager Service Library
A class library describing the service implementation of the interfaces was added to my Visual Studio solution. Separating the service implementation from the host application project, provided one more level of abstraction and separation to better organize the solution. The code is available on GitHub [here].
FileManagerService (Code that Implements the Service Interface)
The service implementation code has service behaviors configured to allow for concurrency and multi-threading while each service call spins up a new service instance (PerCall). Note: In this project I did not utilize the multi-threading capability for the file chunking (file upload) demo. Β The service references a shared class library called βSharedLibraryβ that contains the interface definition for the service and the class definition for a file object (FileInformation).Β The code is available on GitHub [here].
Fields and Properties
The fields section contains a few notable private fields. The first sets an integer representing the chunk size (in bytes) while the second represents the private path on the server to where the file manager is allowing files to be listed, downloaded, and uploaded. The folder defaults to the personal MyDocuments folder, then opens (creates a new folder if not existing) a folder called FileManagerService. This folder then acts as the remote file management directory. To simplify the demo, additional features (ex: change of directory, custom) were not implemented. The FilePath property encapsulated and provided read access to the service file management directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// constant value of chunk size const int CHUNK_SIZE = 10000; // Private path string to local directory // In MyDocuments (Personal) Folder + FileManagerService private static string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "FileManagerService"); /// <summary> /// FilePath Property /// Returns a fixed directory path to a tempoary folder on a user's computer /// This path will be the main directory where the server hosts the files /// and receives files from the client /// In MyDocuments (Personal) Folder + FileManagerLocal /// </summary> private string FilePath { get { try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } return path; } catch (Exception ex) { throw new FaultException(ex.Message); } } } // end of Property |
Service Implementation Methods
The service implementations are described below.
List Files
Returns a list of files in the server directory.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
/// <summary> /// GetFiles() /// Gets the list of all files in the file manager directory /// </summary> /// <returns>List of files (FileInfo object) on server in the directory</returns> public List<FileInformation> GetFiles() { try { List<FileInformation> files = new List<FileInformation>(); string[] allFiles = Directory.GetFiles(FilePath); foreach (string file in allFiles) { FileInfo fi = new FileInfo(file); files.Add(new FileInformation(fi.Name, fi.Length)); } return files; } catch (Exception ex) { throw new FaultException(ex.Message); } } // end of method |
Download File
Stream downloads a specified file from the server to the client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/// <summary> /// GetFile /// Downloads a file (stream) from server to client /// </summary> /// <param name="fileName">Name of file (string) to stream</param> /// <returns>Stream containing the file data</returns> public Stream GetFile(string fileName) { try { string path = Path.Combine(FilePath, fileName); // make sure the file path and file exists if (!File.Exists(path)) { throw new Exception("File not found!"); } // return a stream access to the file return File.Open(path, FileMode.Open, FileAccess.Read); } catch (Exception ex) { System.Reflection.MethodBase mb = System.Reflection.MethodBase.GetCurrentMethod(); System.Diagnostics.EventLog.WriteEntry("FileManagerService", string.Format("{0}.{1}.{2}: {3}", mb.DeclaringType.Namespace, mb.DeclaringType.Name, mb.Name, ex.Message)); throw new FaultException(ex.Message); } } // end of method |
Upload File (Upload Process Part #1)
Uses the chunk process and style of uploading a file from a client to a server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/// <summary> /// AddFile /// Creates a staging directory and returns the default chunk size /// </summary> /// <param name="fileName">Name of file (string) to upload</param> /// <param name="fileSize">Size of file (bytes) to upload</param> /// <returns>Configured size of chunk (int) representing bytes </returns> public int AddFile(string fileName) { try { // Create temp directory for file chunks Directory.CreateDirectory(Path.Combine(FilePath, fileName + "_TEMP")); return CHUNK_SIZE; } catch (Exception ex) { throw new FaultException(ex.Message); } } // end of method |
Add File Chunk (Upload Process Part #2)
This method takes care of adding file chunks on the server for the client to upload/add their file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
/// <summary> /// AddFileChunk /// Adds a file chunk to the stage directory for the file chunks /// </summary> /// <param name="fileName">Name of file (string) being uploaded</param> /// <param name="chunk">Chunk number (int) </param> /// <param name="data">Data for the given chunk (byte[])</param> public void AddFileChunk(string fileName, int chunk, byte[] chunkData) { try { string path = Path.Combine(FilePath, fileName + "_TEMP"); // creates the staging directory if it does not already exist if (!Directory.Exists(path)) { throw new Exception("Directory not found!"); } // Adds the file chunk to the staging directory using (FileStream fs = File.Open(Path.Combine(path, string.Format("{0:00000000}.chunk", chunk)), FileMode.Create, FileAccess.Write)) { fs.Write(chunkData, 0, chunkData.Length); fs.Flush(); fs.Close(); } } catch (Exception ex) { System.Reflection.MethodBase mb = System.Reflection.MethodBase.GetCurrentMethod(); System.Diagnostics.EventLog.WriteEntry("FileManagerService", string.Format("{0}.{1}.{2}: {3}", mb.DeclaringType.Namespace, mb.DeclaringType.Name, mb.Name, ex.Message)); throw new FaultException(ex.Message); } } // end of method |
Complete File Chunking (Upload Process Part #3)
Call from the client to the server to indicate that all chunks are completed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
/// <summary> /// CompleteAddFile /// Call from client to server to indicate all chunks are complete /// Turns the chunks into a single file /// Deletes the chunk files and the staging directory /// </summary> /// <param name="fileName">Name of file (string) being uploaded</param> /// <param name="chunks">Total number of chunks (int)</param> public void CompleteAddFile(string fileName, int chunks) { try { string path = Path.Combine(FilePath, fileName + "_TEMP"); // make sure the staging directory is available if (!Directory.Exists(path)) { throw new Exception("Directory not found!"); } // Wait for all files to be written // Because this could be done in a multithreaded process // Multiple threads creating different chunks (to speed up the upload) // Verify all the chunk files are there before proceeding further... while (true) { System.Threading.Thread.Sleep(500); string[] files = Directory.GetFiles(path); if (files.Length == chunks) { break; } } // end of while loop // File stream the chunks to one file destination using (FileStream fsDest = new FileStream(Path.Combine(FilePath, fileName), FileMode.Create, FileAccess.Write)) { for (int i = 0; i < chunks; i++) { using (FileStream fs = File.Open(Path.Combine(path, string.Format("{0:00000000}.chunk", i)), FileMode.Open, FileAccess.Read)) { byte[] bytes = new byte[CHUNK_SIZE]; int read = fs.Read(bytes, 0, CHUNK_SIZE); fsDest.Write(bytes, 0, read); fs.Close(); } // end of inner using } // end of for fsDest.Flush(); fsDest.Close(); } // end of using // Clean up tempory files and delete the staging directory Directory.Delete(path, true); } catch (Exception ex) { System.Reflection.MethodBase mb = System.Reflection.MethodBase.GetCurrentMethod(); System.Diagnostics.EventLog.WriteEntry("FileManagerService", string.Format("{0}.{1}.{2}: {3}", mb.DeclaringType.Namespace, mb.DeclaringType.Name, mb.Name, ex.Message)); throw new FaultException(ex.Message); } } // end of method |
Application Configuration App.Config
The application configuration simple sets up the service model configuration including the endpoint, interface contract name, basic http protocol, and the base address for this demo. This configuration must be included where the application is hosted (run) (see next section) and is displayed in this service class library project for reference. The code is available on GitHub [here].
FileManager Service Host (FileManagerServerHost)
The FileManagerServerHost project is a simple console application responsible for starting, managing, and stopping the File Manager service. This demo hosts the service using tcp protocol on a custom port address as described in the application configuration file.Β The code is available on GitHub [here].
Main Program
The main program is the entry point for the service host application. It creates a ServiceHost object of type described in the FileManagerServiceLibrary, opens the host up for connections, and waits for the user to manually close the service (pressing enter in the console window).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
/// <summary> /// This program launches the file manager /// service by creating a host and starting the service. /// After it is started it will be available to clients until /// the user terminates the service. /// </summary> class Program { /// <summary> /// Main entry for Service Host /// </summary> /// <param name="args">no input arguments used</param> static void Main(string[] args) { ServiceHost myServiceHost = null; try { myServiceHost = new ServiceHost(typeof(FileManagerService)); Console.WriteLine("Starting file management service..."); myServiceHost.Open(); Console.WriteLine("File management service started."); Console.Write("Press <ENTER> to quit..."); Console.ReadLine(); } catch (Exception ex) { System.Reflection.MethodBase mb = System.Reflection.MethodBase.GetCurrentMethod(); Console.WriteLine("{0}.{1}.{2}: {3}", mb.DeclaringType.Namespace, mb.DeclaringType.Name, mb.Name, ex.Message); Console.Write("Press <ENTER> to quit..."); Console.ReadLine(); } finally { if (myServiceHost != null) { try { // Dispose of the resource myServiceHost.Close(); } catch { myServiceHost.Abort(); } } } } // end of main method } // end of class |
Application Configuration App.Config
The application configuration simple sets up the service model configuration including the endpoint, interface contract name, basic http protocol, and the base address for this demo. The code is available on GitHub [here].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> </startup> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="ServerBehavior"> <serviceMetadata/> <serviceDebug includeExceptionDetailInFaults="true"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <netTcpBinding> <binding name="netTcp" maxBufferPoolSize="60000000" maxBufferSize="60000000" maxReceivedMessageSize="60000000" transferMode="StreamedResponse"> <security mode="None" /> <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/> </binding> </netTcpBinding> </bindings> <services> <service name="FileManagerServiceLibrary.FileManagerService"> <endpoint address="net.tcp://localhost:998/FileManagerService" binding="netTcpBinding" bindingConfiguration="netTcp" contract="SharedLibrary.IFileManagerService" /> </service> </services> </system.serviceModel> </configuration> |
Client βTester to Serviceβ Windows Form Application
The client βtester to serviceβ is a simple windows form application project in the same solution that connects to the βFile Managerβ by use of a ChannelFactory proxy to a specified service name that is defined in the client application configuration. The client program will use this proxy to test the OperationContract or methods available in the ServiceContract and return the results to the user on the form window.
The client application shares and references the βShared Libraryβ β a class library project. It has access to the File Manager service interface to know exactly how it can consume the service with the help of a ChannelFactory proxy (discussed in the next section). The project code is available on GitHub [here].
Features
The client application was meant to only demonstrate consumption of the File Manager service and has limited scope of features. Some of the client application features are:
Asynchronous Processing: After a button is clicked, it spins a new asynchronous task complete with continuation options, cancellation, and its own Action delegate information.
Cancellation: The cancel button allows the user to asynchronously cancel any and all task threads running.
Local Directory Listing: The list box shows a formatted string describing the file name and sizes of all the files in the local directory.
Remote Directory Listing: The list box shows a formatted string describing the file name and sizes of all the files in the remote directory after the user clicks the βList Filesβ button.
Refresh: The Refresh button allows the immediate refresh of the local directory list box.
List Files: Starts an asynchronous task to call the service to retrieve a collection of the files available in the remote directory being hosted on the service.
Get File: Starts and asynchronous task to call the service to stream download the file selected by the user from the remote list box. Refreshes the local list box after the file is downloaded.
Add File: Starts and asynchronous task to call the service to upload the file (by chunking) selected by the user from the local list box. Refreshes the remote the list box after the file is uploaded.
Form Code
The code behind file is for the client tester and manages the application in one file. A separate business logic/process layer should had been added but the focus of the demo was on the service and consumption, not a UI/UX design. The code behind the form is available on GitHub [here].
Fields
- Private fields allow the client to work with the following:
- Cancellation
- User selected file objects (from the ListBox control on the form)
- Stream buffer size
- Lists of local and remote file objects available
- Path to the local client directory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#region fields // Monitoring for Cancellation private CancellationTokenSource m_TokenSource = null; // Selected Remote File private static FileInformation selectedRemoteFile; // Selected Local File private static FileInformation selectedLocalFile; // Constant for Stream Buffer Size private const int BUFFER = 5000; // List of Current Local Files private static List<FileInformation> myLocalFiles = new List<FileInformation>(); // List of Current Remote Files private static List<FileInformation> remoteFiles = new List<FileInformation>(); // Private path string to local directory // In MyDocuments (Personal) Folder + FileManagerLocal private static string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "FileManagerLocal"); #endregion fields |
Properties
The Local Directory is encapsulated in a property allowing it to be retrieved only. If it does not exist, it will attempt to create a βFileManagerLocalβ folder in the userβs personal MyDocuments folder.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
#region properties // Local Directory Property // Example: C:\Users\kathl\OneDrive\Documents\FileManagerLocal public static string LocalDirectory { get { try { if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } return path; } catch (Exception ex) { throw new FaultException($"Cannot Create Directory {ex.Message}"); } } set { // Validate the Path if (Directory.Exists(value)) { path = value; } else { throw new FaultException("Invalid Directory Set"); } } } // end of Property #endregion properties |
Constructors
When the form is instantiated, it does some house keeping on the form GUI (ex: button enable/disable)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#region constructor public Client() { InitializeComponent(); // Initialize my GUI Buttons to Enabled UpdateGUIButtons(ServiceMenu.Load, true); // Update the Local Files UpdateLocalFiles(); // Clear the Remote Files lstRemoteFiles.Items.Clear(); } // end of constructor #endregion constructor |
Events
List Files Button Click
This updates the GUI, creates a new delegate, and passes action to the StartNewTask method (creates and starts a new task) with two parameters:
1.) the newly created action delegate pointing to the target method to call when the task starts and
2.) an enumeration defining the role as ServiceMenu.ListFiles (Listing Files)
Get File Button Click
This updates the GUI, creates a new delegate, and passes action to the StartNewTask method (creates and starts a new task) with two parameters:
1.) the newly created action delegate pointing to the target method to call when the task starts and
2.) an enumeration defining the role as ServiceMenu.GetFile (Get a File)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/// <summary> /// GetFile /// Processes the button click event /// </summary> /// <param name="sender">not used</param> /// <param name="e">not used</param> private void btnGetFile_Click(object sender, EventArgs e) { // Validate there is a file Selected if (lstRemoteFiles.SelectedIndex < 0) return; // Record which file was selected selectedRemoteFile = (FileInformation)lstRemoteFiles.SelectedItem; // Update the GUI Buttons for this menu action enum UpdateGUIButtons(ServiceMenu.GetFile, false); // Create Action Delegate // Target is the Method Name Action method = new Action(GetFile); // Start Task, Passing Action Delegate and // Menu Action Item StartNewTask(method, ServiceMenu.GetFile); } // end of method |
Refresh Local Directory Click
1 2 3 4 5 6 7 8 9 10 |
/// <summary> /// Local Refresh Button Click /// Processes the button click /// </summary> /// <param name="sender">not used</param> /// <param name="e">not used</param> private void btnLocalRefresh_Click(object sender, EventArgs e) { UpdateLocalFiles(); } |
Add File Button Click
This updates the GUI, creates a new delegate, and passes action to the StartNewTask method (creates and starts a new task) with two parameters:
1.) the newly created action delegate pointing to the target method to call when the task starts and
2.) an enumeration defining the role as ServiceMenu.AddFile (Add a File)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
/// <summary> /// AddFile /// Processes the button click event /// </summary> /// <param name="sender">not used</param> /// <param name="e">not used</param> private void btnAddFile_Click(object sender, EventArgs e) { // Validate there is a file Selected if (lstLocalFiles.SelectedIndex < 0) return; // Record which file was selected selectedLocalFile = (FileInformation)lstLocalFiles.SelectedItem; // Update the GUI Buttons for this menu action enum UpdateGUIButtons(ServiceMenu.AddFile, false); // Create Action Delegate // Target is the Method Name Action method = new Action(AddFile); // Start Task, Passing Action Delegate and // Menu Action Item StartNewTask(method, ServiceMenu.AddFile); } // end of method |
Cancel Button Click
This calls the cancel command on the cancellation token so any asynchronous tasks running should soon cancel.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/// <summary> /// Cancel /// Processes the button click event /// </summary> /// <param name="sender">not used</param> /// <param name="e">not used</param> private void btnCancel_Click(object sender, EventArgs e) { // if a valid token source is available then signal // to any listeners that the Task has been cancelled if (m_TokenSource != null) { // Send Cancel to Token m_TokenSource.Cancel(); } // end of if } // end of Cancel event |
Methods
StartNewTask
This creates, configures, and starts a new thread task for the requested service call. Includes continuation and cancellation option features which include UI cleanup/refresh.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
/// <summary> /// StartNewTask /// Starts a new thread task for the service request /// </summary> /// <param name="method">the method target (Action delegate)</param> /// <param name="menu">the type of request enum (ServiceMenu)</param> private void StartNewTask(Action method, ServiceMenu menu) { // set the CancelationTokenSource object to a new instance m_TokenSource = new CancellationTokenSource(); // When Tasks complete, TPL provides a mechanism to automatically // continue execution on a WinForm or WPF UI thread.To do this // we need a handle to the UI thread which weβll use later TaskScheduler ui = TaskScheduler.FromCurrentSynchronizationContext(); // start the Task, passing it the name of the method that // will be the entry point for the Task and the token used for cancellation: Task myServiceCallTask = Task.Run(method, m_TokenSource.Token); // first continuation call will execute only when the Task completes successfully // notifies the user by showing a message box then resetting the buttons to their // default configuration. Notice that the last parameter to ContinueWith is βuiβ. // This tells ContinueWith to execute the lambda statements to execute within // the context of the UI thread.No Invoke / BeginInvoke needed here. Task resultOK = myServiceCallTask.ContinueWith(resultTask => { // Update GUI After Task is Complete // Refresh the Remote Files UpdateRemoteFiles(); // Refresh the Local Files UpdateLocalFiles(); // Reset this menu action button UpdateGUIButtons(menu, true); // Refresh the Remote Directory if(menu == ServiceMenu.AddFile) { btnListFiles_Click(this, null); } }, CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion, ui); // second continuation call only executes if the task is cancelled Task resultCancel = myServiceCallTask.ContinueWith(resultTask => { // Update GUI After Cancellation // Refresh the Remote Files UpdateRemoteFiles(); // Refresh the Local Files UpdateLocalFiles(); // Reset this menu action button UpdateGUIButtons(menu, true); }, CancellationToken.None, TaskContinuationOptions.OnlyOnCanceled, ui); } // end of method |
CallService
This section of code probably should had been on a separate business process layer, but it is currently in the form code. This creates a ChannelFactory proxy to the File Manager Service based on the settings in the client application configuration file. Based on the given enumeration type (ServiceMenu) it switches to the desired service call code block.
AddFile
This calls the service method to add a file.
GetFile
This calls the service method to get a file.
ListFiles
This calls the service to get a list of files on the remote directory.
UpdateGUIButtons
This updates the UI buttons
UpdateLocalFile
This updates the ListBox control on the UI showing the list of files in the userβs local directory.
UpdateRemoteFiles
This updates the ListBox control on the UI showing the list of files in the remote directory.
ChannelFactory Proxy and Application Configuration
The application configuration file (App.config) specifies the service endpoint address, binding, and contract information for the client to create a proxy and connect. The service contract information is in the Shared Class Library that has the service interface. The code is available on GitHub [here].
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> </startup> <system.serviceModel> <bindings> <netTcpBinding> <binding name="netTcp" maxBufferPoolSize="60000000" maxBufferSize="60000000" maxReceivedMessageSize="60000000" transferMode="StreamedResponse"> <security mode="None" /> <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647"/> </binding> </netTcpBinding> </bindings> <client> <endpoint address="net.tcp://localhost:998/FileManagerService" binding="netTcpBinding" bindingConfiguration="netTcp" contract="SharedLibrary.IFileManagerService" name="NetTcpBinding_IFileManagerService" /> </client> </system.serviceModel> </configuration> |
Demo
Code
Permission to publish my coding project using class coding demo examples and lab projects was granted by the professor. However, most of the code in this project is original to the student.
The entire project code repository is available on GitHub [here].