Un controllo per l'upload multiplo di file con Silverlight 4.0

di Alessio Leoncini, in Silverlight 3.0,

Con la classe HttpWebRequest possiamo creare richieste Http ed inviare una serie di byte corrispondenti ad un file, per eseguirne il salvataggio su file system: il cosìddetto upload.
Grazie all'oggetto OpenFileDialog possiamo aprire una finestra di dialogo sul disco dell'utente e scegliere i file da inviare.

C#
OpenFileDialog dialog = new OpenFileDialog();
dialog.Multiselect = true;

if (dialog.ShowDialog() == true)
{
    Dispatcher.BeginInvoke(() =>
    {
        UploadingItemContainer.Children.Clear();
        oList.Clear();

        //recupero info sui file selezionati dall'utente
        foreach (FileInfo item in dialog.Files)
        {
            nItems++;
            UploaderItem uItem = new UploaderItem();

            Obj o = new Obj();

            using (System.IO.FileStream fileStream1 = item.OpenRead())
            {
                int streamLenght1 = (int)fileStream1.Length;
                o.Buffer = new byte[streamLenght1];
                o.Chunk = o.Buffer.Length / o.Total;
                fileStream1.Read(o.Buffer, 0, streamLenght1);
            }

            //nome del file da salvare sul server
            o.ItemName = String.Format("{0}{1}{2}", DateTime.Now.Ticks.ToString(), item.Name, item.Extension);
                       
            uItem.ImageName.Text = item.Name; UploadingItemContainer.Children.Add(uItem);
                        
            o.UploaderItem = uItem;
            oList.Add(o);
        }
    });
}
Quello che vogliamo realizzare è avere la possibilità di selezionare più file ed eseguirne l'upload contemporaneamente, quindi, per ogni file selezionato andiamo ad aprire il FileStream corrispondente, recuperiamo i byte ed andiamo ad impostare i pacchetti e nome del file
utilizzando un nostro oggetto di appoggio, il quale contiene anche un'istanza dell'elemento grafico che useremo per visualizzare lo stato di avanzamento dell'upload, nel nostro esempio si tratta di un semplice UserControl con un TextBlock ed un Rectangle.
Ciascuno di questi oggetti viene memorizzato in una lista generica per essere elaborato successivamente in sequenza, mentre l'elemento grafico viene aggiunto all'interfaccia all'interno di uno ScrollViewer.

Alla pressione del tasto "Salva" scorriamo la lista e iniziamo l'upload di ciascun elemento sfruttando il modello asincrono di HttpWebRequest, come da snippet seguente:


C#
private void SaveImageButton_Click(object sender, RoutedEventArgs e)
{
    if (oList.Count > 0)
    {
        SaveImageButton.Content = "Caricamento in corso..";
        SaveImageButton.IsEnabled = false;
    }

    foreach (Obj o in oList)
    {
        UploadImage(o);
    }
}

private void UploadImage(Obj o)
{
    Uri uri = new Uri(String.Format(
                remoteUploadPage
            , o.Total
            , o.Current
            , o.ItemName));

    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = "POST";

    CallState state = new CallState() { Request = request, Item = o };

    //richiesta di apertura dello stream sul server
    request.BeginGetRequestStream(new AsyncCallback(RequestCallBack), state);
}

Il metodo BeginGetRequestStream richiede l'apertura dello stream sul
server, ad operazione avvenuta viene richiamato il metodo di callback che inizia
effettivamente la scrittura dei byte nello stream:


C#
private void RequestCallBack(IAsyncResult asyncResult)
{
    CallState state = asyncResult.AsyncState as CallState;
    Obj o = state.Item;
    HttpWebRequest request = state.Request;

    using (Stream postedStream = request.EndGetRequestStream(asyncResult))
    {
        int offset = (o.Current - 1) * o.Chunk;
        int count = o.Total == o.Current ? o.Buffer.Length / o.Total + o.Buffer.Length % o.Total : o.Buffer.Length / o.Total;
        //scrittura nello stream dei byte di ciascun pacchetto
        postedStream.Write(o.Buffer, offset, count);
    }
    request.BeginGetResponse(new AsyncCallback(ResponseCallBack), state);
}

Come da requisito, vengono scritti solo i byte corrispondenti ad un pacchetto; lato server possiamo utilizzare una qualunque procedura che possa recuperare tali dati, che sia un HttpHandler o un WebService. Nel nostro esempio utilizziamo una semplice pagina ASPX con il seguente codice:



C#
private void Page_Load(object sender, System.EventArgs e)
{
    string physicalApplicationPath = HttpContext.Current.Request.PhysicalApplicationPath;

    if (Request.QueryString["total"] != null &&
        Request.QueryString["current"] != null &&
        Request.QueryString["filename"] != null)
    {
        int current = Convert.ToInt32(Request.QueryString["current"]);
        int total = Convert.ToInt32(Request.QueryString["total"]);
        string filename = Convert.ToString(Request.QueryString["filename"]);
        filename = HttpUtility.HtmlDecode(filename);

        string path = "Images";

        string filePath = String.Format("{0}\\{1}\\{2}", physicalApplicationPath, path, filename);

        int bufferLenght = (int)Request.InputStream.Length;
        byte[] buffer = new byte[bufferLenght];
        Request.InputStream.Read(buffer, 0, bufferLenght);

        if (current == 1)
        {
            using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Write))
            {
                fileStream.Write(buffer, 0, bufferLenght);
            }
        }
        else
        {
            using (FileStream fileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Write))
            {
                fileStream.Write(buffer, 0, bufferLenght);
            }
        }

        Response.Clear();
        Response.End();
    }
    else
    {
        Response.Clear();
        Response.Write("error");
        Response.End();
    }
}
Nel codice non facciamo altro che recuperare nome del file e numero del pacchetto in querystring, leggiamo i byte dalla proprietà InputStream dell'oggetto Request che andiamo a scrivere
su disco attraverso un oggetto FileStream.
Al termine dell'operazione nel metodo ResponseCallBack andiamo ad aggiornare l'interfaccia con lo stato di avanzamento, oppure segnaliamo eventuali errori.
C#
private void ResponseCallBack(IAsyncResult asyncResult)
{
    CallState state = asyncResult.AsyncState as CallState;
    Obj o = state.Item;
    HttpWebRequest request = state.Request;

    HttpWebResponse response = request.EndGetResponse(asyncResult) as HttpWebResponse;
    Stream responseStream = response.GetResponseStream();
    StreamReader reader = new StreamReader(responseStream);

    string result = reader.ReadToEnd();

    if (String.IsNullOrEmpty(result))
    {
        //aggiornamento dell'interfaccia utente per la progressione di upload
        Dispatcher.BeginInvoke(() => { o.UploaderItem.UploadingImage.Width = 30 * o.Current; });

        o.Current++;

        if (o.Current <= o.Total)
        {
            UploadImage(o);
        }
        else
        {
            Dispatcher.BeginInvoke(() =>
            {
                ResetUploadingImageBar(o);
            });
        }
    }
    else
    {
        Dispatcher.BeginInvoke(() =>
        {
            ResetUploadingImageBar(o);
            o.UploaderItem.ImageName.Text = "errore";
        });
    }
}
Il tutto avviene utilizzando il Dispatcher per mettere in comunicazione il thread di risposta a quello dell'interfaccia utente.
In allegato è possibile trovare il codice completo e commentato nelle parti più importanti, ovviamente può essere ottimizzato ad esempio nel ridurre l'utilizzo della memoria in fase di lettura dei byte dei file.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi