topbanner_forum
  *

avatar image

Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
  • Thursday October 31, 2024, 7:10 pm
  • Proudly celebrating 15+ years online.
  • Donate now to become a lifetime supporting member of the site and get a non-expiring license key for all of our programs.
  • donate

Last post Author Topic: functional C# class/component to upload to my DC space via explicit FTP over TLS  (Read 35952 times)

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
This database would hold the submitted files?

mouser

  • First Author
  • Administrator
  • Joined in 2005
  • *****
  • Posts: 40,913
    • View Profile
    • Mouser's Software Zone on DonationCoder.com
    • Read more about this member.
    • Donate to Member
you wont be storing submitted files.  you will be storing HIGH SCORE DATA in a database.  And your desktop app won't be submitting FILES it will be submitting a simple http request with the users score submission data.

it's really much simpler than you are thinking, and it will all start to make sense as you learn about it.  but you'll need to stop thinking in terms of files moving around.
« Last Edit: May 14, 2011, 02:34 PM by mouser »

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Ah, I get it!  Yeah, I've been making it way harder than it is.  I keep thinking in terms of files because

(a) you'd send me a "file" via email
(b) my desktop apps deal with "files"

But all the webpage script needs is the data that's in the files.  Sweet.

So the database you'd eventually create would only be for my game's highscore data, right?  And later, if I had another project that needed such web-reciprocity, it'd use a different database (understanding, of course, that it might be possible that some apps could share certain databases, depending on circumstances).
« Last Edit: May 14, 2011, 02:40 PM by kyrathaba »

mouser

  • First Author
  • Administrator
  • Joined in 2005
  • *****
  • Posts: 40,913
    • View Profile
    • Mouser's Software Zone on DonationCoder.com
    • Read more about this member.
    • Donate to Member
Databases hold "tables" which are like the tables in a spreadsheet, with columns (fields) and rows.

So you can actually put lots of different tables for different projects in the same single database.

But in general people tend to use separate databases for different large projects.  So a wordpress blog, or this forum, uses one database to store all of its many tables, and that database does not contain tables for other projects.

For small experimental projects, you would probably use one database for all of them, using different tables for your different experiments.

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Do I want to install the three services shown in this setup screen?  Or will each be activated 'as needed' if I don't make them services?  In short, I don't mind installing them as services if they'll avoid headaches as I learn PHP.  But, if the differences would be transparent to me, I'd just soon not install them as services.

XAMPPoptions.png


worstje

  • Honorary Member
  • Joined in 2009
  • **
  • Posts: 588
  • The Gent with the White Hat
    • View Profile
    • Donate to Member
For PHP, you only need Apache to run a webserver.
MySQL is the most popular database which you'll probably need if you want to do more complicated stuff. (Although if you've got other databases available already, you can use those instead and won't need MySQL.)
Filezilla is a ftp server.

Since you only want to use the http protocol, you only need the first for a php development environment.

mouser

  • First Author
  • Administrator
  • Joined in 2005
  • *****
  • Posts: 40,913
    • View Profile
    • Mouser's Software Zone on DonationCoder.com
    • Read more about this member.
    • Donate to Member
you are going to need mysql (as well as php) for your development coding, but whether it has to be installed as a service is a different matter.

i'd say for getting started, just install both php and mysql as services.  then you can undo that later if you wish.

Deozaan

  • Charter Member
  • Joined in 2006
  • ***
  • Points: 1
  • Posts: 9,768
    • View Profile
    • Read more about this member.
    • Donate to Member
FYI for a "Portable" version of XAMPP:

XAMPP isn't in PortableApps.com Format, but it easily integrates with the PortableApps.com Suite by using the XAMPP Launcher 1.4 and installing XAMPP in the root directory of your portable device (as recommended).

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Thanks, folks!  Installing ............. 63% ......

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Look okay for what I'll need, just to begin learning and to complete the PHP lessons in Programming School?

XAMPPstatus.png

