与众不同 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
[源码下载]