Entwickler-Ecke

ASP.NET und Web - WebClient.DownloadDataAsync und Fortschritt


Delete - Mo 20.06.11 10:46
Titel: WebClient.DownloadDataAsync und Fortschritt
Ich mache einen FTP-Download mit DownloadDataAsync:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
void Start () {      
    ftpRequest = new WebClient();
    Debug.Log("Start: " + sourceFilePath + "/" + filename);
    System.Uri uri = new System.Uri(sourceFilePath + "/" + filename);
    try {
      ftpRequest.Credentials = new NetworkCredential(user, password);
      ftpRequest.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback);
      ftpRequest.DownloadDataCompleted += new DownloadDataCompletedEventHandler(DownloadCompleteCallback);
      ftpRequest.DownloadDataAsync(uri);      
    }
    catch(System.Exception e) {
      Debug.Log("Start: " + e.Message);
    }
  }
  
  void DownloadProgressCallback(System.Object sender, DownloadProgressChangedEventArgs e) {    
    bytesReceived += e.BytesReceived;    
    progressOutput = String.Format("{0:N} Bytes von {1:N} Bytes empfangen ({2:D}%)", bytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
    Debug.Log(progressOutput);
  }
  
  void DownloadCompleteCallback(System.Object sender, DownloadDataCompletedEventArgs e) {    
    try {
      byte[] data = (byte[])e.Result;
      if (data != null) {
        Debug.Log("DownloadCompleteCallback: " + destFilePath + "\\" + filename);
        FileStream fs = new FileStream(destFilePath + "\\" + filename, FileMode.Create);      
        fs.Write(data, 0, data.Length);      
        fs.Close();
      }
      else {        
        Debug.Log("DownloadCompleteCallback (data == null): " + e.Error);
      }
    }
    catch(System.Exception exception) {
      Debug.Log("DownloadCompleteCallback: " + exception.Message);
    }
  }

as funktioniert wunderbar. Nur leider sind die Werte e.TotalBytesToReceive -1 und entsprechend e.ProgressPercentage 0. am Webserver kann es nicht liegen, dass dieser die Daten nicht übermittelt, denn mit Filezilla wird ein Fortschritt angezeigt.

Muss ich da noch irgendwas beachten oder wo mache ich da noch einen Fehler?

Crosspost in der DP: http://www.delphipraxis.net/161114-webclient-downloaddataasync-und-fortschritt.html


jaenicke - Mo 20.06.11 10:49

Ich denke einmal dieser Teil aus der Doku ist für dich entscheidend:
Doku zu WebClient.DownloadProgressChanged-Ereignis [http://msdn.microsoft.com/de-de/library/system.net.webclient.downloadprogresschanged.aspx] hat folgendes geschrieben:
Bei einer Dateiübertragung mit passivem FTP wird immer ein Fortschrittsprozentsatz von 0 (null) angezeigt, da der Server keine Dateigröße sendet. Um den Fortschritt anzuzeigen, können Sie die FTP-Verbindung durch Überschreiben der virtuellen GetWebRequest-Methode in eine aktive Verbindung ändern:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
internal class MyWebClient:WebClient{
        protected override WebRequest GetWebRequest(Uri address) {
            FtpWebRequest req = (FtpWebRequest)base.GetWebRequest(address);
            req.UsePassive = false;
            return req;
        }
    }


Delete - Mo 20.06.11 11:01

Mist, dann habe ich den Hinweis übersehen. Aber ich habe Probleme das in meinen bestehenden Code einzubauen. Kannst du mir da noch mal helfen?


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
using UnityEngine;
using System;
using System.Collections;
using System.IO;
using System.Net;

public class NewBehaviourScript2 : MonoBehaviour {
  
  WebClient ftpRequest;
  private string progressOutput = "";
  private string user = "l3s11195";
  private string password = "!BSfBtrp#";
  private string filename = "BWL.pdf";
  private string sourceFilePath = "ftp://michael-puff.de/html/Archiv/Ausbildung_FIAE";  //http://michael-puff.de/Archiv/Ausbildung_FIAE/BWL.pdf
  private string destFilePath = "C:\\Dokumente und Einstellungen\\mp\\Eigene Dateien";
  private long bytesReceived;

  internal class ???.WebClient{
        protected override WebRequest GetWebRequest(Uri address) {
            FtpWebRequest req = (FtpWebRequest)base.GetWebRequest(address);
            req.UsePassive = false;
            return req;
        }
    }


Das ist erst mal so vorgegeben von Unity3D Engine.


jaenicke - Mo 20.06.11 12:00

Du kannst MyWebClient da stehen lassen oder etwas anderes, entscheidend ist, dass du statt new WebClient dann schreibst new MyWebClient oder ähnliches. ;-)


Delete - Mo 20.06.11 12:02

Ah, jetzt verstehe ich. Ich kann es zwar noch nicht in Worte fassen, aber es kommt langsam.


Delete - Mo 20.06.11 12:15

Jetzt scheine ich aber keine Verbindung zum Server zu bekommen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
using UnityEngine;
using System;
using System.Collections;
using System.IO;
using System.Net;


public class NewBehaviourScript2 : MonoBehaviour {

    internal class MyWebClient : WebClient
    {
        protected override WebRequest GetWebRequest(Uri address)
        {
            FtpWebRequest req = (FtpWebRequest)base.GetWebRequest(address);
            req.UsePassive = false;
            return req;
        }
    }

  MyWebClient ftpRequest;
  private string progressOutput = "";
  private string user = "???";
  private string password = "???";
  private string filename = "BWL.pdf";
  private string sourceFilePath = "ftp://michael-puff.de/html/Archiv/Ausbildung_FIAE";
  private string destFilePath = "C:\\Dokumente und Einstellungen\\mp\\Eigene Dateien";
  private long bytesReceived;  
  
  // Use this for initialization
  void Start () {      
    ftpRequest = new MyWebClient();
    Debug.Log("Start: " + sourceFilePath + "/" + filename);
    System.Uri uri = new System.Uri(sourceFilePath + "/" + filename);
    try {
      ftpRequest.Credentials = new NetworkCredential(user, password);
      ftpRequest.DownloadProgressChanged += new DownloadProgressChangedEventHandler(DownloadProgressCallback);
      ftpRequest.DownloadDataCompleted += new DownloadDataCompletedEventHandler(DownloadCompleteCallback);
      ftpRequest.DownloadDataAsync(uri);          
    }
    catch(System.Exception e) {            
      Debug.Log("Start: " + e.Message);
    }
  }
  
  void DownloadProgressCallback(System.Object sender, DownloadProgressChangedEventArgs e) {    
    bytesReceived += e.BytesReceived;    
    progressOutput = String.Format("{0:N} Bytes von {1:N} Bytes empfangen ({2:D}%)", bytesReceived, e.TotalBytesToReceive, e.ProgressPercentage);
    Debug.Log(progressOutput);
  }
  
  void DownloadCompleteCallback(System.Object sender, DownloadDataCompletedEventArgs e) {    
    try {
      byte[] data = (byte[])e.Result;            
      if (data != null) {
        Debug.Log("DownloadCompleteCallback: " + destFilePath + "\\" + filename);
        FileStream fs = new FileStream(destFilePath + "\\" + filename, FileMode.Create);      
        fs.Write(data, 0, data.Length);          
        fs.Close();
      }
      else {        
        Debug.Log("DownloadCompleteCallback (data == null): " + e.Error);
      }
    }
    catch(System.Exception exception) {
      Debug.Log("DownloadCompleteCallback: " + exception.Message);
    }
  }
}

Selbst wenn ich req.UsePassive = false; auf true setzte. An aktiv oder passiv kann es also nicht liegen. Die genau Fehlermeldung lautet:
Zitat:
DownloadCompleteCallback (data == null): System.Net.WebException: An error occurred performing a WebClient request. ---> System.IO.IOException: Not connected
at System.Net.Sockets.NetworkStream..ctor (System.Net.Sockets.Socket socket, FileAccess access, Boolean owns_socket) [0x00000] in <filename unknown>:0
at System.Net.Sockets.NetworkStream..ctor (System.Net.Sockets.Socket socket, Boolean owns_socket) [0x00000] in <filename unknown>:0
at (wrapper remoting-invoke-with-check) System.Net.Sockets.NetworkStream:.ctor (System.Net.Sockets.Socket,bool)
at System.Net.FtpWebRequest.OpenDataConnection () [0x00000] in <filename unknown>:0
at System.Net.FtpWebRequest.DownloadData () [0x00000] in <filename unknown>:0
at System.Net.FtpWebRequest.ProcessMethod () [0x00000] in <filename unknown>:0
at System.Net.FtpWebRequest.ProcessRequest () [0x00000] in <filename unknown>:0
--- End of inner exception stack trace ---
at System.Net.WebClient.DownloadDataCore (System.Uri address, System.Object userToken) [0x00000] in <filename unknown>:0
at System.Net.WebClient.<DownloadDataAsync>m__D (System.Object state) [0x00000] in <filename unknown>:0


jaenicke - Mo 20.06.11 12:30

Ich schaue es mir heute Abend an. Im Moment bin ich bei der Arbeit, da habe ich natürlich nicht die Zeit. ;-)

Aber vielleicht antwortet ja auch einer von den .NET Cracks hier vorher. :D


Andreas L. - Mo 20.06.11 12:32

@Luckie: Ich hoffe das ist nicht dein echtes Passwort:   private string password = "!BSfBtrp#"; :hair:


Delete - Mo 20.06.11 13:02

Nein. ;)


