我在 Silverlight 2 中的第一个数据应用程序






4.87/5 (109投票s)
介绍如何从数据库检索数据并在 Silverlight 应用程序中使用它。

目录
- 引言
- 我们将涵盖的内容
- 运行示例应用程序
- ASP.NET 程序员请注意
- 为什么是 WCF,我不能直接访问数据库吗?
- 开始吧
- 生成 LINQ 类
- 创建 Silverlight WCF 服务
- 向 Silverlight 项目添加服务引用
- 创建用户界面
- 编写一些代码
- 检查点已到达..运行项目
- 定义列
- 第二次运行
- 将数据写回数据库
- 结论
- 历史
引言
我几周前开始使用 Silverlight,它看起来非常棒。Silverlight 结合了 WPF 的表现力与 C# 的强大功能,显得非常强大。本文的重点将是从数据库检索数据并在 Silverlight 应用程序中使用它。如果您是 ASP.NET 开发者并希望开始探索 Silverlight,那么这里可能是个不错的起点,因为我们还将探讨 Silverlight 和 ASP.NET 之间的相似之处。
我们将涵盖的内容
我们将开发我们在 Silverlight 中的第一个业务应用程序。由于几乎每个业务应用程序都与数据和数据库有关,我们将研究如何在 Silverlight 应用程序中显示数据(来自数据库)。以 Northwind 数据库为例,我们将生成一些 LINQ 类、一个用于检索数据的 WCF 服务,最后,还有一个 ListBox
和一个 DataGrid
以及用于数据呈现的 DataTemplate
。我们的输出将是一个相当简单的“主-从”UI,包含客户、他们的订单以及订单详情。请注意,本文的源代码是针对 Silverlight 2 RC1 编写的。
运行示例应用程序
运行示例项目需要进行一些小的调整。默认情况下,解决方案中没有启动项目,因此我们必须通过打开解决方案的属性窗口手动将 DataApplication.Web
设置为启动项目。
ASP.NET 程序员请注意
如果您是 ASP.NET 程序员,您可能需要注意 Silverlight 项目中的 C# 代码在“客户端”运行,而不是在服务器上运行。也许您可以将其视为 JavaScript。因此,Silverlight 的好处是,您现在可以在不了解或编写任何 JavaScript 的情况下完全控制客户端(至少,我不擅长编写 JavaScript)。
为什么是 WCF,我不能直接访问数据库吗?
嗯,简单原因是因为 C# 代码在客户端运行,我们的服务器数据库将无法直接访问。请注意,Silverlight 项目中没有 DataSet
或 DataSource
等项。包含我们最喜欢的类的 System.Data
命名空间也缺失。但是,好消息是还有其他选择,例如 WCF 服务。本文将演示如何使用 WCF 服务进行数据检索。
开始吧
要开始使用 Visual Studio 创建 Silverlight 应用程序,我们需要安装 Visual Studio 的 Silverlight 工具,可以 此处 下载。安装插件后,将添加两种新的项目类型:Silverlight Application 和 Silverlight Class Library。我们将从创建一个名为 DataApplication 的新“Silverlight Application”项目开始。

Visual Studio 然后会询问我们如何部署 Silverlight 应用程序;我们将选择“ASP.NET Web Application Project”,在该项目中我们将创建 LINQ 类和 WCF 服务。

如果一切顺利,我们将在解决方案中看到两个项目:DataApplication
将是我们的客户端 Silverlight 项目,而 DataApplication.Web
将是我们的 ASP.NET 服务器端项目。
生成 LINQ 类
我们轻量级的全能选手 Northwind(可从 此 链接获取)一直是开始使用的最佳数据库之一。请注意,MDF 文件也包含在示例项目中,并且通过 SQLExpress 连接。要开始生成 LINQ 数据类,我们需要在 ASP.NET 应用程序中添加一个新的 LINQ Data Classes 对象。