mouser

  • First Author
  • Administrator
  • Joined in 2005
  • *****
  • Posts: 40,913
    • View Profile
    • Mouser's Software Zone on DonationCoder.com
    • Read more about this member.
    • Donate to Member

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Thanks.




My first PHP program:

helloWorld_php.png

mouser

  • First Author
  • Administrator
  • Joined in 2005
  • *****
  • Posts: 40,913
    • View Profile
    • Mouser's Software Zone on DonationCoder.com
    • Read more about this member.
    • Donate to Member
having fun yet?

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Yeah, actually! :)

I'm coding a PHP-driven address book that'll use a webpage interfaced with a database to pull data from and push data to the pertinent table in a database...

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Okay, through experimentation and browsing some of the documentation, I've been able to:

(a) create a database containing a single table, using phpMyAdmin
(b) created a single table
(c) populated that table with five rows, consisting of three fields each: ID, DC username, and DC user's website URL

Then, I wrote a PHP/XHTML file to pull data from the database and display it.

The result is shown here, and I've attached the MySql database and my PHP code as a zip.  Am I on the right track?

dcUsersViaPHP.png

I've I'm not mistaken, this example is not much less complex than what I want to do with my game's highscores.  At least, as far as processing data within the while( ) loop in the PHP code.  Reckon I'd just need a bit more code in order to allow a user to submit data from my desktopApp game...
« Last Edit: May 14, 2011, 08:18 PM by kyrathaba »

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Awesomeness... :)

I've gotten the following example working.  I'm able to use the form's post method to add new data to the database and then refresh the webpage to update the html table with the added data (I added Renegade and hamradio's data solely using the web-form's post action):

successfulFormPostToDB.png

Project files developed using XAMPP are attached.

Fantastic!

Mouser, don't you think that what I'm wanting to do with 'highScore' data from the game I'm developing falls along the lines of what this example accomplishes?  Basically, I could have my game app programmatically use such a form post-action to send its data to an online database, right?

For instance, couldn't I replace...

$_POST['MyUsrName'];

with...

$_POST[183];

or

$_POST["Ben Franklin"];

as long as in place of the variable I'm using the type of value the database is expecting (int, string, etc)?
« Last Edit: May 14, 2011, 10:41 PM by kyrathaba »

Deozaan

  • Charter Member
  • Joined in 2006
  • ***
  • Points: 1
  • Posts: 9,768
    • View Profile
    • Read more about this member.
    • Donate to Member
Mouser, don't you think that what I'm wanting to do with 'highScore' data from the game I'm developing falls along the lines of what this example accomplishes?  Basically, I could have my game app programmatically use such a form post-action to send its data to an online database, right?

I know that was directed at mouser, but yeah, I do think that's basically what you want.

But you probably also want some sort of verification that it's coming from the game or that the score is legitimate so people don't just type in their own $_POST[189135105313546843]; to give themselves a cheater high score.

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Excellent point, Deozaan, and easy to do.  Thanks.

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Let's say I have a C# desktop WinForms application that wants to submit a 'DC_username' and 'User_website' just like the php code does in my above example.  How can the C# app silently (in other words, without requiring the user to type in these values on a form) send these to my update.php page so that it is indistinguishable from the form submit, as far as the php is concerned?

For example, I put the following code in a button's click event, in a C# test program.  It adds a row to the HTML table on my .../addressBook.php page, but both fields are blank:

           string URI = "http://localhost/addressBook/update.php";

            WebClient wc = new WebClient();
            NameValueCollection NC = new NameValueCollection();
            NC.Add("sampleUserName","sampleWebsiteURL");
            byte[] response = wc.UploadValues(URI,NC);
            string responseString = Encoding.ASCII.GetString(response);
            MessageBox.Show(responseString);

updatesTableWithBlanks.png

The update.php page is expecting to receive POST data like this:

$Usr = $_POST['MyUsrName'];
$UsrUrl = $_POST['MyUsrWebsite'];

