Windows Phone 使用 HttpWebRequest 对象,通过POST 方式上传图片

 

       Windows Phone 客户端有时候需要把用户的图片保存到服务器端。本示例讲解如果把用户的头像,通过表单传输的方式,把用户的

头像传递到 Web 端。当前的工程选择的是 OS7.1,在 WP8上通用,但需要注意的一点是,当前测试工程的 Web 端的地址是回环地址:

localhost,所以如果在 WP8 的模拟器或者真机中测试时,需要真实 IP 地址,并且需要进行一些 IIS 的配置,这里就不多讲了。

      工程代码下载

 

   1、 首先写一个 Web 端 demo,固定端口号 10000,以便客户端可以调用该上传接口 API。

    1)定义一个名为 PhotoUploadController.ashx 的一般处理程序,用来接收用户的表单字段,并且把用户上传的图片保存到本地的 Images 文件夹中:

namespace MyWebSite
{
    /// <summary>
    /// 处理用户上传的图片,保存到网站的 Images 文件夹中
    /// </summary>
    public class PhotoUploadController : IHttpHandler
    {
        public void ProcessRequest(HttpContext context)
        {
            context.Response.ContentType = "text/plain";

            // 假设用户登录完成后,服务器端分配给用户的 token 凭据
            string access_token = "9c1302d0yarfasdfw3r234wgetr3qwt";
            
            if (context.Request.Params.AllKeys.Contains("access_token"))
            {
                // 判断该用户是否有图片上传的权限
                if (access_token == context.Request["access_token"])
                {
                    // 获取图片
                       HttpPostedFile file = context.Request.Files[0];
                    String fileName = System.IO.Path.GetFileName(file.FileName);
                    file.SaveAs(context.Server.MapPath("~/") + "Images\\" + fileName);

                    // 操作成功,返回给客户端
                    context.Response.Write("ok");
                }
            }
            else
            {
                context.Response.Write("no access_token");
            }
        }

        public bool IsReusable
        {
            get
            {
                return false;
            }
        }
    }
}


    2)然后定义一个 Default.aspx 文件,用来显示用户上传到服务端的图片,读取站点 Images 文件夹中的 photo.jpg 文件:

<body style="background:#eaf0f3">
    <form id="form1" runat="server">
    <div style="width:400px;margin:0px auto; background:#dae7ef; padding:40px">
    <div style="text-align:center;color:#000000; font-size:25px">
        用户的头像
    </div>
        <div style="text-align:center; margin:40px 0px 40px 0px;">

            <!--显示 Images 文件夹中的头像文件-->
            <img src="/Images/photo.jpg" width="200" alt="没有头像" height="200" />
        </div>
    </div>
    </form>
</body>

    当站点的 Images 文件夹中没有图片时,在 IE10 中的显示效果:

 

2、编写客户端逻辑代码。

    1)用户图像的裁剪

        定义一个 PhotoCutPage.xaml 页面,引入 Microsoft.Phone.Controls.Toolkit 命名空间,使用其中的

  GestureListener 来处理用户的缩放、移动等手势操作:

 xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"


页面的 xaml :

    <!--LayoutRoot 是包含所有页面内容的根网格-->
    <Canvas x:Name="LayoutRoot" Background="Transparent" Tap="cutCanvas_Tap">
       
        <!--该 Canvas 对象用来作为 WriteableBitmap 构造函数的 Element,来获取裁剪的图片-->
        <Canvas Width="360" Height="360" x:Name="cutCanvas" Canvas.Left="60" Canvas.Top="220" >
            <Image x:Name="imgSrc"  Stretch="UniformToFill"> <!--用户从图片库选择的图片-->
                <Image.RenderTransform>
                    <CompositeTransform x:Name="transform" />
                </Image.RenderTransform>
                
                <!--处理用户的拖拽、缩放等手势操作--> 
                <toolkit:GestureService.GestureListener>                   
                        <toolkit:GestureListener PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" 