现在,使用服务器资源管理器,创建一个到 Northwind 数据库的新数据连接(使用 SQLExpress 或 SQL Server,如果您有的话),然后将 Customer
、Orders
和 OrderDetails
表拖到 LINQ 设计器中。一个重要的事情是允许我们的 LINQ 生成的数据类进行**序列化**,因为我们将把这些对象传输到 Silverlight 应用程序。要实现这一点,请单击 LINQ 设计器中的空白区域,并使用“属性”窗口将 Serialization
属性更改为 UniDirectional
。

创建 Silverlight WCF 服务
现在,我们将为服务器项目添加一个服务来检索数据。在 Silverlight 2 Beta 2 之前,我们需要对标准 WCF 服务模板进行一些调整才能在 Silverlight 项目中使用,但幸运的是,Beta 2 及更高版本提供了“Silverlight 启用的 WCF 服务”模板,它可以自己处理所有事情。我们将为 ASP.NET 项目添加一个名为 DataService 的新 Silverlight 启用的 WCF 服务。

我们将在服务中编写三个方法:一个返回所有客户,一个返回客户的订单,最后,一个返回特定订单的订单详情。请注意,方法必须标记 [OperationContract]
属性(此属性类似于 ASMX 服务的 [WebMethod]
属性)。以下是使用非常基本的 LINQ 实现数据检索方法的快速示例。将以下内容添加到 DataService.svc.cs 文件中
[OperationContract]
public List<Customer> GetCustomers()
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return datacontext.Customers.ToList();
}
[OperationContract]
public List<Order> GetOrders(string customerID)
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return (from order in datacontext.Orders
where order.CustomerID == customerID
select order).ToList();
}
[OperationContract]
public List<Order_Detail> GetOrderDetails(int orderID)
{
DataClasses1DataContext datacontext = new DataClasses1DataContext();
return (from orderdetail in datacontext.Order_Details
where orderdetail.OrderID == orderID
select orderdetail).ToList();
}
向 Silverlight 项目添加服务引用
这就是服务器端 ASP.NET 项目所需的一切。我们创建了 LINQ 数据类来从数据库获取数据,并创建了 WCF 服务来返回这些 LINQ 对象。现在,我们可以在客户端 Silverlight 项目中使用了。为此,我们需要在 DataApplication
项目中添加一个服务引用。我们可以在“添加服务引用”弹出窗口中单击**发现**,让 Visual Studio 自动找到我们新创建的 WCF 服务。

创建用户界面
Silverlight 页面/控件由一个布局 XAML 文件和一个代码隐藏 xaml.cs 文件组成。通常,与 ASP.NET 页面类似,XAML 文件包含布局定义(如 *.aspx 文件,我们在其中定义 UI),而 xaml.cs 文件包含逻辑和事件处理程序(如我们的 aspx.cs 文件)。让我们开始创建一些基本布局。
添加用于使用 DataGrid 控件的程序集
我们将使用 DataGrid
控件来显示我们的数据,但 Silverlight 默认不包含 DataGrid
控件的引用,因此我们需要添加一个。这个过程与在 ASP.NET 中使用自定义控件非常相似。回想一下,我们在项目中添加了对相应 DLL 的引用,然后在 aspx 页面中添加了 register
标签。要在 Silverlight 中实现这一点,请添加一个新引用(右键单击“引用”并选择“新建引用”),然后在列表中找到 System.Windows.Controls.Data
(这是包含 DataGrid
的程序集)。

添加此引用后,我们需要在 XAML 标记中为该程序集分配一个命名空间。为此,请将以下内容添加到 Page.Xaml 文件的命名空间声明中。
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
Visual Studio IDE 在此处提供帮助,如下面的截图所示

