与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信

原文:与众不同 windows phone (29) - Communication(通信)之与 OData 服务通信

[索引页]
[源码下载]


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

上一篇:阿里云钉钉实战第五课


下一篇:阿里云钉钉ServerLess实战第三课