Delete - Di 21.06.11 09:37

Ich habe leider immer noch keinen Grund finden können, warum er jetzt sich jetzt nicht mehr verbindet.


jaenicke - Di 21.06.11 22:35

Sorry, ich bin gestern nicht mehr dazu gekommen.

Ich habe es jetzt gerade ausprobiert (mit Visual Studio 2010 und Windows Forms mit .NET 4.0 als Target). Da funktioniert der Code 1:1 wie du ihn zuletzt gepostet hast. :nixweiss:

Womit arbeitest du? Also welcher Projekttyp, welche .NET Version, ...?


Delete - Di 21.06.11 23:18

Gute Frage. Ich weiß es nicht. Das ist ein Script, welches vom Unity3D Spiele Engine ausgeführt wird. Keine Ahnung, ob das das installierte .Net Framework nutzt oder ob es ein eigenes mitbringt und dies veraltet ist. Ich weiß nur, dass es System.Console nicht unterstützt.

Gibt es eine Möglichkeit sich die nötigen Informationen von Hand vom FTP-Server zu holen und das dann selber auszurechnen?


jaenicke - Di 21.06.11 23:28

Funktioniert denn die ursprüngliche Version noch? Also ohne die überschriebene WebClient-Klasse?

Ich habe gerade mal ein wenig getestet, egal ob ich für .NET 2.0, 3.0 oder 4.0 kompiliere und ausführe, es klappt.

