1
votes

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.

3

3 Answers

2
votes

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}");
}
3
votes

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.");
    }
0
votes

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://docs.microsoft.com/en-us/azure/data-factory/connector-ftp