UI 布局
这是操作步骤的概述:创建一个名为 LayoutRoot
的 Grid
,其中包含三行:第一行用于应用程序标题(高度=50),第三行用于状态栏(高度=20),中间一行充当主内容持有者(高度=*,占据所有剩余空间)。在第一行中添加一个标题 TextBlock
,在 LayoutRoot
网格的底部一行中添加一个名为 txtStatus
的空 TextBlock
。在 LayoutRoot
网格的中间行(我们确定为主内容持有者)中,再添加一个名为 ContentRoot
的 Grid
,包含两列和两行;左列宽度为 200,右列占据剩余空间。行应按 60% 和 40% 的比例划分。在此 ContentRoot
网格的左列中,添加一个跨越两行的 ListBox
。在右列中,在第一行中添加一个 DataGrid
用于显示客户订单,在第二行中添加另一个 DataGrid
用于显示订单详情。我的疯狂描述是否让您筋疲力尽?这是 page.xaml 的 XAML 版本
<UserControl xmlns:basics="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls"
x:Class="DataApplication.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data"
Width="Auto" Height="Auto">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="55" x:Name="HeaderRow" />
<RowDefinition Height="*" x:Name="ContentRow"/>
<RowDefinition Height="20" x:Name="FooterRow"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Heading -->
<TextBlock x:Name="txtHeader" Grid.Row="0"
FontSize="20" Margin="5,5" Foreground="Blue"
Text="My First Data Application in Silverlight">
</TextBlock>
<!-- A textblock in the footer to be used as an Status bar -->
<TextBlock x:Name="txtStatus" Grid.Row="2"
FontSize="10" Margin="5,0" Foreground="Red">
</TextBlock>
<!-- Content Holder -->
<Grid x:Name="ContentGrid" Grid.Row="1" Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height=".6*" />
<RowDefinition Height=".4*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Listbox for displaying customers -->
<ListBox x:Name="lstCustomers" Grid.Column="0" Grid.RowSpan="2"
DisplayMemberPath="ContactName"
Loaded="lstCustomers_Loaded"
SelectionChanged="lstCustomers_SelectionChanged">
</ListBox>
<!-- DataGrid for displaying orders of a customer
(with autogenerated columns) -->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"
AutoGenerateColumns="True"
SelectionChanged="dgOrders_SelectionChanged">
</data:DataGrid>
<!-- DataGrid for displaying orderdetais for an order -->
<data:DataGrid x:Name="dgOrderDetails" Grid.Row="1" Grid.Column="1"
AutoGenerateColumns="True"
AutoGeneratingColumn="dgOrderDetails_AutoGeneratingColumn">
</data:DataGrid>
</Grid>
</Grid>
</UserControl>
本文将不会详细解释 WPF 布局,因为 CodeProject 和其他地方都有很多资源;例如,Sacha Barber 的 这篇。我们倒是可以快速看一下 ListBox
和 DataGrid
的标记。
ListBox
lstCustomers
列表框将用于显示数据库中的客户列表。我们将在已注册的 Loaded
事件中绑定此列表框。请注意,如果 ListBox
绑定到某个对象源,它将为其每个项目集合显示 object.ToString()
的值。如果我们想显示任何其他值(通常是对象的 string
成员),我们有三个显而易见的选择:
- 重写
object.ToString()
方法(我们不会仅仅为了在ListBox
中显示正确的值而这样做)。 - 定义一个 Data Template(这是最灵活的方法,我们将在手动定义
DataGrid
列时简要介绍DataTemplate
,但现在我们将跳过此选项)。 - 在
DisplayMemberPath
属性中定义要使用的属性(很简单,所以我们暂时保持如此)。
由于我们希望 ListBox
显示绑定到的 Customer
对象的 ContactName
属性,因此我们使用 DispalyMemberPath="ContactName"
。此外,我们还注册了 SelectionChanged
事件,我们将在代码隐藏文件中处理该事件以使用所选客户的订单更新 DataGrid
。
DataGrid
目前,我们没有对 DataGrid
做任何花哨的事情。我们只配置它们在数据绑定时自动生成列。请注意,我们还订阅了 dgOrderDetails
的 AutoGeneratingColumns
事件。这是一个常见的做法,如果我们要快速删除某些不需要的列,并且与自动生成列结合使用。在本文中,我们还将研究如何手动定义列,但现在,让我们保持简单。
编写一些代码
填充 ListBox
我们想将所有客户加载到列表框 lstCustomers
中,为此我们使用列表框的 Loaded
事件。请注意,Silverlight 中的所有服务调用都需要异步进行,因此我们将注册一个回调函数,在该函数中我们将传入的数据绑定到 ListBox
。注意我们如何使用 txtStatus
文本框(回想一下,我们将其放在 LayoutGrid
的底部一行以向用户提供更新)。
private void lstCustomers_Loaded(object sender, RoutedEventArgs e)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading customers...";
svc.GetCustomersCompleted += new
EventHandler<GetCustomersCompletedEventArgs>(svc_GetCustomersCompleted);
svc.GetCustomersAsync();
}
void svc_GetCustomersCompleted(object sender, GetCustomersCompletedEventArgs e)
{
if (e.Error == null)
{
this.lstCustomers.ItemsSource = e.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading customers from database";
}
}
显示客户的订单
现在,我们将编写一些代码来显示当列表框中的客户被选中时发生的订单。在 SelectionChanged
事件处理程序中,我们将调用 WCF 服务,并在检索到数据时将其绑定到 dgOrders
。这次,我们将使用匿名方法以使其更紧凑。
private void lstCustomers_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Customer selectedCustomer = this.lstCustomers.SelectedItem as Customer;
if (selectedCustomer != null)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading orders...";
svc.GetOrdersCompleted +=
delegate(object eventSender, GetOrdersCompletedEventArgs eventArgs)
{
if (eventArgs.Error == null)
{
this.dgOrders.ItemsSource = eventArgs.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading orders from database";
}
};
svc.GetOrdersAsync(selectedCustomer.CustomerID);
}
}
显示选定订单的订单详情
与列表框 SelectionChanged
事件类似,我们将以下代码添加到 dgOrders
的 SelectionChanged
事件中。这次,我们将尝试使用 lambda 表达式。
private void dgOrders_SelectionChanged(object sender, EventArgs e)
{
Order selectedOrder = this.dgOrders.SelectedItem as Order;
if (selectedOrder != null)
{
DataServiceClient svc = new DataServiceClient();
this.txtStatus.Text = "Loading order details...";
svc.GetOrderDetailsCompleted +=
(eventSender, eventArgs) =>
{
if (eventArgs.Error == null)
{
this.dgOrderDetails.ItemsSource = eventArgs.Result;
this.txtStatus.Text = string.Empty;
}
else
{
this.txtStatus.Text =
"Error occurred while loading order details from database";
}
};
svc.GetOrderDetailsAsync(selectedOrder.OrderID);
}
}
从 dgOrderDetails 中删除一些自动生成的列
请注意,在 XAML 中,我们将 DataGrid
的 AutoGenerateColumns
属性设置为 true
。假设我们想摆脱 dgOrderDetails
数据网格中的 OrderID
列。这可以通过在 AutoGeneratingColumns
事件处理程序中编写以下代码来实现。
private void dgOrderDetails_AutoGeneratingColumn(object sender,
DataGridAutoGeneratingColumnEventArgs e)
{
if (e.Column.Header.ToString() == "OrderID")
e.Column.Visibility = Visibility.Collapsed;
}
检查点已到达..运行项目
现在,我们的小应用程序可以观看了。运行它..选择一些客户,查看订单,编辑 DataGrid
中显示的数据,通过单击列标题对 DataGrid
数据进行排序,调整 DataGrid
列的大小,调整浏览器窗口的大小,做任何您想做的事情。是不是很酷?我们现在将看看 DataGrid
列和模板。

