65.9K
CodeProject 正在变化。 阅读更多。
Home

带管理 Entity Framework 和 WCF 服务的 Silverlight 应用程序

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2014年12月5日

CPOL

9分钟阅读

viewsIcon

27851

downloadIcon

510

一个使用 Expression Blend 和 Silverlight 作为前端,从 WCF 服务和 SQL Server 后端获取数据的应用程序构建指南

引言

以下是使用 Expression Blend 和 Silverlight 构建前端应用程序的指南,该应用程序从 WCF 服务和 SQL Server 后端获取数据。

本指南不讨论将 Silverlight 与 WCF RIA Services 结合使用,因为在撰写本文时,Microsoft 已停止了对该技术支持。如果您仍希望实现 RIA Services,请考虑此开源实现:Open RIA Services。

您应该使用 WCF SOAP 服务还是 ASMX ASP.NET Web 服务?它们几乎相同,只是 WCF 对封送处理支持更多。WCF 服务可以支持更多的类型作为服务输入和返回。

请注意,我还写了另一篇与本文类似的教程,但在其中我详细介绍了实现 Model-View-ViewModel 模式,您可以在这里找到,希望对您有所帮助。MVVM 模式针对支持事件驱动编程的 UI 平台,并且更清晰地将 UI 与业务层或后端数据层分离。

目录

1. 在 Expression Blend 中设计

Expression Blend 作为独立工具随 Visual Studio 2013 一起提供。在撰写本文时,您可以免费下载 Blend 和 Visual Studio Express for Windows。它允许 UX 设计师在不编写任何源代码的情况下创建具有吸引力且直观的用户界面。

首次启动 Visual Studio 2013 时,您会看到此页面,您可以在其中选择项目类型。选择 Silverlight Application,并且不要勾选“Enable WCF RIA Services”复选框。我们将在下面的第 3 和第 4 部分详细介绍这一点。

850354/DisabledWcfRiaServices.png

点击 OK 后,右键单击 MainPage.xaml 并选择“Open in Blend...”将显示以下页面

850354/ChooseProject.png

从 Blend 资源窗口拖放一个按钮到左下角,以及几个 Border 控件,如果需要,为每个控件选择一个背景颜色。然后将一个文本块放入较大的边框中,将一个文本框放入较小的边框控件中。

850354/CreatingTextBlockTextBox.png

接下来,在显示屏的左上方放置一个文本块

850354/SmallTextBlock.png

您可以在这里找到更多关于 Blend 的教程和视频。

2. 使用 Expression Blend 和 Visual Studio 进行数据绑定

数据绑定是连接数据源与用户界面元素的必要条件

850354/ProcessOfDataBinding.png

数据绑定的三种主要形式

  1. OneTime:很少更改的项,例如商店名称或付款选项。
  2. OneWay:对于经常更改但基本上只读的项。
  3. TwoWay:与 OneWay 非常相似,不同之处在于用户在此处所做的更改会应用于业务层,例如允许用户从先前使用的列表中选择其地址,并编辑该地址。

850354/DataBindingTypes.png

上一节描述了如何在 Blend 中设计用户界面,接下来我们将更新数据上下文并在 Visual Studio 中完成数据绑定,所以请在该应用程序中打开解决方案文件。

850354/StartVisualStudio.png