DragDelta
="Postcard_ManipulationDelta" PinchCompleted="GestureListener_PinchCompleted"
DragCompleted
="GestureListener_DragCompleted"/> </toolkit:GestureService.GestureListener> </Image> </Canvas> <!--中间透明,四周半黑色透明的 .png 蒙板--> <Image Source="/Images/CutMaster.png"/> <Button Content="确定" Canvas.Left="259" Canvas.Top="707" Width="192" Click="Save_Click"/> <Button Content="取消" Canvas.Left="50" Canvas.Top="707" Width="192" Click="Cancel_Click"/> <!--及时显示给用户在进行完缩放、拖拽后的截图结果--> <Border x:Name="border" Visibility="Collapsed" HorizontalAlignment="Left" VerticalAlignment="Top"
BorderBrush
="#ff1e1ee5" BorderThickness="2" Padding="2" Canvas.Left="178" Canvas.Top="5"> <Image Width="120" Height="120" x:Name="imgResult" /> </Border> </Canvas>


相应的 C# 页面:

  public partial class PhotoCutPage : PhoneApplicationPage
    {
        public PhotoCutPage()
        {
            InitializeComponent();
            this.Loaded += PhotoCutPage_Loaded;
        }

        // 页面加载完成后,即让用户去图片库中选择图片
        void PhotoCutPage_Loaded(object sender, RoutedEventArgs e)
        {
            ChoosePicture();
        }

        // 从图片库选择图片文件
        void ChoosePicture()
        {
            try
            {
                var task = new Microsoft.Phone.Tasks.PhotoChooserTask();
                task.Completed += (s, evt) =>
                {
                    if (evt.Error == null && evt.TaskResult == Microsoft.Phone.Tasks.TaskResult.OK)
                    {
                        BitmapImage bmpImage = new BitmapImage();
                        bmpImage.SetSource(evt.ChosenPhoto);
                        imgSrc.Source = bmpImage;
                    }
                };
                task.Show();
            }
            catch
            {

            }
        }

        // 用来保存用户裁剪过的图片,在进行 back 导航操作时,如果为 null
        // 则说明用户点击了“取消”,如果不为 null,则为“确定”
        public static WriteableBitmap writeBitmap = null;

        //double initialAngle;
        double initialScale; 
        // Pinch 开始
        private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
        {
            //initialAngle = transform.Rotation;
            initialScale = transform.ScaleX; // 每次触摸屏幕时,保存图片的 X 轴的缩放比例
        }

        // 用户两指进行缩放
        private void OnPinchDelta(object sender, PinchGestureEventArgs e)
        {
            //if (transform.ScaleX < 1 || transform.ScaleY < 1)
            //{
            //    return;
            //}

            //transform.Rotation = initialAngle + e.TotalAngleDelta;
            transform.ScaleX = initialScale * e.DistanceRatio; // 两只手指的缩放比例
            transform.ScaleY = initialScale * e.DistanceRatio; // 与 X轴保持相同,实现等比例缩放
        }

        // 图片的位移操作
        private void Postcard_ManipulationDelta(object sender, DragDeltaGestureEventArgs e)
        {
            //if (transform.TranslateX < 0 || transform.TranslateY < 0)
            //{
            //    return;
            //}

            //moving along X axis
            transform.TranslateX += e.HorizontalChange;
            //moving along Y axis
            transform.TranslateY += e.VerticalChange;

        }

        // 用户点击了 “确定”
        private void Save_Click(object sender, RoutedEventArgs e)
        {
            writeBitmap = new WriteableBitmap(cutCanvas, null);

            if (NavigationService.CanGoBack)
            {
                NavigationService.GoBack();
            }
        }

        // 取消
        private void Cancel_Click(object sender, RoutedEventArgs e)
        {
            writeBitmap = null;

            if (NavigationService.CanGoBack)
            {
                NavigationService.GoBack();
            }
        }

        // 两指缩放结束
        private void GestureListener_PinchCompleted(object sender, PinchGestureEventArgs e)
        {
            GetImgResult();
        }

        // 图片位移操作结束
        private void GestureListener_DragCompleted(object sender, DragCompletedGestureEventArgs e)
        {
            GetImgResult();
        }

        // 计算截图,并且显示到屏幕
        void GetImgResult()
        {
            if (imgSrc != null && imgSrc.Source != null)
            {
                imgResult.Source = new WriteableBitmap(cutCanvas, null);
                border.Visibility = System.Windows.Visibility.Visible;
            }
            else
            {
                imgResult.Source = null;
                border.Visibility = System.Windows.Visibility.Collapsed;
            }
        }

        //如果没有选择图片,则单击为选择
        private void cutCanvas_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            e.Handled = true;

            if (imgSrc.Source == null)
            {
                ChoosePicture();
            }
        }
    }


    用户截图操作的显示效果:

 

