1

I have an FTP source that adds daily file and I need to copy the files on daily basis from FTP to blob storage using FluentFTP library in azure function

I am using C# in Azure function, and I did all the coding part but missing to download the file from FTP to copy it directly to the blob destination.

//#r "FluentFTP"
#r "Newtonsoft.Json"
#r "System.Data"
#r "Microsoft.WindowsAzure.Storage"

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Globalization;
using System;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using FluentFTP;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
    string blobConnectionString = "ConnectionString";

    // Gestione BLOB Storage
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(blobConnectionString);
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    CloudBlobContainer container = blobClient.GetContainerReference("out");

    using (FtpClient conn = new FtpClient()) 
    {
        conn.Host = "ftp server";
        conn.Credentials = new NetworkCredential("username", "pass");

        // get a list of files and directories in the "/OUT" folder
        foreach (FtpListItem item in conn.GetListing("/OUT")) 
        {
            // if this is a file and ends with CSV
            if (
            item.Type == FtpFileSystemObjectType.File
            &&
            item.FullName.ToLower().EndsWith(".csv")
            )
            {

                string yyyy = item.FullName.Substring(10,4);
                string mm   = item.FullName.Substring(14,2);
                string dd   = item.FullName.Substring(16,2);
                var fileName = "out/brt/" + yyyy + "/"+ mm + "/"+ dd + "/" + item.Name;

                CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);
                // download the file
                conn.DownloadFile( blockBlob , item.FullName);
            }
        }

        return new OkObjectResult($"Hello");
    }
}

If I can use a blob container as a destination for the FluentFTP function then it would be the best, but this way I am getting the error that this blob block that I am using is not an a destination

This is the error that I am getting

cannot convert from 'Microsoft.WindowsAzure.Storage.Blob.CloudBlockBlob' to 'string'

I don't know if there is another way to download the file locally so I can upload it to the blob instead of using this client.DownloadFile function.

Ahmed Essam
  • 69
  • 3
  • 13

3 Answers3

3

If you want to use FluentFTP, you can get a blob upload stream using one of these two methods:

  1. CloudBlockBlob.OpenWrite()
  2. CloudBlockBlob.OpenWriteAsync()

Then you can use the FTPClient.Download method which takes a Stream

public bool Download(Stream outStream, string remotePath, IProgress<double> progress = null)

Something like this:

[FunctionName("HttpTriggerCSharp")]
    public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        var ftpHost = "xxxxx";
        var ftpUserName = "xxxx";
        var ftpPassword = "xxxx";
        var filename = "xxxxx";            
        string blobConnectionString = "xxxxxxxxxxx";

        CloudStorageAccount storageAccount = CloudStorageAccount.Parse(blobConnectionString);
        CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
        CloudBlobContainer container = blobClient.GetContainerReference("xxxxx");


        FtpClient client = new FtpClient(ftpHost, ftpUserName, ftpPassword); // or set Host & Credentials
        client.EncryptionMode = FtpEncryptionMode.Implicit;
        client.SslProtocols = SslProtocols.None;
        client.ValidateCertificate += new FtpSslValidation(OnValidateCertificate);
        client.Connect();

        void OnValidateCertificate(FtpClient control, FtpSslValidationEventArgs e) {
            // add logic to test if certificate is valid here
            e.Accept = true;
        }

        CloudBlockBlob blockBlob = container.GetBlockBlobReference(filename);
        var outStream = blockBlob.OpenWrite();
        client.Download(outStream,filename);
        outStream.Commit(); // I'm not sure if this is needed?

        log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");


        return  (ActionResult)new OkObjectResult($"Action succeeded.");
    }
  • outStream.Commit is very needed, otherwise you end up with a 0b file in blob storage ;) – Kyle Jul 22 '21 at 15:06
  • In response to my previous comment, I just tested now with v12 of Azure Blob SDK and the commit is not needed to save the file. But it is needed for v11. – Kyle Jul 23 '21 at 06:45
2

It's probably not possible with Fluent FTP.

There's FtpClient.Download method which takes Stream.

public bool Download(Stream outStream, string remotePath, IProgress<double> progress = null)

But it does not look like there's API to get "blob upload Stream".

Conversely, you cannot get "FTP download Stream" from FluentFTP (which you would be able to use with blob API).


But you can use native .NET FtpWebRequest FTP client, which has API to get "FTP download Stream":

public static async System.Threading.Tasks.Task RunAsync([TimerTrigger("0 */1 * * * *")]TimerInfo myTimer, ILogger log)
{
    var ftpUserName = "xxxxxx";
    var ftpPassword = "xxxxxxxxx";
    var filename = "test.png";
    string blobConnectionString = "xxxxxxx";
    CloudStorageAccount storageAccount = CloudStorageAccount.Parse(blobConnectionString);
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    CloudBlobContainer container = blobClient.GetContainerReference("myblobcontainer");
    FtpWebRequest fileRequest = (FtpWebRequest)WebRequest.Create("ftp://xxxxxx/" + filename);
    fileRequest.Method = WebRequestMethods.Ftp.DownloadFile;
    fileRequest.Credentials = new NetworkCredential(ftpUserName, ftpPassword);
    FtpWebResponse fileResponse = (FtpWebResponse)fileRequest.GetResponse();
    Stream fileStream = fileResponse.GetResponseStream();
    CloudBlockBlob blockBlob = container.GetBlockBlobReference(filename);

    await blockBlob.UploadFromStreamAsync(fileStream);
    log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
}
Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
George Chen
  • 13,703
  • 2
  • 11
  • 26
0

You cannot directly move the files from the FTP to Blob using the SDK. You have to download the files temporarily first - either into a stream (depending how large they become) or to a temp file.

However, if all you want to do is to move files from FTP to Blob on a schedule, I would actually look into using Azure Data Factory which is built exactly for tasks like that: https://learn.microsoft.com/en-us/azure/data-factory/connector-ftp

silent
  • 14,494
  • 4
  • 46
  • 86
  • thank you for the answer, can u please give me some libraries or a hint for how to download them in a stream or in temp file (I did not do that before ) , about data factory, I already used it but I am investigating using the function instead cause the DF is not cheap. – Ahmed Essam Jul 02 '19 at 08:28
  • I haven't used a FTP library in C# (at least for a long time). But I would assume the FluentFtp library you are using there will have something like download. When you say Data Factory is not cheap, would do you have in mind in terms of pricing? ADF charges "Data movement activities: €0.211/DIU-hour". – silent Jul 02 '19 at 08:33
  • thank u for the clarification, I will re-check about the DF. – Ahmed Essam Jul 02 '19 at 08:41