與眾不同 windows phone (29) - Communication(通信)之與 OData 服務通信
作者:webabcd
介紹
與眾不同 windows phone 7.5 (sdk 7.1) 之通信
- 與 OData 服務通信
示例
1、服務端(采用 Northwind 示例數據庫)
NorthwindService.svc.cs
/* * 提供 OData 服務,詳細說明請參見 WCF Data Services 的相關文章:http://www.cnblogs.com/webabcd/category/181636.html */ using System; using System.Collections.Generic; using System.Data.Services; using System.Data.Services.Common; using System.Linq; using System.ServiceModel.Web; using System.Web; namespace Web.Communication { // 出現異常時,返回詳細的錯誤信息 [System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)] public class NorthwindService : DataService<NorthwindEntities> { public static void InitializeService(DataServiceConfiguration config) { // 全部實體全部權限 config.SetEntitySetAccessRule("*", EntitySetRights.All); // 指定服務所支持的 OData 協議的最大版本 config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; } } }
2、客戶端(ViewModel 層)
MyViewModel.cs
/* * 調用 OData 服務的 ViewModel 層 * * DataServiceCollection<T> - OData 服務的實體數據集合,添加、刪除和更新數據時都會提供通知,繼承自 ObservableCollection<T> * Count - 實體數據集合中的實體數 * CollectionChangedCallback - 實體集合更改時調用的委托 * EntityChangedCallback - 實體更改時調用的委托 * Continuation - 是否還有下一頁數據 * LoadAsync(IQueryable<T> query) - 通過指定的查詢異步加載數據 * CancelAsyncLoad() - 取消異步加載請求 * LoadNextPartialSetAsync() - 加載下一頁數據 * Detach() - 停止跟蹤集合中的全部實體 * LoadCompleted - 異步數據加載完成后觸發的事件 * * * DataServiceState - 服務狀態 * Serialize(DataServiceContext context, Dictionary<string, Object> collections) - 序列化 DataServiceContext 對象以及頁面的相關數據 * 注:引用 OData 服務后,會自動生成一個繼承自 DataServiceContext 的代理類,如果需要序列化的話,則必須手動為其加上 [System.Runtime.Serialization.DataContract] * Deserialize() - 反序列化,返回 DataServiceState 類型的數據 * Context - 相關的 DataServiceContext 對象 * RootCollections - 相關的數據 * * * 注: * OData 服務的相關查詢參數參見:http://msdn.microsoft.com/en-us/library/gg309461.aspx */ using System; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.ComponentModel; using Demo.NorthwindContext; using System.Data.Services.Client; using System.Linq; using System.Collections.Generic; namespace Demo.Communication.ODataClient.ViewModel { public class MyViewModel : INotifyPropertyChanged { // 引用過來的 OData 服務 private NorthwindEntities _context; // OData 服務地址 private Uri _northwindUri = new Uri("http://localhost:15482/Communication/NorthwindService.svc/"); // 相關數據,DataServiceCollection<T> 繼承自 ObservableCollection<T> public DataServiceCollection<Category> Categories { get; private set; } public void LoadData() { _context = new NorthwindEntities(_northwindUri); // 實例化 DataServiceCollection<Category>,並注冊相關事件 Categories = new DataServiceCollection<Category>(_context); Categories.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(Categories_LoadCompleted); // 獲取全部 Categories 數據,並且一次請求帶上 Categories 的所有 Products DataServiceQuery<Category> query = _context.Categories.Expand("Products"); Categories.LoadAsync(query); NotifyPropertyChanged("Categories"); } void Categories_LoadCompleted(object sender, LoadCompletedEventArgs e) { if (e.Error == null) { // 如果服務端有分頁,則繼續獲取直至獲取完全部數據 if (Categories.Continuation != null) Categories.LoadNextPartialSetAsync(); } else { Deployment.Current.Dispatcher.BeginInvoke(delegate { MessageBox.Show(e.Error.ToString()); }); } } // 新增產品數據 public void AddProduct(Product product) { _context.AddToProducts(product); Categories.Single(p => p.CategoryID == product.CategoryID).Products.Add(product); SaveChanges(); } // 刪除產品數據 public void DeleteProduct(Product product) { _context.DeleteObject(product); Categories.Single(p => p.CategoryID == product.CategoryID).Products.Remove(product); SaveChanges(); } // 更新產品數據 public void UpdateProduct(Product product) { _context.UpdateObject(product); SaveChanges(); } public void SaveChanges() { // 異步向 OData 服務提交最近的數據變化 _context.BeginSaveChanges(OnChangesSaved, _context); } private void OnChangesSaved(IAsyncResult result) { Deployment.Current.Dispatcher.BeginInvoke(delegate { _context = result.AsyncState as NorthwindEntities; try { // 完成數據提交 _context.EndSaveChanges(result); } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }); } // 一個異步獲取數據的例子,此 Demo 沒用到此方法 public void GetData() { // 查找 Products 里 ProductID 為 1 的數據 var query = _context.CreateQuery<Product>("Products").AddQueryOption("$filter", "ProductID eq 1"); // 異步請求 OData 服務 query.BeginExecute(p => { var myQuery = p.AsyncState as DataServiceQuery<Product>; try { // 獲取請求結果 var result = myQuery.EndExecute(p).First(); // 更新數據 _context.UpdateObject(result); // 將更新后的結果提交到 OData 服務 SaveChanges(); } catch (Exception ex) { Deployment.Current.Dispatcher.BeginInvoke(delegate { MessageBox.Show(ex.ToString()); }); } }, query); } // 序列化當前數據,Deactivated 保存此值到應用程序狀態,參見 App.xaml.cs public string GetState() { var dict = new Dictionary<string, object>(); dict["categories"] = Categories; return DataServiceState.Serialize(_context, dict); } // 恢復數據,當從 tombstone 狀態進入 Activated 時,則從應用程序狀態中恢復數據,參見 App.xaml.cs public void RestoreState(string appState) { Dictionary<string, object> dict; if (!string.IsNullOrEmpty(appState)) { DataServiceState state = DataServiceState.Deserialize(appState); // 獲取相關數據 var context = state.Context as NorthwindEntities; dict = state.RootCollections; DataServiceCollection<Category> categories = dict["Categories"] as DataServiceCollection<Category>; // 恢復相關數據 _context = context; Categories = categories; Categories.LoadCompleted += new EventHandler<LoadCompletedEventArgs>(Categories_LoadCompleted); NotifyPropertyChanged("Categories"); } } public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(string propertyName) { var propertyChanged = PropertyChanged; if (propertyChanged != null) propertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
3、客戶端(View 層)
App.xaml
<Application x:Class="Demo.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:odataViewModel="clr-namespace:Demo.Communication.ODataClient.ViewModel"> <Application.Resources> <odataViewModel:MyViewModel x:Key="ODataViewModel" /> </Application.Resources> </Application>
App.xaml.cs
private void Application_Activated(object sender, ActivatedEventArgs e) { // 從 tombstone 狀態返回,則恢復相關數據,參見:“調用 OData 服務” if (!e.IsApplicationInstancePreserved) { if (PhoneApplicationService.Current.State.ContainsKey("categoriesState")) { var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; string categoriesState = PhoneApplicationService.Current.State["categoriesState"] as string; vm.RestoreState(categoriesState); } } } private void Application_Deactivated(object sender, DeactivatedEventArgs e) { // 保存相關數據,參見:“調用 OData 服務” var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; PhoneApplicationService.Current.State["categoriesState"] = vm.GetState(); }
Demo.xaml
<phone:PhoneApplicationPage x:Class="Demo.Communication.ODataClient.Demo" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone" xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" FontFamily="{StaticResource PhoneFontFamilyNormal}" FontSize="{StaticResource PhoneFontSizeNormal}" Foreground="{StaticResource PhoneForegroundBrush}" SupportedOrientations="Portrait" Orientation="Portrait" mc:Ignorable="d" d:DesignHeight="696" d:DesignWidth="480" shell:SystemTray.IsVisible="True" DataContext="{Binding Source={StaticResource ODataViewModel}}" xmlns:controls="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls"> <phone:PhoneApplicationPage.Resources> <DataTemplate x:Key="item"> <ListBox ItemsSource="{Binding Products}"> <ListBox.ItemTemplate> <DataTemplate> <Grid Width="460" HorizontalAlignment="Center"> <Grid.ColumnDefinitions> <ColumnDefinition Width="5*" /> <ColumnDefinition Width="5*" /> </Grid.ColumnDefinitions> <StackPanel Orientation="Horizontal" VerticalAlignment="Center"> <TextBlock Text="{Binding ProductName}" /> <TextBlock Text="{Binding UnitPrice}" Margin="10 0 0 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Right"> <Button x:Name="btnUpdate" Content="更新" Click="btnUpdate_Click" /> <Button x:Name="btnDelete" Content="刪除" Click="btnDelete_Click" /> </StackPanel> </Grid> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </DataTemplate> <DataTemplate x:Key="header"> <TextBlock Text="{Binding CategoryName}" /> </DataTemplate> </phone:PhoneApplicationPage.Resources> <Grid x:Name="LayoutRoot" Background="Transparent"> <controls:Pivot x:Name="pivot" Title="產品列表" ItemTemplate="{StaticResource item}" HeaderTemplate="{StaticResource header}" ItemsSource="{Binding Categories}"> </controls:Pivot> </Grid> <phone:PhoneApplicationPage.ApplicationBar> <shell:ApplicationBar Mode="Default" IsVisible="True"> <shell:ApplicationBarIconButton x:Name="btnAd" IconUri="/ApplicationBarDemo/Assets/appbar.add.rest.png" Text="添加" Click="btnAd_Click" /> </shell:ApplicationBar> </phone:PhoneApplicationPage.ApplicationBar> </phone:PhoneApplicationPage>
Demo.xaml.cs
/* * 調用 OData 服務的 View 層 */ using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using Microsoft.Phone.Controls; using Demo.Communication.ODataClient.ViewModel; using Demo.NorthwindContext; namespace Demo.Communication.ODataClient { public partial class Demo : PhoneApplicationPage { public Demo() { InitializeComponent(); this.Loaded += new RoutedEventHandler(Demo_Loaded); } void Demo_Loaded(object sender, RoutedEventArgs e) { // 加載數據 var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; vm.LoadData(); } private void btnDelete_Click(object sender, RoutedEventArgs e) { var product = (sender as Button).DataContext as Product; // 刪除指定的產品數據 var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; vm.DeleteProduct(product); } private void btnUpdate_Click(object sender, RoutedEventArgs e) { // 更新指定的產品數據 var product = (sender as Button).DataContext as Product; product.UnitPrice = 8.88m; var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; vm.UpdateProduct(product); } private void btnAd_Click(object sender, EventArgs e) { // 在指定的類別下添加一個新的產品數據 var category = pivot.SelectedItem as Category; Product product = new Product(); product.CategoryID = category.CategoryID; product.ProductName = "測試" + new Random().Next(10, 99); product.UnitPrice = 6.66m; product.Discontinued = false; var vm = App.Current.Resources["ODataViewModel"] as MyViewModel; vm.AddProduct(product); } } }
OK
[源碼下載]