Mở đầu
Thể theo yêu cầu của nhiều bạn muốn tìm hiểu về cách thức xử lý download và upload ảnh qua service trong bài viết này chúng ta sẽ cùng đi tìm hiểu cách thức download và upload ảnh sử dụng WCF REST Service trên Windows Phone bằng cách dùng Base64 String. Ứng dụng demo trong bài viết này là ứng dụng đổi avatar của người dùng, một phần không thể thiếu trong các ứng dụng social hiện nay. Bài này ngầm định là các bạn đã biết cách tạo ra và sử dụng các service sử dụng công nghệ WCF REST Service. Nếu bạn chưa biết vui lòng xem qua 2 bài viết sau:
Tạo WCF REST Service download upload ảnh dùng Base64 string
- Bước 1: Tạo project web WCFRESTImage và chọn template Web Forms như hình dưới
- Bước 2: Tạo folder Services và thêm một WCF Service đặt tên là ImageService
- Bước 3: Xóa example DoWork và bổ sung 2 operation vào IImageService như sau:1234567891011121314namespace WCFRESTImage.Services{[ServiceContract]public interface IImageService{[OperationContract][WebGet( RequestFormat=WebMessageFormat.Json, ResponseFormat=WebMessageFormat.Json)]UserProfile GetAvatarBase64(int userId);[OperationContract][WebInvoke(Method="POST" ,RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)]bool SetAvatarBase64(UserProfile userProfile);}}
Ở đây chúng ta sử dụng class UserProfile để lưu thông tin người dùng truyền qua lại giữa client và server12345678namespace WCFRESTImage.Services{public class UserProfile{public int UserId { get; set; }public string AvatarBase64String { get; set; }}} - Bước 4: Implement 2 operation này như sau:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110namespace WCFRESTImage.Services{/// <summary>/// Service demo handling image download and upload by WCF REST Service/// Created by tungnt.net 2/2015/// </summary>public class ImageService : IImageService{#region UseBaseString64/// <summary>/// Get Avatar image by userId/// </summary>/// <param name="userId"></param>/// <returns></returns>/// @Created by tungnt.net - 2/2015public UserProfile GetAvatarBase64(int userId){string imagePath;UserProfile userProfile = new UserProfile() { UserId = userId };try{//For demo purpose I only use jpg file and save file name by userId integer;imagePath = HttpContext.Current.Server.MapPath("~/Avatar/") + userId + ".jpg";if (File.Exists(imagePath)){using (Image img = Image.FromFile(imagePath)){if (img != null){userProfile.AvatarBase64String = ImageToBase64(img, ImageFormat.Jpeg);}}}}catch (Exception){userProfile.AvatarBase64String = null;}return userProfile;}private string ImageToBase64(Image image, ImageFormat format){string base64String;using (MemoryStream ms = new MemoryStream()){// Convert Image to byte[]image.Save(ms, format);ms.Position = 0;byte[] imageBytes = ms.ToArray();// Convert byte[] to Base64 Stringbase64String = Convert.ToBase64String(imageBytes);}return base64String;}/// <summary>/// Save image to Folder's Avatar/// </summary>/// <param name="userProfile"></param>/// <returns></returns>/// @Created by tungnt.net - 2/2015public bool SetAvatarBase64(UserProfile userProfile){bool result = false;try{//For demo purpose I only use jpg file and save file name by userId integerif (!string.IsNullOrEmpty(userProfile.AvatarBase64String)){using (Image image = Base64ToImage(userProfile.AvatarBase64String)){string strFileName = "~/Avatar/" + userProfile.UserId + ".jpg";image.Save(HttpContext.Current.Server.MapPath(strFileName), ImageFormat.Jpeg);result = true;}}}catch (Exception){result = false;}return result;}private Image Base64ToImage(string base64String){// Convert Base64 String to byte[]byte[] imageBytes = Convert.FromBase64String(base64String);Bitmap tempBmp;using (MemoryStream ms = new MemoryStream(imageBytes, 0, imageBytes.Length)){// Convert byte[] to Imagems.Write(imageBytes, 0, imageBytes.Length);using (Image image = Image.FromStream(ms, true)){//Create another object image for dispose old image handlertempBmp = new Bitmap(image.Width, image.Height);Graphics g = Graphics.FromImage(tempBmp);g.DrawImage(image, 0, 0, image.Width, image.Height);}}return tempBmp;}#endregion}}
Ở đây sử dụng cho mục đích demo nên chúng ta sẽ lưu ảnh vào một thư mục tên là Avatar trên server nên nếu chưa có bạn vui lòng tạo ra trước. Bên cạnh đó để cho đơn giản chúng ta sẽ lưu tên file ảnh avatar theo id người dùng dạng integer và fix định dạng ảnh hỗ trợ là jpg. Các bạn có thể mở rộng ra như là lưu avatar vào database thay vì file và hỗ trợ thêm các định dạng ảnh khác. - Bước 5: Cấu hình wcf service trả về dạng JSON:1234567891011121314151617181920212223<system.serviceModel><behaviors><endpointBehaviors><behavior name="restBehavior"><webHttp helpEnabled="true" /></behavior></endpointBehaviors><serviceBehaviors><behavior name=""><serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" /><serviceDebug includeExceptionDetailInFaults="false" /></behavior></serviceBehaviors></behaviors><serviceHostingEnvironment aspNetCompatibilityEnabled="true"multipleSiteBindingsEnabled="true" /><services><service name="WCFRESTImage.Services.ImageService"><endpoint address="rest" behaviorConfiguration="restBehavior"binding="webHttpBinding" bindingConfiguration="" contract="WCFRESTImage.Services.IImageService" /></service></services></system.serviceModel>
Đến đây các bước chuyển bị service đã hoàn tất chúng ta cùng bước vào phần tiếp theo
Tạo ứng dụng Windows Phone cho phép đổi avatar người dùng
Ở bước kế tiếp này chúng ta sẽ tạo ra một ứng dụng demo việc đổi avatar cho người dùng như sau:
- Bước 1: Thêm 1 project Windows Phone Apps, chọn template Blank App và đặt tên là WCFRESTImageWP
- Bước 2: Thiết kế UI MainPage.xaml chỉ gồm có một control Image Avatar, Avatar sẽ hiển thị khi người dùng chạy ứng dụng và cho phép đổi Avatar khi người dùng chạm vào Image này123456789101112131415161718192021<Grid><Grid Margin="0 40 0 40"Height="152"Width="152"Tapped="Grid_Tapped"Background="Honeydew"><Grid.RowDefinitions><RowDefinition Height="auto" /></Grid.RowDefinitions><Border CornerRadius="5"BorderBrush="Brown"><Image Width="140"x:Name="imgAvatar"Margin="0 5 0 0"Stretch="Fill"Height="140"Grid.Row="0"/></Border></Grid></Grid>
- Bước 3: Viết code trong method OnNavigatedTo để GetAvatar và hiển thị lên trên Image. Chú ý cần tạo ra class UserProfile tương tự như trên server để sử dụng và sử dụng lại thư viện POST/GET dữ liệu đã biết trong các bài viết trước12345678910111213141516171819202122232425262728293031323334353637383940protected async override void OnNavigatedTo(NavigationEventArgs e){try{//For demo purpose we fix userId = 1string base64String = await WCFRESTServiceCall("GET", "GetAvatarBase64?userId=" + userId);userProfile = JsonConvert.DeserializeObject<UserProfile>(base64String);if (userProfile.AvatarBase64String != null){SetAvatarFromBase64String(userProfile.AvatarBase64String);}}catch (Exception){throw;}}/// <summary>/// Utility function to get/post WCFRESTService/// </summary>/// <param name="methodRequestType">RequestType:GET/POST</param>/// <param name="methodName">WCFREST Method Name To GET/POST</param>/// <param name="bodyParam">Parameter of POST Method (Need serialize to JSON before passed in)</param>/// <returns></returns>/// Created by tungnt.net - 1/2015private async Task<string> WCFRESTServiceCall(string methodRequestType, string methodName, string bodyParam = ""){string ServiceURI = "http://localhost:5550/Services/ImageService.svc/rest/" + methodName;HttpClient httpClient = new HttpClient();HttpRequestMessage request = new HttpRequestMessage(methodRequestType == "GET" ? HttpMethod.Get : HttpMethod.Post,ServiceURI);if (!string.IsNullOrEmpty(bodyParam)){request.Content = new StringContent(bodyParam, Encoding.UTF8, "application/json");}HttpResponseMessage response = await httpClient.SendAsync(request);string returnString = await response.Content.ReadAsStringAsync();return returnString;}
Sau khi lấy được base64 string từ service, trong hàm SetAvatarFromBase64String chúng ta sẽ dùng DataWriter và Stream để tạo ra BitmapImage và gán cho Image Avatar:1234567891011121314151617181920/// <summary>/// Set Avatar Image From Base64String use InMemoryRandomAccessStream/// </summary>/// <param name="base64String"></param>/// @created by tungnt.net - 2/2015private void SetAvatarFromBase64String(string base64String){using (InMemoryRandomAccessStream ms = new InMemoryRandomAccessStream()){using (DataWriter writer = new DataWriter(ms.GetOutputStreamAt(0))){byte[] imageBytes = Convert.FromBase64String(base64String);writer.WriteBytes((byte[])imageBytes);writer.StoreAsync().GetResults();}var image = new BitmapImage();image.SetSource(ms);imgAvatar.Source = image;}}
Ở đây chúng ta sử dụng thư viện Json.NET để serialize string JSON thành object UserProfile, bạn có thể thêm thư viện này qua NuGet.
- Bước 4: Viết code cho sự kiện Tapped của Grid để đổi Avatar như sau:123456789101112131415161718192021222324252627private void Grid_Tapped(object sender, TappedRoutedEventArgs e){try{var openPicker = new FileOpenPicker{SuggestedStartLocation = PickerLocationId.PicturesLibrary,ViewMode = PickerViewMode.Thumbnail};//Filter to include a sample subset of file types.//For demo purpose we only use jpg imageopenPicker.FileTypeFilter.Clear();//openPicker.FileTypeFilter.Add(".jpeg");//openPicker.FileTypeFilter.Add(".bmp");//openPicker.FileTypeFilter.Add(".png");openPicker.FileTypeFilter.Add(".jpg");//Handle Activated event for SetAvatar and POST to service after choose imageview.Activated += viewActivated;openPicker.PickSingleFileAndContinue();}catch (Exception){throw;}}
Chúng ta dùng FileOpenPicker để lấy ảnh trong PictureLibrary và Filter loại file cho phép sử dụng là jpg, bạn có thể bổ sung filter để hỗ trợ thêm các định dạng ảnh khác nhưng cần chú ý sửa lại phần server cho tương ứng. Sau đó chúng ta bắt event Activated của CurrentView để POST avatar lên service và set lại ảnh avatar mới sau khi chọn ảnh avatar từ thư viện ảnh như sau:123456789101112131415161718192021222324252627282930313233private async void viewActivated(CoreApplicationView sender, IActivatedEventArgs args){view.Activated -= viewActivated;FileOpenPickerContinuationEventArgs fileOpenPickerEventArgs = args as FileOpenPickerContinuationEventArgs;if (fileOpenPickerEventArgs != null){if (fileOpenPickerEventArgs.Files.Count == 0) return;StorageFile file = fileOpenPickerEventArgs.Files[0];if (file != null){IRandomAccessStream fileStream = await file.OpenAsync(FileAccessMode.Read);string errorMessage = null;try{bool setAvatarResult = await PostAvatarToService(fileStream);if (setAvatarResult){SetAvatarFromBase64String(userProfile.AvatarBase64String);}}catch (Exception exception){errorMessage = exception.Message;}if (!string.IsNullOrEmpty(errorMessage)){var dialog = new MessageDialog(errorMessage);await dialog.ShowAsync();}}}}
Trong event viewActivated chúng ta sẽ lấy ra file vừa chọn sau đó POST lên service qua method PostAvatarToService, nếu thành công thì sẽ set lại ảnh avatar mới cho Image bằng method SetAvatarFromBase64String12345678910111213141516171819202122/// <summary>/// Post avatar to wcf rest service from RandomAccessStream/// </summary>/// <param name="fileStream"></param>/// <returns></returns>/// @created by tungnt.net - 2/2015private async Task<bool> PostAvatarToService(IRandomAccessStream fileStream){bool setAvatarResult = false;using (InMemoryRandomAccessStream ms = new InMemoryRandomAccessStream()){using (DataReader reader = new DataReader(ms.GetInputStreamAt(0))){byte[] imageBytes = new byte[fileStream.Size];await fileStream.ReadAsync(imageBytes.AsBuffer(), (uint)fileStream.Size, Windows.Storage.Streams.InputStreamOptions.None);string avatarBase64String = Convert.ToBase64String(imageBytes);userProfile.AvatarBase64String = avatarBase64String;}}setAvatarResult = JsonConvert.DeserializeObject<bool>(await WCFRESTServiceCall("POST", "SetAvatarBase64", JsonConvert.SerializeObject(userProfile)));return setAvatarResult;}
Mọi thứ đã hoàn tất các bạn có thể chạy thử ứng dụng, trước khi chạy thử chúng ta cần set cho 2 project web service và windows phone startup cùng lúc như sau:
- Right-click Solution và chọn Properties
- Tại đây chọn Startup Projects, đổi từ Single sang Multiple startup projects và chọn Action cho 2 project cùng là Start và click OK
Bây giờ chỉ cần Ctrl+F5 để hưởng thụ thành quả vừa xây dựng xong.
Kết luận
Trong bài viết này tôi đã hướng dẫn các bạn cách xây dựng service xử lý download và upload ảnh sử dụng WCF REST Service trên Windows Phone bằng cách dùng Base64 String qua một ứng dụng demo thay đổi Avatar của người dùng. Cách thức sử dụng Base64 String có ưu điểm là sử dụng string nên việc xử lý ở phần service và client đều đơn giản nhưng chúng cũng có nhược điểm rất lớn đó là dữ liệu chuyển thành dạng base64 nên dung lượng sẽ lớn hơn ảnh thật thêm khoảng 30%.
Trong các bài viết sau chúng ta sẽ cùng tìm hiểu các cách thức xử lý ảnh trên các công nghệ .NET khác. Hy vọng bài viết này sẽ giúp ích cho các bạn trong quá trình xây dựng các ứng dụng của riêng mình. Nếu bạn có bất kì câu hỏi hay kinh nghiệm nào hãy chia sẻ bằng comment bên dưới bài viết và đừng quên chia sẻ cho bạn bè nếu thấy hữu ích.
Happy coding. Stay tuned.
P/s: Source code example các bạn có thể download tại đây: WCFRESTImage
anh ơi cho em hỏi trên WP có dùng được các thư viện của google maps ko ạ?
Hi Bong, Theo những gì anh biết thì trên WP không sử dụng được thư viện của Google Maps em ạ.
Như vậy là chỉ dùng được Bing map thôi ạ? Anh có biết dùng thư viện gì để có thể vẽ được hình (shape) trên maps không ạ? Như trên Google map thì dùng polygon nhưng WP lại ko dùng đc GG map :((
Hi Bong,
Em thử theo hướng dẫn ở đây nhé. Rất chi tiết cách implement từng bước, có thể vẽ hình lên map được.
Regards
Em không thấy link anh ơi?? Bài nào ạ?
Sorry anh quên link: http://www.geekchamp.com/articles/windows-phone-drawing-custom-pushpins-on-the-map-control-what-options-do-we-have
Bài đó e ko thấy có vẽ shape a ạ, có bài nào vẽ và tính diện tích ko ạ,e ko tìm ra
Anh đã xài qua Zalo trên WP chưa ạ? cho em hỏi layout của nó có phải dạng Pivot không ạ,làm thế nào để mỗi page có application bar khác nhau và cách tạo biểu tượng cái loa và avatar trên góc phải ạ?
Hi em đúng là dạng Pivot đấy. ApplicationBar thì tự tạo trong code mỗi lần thực hiện chuyển item. Ở trên đỉnh thì là button thôi
Anh cho em hỏi đoạn code:
client.AddUserCompleted += new EventHandler(client_AddUser);
client.AddUserAsync(userInfo);
nó báo lỗi The type or namespace name does not exist in the namespace (are you missing an assembly reference ) ở hàm client_AddUser() thì fix nó ntn ạ? Em đã add Servicereference vào rồi
Hi em, lỗi trên là thiếu using namespace hoặc chưa có hàm đó. Em để con trỏ chuột vào chỗ thiếu rồi Ctrl+. để using hoặc tạo ra hàm client_AddUser là được.
Em đang làm việc với BingMap, giả sử em GET một đía chỉ bất ký hay một tọa độ từ trên WebService về, sau đó muốn marked cái tọa độ hay cái địa chỉ đó thì phải làm soa ạ. Cám ơn anh.
Hi em,
Cái đó gọi là Pushpin. Em có thể add nó vào maps theo hướng dẫn sau nhé: http://stackoverflow.com/questions/24553072/add-pushpin-to-map-from-code-behind-in-windows-phone-8
Regards.
Thanks you again.
Ok em. Subscribe nhận bài viết mới để ủng hộ blog của anh nhé.
bạn ơi!. bạn có biết làm thế nào để up ảnh trong ứng dụng của Facebook không? nếu biết chia sẽ mình với, mình có làm 1 ứng dụng facebook nhưng chưa biết cách đưa ảnh của user lên
Hi Thuận,
Mình chưa làm ứng dụng up ảnh nhưng đã từng làm qua ứng dụng post thông tin lên Facebook và thấy cũng khá đơn giản. Bạn có thể thử làm theo link này nhé: http://igrali.com/2012/06/07/share-photos-on-facebook-from-windows-phone-app/
Regards
anh ơi hinh như ảnh kích thước lớn là không up được
Hi Nam, Em cần cấu hình để WCF có thể sử dụng được cho file kích thước lớn. Cách cấu hình xem tại đây nhé: https://smehrozalam.wordpress.com/2009/01/29/retrieving-huge-amount-of-data-from-wcf-service-in-silverlight-application/
anh hướng dẫn cho em với. e làm mãi mà không được
Hi Nam em đã thử cấu hình binding và readerquota như trong bài hướng dẫn chưa. Hiện thời có lỗi gì?
rồi anh, em đã thêm mấy cái đó vào. dưới chỗ endpoint em thêm bindingConfiguration=”MyBasicHttpBinding”
thử up thì ảnh nào cao là nó k chạy vào service luôn.
à anh endpointBehaviors với behaviors là gì vậy anh
anh coi cho em với
Trong source code sample của anh cũng đã cấu hình để trả về file dung lượng lớn rồi, nếu em muốn dung lượng lớn hơn thì tăng phần maxReceivedMessageSize, mãBufferSize, maxStringContentLength lên, hiện thời anh đang để là 1.5 MB và 600KB.
Em cũng chú ý là ở bài này đang dùng webHttpBinging nên em phải tạo bindingConfiguration trong phần webHttpBinding.
config của em giống bài hướng dẫn tạo rest service của a
anh ơi có cách nào up ảnh từ mobile lên mà mình lấy dc link ảnh như thế này http://doctruyen.4pro.vn/watermark.php?src=nhotruyen&img=11944531238162059-69×80.jpg
luôn ko ạ ?
Hi em,
Đơn giản là như hàm SetAvatar kia thay vì trả về kiểu bool thì em sẽ trả về string chứa link ảnh là được.
a ơi, a viết bài về download với upload ảnh sử dụng web api đi ạ
Hi em, Hiện thời anh chưa có ý định viết bài về cách download/upload ảnh sử dụng web api. Về cơ bản nó phía windows phone là tương tự, chỉ có phía service là khác chút thôi. Anh sẽ plan viết sau nhé. Thanks em
giới hạn up ảnh như thế nào vậy anh. em muốn chỉ cho up những file ít hơn 2mb.
với ảnh mà lớn thì giảm chất lượng được không anh
Giới hạn up ảnh 2MB thì sử dụng mảeceivedMessageSize = “2048000” và mãBufferSize=”2048000″. Ảnh lớn thì em resize lại cho nhỏ đi trước khi up, nên hạn chế ảnh nhỏ vài chục đến trăm KB thôi.
anh hướng dẫn em cách resize với
Hi Nam em nên tìm hiểu trước xem có những cách nào và làm thử theo những cách đấy, anh chỉ có thể gợi ý cho em như vậy.
Nếu không giải quyết được hoặc gặp lỗi ở đâu đó thì hỏi anh anh sẽ trợ giúp.
Regards.
dạ. cảm ơn anh. để e tìm hiểu thử
Em đang phát triển một app WP. có dùng DB từ My SQL, Em muốn viết một hàm tìm kiếm truy suất từ DB nhưng không thấy có nhiều hướng dẫn. Có thằng Search task nhưng chỉ cho phép tìm từ Web và Device thôi ạ. Nhờ anh chỉ giúp. Cám ơn anh
Hi em,
Hiện thời em đang gặp khó khăn ở đoạn nào? Bài toán anh nhìn cũng clear đâu thấy có gì phức tạp. Em viết hàm search ở server, tham số là searchterm, rồi query database sau đó trả list result về device để hiển thị lên thôi.
Regards
I’m a new guy in WP programming. Assume I have a page with the field to put the keyword need to search. So when I clicked search button, Search function on server will run and returned the result to device, right. Can I have an example for this case? I don’t know what should do. Thanks.
Hi Woody,
If your app always need online and the result is small enough you can search on server-side and return the result to app but if the result is large I think you should store database on local on search on it for the best performance. The example about this, I will blog next time so you should subscribe to my blog to receive notification on this post.
Thanks and best regards.
Thank you again. I’m waiting to see yous new post that can show me more.
Thanks Woody, you’re welcome.
ANh ơi, cho e hỏi localhost của e up ảnh thì được, nhưng khi up lên host thật thì nó ko up được, anh biết cách làm iarm dung lượng, hoặc fix size ảnh trước khi chuyển sang base64 rồi up lên ko anh ? hay có vấn đề khác khắc phục không ? Em cảm ơn anh.
Hi em,
Để giải quyết vấn đề giới hạn dung lượng truyền qua wcf anh đã trả lời bạn Duc Nam ở trên, em xem trong link để biết cách cấu hình nhé.
Regards.
Anh cho em hỏi nếu lưu ảnh trong csdl thì phải thêm ntn để hiển thị lên được windows phone
Hi Anh. Nếu em đã lưu ảnh vào database thì nó đã lưu dạng byte[] rồi nên em không cần phải làm bước convert từ ảnh thành byte[] nữa mà convert thành base64 string rồi trả về luôn.
Ví dụ em dùng Wcf service có một bảng gồm name,image. và một view để xem toàn bộ thông tin thông qua name.
[OperationContract]
view_get_all_user GetUserInfo(string Name);
public view_get_all_user GetUserInfo (string Name)
{
view_get_all_user resultUser = new view_get_all_user();
resultUser = (from m in dbc.view_get_all_users where m.Name.Equals(Name) select m).FirstOrDefault();
return resultUser;
}
View này em hiển thị trên app nhưng byte[] của image thì chưa hiển thị được.
Thế thì chèn hàm getavatar, setavatar vào ntn ạ. Em mới tìm hiểu thôi, mong anh thông cảm
Hi em, như trên thì trong class view_get_all_user em có thuộc tính vd ImageBase64, em cần dùng getavatar để convert từ byte[] sang string trước khi trả về client, còn khi đẩy từ client lên thì lại dùng setavatar để convert ngược lại trước khi lưu và database.
Bản chất là em phải convert qua lại base64string và sử dụng thôi.
anh viết cụ thể 1 đoạn class view trong code của em ntn với.em vẫn không hiểu.
Anh cho em hỏi: em đang làm về giới thiệu cửa hàng ăn trên winphone sử dụng nhiều ảnh quá mà lưu trực tiếp ảnh vào csdl thì rất nặng. vậy có cách nào ko. và lúc gọi trên wcf service có khác ko?
Chào Vân Anh,
Về cách gọi lên service thì ko có gì khác cả như anh đã nói trong comment trước chỉ thay vì lưu vào db thì em lưu thành file thôi.
Có lẽ đợi anh viết một bài về cách upload nhiều file lên service em sẽ hiểu rõ hơn
Em có lưu đường dẫn ảnh vào sql Ví dụ là E:/tenanh.png. Trên winphone em gắn source ảnh bằng đưỡng dẫn đấy thông qua wcf.Em đã lấy được link rồi, sao trên winphone lúc hiển thị lại không được vậy a ?