定义列
Silverlight DataGrid
中的列可以以与 ASP.NET 中非常相似的方式进行定义。DataGrid
可以接受三种类型的列:
DataGridTextBoxColumn
- 此列类型使用TextBlock
来显示其数据,并使用TextBox
来允许编辑其数据。我们需要使用DisplayMemberPath
来告知要显示数据绑定对象的哪个属性。DataGridCheckBoxColumn
- 此列类型提供一个只读的CheckBox
来显示布尔值或可空布尔值,以及一个普通的CheckBox
来允许编辑该值。DataGridTemplateColumn
- 此强大的列类型允许我们定义DataTemplate
并选择我们自己选择的控件,就像 ASP.NET 的TemplateColumn
一样。有关 DataTemplating 的更多信息,请参阅 MSDN 此处。
如果您想了解更多,Scott Morris 在列类型方面有一个很好的博客文章 此处。
因此,让我们在应用程序中使用这些知识。为了简单起见,我们将只定义四列:我们将为 OrderID
和 EmployeeID
列使用 DataGridTextBoxColumn
。我们将为 OrderDate
定义一个 DataGridTemplateColumn
,在其 CellTemplate
中有一个 TextBlock
,在其 CellEditingTemplate
中有一个 DatePicker
控件。最后,我们将为 Frieght
定义另一个 TemplateColumn
。但是,这次我们将在其 CellEditingTemplate
中定义两个控件:一个 Slider
用于增加/减少 Frieght
值,以及一个 TextBlock
用于显示滑块的当前值。这两个控件都将放置在一个水平 StackPanel
中,因为我们在 DataTemplate
中只能定义一个项。
以下代码应替换 dgOrders
标记
<!-- DataGrid for displaying orders of a customer -->
<data:DataGrid x:Name="dgOrders" Grid.Row="0" Grid.Column="1"
AutoGenerateColumns="False"
SelectionChanged="dgOrders_SelectionChanged">
<data:DataGrid.Columns>
<!-- OrderID text column -->
<data:DataGridTextColumn Header="Order ID" Binding="{Binding OrderID}" />
<!-- EmployeeID text column -->
<data:DataGridTextColumn Header="Employee ID" Binding="{Binding EmployeeID}" />
<!-- OrderDate template column -->
<data:DataGridTemplateColumn Header="Order Date" Width="150">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding OrderDate}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<basics:DatePicker SelectedDate="{Binding OrderDate, Mode=TwoWay}" />
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
<!-- Freight template column -->
<data:DataGridTemplateColumn Header="Freight" Width="150">
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Freight}"></TextBlock>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
<data:DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Freight}" Width="50" />
<Slider Value="{Binding Freight, Mode=TwoWay}" Width="100"
Minimum="0" Maximum="500" />
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellEditingTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
</data:DataGrid>
同样,我们可以为 ListBox
使用 DataTemplate
。假设在我们的对象模型中,我们有一个 Customer
的 PictureProperty
(返回 BitmapImage
)。然后,我们可以像这样在 ListBox
中显示图片
<ListBox x:Name="lstCustomer">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding NameProperty}"></TextBlock>
<Image Source="{Binding PictureProperty}"></Image>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
请注意,上面的代码只是一个示例。由于我们的对象中没有图片,因此我们无法在应用程序中使用此代码段。
第二次运行
再次运行项目。双击 OrderDate
并观察 DatePicker
如何弹出。使用滑块控件修改 Freight
值。类似地,可以在项模板中使用任何其他控件或控件组合。请注意,在第二次迭代中,我们没有修改 *.cs 文件;我们所做的所有更改都在布局方面。这就是 Windows Presentation Foundation 框架的优点。