Is there some way I need to change the format of the data I'm passing into NC above?  Obviously, my update.php page is receiving something, or it wouldn't update the HTML table with blank values.  I need need to figure out how to make my C# app send data in a way that the php recognizes it as the two post variables shown above, right?
« Last Edit: May 15, 2011, 09:38 AM by kyrathaba »

worstje

  • Honorary Member
  • Joined in 2009
  • **
  • Posts: 588
  • The Gent with the White Hat
    • View Profile
    • Donate to Member
Give me a few minutes to edit my JottiQ code to remove some passwords and other stuff you don't need, and you'll have a sample. :)

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Give me a few minutes to edit my JottiQ code to remove some passwords and other stuff you don't need, and you'll have a sample. smiley

Wonderful.  I'll check back in a couple hours.  Thanks!

worstje

  • Honorary Member
  • Joined in 2009
  • **
  • Posts: 588
  • The Gent with the White Hat
    • View Profile
    • Donate to Member
Here, code plus example-ish. You'll want to gut the stuff further.

XmlHelper.cs, gutted a fair amount and an excellent example of ugly code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Net;
using System.Xml;
using System.IO;

using System.Reflection;
using System.ComponentModel;
using System.Threading;

namespace JottiQ
{
    public enum HttpMethod
    {
        Get,
        Post,
    }

    public class UserCanceledException : Exception
    {
        public UserCanceledException()
            : base()
        {
        }

        public UserCanceledException(string message)
            : base(message)
        {
        }

        public UserCanceledException(string message, Exception inner)
            : base(message, inner)
        {
        }
    }

    class XmlHelper
    {
        static private NetworkCredential ApiCredentials = new NetworkCredential("Wouldn't_You", "Like_To_Know");
        static private string userAgent = null;

        public static string UserAgent
        {
            get
            {
                if (userAgent == null)
                {
                    System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
                    System.Reflection.AssemblyName name = assembly.GetName();
                    System.Version version = name.Version;

                    userAgent = name.Name + " v" + version.ToString();
                }

                return userAgent;
            }
        }

        public class FileUploadData : INotifyPropertyChanged, Status.IProgressStatusReporter
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public delegate bool CanContinueDelegate();

            private string filename;
            private Stream stream;
            private JottiItem ji;   /* TODO: Temporary. Please fix! */
            private CanContinueDelegate ccd;

            public string FileName
            {
                get { return this.filename; }
            }

            public Stream Stream
            {
                get { return this.stream; }
            }

            public JottiItem JI
            {
                get { return ji; }
            }

            public CanContinueDelegate CanContinue
            {
                get { return ccd; }
            }

            public FileUploadData(string filename, Stream stream, JottiItem ji, CanContinueDelegate canContinue)
            {
                this.filename = filename;
                this.stream = stream;
                this.ji = ji;
                this.ccd = canContinue;
            }

            protected void OnPropertyChanged(string name)
            {
                if (PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs(name));
                }
            }

            /* IProgressStatusReporter implementation */
            public long Min
            {
                get
                {
                    return 0;
                }
            }

            public long Max
            {
                get
                {
                    return (this.stream != null) ? this.stream.Length : 0;
                }
            }

            public long Progress
            {
                get
                {
                    return (this.stream != null) ? this.stream.Position : 0;//(this.stream.Position*10)/this.stream.Length : 0;
                }
            }

