Contact Me

Contact Us
For an informal discussion

Everyone's business is different. Our business is software - ask where we can help.
eMail: john at appsolo.com

WCF v ASP services for FTP access from Silverlight 4 Part 1

Posted on Thursday, 11th August, 2011

We  at Appsolo have been charged with writing a SL 4 app that requires access to an FTP server  for a CRM system that we are writing. The system will maintain licences for Flash based software products  our business client produces at the moment. This package of software artefacts are uploaded by our SL client to an FTP server. A HTTP link to the  licenced package is then produced to be emailed to the customers of our business client. This presents a number of  problems that had to be solved.

Firstly you cannot access FTP directly from a SL client as the ports used are not available to the browser.

Secondly the SL client runs in a UI thread, ASP service(s) runs in another thread(s). I have two HTTP based ASP services. A first to create an FTP directory to place the files in. Upload speed needs to be addressed. A second ASP HTTP service to handle the streamed upload of the file. The timing of Threading of all these needs to handled.

Thirdly is the problem of reporting back to the SL client that the File has been delivered to the FTP server having first stopped off at the ASP service on the way. A multi streamed upload HTTP service as is used here is quick as it threads to handle requests. But you cannot access the HTTP response from the SL client as it is write and not a read. Also you do not want to tie up the client waiting for the slower FTP service to complete as called from the ASP service. I integrated a WCF Duplex Polling Service handle the polling of the FTP directory to report on the Files delivered and to decide when the upload is finished.

I’ll discuss things here that I found out in the course of bring this all together.

So part of the solution is to use an ASP based HTTP file streaming (WCF is not really built for this  type of service – although it can be done) service to upload the files to a staging area Directory on the host website. We also needed to create a directory to store the files in on the FTP Server. This is where it starts to get tricky. You want to make sure that the directory is created before you start sending Files to it (or check if it exists already). So on the SL client your have to have nested delegates

NOTE: there a lot of different ideas mixed in here that I got from various people out there, some of which I cannot remember. I’ll list all the references at the end of the posts.

   1:  // Host value is set in ServiceReferences.ClientConfig
   2:                  string hostbase = getAppSetting("Host");
   3:                  UriBuilder packagehandlerUrl = new UriBuilder(hostbase + "FTPSetupPackageFolder.ashx");
   4:                  string FTPDirName = "FTPpath=" + PackageTextBox.Text;
   5:                  packagehandlerUrl.Query = FTPDirName;
   6:                  WebClient FTPCreateDir = new WebClient();
   7:                  // Call asp handler
   8:                  FTPCreateDir.OpenReadAsync(packagehandlerUrl.Uri);
   9:                  // Read the response sent back from the packagehandlerUrl call
  10:                  FTPCreateDir.OpenReadCompleted += (read, ev) =>
  11:                  {
  12:                      int len = (int)ev.Result.Length;
  13:                      byte[] b = new byte[len];
  14:                      ev.Result.Read(b, 0, len);
  15:                      // Check the return message to see if directory already exists and report
  16:                      // Could put choice here
  17:                      if(len == 0)
  18:                          MessageBox.Show("Directory already exists ");
  19:                      else MessageBox.Show("Directory Created " + System.Text.UTF8Encoding.UTF8.GetString(b,0,len) );
  20:                      txtMessage.Text = "FTP Uploading........";
  21:                      // Kick off FTP service Listing operation See end of this file
  22:                      FtpListing();
  23:                      // Do Upload
  24:                      #region HTTP Handled Streamed Upload
  25:                      foreach (FileInfo file in filesToUpload)
  26:                      {
  27:                          // This section uses Asynchronous streamed uploading to upload the selected Files
  28:                          UriBuilder FilehandlerUrl = new UriBuilder(hostbase + "UploadFileHandler.ashx");
  29:                          string InputFile = "InputFile=" + file.Name;
  30:                          FilehandlerUrl.Query = InputFile + "&" + FTPDirName;
  31:   
  32:                          string fileName = file.Name;
  33:                          FileStream FsInputFile = file.OpenRead();
  34:                          WebClient webClient = new WebClient();
  35:                          //Now make an async class for writing the file to the server
  36:                          //Here I am using Lambda Expression
  37:                          webClient.Encoding = System.Text.UTF8Encoding.UTF8;
  38:                          webClient.OpenWriteCompleted += (sent, evt) =>
  39:                          {
  40:                              try
  41:                              {
  42:                                  // Do the actual streamed Upload which goes off on it's merry way
  43:                                  // and we continue
  44:                                  UploadFileData(FsInputFile, evt.Result);
  45:                                  evt.Result.Close();
  46:                                  FsInputFile.Close();
  47:                              }
  48:                              catch (Exception ex)
  49:                              { MessageBox.Show(ex.Message); }
  50:   
  51:                          };
  52:                          webClient.OpenWriteAsync(FilehandlerUrl.Uri);
  53:   
  54:                      }
  55:                      #endregion
  56:                  };

 