接下来,向应用程序添加以下 Product

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace WcfEntitiesSample.Model
{
    public class Product
    {
        private string _name;

        public string Name
        {
            get { return _name; }
            set 
            {
                NotifyPropertyChanged("Name");
                _name = value; 
            }
        }
        private string _description;

        public string Description
        {
            get { return _description; }
            set 
            {
                NotifyPropertyChanged("Description");
                _description = value; 
            }
        }

        private int _inventoryCount = 0;

        public int InventoryCount
        {
            get { return _inventoryCount; }
            set 
            { 
                _inventoryCount = value;
                NotifyPropertyChanged("InventoryCount");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public Product()
        {
        }

        public Product(string name, string description, int inventory)
        {
            _name = name;
            _description = description;
            _inventoryCount = inventory;
        }

        private void NotifyPropertyChanged(String propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

PropertyChangedEventHandler 用于数据绑定,以确保源数据的更新传递到绑定的用户界面属性。创建 PropertyChangedEventHandler 将标识处理事件的方法。

然后打开 MainPage.xaml 设计器,单击按钮,然后通过双击“Properties”窗口中的“Click”项来为按钮创建事件处理程序。

850354/ButtonEventHandler.png

接下来,打开 MainPage.xaml.cs 文件,并添加以下代码

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
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;

namespace WcfEntitiesSample
{
    public partial class MainPage : UserControl
    {
        private ObservableCollection<Product> Inventory;
        private int inventoryIndex = 0;

        public MainPage()
        {
            // Required to initialize variables
            InitializeComponent();

            // Initialize variables
            Inventory = new ObservableCollection<Product>();

            // Populate Inventory List
            Inventory.Add(new Product
            ("Stapler", "A hinged device that binds soft things together.", 32));
            Inventory.Add(new Product
            ("Tape", "Sticky strips with impressive binding.", 16));
            Inventory.Add(new Product
            ("Glue", "Unimpressive while wet, but can bind materials together.", 8));

            this.Resources.Add("items", Inventory);
            this.DataContext = Inventory[0];
        }

        private void NextProduct_Click(object sender, RoutedEventArgs e)
        {
            inventoryIndex++;
            inventoryIndex = inventoryIndex % Inventory.Count;
            this.DataContext = Inventory[inventoryIndex];
        }
    }
}

ObservableCollection 允许用户界面中的数据项与底层数据源中的数据项保持同步。每当您添加、删除、移动、刷新或替换集合中的项时,它都会引发 CollectionChanged 事件。请注意,虽然 ObservableCollection 会监视其元素的更改,但它不关心其元素属性的更改。

this.DataContext 对象在用户界面元素参与数据绑定时获取或设置其数据上下文。

接下来,打开 MainPage.xaml,并将 XAML 源代码更新为以下内容。您使用 Binding 关键字来实现数据绑定,以与 ObservableCollection DataContext 配合使用。

850354/XamlCode.png

如果您在 Visual Studio 中点击 start 进行应用程序调试,您应该会注意到用户界面元素在每次点击“Next Product”按钮时都会发生变化。

3. 设置数据库

MSDN下载并安装带有高级服务的 SQL Server Express。

Codeplex下载并安装相应版本的 AdventureWorks 数据库。

将此视图添加到数据库并命名为 vProductProductInventory

SELECT        Production.Product.ProductID, Production.Product.Name, 
              SUM(Production.ProductInventory.Quantity) AS InventoryCount, 
              'Color = ' + Production.Product.Color + ', 
              Size = ' + Production.Product.Size + ', 
              Weight = ' + CAST(Production.Product.Weight AS NVARCHAR) + 
              ', Style = ' + Production.Product.Style AS Description
FROM          Production.Product LEFT OUTER JOIN
              Production.ProductInventory ON Production.Product.ProductID = Production.ProductInventory.ProductID
GROUP BY Production.Product.Name, Production.Product.Color, Production.Product.Size, Production.Product.Weight, Production.Product.Style, Production.Product.ProductID

4. 创建 WCF 服务组件

WCF 服务是一项服务器端技术,与 XML Web 服务不同,它是一项接口驱动的技术。该接口具有 ServiceContract 属性,并且每个类方法都有一个 OperationContract 属性。WCF 可以使用 XML 或 JSON。

接口文件中的数据协定也会注释任何将被序列化到互联网的类。

在 Visual Studio 中右键单击您创建的解决方案中的 *.Web 项目(我称之为 WcfEntitiesSample.Web),然后选择添加一个名为“Services”的文件夹。

然后右键单击您创建的此文件夹,选择“Add New Item”并选择“WCF Service”。

850354/AddWcfService.png

根据您为服务命名的不同,您的代码可能如下所示

namespace WcfEntitiesSample.Web.Services
{
    // NOTE: You can use the "Rename" command on the "Refactor" menu 
    // to change the class name "AdventureWorksService" in code, svc and config file together.
    // NOTE: In order to launch WCF Test Client for testing this service, 
    // please select AdventureWorksService.svc or AdventureWorksService.svc.cs 
    // at the Solution Explorer and start debugging.
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class AdventureWorksService : IAdventureWorksService
    {
        public void DoWork()
        {
        }
    }
}

此外,您的 web.config 应包含以下内容

850354/WebConfigBindings.png

二进制消息编码是一项节省带宽的功能。

接下来,将 DoWork 更改为适用于 Adventure Works 数据库的内容。

    [ServiceContract]
    public interface IAdventureWorksService
    {
        [OperationContract]
        Product GetProductByID(int id);
    }
    
    [ServiceContract(Namespace = "")]
    [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    public class AdventureWorksService : IAdventureWorksService
    {
        [OperationContract]
        public Product GetProductByID(int id)
        {
            return new Product()
            {
                ProductID = id,
                Name = "BB Ball Bearing",
                InventoryCount = 1109,
                Description = ""
            };
        }
    }

目前,我们只创建一个虚拟构造函数。稍后,在 LINQ to Entities 部分,我们将实际从 AdventureWorks 数据库中获取此数据。

要使您的服务方法显示在生成的代理中,它必须是 public 并且标记有 [OperationContract] 属性。

由于当今的业务逻辑越来越复杂,因此将应用程序的各个组件分离到表示层、业务逻辑层和数据层非常重要。Web 服务仅仅是业务逻辑层和表示层之间的一种通信方式。将应用程序分离成不同的组件有助于降低复杂性并使单元测试更容易。

Silverlight 不了解 ADO.NET 类型 DataRow DataTable,因此我们需要创建一个类型来表示我们的数据。将以下类添加到单独的 Silverlight Class 5 Library 并将该文件链接到另一个 .NET 4 Class Library,这有助于更好地分离关注点。如果您选择这样做,那么 Silverlight Class Library 和 .NET Class Library 拥有相同的根命名空间非常重要。这使您无需担心不同的命名空间即可在客户端和服务器之间共享文件。

        public class Product
        {
            public int ProductID { get; set; }
            public string Name { get; set; }
            public int? InventoryCount { get; set; }
            public string Description { get; set; }
        }

编译器在生成客户端代理时会将其保留在服务描述中。如果您选择将 Product 类保留在与 AdventureWorksService 相同的文件中,那么 [DataContract] 属性就会起到这个作用。

在开始下一步之前,请生成解决方案。

4.1 添加服务引用

现在您应该有一个指向完整 .NET 模型的 Web 项目和一个指向 Silverlight 模型的 Silverlight 项目,为 WcfEntitiesSample 项目添加服务引用并生成代理。

您希望主项目和 Silverlight 项目都引用各自的 Silverlight Class Library 和 .NET Class Library 的原因是,这样客户端代理生成器就不会在客户端生成模型类的副本。

因此,下一步是右键单击 Silverlight 客户端项目并选择“Add Service Reference”。在出现的下一个对话框中,单击 Discover 按钮。

850354/AddServiceReference.png

单击 OK 后,将生成客户端配置文件。

4.2 更新 UI 以测试服务

添加一个新的 textbox ,您可以在其中输入 ID,以及一个搜索按钮。

850354/UpdateUI.png

接下来,使用此代码更新 MainPage.xaml.cs 文件。

using System.Windows;
using System.Windows.Controls;
using WcfEntitiesSample.ServiceRefs;

namespace WcfEntitiesSample
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            // Required to initialize variables
            InitializeComponent();
        }

        private void btnSearch_Click(object sender, RoutedEventArgs e)
        {
            int searchId;

            if (int.TryParse(txtID.Text, out searchId))
            {

                var client = new AdventureWorksServiceClient();

                client.GetProductByIDCompleted += (s, ea) =>
                {
                    DataContext = ea.Result;
                };

                client.GetProductByIDAsync(searchId);
            }
            else
            {
                MessageBox.Show("Invalid customer ID. Please enter a number.");
            }
        }
    }
}

现在,通过选择 WcfEntitiesSample.Web 作为启动项目来测试您的应用程序并开始调试。尝试点击搜索按钮 - 它应该返回您在上面的虚拟构造函数中硬编码的值。

5. 添加 LINQ to Entities

可以有多种不同类型的数据层,包括 LINQ-to-SQL、CLR 对象、Web 服务或 LINQ-to-Enties。我们将使用后者。

创建一个新的 Class Library 项目并将其命名为 WcfEntitiesSample.Data。然后选择添加新项。在显示的对话框中,选择 ADO.NET Entity Data Model。

850354/ADOEntities.png

然后选择“EF Designer from database:”

850354/EntityWizard.png

接下来,我们需要创建到数据库的连接,因此在 textbox 中输入“ConnecctionString”,然后单击 new connection 按钮。

850354/ChooseDataSource.png

在以下对话框中,选择您的 SQL Server 计算机名称,并使用 Windows 身份验证选择 AdventureWorks 数据库。

850354/ConnectionProperties.png

在此之后,您需要选中所有表、视图和存储过程。此外,请确保选中“Include foreign key columns in the model”,然后单击 Finish。

850354/EntityWizardObject.png

然后您应该会在解决方案中看到一个 *.edmx 文件,其中显示了您选择的表。

850354/EdmxFile.png

现在,我们需要将 EntityFramework.dll 引用添加到 WcfEntitiesSample.Data 项目中,方法是浏览到此目录选择文件:~\WcfEntitiesSample\packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll

接下来,将 ProductManager 类添加到 WcfEntitiesSample.Model.Net Class Library。

using System.Collections.Generic;
using System.Linq;
using WcfEntitiesSample.Data;

namespace WcfEntitiesSample.Model
{
    public class ProductManager
    {
        public Product GetProductById(int productID)
        {
            IEnumerable<Product> products = null;

                var db = new AdventureWorksEntities();
                products = db.vProductProductInventories
                    .Where(prod => prod.ProductID == productID)
                    .Select(p => new Product
                    {
                        ProductID = p.ProductID,
                        Name = p.Name,
                        InventoryCount = p.InventoryCount,
                        Description = p.Description
                    });

            return products.ToList()[0];
        }

现在转到 AdventureWorksService.svc.cs 文件并更改此代码。

        [OperationContract]
        public Product GetProductByID(int id)
        {
            return new Product()
            {
                ProductID = id,
                Name = "BB Ball Bearing",
                InventoryCount = 1109,
                Description = ""
            };
        }

变为这样:

        [OperationContract]
        public Product GetProductByID(int id)
        {
            var mgr = new ProductManager();
            return mgr.GetProductById(id);
        }

接下来,在您的 WcfEntitiesSample.Web 项目中,确保将连接字符串放入 web.config 文件中。

850354/ModelConnectionString.png

运行您的项目以验证输入不同的 ProductID 值时产品是否发生变化。

850354/FinishedProduct.png

参考文献

历史

  • 2014 年 12 月 19 日:初始版本
  • 2014 年 12 月 20 日:文章更新
    • 更正了 VS 2013 和 Blend 的说明,并更新了对第 3 和第 4 部分的引用,并更正了代码。
    • 还添加了有关 Microsoft 终止对 WCF RIA Services 支持的详细信息链接。
    • 添加了关于为什么需要 N 层应用程序的解释。
    • 添加了超链接的目录。
    • 添加了关于 MVVM 的信息。
© . All rights reserved.