            public void Update()
            {
                OnPropertyChanged("Progress");
            }
        }

        static public XmlDocument getXmlResponse(string inURL, HttpMethod hm, Dictionary<string, object> fields)
        {
            /* HTTPWebRequest, XMLDocument */
            HttpWebRequest myHttpWebRequest;
            HttpWebResponse myHttpWebResponse;
            XmlDocument myXMLDocument;
            XmlTextReader myXMLReader;

            try
            {
                if ((hm == HttpMethod.Get) && (fields != null))
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append(inURL);
                    sb.Append("?");

                    foreach (KeyValuePair<string, object> pair in fields)
                    {
                        sb.Append(pair.Key);
                        sb.Append("=");
                        sb.Append(pair.Value);
                        sb.Append("&");

                    }

                    inURL = sb.ToString();
                }

                //Create Request
                myHttpWebRequest = (HttpWebRequest)HttpWebRequest.Create(inURL);
                myHttpWebRequest.Credentials = ApiCredentials;
                myHttpWebRequest.PreAuthenticate = true;
                myHttpWebRequest.UserAgent = UserAgent;

                if (hm == HttpMethod.Get)
                {
                    myHttpWebRequest.Method = "GET";
                    myHttpWebRequest.ContentType = "application/x-www-form-urlencoded; encoding='utf-8'"; //"text/xml; encoding='utf-8'";
                }
                else if (fields == null)
                {
                    /* If there's no parameters to send, we don't need to be difficult either. */
                    myHttpWebRequest.Method = "POST";
                }
                else
                {
                    myHttpWebRequest.Method = "POST";
                    // The value "multipart/form-data" should be used in combination with the INPUT element, type="file".

                    /* When we're uploading a good chunk of data, there is a huge chance of a
                     * KeepAlive bug crashing us halfway through. Disable those. */
                    myHttpWebRequest.KeepAlive = false;
                    myHttpWebRequest.Timeout = 6 * 60 * 60 * 1000;   // 6 hours should be enough for everyone.

                    /* Since we are using multipart/form-data, we need to be difficult with boundaries. */
                    string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");
                    myHttpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;

                    // Get the boundary in bytes
                    byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

                    // 1. Calculate total length of entire message (by fake-building it)
                    // 2. Send content-length.
                    // 3. Send actual multipart stuff. This avoids having a huge file upload in memory.

                    // --BOUNDARY
                    // Content-Disposition: form-data; name="<NAME>"
                    //
                    // sadfasdfasdf
                    // --BOUNDARY
                    // Content-Disposition: form-data; name="<NAME>"; filename="C:\file1.txt"
                    // Content-Type: application/octet-stream
                    // --BOUNDARY--

                    long contentLength = 0;
                    string fileTemplate = "Content-Disposition: form-data; name=\"{0}\";filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
                    string miscTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n";

                    foreach (KeyValuePair<string, object> pair in fields)
                    {
                        if (pair.Value is FileUploadData)
                        {
                            FileUploadData fud = (FileUploadData)pair.Value;
                            string fileHeader = string.Format(fileTemplate, pair.Key, fud.FileName);

                            //convert the header to a byte array
                            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(fileHeader);

                            contentLength += boundarybytes.Length + bytes.Length + fud.Stream.Length;
                        }
                        else
                        {
                            string miscHeader = string.Format(miscTemplate, pair.Key);
                            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(miscHeader);
                            contentLength += boundarybytes.Length + bytes.Length + pair.Value.ToString().Length;
                        }
                    }

                    contentLength += boundarybytes.Length + 2;   /* extra 2 -- at the end of final boundary */
                    myHttpWebRequest.ContentLength = contentLength;

                    /* Step 3. Send the actual data and stuff. */
                    Stream requestStream = myHttpWebRequest.GetRequestStream();
                    
                    foreach (KeyValuePair<string, object> pair in fields)
                    {
                        requestStream.Write(boundarybytes, 0, boundarybytes.Length);
                        
                        if (pair.Value is FileUploadData)
                        {
                            FileUploadData fud = (FileUploadData)pair.Value;
                            string fileHeader = string.Format(fileTemplate, pair.Key, fud.FileName);
                            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(fileHeader);
                            requestStream.Write(bytes, 0, bytes.Length);

                            // Use 4096*2 for the buffer / make that *4 to streamline IO over UI updates
                            byte[] buffer = new byte[4096*4];

                            int bytesRead = 0;
                            int bytesTotal = 0;
                            // Loop through whole file uploading parts in a stream.
                            while ((bytesRead = fud.Stream.Read(buffer, 0, buffer.Length)) != 0)
                            {
                                requestStream.Write(buffer, 0, bytesRead);
                                requestStream.Flush();

                                /* Temporary solution for live updates. */
                                bytesTotal += bytesRead;
                                fud.JI.StatusMessage = "Upload " + ((bytesTotal * 100) / fud.Stream.Length).ToString() + "% complete.";

                                fud.Update();
                                if (!fud.CanContinue())
                                    throw new UserCanceledException("Upload has been canceled by user.");
                            }
                            fud.JI.StatusMessage = "Upload done.";
                        }
                        else
                        {
                            string miscHeader = string.Format(miscTemplate, pair.Key);
                            byte[] bytes = System.Text.Encoding.UTF8.GetBytes(miscHeader);
                            requestStream.Write(bytes, 0, bytes.Length);

                            bytes = System.Text.Encoding.UTF8.GetBytes(pair.Value.ToString());
                            requestStream.Write(bytes, 0, bytes.Length);
                        }
                    }

                    // Write out the final boundary
                    boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");
                    requestStream.Write(boundarybytes, 0, boundarybytes.Length);

                    // Close our stream.
                    requestStream.Close();
                }

                //Get Response
                myHttpWebResponse = (HttpWebResponse)myHttpWebRequest.GetResponse();

                //Now load the XML Document
                myXMLDocument = new XmlDocument();

                //Load response stream into XMLReader
                myXMLReader = new XmlTextReader(myHttpWebResponse.GetResponseStream());
                myXMLDocument.Load(myXMLReader);
            }
            catch (Exception myException)
            {
                throw new Exception("Error Occurred in getXMLResponse()", myException);
                //return null;
            }
            finally
            {
                myHttpWebRequest = null;
                myHttpWebResponse = null;
                myXMLReader = null;
            }

            return myXMLDocument;
        }

    }
}