Lines 2 and 3 retrieve information from the ServiceReferences.ClientConfig file that is a handy place for storing Host based information which can be changed at deployment time after development and testing.

The contents if the ServiceReferences.ClientConfig XML file are something like this

   1:  <configuration>
   2:    <appSettings>
   3:      <add key="Host" value="http://[YourHost]/FTPFileServer/" />
   4:      <add key="FileServiceEndPoint" value="http://[YourHost or DifferentHost]/FTPFileServer/" />
   5:    </appSettings>
   6:  </configuration>

The associated Function to read this configuration file

   1:  private string getAppSetting(string strKey)
   2:          {
   3:              string strValue = string.Empty;
   4:              XmlReaderSettings settings = new XmlReaderSettings();
   5:              settings.XmlResolver = new XmlXapResolver();
   6:              XmlReader reader = XmlReader.Create("ServiceReferences.ClientConfig");
   7:              reader.MoveToContent();
   8:              while (reader.Read())
   9:              {
  10:                  if (reader.NodeType == XmlNodeType.Element && reader.Name == "add")
  11:                  {
  12:                      if (reader.HasAttributes)
  13:                      {
  14:                          strValue = reader.GetAttribute("key");
  15:                          if (!string.IsNullOrEmpty(strValue) && strValue == strKey)
  16:                          {
  17:                              strValue = reader.GetAttribute("value");
  18:                              return strValue;
  19:                          }
  20:                      }
  21:                  }
  22:              }
  23:              return strValue;
  24:          }
  25:      }

Lines 5 to 20 call and read the response from FTPSetupPackageFolder.ashx to find out if the Directory existed or not.

The ASHX Service to handle this call on the server is….