2)定义一个处理用户上传的类 UploadUserPhotoController.cs,用来把客户端的图片流 和其它一些表单字段

    通过 POST 的方式上传到服务器端,上传的数据类似于下面:

    //-----------------------------5d159c1302d0y0
    //Content-Disposition: form-data; name="access_token"
    //Content-Type: text/plain; charset=ISO-8859-1
    //Content-Transfer-Encoding: 8bit

    //9c1302d0yarfasdfw3r234wgetr3qwt
    //-----------------------------5d159c1302d0y0
    //Content-Disposition: form-data; name="avatar"; filename="photo.jpg"
    //Content-Type: jpeg
    //Content-Transfer-Encoding: binary

 

通过字符串 -----------------------------5d159c1302d0y0 分割表单中的各个字段。

UploadUserPhotoController 类的定义:

 

namespace UploadPhotoDemo
{
    /// <summary>
    /// 通过 POST 方式,上传图片和表单字段
    /// </summary>
    public class UploadUserPhotoController
    {
        // 上传完成后,触发的完成事件
        public event EventHandler OpenCompleted;

        // 保存当前需要上传的图片
        public WriteableBitmap bitmap4Upload;

#if DEBUG
        //本地测试服务器地址
        string Uri = "http://localhost:10000/PhotoUploadController.ashx";
#else
        string  Uri = "真实网络地址";
#endif

        // 假设用户登录成功后,服务器分配给用户的 token 凭据
        string access_token = "9c1302d0yarfasdfw3r234wgetr3qwt";

        // 表单字段的分隔符
        string strBoundary = "---------------------------5d159c1302d0y0";

        public void Open()
        {
            HttpWebRequest request;
            request = WebRequest.Create(new Uri(Uri, UriKind.Absolute)) as HttpWebRequest;
            request.Method = "POST";

            request.ContentType = "multipart/form-data; boundary=" + strBoundary;

            IAsyncResult asyncResult = request.BeginGetRequestStream(new AsyncCallback(RequestStreamCallback), request);
        }


