Endpoint from scratch HowTo - Creating a new endpoint
Scenario: Get and put files via HTTP
You want to fetch a file with HTTP from a specific URL or POST files to a web page.
This example has been designed to work on an environment which has IIS 7 set up. A few tests have been written for the HttpEndPoint
. The example solution location can be found here.
The unit tests for the end point can be found in the Frends.Cobalt.Extensions.Tests project's HttpEndPointTests.cs
file and the integration tests are located in IntegrationTests/HttpEndPointWorkflowIntegrationTest.cs
file. The integration tests test the EndPoint with the Cobalt workflow. The requirements for the test environment can be found from the beginning of the test files.
Basics
All endpoints have to be derived from the abstract EndPointBase class. Which is defined as following:
001 | public interface IEndPointDetails |
004 | /// The name of the file (or file mask) that this endpoint is configured to use. Meant for reporting purposes. |
006 | string FileName { get ; } |
009 | /// The name of the directory this endpoint is configured to use. Meant for reporting purposes. |
014 | /// The possible server address this endpoint is configured to use. Meant for reporting purposes. |
016 | string Address { get ; } |
019 | /// The string representation of the type of transfer for the endpoint. Meant for reporting purposes. |
021 | string TransferType { get ; } |
025 | /// The common interface for all Cobalt endpoints. |
026 | /// This interface defines the methods needed for both source and destination endpoints |
028 | public abstract class EndPointBase : IEndPointDetails, IDisposable |
032 | /// Returns the name or description of EndPoint |
034 | /// <returns></returns> |
035 | public virtual string GetEndPointName(); |
037 | /// Deletes the given remote file. |
039 | ///<param name="remoteFile">The name of the remote file to remove. |
040 | /// If this is a relative path ("foo/file.txt") or just a file name ("file.txt"), it will be based on the current directory. |
041 | /// If you give an absolute path (that has a root, i.e. '/' or e.g. 'C:\'), it will be used as is |
043 | public abstract void Delete( string remoteFile); |
046 | /// Renames the remote file. |
047 | /// <remarks>This method is called when: |
048 | /// <list type="bullet"> |
049 | /// <item>Starting the transfer of a file: renames the source file with a unique name in order to lock the file during transfer</item> |
050 | /// <item>Executing the source file operation: Nothing renames the source temp file back to the original file name, Rename renames the source temp file to the given name</item> |
051 | /// <item>Rolling back the transfer (due to an error): The temporary source file is renamed back to the original file name to release it.</item> |
052 | /// <item>Ending the transfer: renames the temporary destination file (again, done to lock the file during transfer) to the final file name</item> |
057 | ///<param name="remoteFile"> |
058 | /// The name of the remote file to rename. |
059 | /// If this is a relative path ("foo/file.txt") or just a file name ("file.txt"), it will be based on the current directory. |
060 | /// If you give an absolute path (that has a root, i.e. '/' or e.g. 'C:\'), it will be used as is. |
062 | ///<param name="toFile"> |
063 | /// The new name of the file. |
064 | /// As with <see cref="remoteFile"/>, this can be either a relative or absolute path. |
066 | ///<returns>The full path to the renamed file (so it can be used in when executing the SourceOperation, Get etc.) </returns> |
067 | public abstract string Rename( string remoteFile, string toFile); |
070 | /// Moves the file to another directory |
072 | /// Used when executing the source file operation: Move moves the file to the given directory |
075 | /// <param name="remoteFile">Source file name</param> |
076 | /// <param name="toPath">Full path, with filename, where to move source file after transfer</param> |
077 | public abstract string Move( string remoteFile, string toPath); |
080 | /// Opens the endpoint connection. |
081 | /// This method must be called before using the endpoint, and |
082 | /// after this method has been called, the user should be able to call other methods of the endpoint |
083 | /// This can mean different things depending on the endpoint type, e.g. |
084 | /// opening a connection, logging in and setting the default directory |
086 | public abstract void Open(); |
089 | /// Closes the endpoint connection. |
090 | /// When this method has been called, all internal connections should be disconnected and resources released. |
092 | public abstract void Close(); |
095 | /// Gets a file from the endpoint. |
096 | /// Called for each file returned by <see cref="ListFiles"/> |
098 | /// <param name="remoteFile"> |
099 | /// The name of the file to fetch from the source. |
100 | /// Relative path ("foo/file.txt") or just a file name ("file.txt") will be based on the current directory. |
101 | /// Absolute path (that has a root, i.e. '/' or e.g. 'C:\') will be used as is. |
103 | /// <param name="localFilePath">The full path to the local temporary file to store the file to</param> |
104 | public abstract void Get( string remoteFile, string localFilePath); |
107 | /// Creates and uploads a file to the endpoint |
109 | /// <param name="sourceFile"> |
110 | /// The full path to the local temporary file to upload (the result of possible transforms etc.) |
112 | /// <param name="remoteFile"> |
113 | /// The name of the file to create on the remote endpoint. |
114 | /// Relative path ("foo/file.txt") or just a file name ("file.txt") will be based on the current directory. |
115 | /// Absolute path (that has a root, i.e. '/' or e.g. 'C:\') will be used as is. |
117 | public abstract void Put( string sourceFile, string remoteFile); |
120 | /// Appends the content of the local source file to the destination file |
122 | /// <param name="sourceFile">The full path to the local temporary file</param> |
123 | /// <param name="remoteFile">The name of the file to append to on the remote endpoint. |
124 | /// Relative path ("foo/file.txt") or just a file name ("file.txt") will be based on the current directory. |
125 | /// Absolute path (that has a root, i.e. '/' or e.g. 'C:\') will be used as is. |
127 | public abstract void Append( string sourceFile, string remoteFile); |
130 | /// Gets the list of files from the source endpoint that match the <see cref="FileName"/> mask. |
131 | /// The method skips all entries for the routine |
133 | /// <returns>The list of file details</returns> |
134 | public abstract IList<FileItem> ListFiles(); |
137 | /// Checks if the given file exists on the remote endpoint |
139 | /// <param name="remoteFilePath">Full path to the file to check</param> |
140 | /// <returns>True if the file exists</returns> |
141 | public abstract bool FileExists( string remoteFilePath); |
For this HTTP example, the most important methods to implement are:
- ListFiles - When used as a source endpoint, ListFiles
is called to return the list of files that match the file mask. The files
will then be transferred one by one. Because HTTP cannot list directory
contents, the method should just return the combined path for directory/file
name
- Get - When used as a source endpoint, Get is called to
actually transfer the file from the source location to the local, temporary
file on the server running the Cobalt transfer. For HTTP, this is a simple
GET command.
- Put - When used as a destination endpoint, Put is
called to transfer the local temporary file (fetched with Get and possibly
modified by message processing steps) to the destination location. For HTTP,
this could be the PUT or POST command. In this example, we use POST.
If an endpoint can not support some of the methods defined in the base, it should throw an exception if those methods are called. In our HTTP example these unsupported methods are:
- Delete
- Rename
- Move
- Append
These are not supported by HTTP alone and would have to have specific pages or web services to handle their functionality. The Put
functionality also needs help to function and we have provided a simple
example ASP .NET-page which can receive a file through a POST-request. This page can be found in the Frends.Cobalt.Extensions project.
HTTP example walkthrough
WebClientWrapper
We wrap the .NET class WebClient
in our own wrapper WebClientWrapper
which implements the interface IWebClientWrapper
to make it mockable.
01 | [assembly: InternalsVisibleTo( "Frends.Cobalt.Extensions.Tests" )] |
02 | namespace Frends.Cobalt.Extensions |
04 | public class WebClientWrapper : IWebClientWrapper |
06 | private WebClient _client; |
09 | public WebClientWrapper(WebClient client) |
24 | public ICredentials Credentials |
26 | get { return _client.Credentials; } |
27 | set { _client.Credentials = value; } |
30 | public byte [] DownloadData(Uri uri) |
32 | return _client.DownloadData(uri); |
35 | public byte [] UploadFile(Uri uri, string method, string file) |
37 | return _client.UploadFile(uri, method, file); |
41 | public interface IWebClientWrapper |
44 | ICredentials Credentials { get ; set ; } |
45 | byte [] DownloadData(Uri uri); |
46 | byte [] UploadFile(Uri uri, string method, string file); |
Dispose method
Since the EndPointBase interface implements IDisposable
we have to write a Dispose()
method which frees the resources reserved by the class. This means we should dispose of all disposable classes we have initialized. In our case this means the WebClient
.
01 | public override void Dispose() |
06 | private void Dispose( bool disposing) |
11 | if (_webClient != null ) |
The properties
In addition to the properties inherited from EndPointBase
class, HTTP endpoint has some other data stored in private fields as well. We get all of these from the TransferEndPointConfig
, which is initialized from the config-xml, except for the WebClient
.
1 | private readonly string _username; |
2 | private readonly string _password; |
3 | private readonly int _port; |
5 | private IWebClientWrapper _webClient; |
The constructors
There are two constructors, one to enable testing with a mocked WebClientWrapper
and another one to implement the EndPointBase
, which initializes a new WebClient
. TransferEndPointConfig
contains the basic connection information for the enpoint. If there were some extra parameters that we needed, we would parse them from the parameters string in the constructor, which is defined in the xml-configuration.
01 | internal HttpEndPoint(TransferEndPointConfig endPointConfig, IWebClientWrapper webClient) : base (endPointConfig) |
03 | _webClient = webClient; |
05 | TransferType = "Http" ; |
06 | _port = endPointConfig.ServerPort <= 0 ? 80 : endPointConfig.ServerPort; |
07 | _username = endPointConfig.ServerUsername ?? "" ; |
08 | _password = endPointConfig.ServerPassword ?? "" ; |
11 | SetPossibleCredentials(); |
14 | public HttpEndPoint(TransferEndPointConfig endPointConfig) : this (endPointConfig, new WebClientWrapper( new WebClient())) { } |
GetEndPointName -method
This returns a basic description of the endpoint, in our case we return the URL for the endpoint. It could contain more information, such as the file size if we knew it.
1 | public string GetEndPointName() |
3 | return GetUrl(FileName).ToString(); |
Delete -method
This is the delete method used for deleting files at the endpoint, the workflow uses this with Source operation: Delete
and Destination File Exists operation: Overwrite
. The delete operation is not possible with the HTTP-endpoint, so an exception is thrown if it is called.
1 | public void Delete( string remoteFile) |
4 | throw new NotSupportedException( "Deletion is not possible with HTTP Endpoint" ); |
Rename -method
The Rename -method is used while transferring files for locking purposes. Since renaming is not always possible with endpoints, it can be disabled in the configuration Paramaters
section with the RenameSourceFileBeforeTransfer
and RenameDestinationFileDuringTransfer
-settings. Rename is not supported by HTTP, so an exception is thrown if it is called.
1 | public string Rename( string remoteFile, string toFile) |
4 | throw new NotSupportedException( "Renaming is not possible with HTTP Endpoint" ); |
Move -method
The Move -method is used with the Source Operation: Move
to move the file that was transferred to another directory. This is not supported by the HTTP -endpoint so an exception is thrown when called.
1 | public string Move( string remoteFile, string toPath) |
4 | throw new NotSupportedException( "Moving files is not possible with HTTP Endpoint" ); |
Open/Close -methods
The open method is called when the workflow is started and close is called when the workflow has finished its transfer. They should contain the code for opening and closing the connection if needed. Our HTTP -endpoint does not need them because each transfer is handled in its own connection, so we do nothing in these methods.
GetUrl -utility method
This method does not belong to the interface and is used to build the url for the webpage. This is used to create the URI for the WebClient
and with the GetEndPointName
-method.
01 | internal Uri GetUrl( string remoteFile) |
03 | UriBuilder builder = new UriBuilder(); |
04 | builder.Host = Address; |
06 | builder.Scheme = "http" ; |
07 | builder.Path = Path.Combine(Dir, remoteFile); |
Get -method
The Get -method contains the code used for getting the files from the source endpoint. In our HTTP -endpoint we use the .NET WebClient
class to download the data from the URI.
1 | public void Get( string remoteFile, string localFilePath) |
3 | Uri uri = GetUrl(remoteFile); |
5 | var fileBytes = _webClient.DownloadData(uri); |
7 | File.WriteAllBytes(localFilePath, fileBytes); |
SetPossibleCredentials -utility method
This method does not belong to the interface and is used to setup the credentials to our WebClient
if either username
or password
is provided.
1 | private void SetPossibleCredentials() |
3 | if (!String.IsNullOrEmpty(_username) || !String.IsNullOrEmpty(_password)) |
4 | _webClient.Credentials = new NetworkCredential(_username, _password); |
Put -method
The Put
method contains the code used for sending the files to destination endpoint. The HTTP -endpoint simply uses the .NET WebClient
class to POST the file the URI. The response is discarded, but it could be used to detect errors.
1 | public void Put( string sourceFile, string remoteFile) |
3 | Uri uri = GetUrl(remoteFile); |
5 | var response = _webClient.UploadFile(uri, "POST" , sourceFile); |
Append -method
Append method is called if the Destination Exists operation: Append
is selected and the destination file exists(FileExists
returns true
). The method should contain the code to append the content of the sourcefile to the destination file. The sourcefile is the path to the temporary local file and remoteFile is the path to the file on the remote machine. The HTTP -endpoint does not support append operations so it throws an exception if called.
1 | public void Append( string sourceFile, string remoteFile) |
3 | throw new NotSupportedException( "Append is not supported by http" ); |
ListFiles -method
The ListFiles -method is used in the beginning of the workflow to retrieve the list of files to transfer from the source endpoint. It should handle matching the filenames to the Filemask
and only return an IList
of FileItems
which will be transferred. The HTTP -endpoint as a source endpoint supports only the retrieval of a single file, so it returns the filename that was provided in the config.
1 | public IList<FileItem> ListFiles() |
8 | return new List<FileItem>() { new HttpFileItem(FileName) }; |
FileExists -method
FileExists
is called when transferring files to the destination endpoint before each transfer. If it returns true
the workflow acts accordingly to the DestinationFileExists Operation
setting in the config. The HTTP -endpoint always returns false
because the destination page should always exist because it handles receiving the file data.
1 | public bool FileExists( string remoteFilePath) |
HttpFileItem -utility class
We create a new HttpFileItem
class that inherits the FileItem
class. We don't use the base class because most of the data defined is unavailable for us with the HTTP -endpoint. This is why we instantiate the FileItem
object with some default values.
01 | public class HttpFileItem : FileItem |
03 | public HttpFileItem( string fileName) |
06 | this .Modified = DateTime.MaxValue; |
Using the new module in FRENDS Cobalt
Step 1: Compile and deploy the assembly
In order to use the code with Cobalt you first need to compile it to an assembly and deploy the assembly somewhere FRENDS Iron can find the code.
This means either the \Program Files\Frends Technology\FRENDS Iron
-directory or the GAC
Step 2: Configure Cobalt
Once this is done, you need to configure Cobalt to use the new custom endpoint. To do this, just create a new Cobalt
connection point as described in the manual and:
- Change the
Source
endpoint type to "Custom"
- The filename has to be the actual filename of the file to get, it cannot contain masks, e.g.
index.htm
, text.txt
, order.xml
, but not *.txt
- Change the
Destination
endpoint type to "Custom"
- Change the
Directory
to the location of the page that will receive the files
- Under the
Custom
elements under Source
and Destination
:
- Set the
AssemblyName
to the name of the assembly containing the custom class, e.g.
Frends.Cobalt.Extensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
and ClassName
to the name of the endpoint class, e.g. Frends.Cobalt.Extensions.HttpEndPoint
- Leave the
Parameters
empty, as our custom endpoint does not take any parameters.
- Change the
RenameSourceFileBeforeTransfer
to false, because our source end point is a HttpEndPoint, which does not support renaming.
- Change the
RenameDestinationFileDuringTransfer
to false, because our destination end point is a HttpEndPoint, which does not support renaming.
Sample configuration which gets the file from http://localhost:80/test/test.txt and POSTs it to http://localhost:80/test/HttpFileReceiver.aspx.
You could also put the whole address+query to Directory field (http://localhost:80/test/test.txt) and leave FileName empty (server address and port will be ignored).
Step 3: Create and execute the file transfer routine
As in the manual create and execute file transfer routine.