It works to the extent I need it to, which admittedly isn't much at all. :) You'll want the getXmlResponse() method, which I originally took from some online example and heavily adjusted to suit my own needs. For JottiQ fileuploads, I call it like this (in a seperate thread):

XmlHelper.FileUploadData fud = new XmlHelper.FileUploadData(
                            System.IO.Path.GetFileName(ji.FilePath),
                            fileStream, ji,
                            delegate() { return !_terminated; });
//getXmlResponseWrapper() wraps various error-handling stuffs.
XmlDocument xml = getXmlResponseWrapper(ji, true, inURL, HttpMethod.Post,
                new Dictionary<string, object>()
                    {
                        {"token", token},
                        {"scanfile", fud}
                    }
                    );

The entire passing of ji to the function or its upload components is a bit of a hack I used for updating the display while uploading; I am actually in the process of taking that out which is why the code is very bi-polar in nature right now. The delegate serves the purpose of making the upload process cancelable.

In the above example, the token is a string. scanfile acts like like one of those file upload controls. I ended up reinventing the wheel of implementing the POST protocol because I couldn't find any builtin libraries that actually did that already. (Also, file upload things only work for POST method since the data is too much to fit in a GET request.) In your example, you can probably suffice with a single call like:

XmlDocument xml = getXmlResponse(inURL, HttpMethod.Post,
                new Dictionary<string, object>()
                    {
                        {"user", getUsernameString()},
                        {"uid", getUniqueID()}
                    }
                    );

...and so forth. The code doesn't do much error-checking, or URL encoding of parameter names (& -> %26 or + -> %2B) and the sort since my needs were simple and very well-defined so that I knew I would not need to worry about those cases. That may not be the case for you however. Hell, you may not even want to implement your script to return XML data, so then you could gut the XML loading bit out too.

Code highlighting removed since the highlighter turns all the indenting into &#160; repetitions. I'll gladly put it back in when mouser fixes the highlighter.
« Last Edit: May 15, 2011, 10:19 AM by worstje »

kyrathaba

  • N.A.N.Y. Organizer
  • Honorary Member
  • Joined in 2006
  • **
  • Posts: 3,200
    • View Profile
    • Donate to Member
Thanks very much.  I'll see what I can do with this! :)