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="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 页面,读取用户上传的头像: