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





5.00/5 (3投票s)
一个使用 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 中设计
- 2. 使用 Expression Blend 和 Visual Studio 进行数据绑定
- 3. 设置数据库
- 4. 创建 WCF 服务组件
- 5. 添加 LINQ to Entities
- 参考文献
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 部分详细介绍这一点。
点击 OK 后,右键单击 MainPage.xaml 并选择“Open in Blend...”将显示以下页面
从 Blend 资源窗口拖放一个按钮到左下角,以及几个 Border 控件,如果需要,为每个控件选择一个背景颜色。然后将一个文本块放入较大的边框中,将一个文本框放入较小的边框控件中。
接下来,在显示屏的左上方放置一个文本块
您可以在这里找到更多关于 Blend 的教程和视频。
2. 使用 Expression Blend 和 Visual Studio 进行数据绑定
数据绑定是连接数据源与用户界面元素的必要条件
数据绑定的三种主要形式
OneTime
:很少更改的项,例如商店名称或付款选项。OneWay
:对于经常更改但基本上只读的项。TwoWay
:与OneWay
非常相似,不同之处在于用户在此处所做的更改会应用于业务层,例如允许用户从先前使用的列表中选择其地址,并编辑该地址。
上一节描述了如何在 Blend 中设计用户界面,接下来我们将更新数据上下文并在 Visual Studio 中完成数据绑定,所以请在该应用程序中打开解决方案文件。
接下来,向应用程序添加以下 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”项来为按钮创建事件处理程序。
接下来,打开 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
配合使用。
如果您在 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”。
根据您为服务命名的不同,您的代码可能如下所示
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 应包含以下内容
二进制消息编码是一项节省带宽的功能。
接下来,将 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 按钮。
单击 OK 后,将生成客户端配置文件。
4.2 更新 UI 以测试服务
添加一个新的 textbox
,您可以在其中输入 ID,以及一个搜索按钮。
接下来,使用此代码更新 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。
然后选择“EF Designer from database:”
接下来,我们需要创建到数据库的连接,因此在 textbox
中输入“ConnecctionString
”,然后单击 new connection 按钮。
在以下对话框中,选择您的 SQL Server 计算机名称,并使用 Windows 身份验证选择 AdventureWorks
数据库。
在此之后,您需要选中所有表、视图和存储过程。此外,请确保选中“Include foreign key columns in the model”,然后单击 Finish。
然后您应该会在解决方案中看到一个 *.edmx 文件,其中显示了您选择的表。
现在,我们需要将 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 文件中。
运行您的项目以验证输入不同的 ProductID
值时产品是否发生变化。
参考文献
- Microsoft Expression Blend
- 使用 Expression Blend 在 Silverlight 中显示动态内容
- FrameworkElement.DataContext 属性
- ObservableCollection 类
- Who Moved My Cheese: RAI Services
- Adventure Works 示例数据库
- 《Silverlight 5 in Action》作者:Pete Brown
历史
- 2014 年 12 月 19 日:初始版本
- 2014 年 12 月 20 日:文章更新
- 更正了 VS 2013 和 Blend 的说明,并更新了对第 3 和第 4 部分的引用,并更正了代码。
- 还添加了有关 Microsoft 终止对 WCF RIA Services 支持的详细信息链接。
- 添加了关于为什么需要 N 层应用程序的解释。
- 添加了超链接的目录。
- 添加了关于 MVVM 的信息。