// EDIT:
Sonst fällt mir nur ein die Verbindung selbst zu machen, z.B. über die API [http://msdn.microsoft.com/en-us/library/aa384180.aspx] oder direkt über Sockets.


Delete - Di 21.06.11 23:55

Ja, die ursprüngliche Version funktioniert. Aber es ist auch im Moment nicht so wichtig. Ist erst mal auch nur ein Proof-of-concept.


Kha - Mi 22.06.11 12:47

Das dürfte Mono sein, WebClient.<DownloadDataAsync>m__D existiert in der CLR gar nicht. In der History auf Github sehe ich keinen Hinweis auf gefixte Bugs, zu alt kann es also auch nicht sein. Was passiert denn bei einem hamrlosen

C#-Quelltext
1:
2:
3:
4:
        protected override WebRequest GetWebRequest(Uri address)
        {
            return (FtpWebRequest)base.GetWebRequest(address);
        }
?
Auch wenn die Property [https://github.com/mono/mono/blob/master/mcs/class/System/System.Net/FtpWebRequest.cs#L293] mindestens genauso harmlos ausschaut :gruebel: ...


Delete - Do 23.06.11 09:23

Ja, ich habe im Unity3D Ordner einen Ordner Mono gefunden. Ich werde das am Montag mal ausprobieren.


Delete - Mo 27.06.11 10:36

Wenn ich den Code von Kha nehme, bekomme ich folgende Fehlermeldung:
Zitat:
Assets/FTPWebClientDownload.cs(13,35): error CS0115: `FTPWebClientDownload.GetWebRequest(System.Uri)' is marked as an override but no suitable method found to override

Zeile 13 ist die Zeile protected override WebRequest GetWebRequest(Uri address) {.

Und in der Entwicklungsumgebung wird mir angezeigt, dass
Zitat:
"UnityEngine.MonoBehaviour" enthält keine Definition für "GetWebRequest"
.


Kha - Mo 27.06.11 18:28

Ich meinte schon weiterhin innerhalb von MyWebClient ;) .


Delete - Mo 27.06.11 23:28

Ach so, morgen mal ausprobieren.


Delete - Di 28.06.11 09:53

Ich habe es jetzt so:

C#-Quelltext
1:
2:
3:
4:
5:
    internal class MyWebClient : WebClient {
        protected override WebRequest GetWebRequest(Uri address) {
            return (FtpWebRequest)base.GetWebRequest(address);
        }
    }

So kompiliert und läuft es zwar, aber ich bekomme immer noch keine Werte für die Gesamtgröße und den Fortschritt.


jaenicke - Di 28.06.11 11:15

Gut, und wenn du jetzt an dieser Stelle nur noch das mit UsePassive einbaust, was passiert dann?


Delete - Di 28.06.11 14:03

Werde ich morgen mal ausprobieren. Aber das Proof of concept ist erst mal abgeschlossen und liegt auf Eis. Aber trotzdem Danke für eure Hilfe.