将数据写回数据库
嗯,这对于这篇入门文章来说太多了。但是,如果您已经了解了 WCF 服务如何用于在客户端和服务器之间通信,那么您可以通过在服务中创建一些函数并在 Silverlight 应用程序中调用它们来轻松地将数据写回数据库。请注意,本文中的数据绑定是双向的,也就是说,在 DataGrid
中更改值实际上会更改绑定到的 DataContext
项中的值。我们只需要使用 WCF 服务将这些更新发送到我们的 ASP.NET 项目,并在那里处理它们来更新数据库。Ronnie Saurenmann 在 此处 提供了一些视频。他使用类似 DataSet
的方法,保留数据的修改副本和原始副本,然后使用一些辅助类将相关记录仅发送到服务器。我强烈推荐观看他的视频。
结论
就这样。我写这篇文章是为了演示构建任何数据应用程序的基础是多么容易。让我们回顾一下我们学到的内容:我们在服务器端的 ASP.NET 项目中创建了一个使用 LINQ 的数据访问层,并通过 WCF 服务将其公开。我们在 Silverlight 应用程序中通过服务客户端检索了数据,最后,我们使用了一些数据模板来更精细地控制数据呈现。我希望本文能激发您开始在 Silverlight 中构建未来的应用程序。祝您 Silverlight 愉快……
历史
- 12 Aug 08 - 文章发布
- 19 Oct 08 - 文章更新为 Silverlight 2 RC1