        private void RequestStreamCallback(IAsyncResult result)
        {
            HttpWebRequest request = result.AsyncState as HttpWebRequest;

            Stream requestStream = request.EndGetRequestStream(result);

            //StreamWriter streamWriter = new StreamWriter(requestStream);

            // 把 WriteableBitmap 对象保存到 Stream 对象中
            MemoryStream memoryStream = new MemoryStream();
            System.Windows.Media.Imaging.Extensions.SaveJpeg(bitmap4Upload, memoryStream, 
bitmap4Upload.PixelWidth, bitmap4Upload.PixelHeight,
0, 100); string postData = PrepareReqArgs(); // 组合表单数据 byte[] byteHead = Encoding.UTF8.GetBytes(postData); // 如果图片小于 4KB,则一次上次;如果大于 4KB,则分段上传,每次 4KB byte[] buffer = new Byte[checked((uint)Math.Min(4096, (int)memoryStream.Length))]; byte[] byteEnd = Encoding.UTF8.GetBytes("\r\n--" + strBoundary + "--\r\n"); requestStream.Write(byteHead, 0, byteHead.Length); memoryStream.Seek(0, SeekOrigin.Begin); int bytesRead = 0; while ((bytesRead = memoryStream.Read(buffer, 0, buffer.Length)) != 0) requestStream.Write(buffer, 0, bytesRead); requestStream.Write(byteEnd, 0, byteEnd.Length); requestStream.Close(); request.BeginGetResponse(new AsyncCallback(ResponseCallback), request); } string PrepareReqArgs() { StringBuilder sb = new StringBuilder(200); sb.Append("\r\n--"); sb.Append(strBoundary); sb.Append("\r\n"); sb.Append(@"Content-Disposition: form-data; name=""access_token"""); sb.Append("\r\n"); sb.Append(@"Content-Type: text/plain; charset=ISO-8859-1"); sb.Append("\r\n"); sb.Append("Content-Transfer-Encoding: 8bit"); sb.Append("\r\n\r\n"); sb.Append(this.access_token); //服务器端判断是否包含访问凭据 (Access_token) sb.Append("\r\n"); sb.Append("--"); sb.Append(strBoundary); sb.Append("\r\n"); sb.Append(@"Content-Disposition: form-data; name=""avatar""; filename=""photo.jpg"""); sb.Append("\r\n"); sb.Append("Content-Type: jpeg"); sb.Append("\r\n"); sb.Append("Content-Transfer-Encoding: binary"); sb.Append("\r\n\r\n"); return sb.ToString(); } // 上传完成 private void ResponseCallback(IAsyncResult result) { HttpWebRequest request = result.AsyncState as HttpWebRequest; WebResponse response = null; try { response = request.EndGetResponse(result); } catch (Exception ex) { Exception e = ex; // 没有网络链接,或者服务器端抛出异常 if (OpenCompleted != null) { OpenCompleted("serverError", null); } //_exception = ex.ToString(); return; } HttpWebResponse httpResponse = response as HttpWebResponse; if (httpResponse != null && httpResponse.StatusCode == HttpStatusCode.OK) { Stream responseStream = response.GetResponseStream(); using (StreamReader sr = new StreamReader(responseStream)) { string Text = sr.ReadToEnd(); try { // 处理服务器返回参数 if (OpenCompleted != null) { OpenCompleted(Text, null); } } catch { // 处理异常 } } } else { //string Text = _exception; } } } }

 

 

 

3)定义 WP工程中的 MainPage.xaml 页面,将用户选择的截图,在按钮事件中进行上传操作:

 

        // 使用 2) 中自定义图片上传操作的类
        UploadUserPhotoController uploadController;

        private void btnUpload_Click(object sender, RoutedEventArgs e)
        {
            if (uploadController == null)
            {
                uploadController = new UploadUserPhotoController();
                uploadController.OpenCompleted += uploadController_OpenCompleted;
            }

            // 将要上传的图片对象
            uploadController.bitmap4Upload = (imgPhoto.Source as System.Windows.Media.Imaging.WriteableBitmap);

            // 开始上传
            uploadController.Open();
        }

 

 

上传操作完成后的回调:

 

       // 图片上传完成触发的回调
        void uploadController_OpenCompleted(object sender, EventArgs e)
        {
            if (sender != null && sender.ToString() == "ok")
            {
                this.Dispatcher.BeginInvoke(delegate
                {
                    MessageBox.Show("上传头像成功");
                });
            }
            else
            {
                this.Dispatcher.BeginInvoke(delegate
                {
                    MessageBox.Show("上传头像失败");
                });
            }
        }

 

 

操作截图:

 

上传成功后:

 

网站端在刷新 Default.aspx 页面,读取用户上传的头像:

posted @ 2013-06-04 09:49  博琼  阅读(848)  评论(0编辑  收藏  举报