public void ProcessRequest(HttpContext context)
        {
            string FtpRootDir = context.Request.QueryString["FTPpath"].ToString();
            string FtpBaseAddress = null;
            string FtpUserName = null;
            string FtpPassword = null;
            if (System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"] != null)
                FtpBaseAddress = System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"].ToString();
            //Get FTP Credential settings
            if (System.Configuration.ConfigurationManager.AppSettings["FtpUserName"] != null)
                FtpUserName = System.Configuration.ConfigurationManager.AppSettings["FtpUserName"].ToString();
            if (System.Configuration.ConfigurationManager.AppSettings["FtpPassword"] != null)
                FtpPassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();

            NetworkCredential credentials = new NetworkCredential(FtpUserName, FtpPassword);
            string DirName = FtpBaseAddress;
            string WorkingDirectory = DirName + FtpRootDir + "/";
            if (!FtpDirectoryExists(credentials, WorkingDirectory))
            {
                FtpWebRequest setupDir = (FtpWebRequest)WebRequest.Create(WorkingDirectory);
                setupDir.Method = WebRequestMethods.Ftp.MakeDirectory;
                setupDir.Credentials = credentials;
                setupDir.Timeout = -1; // Don't timeout for Debugging and slow connection
                FtpWebResponse dirResponse = (FtpWebResponse)setupDir.GetResponse();
                context.Response.Write(dirResponse.StatusDescription.ToString());
                dirResponse.Close();
            }

        }

Again the web.config XML file on the server side holds the relevant values for flexibility of deployment.

Line 22 calls the WCF service to start reporting on delivery of Files to the created directory See next post.

Line 25 to 52 then uploads whatever files are in the FilesToUpload collection in separate threads. It calls the function to open read and write the appropriate streams to the HTTP service

private void UploadFileData(Stream inputFile,Stream resultFile)
        {
            try
            {
                byte[] fileData = new byte[4096];
                int fileDataToRead;
                while ((fileDataToRead = inputFile.Read(fileData, 0, fileData.Length)) != 0)
                {
                    resultFile.Write(fileData, 0, fileDataToRead);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Exception Thrown --> " + ex.Message);
            }
        }

The ASHX HTTP service to handle the uploading stream is as follows

public void ProcessRequest(HttpContext context)
        {

                ctx = context;
                //Query Parameters
                string filename = context.Request.QueryString["InputFile"].ToString();
                string FtpRootDir = context.Request.QueryString["FTPpath"].ToString();
                // Local HTTP Storage folder for staging to FTP upload
                string filePath = context.Server.MapPath("~/FilesServer/");
                // Get the Base address of the FTP server folder from the web.Config
                string  FtpBaseAddress = null;
                string FtpUserName = null;
                string FtpPassword = null;

                // Upload via HTTP stream initiatied by client
                using (FileStream fileStream = File.Create(context.Server.MapPath("~/FilesServer/" + filename)))
                {

                    byte[] bufferData = new byte[4096];
                    int bytesToBeRead;
                    while ((bytesToBeRead = context.Request.InputStream.Read(bufferData, 0, bufferData.Length)) != 0)
                    {
                        fileStream.Write(bufferData, 0, bytesToBeRead);
                    }
                    fileStream.Close();
                }
                try
                {
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"] != null)
                        FtpBaseAddress = System.Configuration.ConfigurationManager.AppSettings["FtpBaseFolder"].ToString();
                    //Get FTP Credential settings
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpUserName"] != null)
                        FtpUserName = System.Configuration.ConfigurationManager.AppSettings["FtpUserName"].ToString();
                    if (System.Configuration.ConfigurationManager.AppSettings["FtpPassword"] != null)
                        FtpPassword = System.Configuration.ConfigurationManager.AppSettings["FtpPassword"].ToString();

                    // Create logon Credential
                    NetworkCredential credentials = new NetworkCredential(FtpUserName, FtpPassword);
                    string DirName = FtpBaseAddress;
                    string WorkingDirectory = DirName + FtpRootDir + "/";
                    // Upload File
                    FtpWebRequest request = (FtpWebRequest)WebRequest.Create(WorkingDirectory + filename);
                    request.Method = WebRequestMethods.Ftp.UploadFile;
                    request.Credentials = credentials;
                    request.Timeout = -1; // never timeout incase of slow connection
                    // Copy the contents of the file to the request stream.
                    byte[] bufferData = new byte[7168];
                    int length = bufferData.Length;
                    using (FileStream fileStream =
                        new FileStream(context.Server.MapPath("~/FilesServer/" + filename)
                            , FileMode.Open, FileAccess.Read, FileShare.Read, length, false)) // Last argument should block thread
                    {
                        Stream requestStream = request.GetRequestStream();
                        int bytesToBeRead;
                        while ((bytesToBeRead = fileStream.Read(bufferData, 0, bufferData.Length)) != 0)
                        {
                            requestStream.Write(bufferData, 0, bytesToBeRead);
                        }
                        requestStream.Flush(); // Flush any outstanding buffer
                        requestStream.Close();
                        fileStream.Close();
                        File.Delete(context.Server.MapPath("~/FilesServer/" + filename)); //

                    }
                    //context.Response.Write(filename + "Uploaded");     no can do it's a writable stream processor
                }
        

In the Next Post I’ll Cover the Duplex Polling WCF service to poll the FTP folder to check if the files have arrived at the FTP site.

That’s all the Time I have for now. Cheers. P.

Comments are closed.

latest news

Running a web site hosting SL4 OOB on local host using II express

Posted on Saturday, 4th February, 2012

Free Tools available:
IIS Express free download
Visual Studio 2010 express free download (there might be a licensing issue here)
or Visual Web developer express 2010
SQL Server 2008  Express R2 free download (2010 version is available in RC0 version)
Making sure that IIS Express is installed you can develop against it
So you create your web site and client [...]

Testimonials

Excellent design skills

Posted on Sunday, 2nd May, 2010

We at Taxonomy.ie are happy to be associated with Appsolo and look forward to further work together.

follow me

twitter facebook delicious

AppsoloLtd. VAT No. IE97548691 - Copyright © 2010.