Silverlight Web 服务





3.00/5 (3投票s)
Silverlight Web 服务中的数据绑定。
引言
我确信关于这个话题已经有很多讨论,也有很多人写过文章,但我发现大多数例子要么太复杂,要么过于简单,比如“Hello World”。所以,从一个简化的角度来看,这是我的观点。
问题
Silverlight 是一个非常好的平台,它确实融合了丰富的 Web 体验应用程序平台;然而,问题在于如何使用 Web 服务集成数据,这对于像我这样的人来说可能有点挑战。在这里,我将尝试从一个简化的角度演示如何实现自定义的 `Lists`,以实现与传统 `DataSet` Web 服务相同的绑定功能。
先决条件
通用 Web 服务概念以及 `System.Collections.Generic.Lists` 的理解;当我提到 Web 服务时,我指的是使用 Visual Studio 创建和托管标准的 ASP.NET Web 服务。这里有一篇关于“使用 Visual Studio .NET 创建和使用 Web 服务”的好文章:Creating and consuming a Web Service using Visual Studio .NET
理解 Silverlight 应用程序、WPF 和 Microsoft Expression Blend 的创建;您可以选择性地获取 Silverlight Toolkit SDK。
为什么我们需要 `System.Collections.Generic.Lists`,是因为 Silverlight 应用程序不理解 `System.Data.DataSet` 对象,并且不支持它是有充分理由的。与 `System.Data.DataSet` 相比,您可以将 Silverlight 对象绑定到 `System.Collections`,这样更加有组织、类型安全,并且可以实现额外的元素级保护。
Patron
我确信我的公司安全团队在看到 Web 上的“Patron”一词时会竖起耳朵/眼睛,以及其他一切可能的反应,并且这同样是有充分理由的,因为这是核心业务对象。然而,我在这里试图做的是为一些我们可以关注的数据元素提供背景,以了解我们如何编写 Silverlight Web 服务,同时保护基于请求的单个敏感数据,例如元素掩码。
|
|
Contract(s)
由于 Silverlight 不理解 `System.Data.DataSet`,因此我们需要为所有将返回到 Silverlight 应用程序的数据定义 Data Contracts。如果您不想深入研究 ServiceContract / OperationContract / DataContract,那么从非常简化的角度来看,您可以将 DataContract 看作是消费者和发布者之间就强数据绑定达成的约定数据交换合同;另一方面,ServiceContract 是您服务的入口点,通常称为 EndPointAddress(奇怪但真实,因为这是消费者请求操作的地方),而这就是 OperationsContract 或消费者将调用的 `Method()`。
DataContract - Patron
因此,让我们定义一个类并将其标记为 `System.Runtime.Serialization.DataContract` 类。
namespace SilverLightWS
{
// Datacontract
[DataContract]
public class TPatron
{
// Each Datacontract should at least have one DataMember
[DataMember(Name="Company Code", Order=1)]
public String CompanyCode { get; set; }
[DataMember(Name="Patron ID", Order=2)]
public String PatronID { get; set; }
[DataMember(Name="Patron Name", Order=3)]
public String PatronName { get; set; }
}
}
DataContract - Transaction
namespace SilverLightWS
{
[DataContract]
public class TTransaction
{
[DataMember(Name="Company Code", Order=1)]
public String CompanyCode { get; set; }
[DataMember(Name="Patron ID", Order=2)]
public String PatronID { get; set; }
[DataMember(Name="Patron Name", Order=3)]
public String PatronName { get; set; }
[DataMember(Name="Chip Purchase Voucher", Order=4)]
public String ChipPurchaseVoucher { get; set; }
[DataMember(Name="CPV Sequence", Order=5)]
public int CPVSequence { get; set; }
[DataMember(Name="Gaming Date", Order=6)]
public DateTime GameDate { get; set; }
[DataMember(Name="Patron Account", Order=7)]
public String PAccount { get; set; }
[DataMember(Name="Patron Account", Order=8)]
public Currency PAccBal { get; set; }
[DataMember(Name="Total Amount", Order=9)]
public Currency DocumentAmount { get; set; }
[DataMember(Name="Balance Due", Order=10)]
public Currency BalanceDue { get; set; }
public TTransaction()
{
// By Default these elements are Masked for general level of users.
this.PatronName = "****";
this.PatronID = "****";
}
}
}
ServiceContract - getCasinoDocuments()
让我们定义 Silverlight 客户端将使用的 Web 方法。
namespace CasinoWebService
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class CasinoDocuments
{
private DataSet dsCasinoDocuments;
private OdbcConnection oCMConnection;
private OdbcDataAdapter daCasinoDocuments;
// This will be our entry point into the system.
[OperationContract]
public List<TTransaction> getCasinoDocuments(String CompanyCode, DateTime StartDate, DateTime EndDate)
{
try
{
// This is a standard data access element.
String SelectStatement = "SELECT * FROM MyDataTable"
oCMConnection = new OdbcConnection();
oCMConnection.ConnectionString = ConfigurationManager.ConnectionStrings[ConnectionName].ConnectionString;
oCMConnection.Open();
daCasinoDocuments = new OdbcDataAdapter(SelectStatement, oCMConnection);
dsCasinoDocuments = new DataSet();
daCasinoDocuments.Fill(dsCasinoDocuments, "vwtable");
oCMConnection.Close();
var CMDocs = new List<TTransaction>();
// This is not the best way to do; you should use LINQ but i have expanded it to show the general idea of loading
// a Generic List with element masking.
foreach (DataRow r in ds.Tables[0].Rows)
{
TTransaction oRow = new TTransaction();
oRow.CompanyCode = r["CompanyCode"].ToString();
// Element Masking can be performed using the Current User or other mechanism
// Note this will only work if you are doing a direct client authentication, for pass-through and proxy types you will have
// implement a different mechanism.
if (HttpContext.Current.User.IsInRole(Environment.MachineName + "\\AllowedAccess"))
{
oRow.PatronID = r["PatronID"].ToString();
DataSet dsPatron = oService.getPatron(oRow.CompanyCode, oRow.PatronID);
if (dsPatron.Tables[0].Rows.Count > 0) oRow.PatronName = dsPatron.Tables[0].Rows[0]["PatronName"].ToString();
}
oRow.ChipPurchaseVoucher = r["ChipPurchaseVoucher"].ToString();
oRow.CPVSequence = Convert.ToInt(r["CPVSequence"].ToString());
oRow.GameDate = Convert.ToDateTime(r["GameDate"].ToString());
oRow.PAccount = r["PAccount"].ToString();
oRow.PAccBal = Convert.ToDecimal(r["PAccBal"].ToString());
oRow.DocumentAmount = Convert.ToDecimal(r["DocumentAmount"].ToString());
oRow.BalanceDue = Convert.ToDecimal(r["BalanceDue"].ToString());
CMDocs.Add(oRow);
}
return CMDocs;
}
catch (Exception e)
{
// This is not correct; generally you should define a <TError> DataContract which then enables the client side
// to take corrective actions rather than pooping out on the server side.
throw e;
}
}
}
这基本就是 Web 服务端的部分了;如果您想在标准的 IIS 环境中托管它,那么可以继续发布它,Visual Studio 会将所有必要的标准 serviceModel 行为和绑定放在 Web.config 中。但是,如果您在客户端位于代理或防火墙后面的复杂环境中托管它,那么您需要理解 `System.serviceModel`。
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="CasinoWebService.DocumentsBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<customBinding>
<binding name="customBinding0">
<binaryMessageEncoding />
<httpTransport />
</binding>
</customBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
<services>
<service behaviorConfiguration="CasinoWebService.DocumentsBehavior"
name="CasinoWebService.Documents">
<endpoint address="" binding="customBinding" bindingConfiguration="customBinding0" contract="CasinoWebService.Documents" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
</system.serviceModel>
Silverlight客户端
在 VS 中创建一个新的 Silverlight 应用程序项目(我强烈推荐获取 Expression Blend),右键单击项目“添加服务引用”;输入您已发布 Web 服务的 URL 并添加 Web 引用。这里有一篇关于“如何…”的好文章:how to...;VS 将创建 ServiceReferences.ClientConfig 文件。
我们将保留默认的 `System.Collections.Generic.Dictionary` 作为数据类型;因为我们将返回 `List<T>`。
ServiceReferences.ClientConfig
<configuration>
<system.serviceModel>
<bindings>
<customBinding>
<binding name="CustomBinding_Documents">
<binaryMessageEncoding />
<httpTransport maxReceivedMessageSize="2147483647" maxBufferSize="2147483647" />
</binding>
</customBinding>
</bindings>
<client>
<!-- This is the same address as the place where you have published your WEB Service -->
<endpoint address="https:///POC/Documents.svc"
binding="customBinding" bindingConfiguration="CustomBinding_Documents"
contract="ServiceReference1.Documents" name="CustomBinding_Documents" />
</client>
</system.serviceModel>
</configuration>
整合
以下是 MainPage.XAML 的 XAML 代码,这是 VS 默认创建的用户控件;将 `DataGrid` 的 `DataContext` 绑定到服务定义的 DataContract。
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns:CasinoWebService_wssSycoSL="clr-namespace:CasinoWebService.wssSycoSL"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:expressionDark="clr-namespace:System.Windows.Controls.Theming;assembly=System.Windows.Controls.Theming.ExpressionDark"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:local="clr-namespace:CasinoWebService"
x:Class="CasinoWebService.MainPage" Height="672" Width="1128">
<Grid x:Name="LayoutRoot">
<Grid x:Name="grdSearch" Background="#FF9DB4F2" Height="136" VerticalAlignment="Top" HorizontalAlignment="Left" Width="328" Margin="8,8,0,0" >
<TextBlock x:Name="lblCompanyCode" Text="Company Code" Height="24" VerticalAlignment="Top" HorizontalAlignment="Left" Width="88" Margin="8,8,0,0" />
<TextBox x:Name="txtCompanyCode" Height="24" VerticalAlignment="Top" HorizontalAlignment="Left" Width="96" Margin="112,8,0,0" Text="502"/>
<TextBox x:Name="txtStartDate" Height="24" VerticalAlignment="Top" Margin="112,36,0,40" HorizontalAlignment="Left" Width="96" Text="11/9/2009" />
<TextBox x:Name="txtEndDate" HorizontalAlignment="Left" Width="96" Margin="112,64,0,45" Text="11/9/2009" d:LayoutOverrides="VerticalAlignment"/>
<Button x:Name="btnView" Content="View" Click="btnView_Click" HorizontalAlignment="Left" Width="96" Margin="112,0,0,8" Height="33" VerticalAlignment="Bottom" d:LayoutOverrides="VerticalAlignment" />
<Button x:Name="btnSave" Content="Save" Click="btnSave_Click" Height="33" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="96" Margin="212,0,0,8" d:LayoutOverrides="VerticalAlignment" />
<TextBlock x:Name="lblStartDate" Text="Start Date" HorizontalAlignment="Left" Width="88" Margin="8,36,0,84" FontSize="10.667"/>
<TextBlock x:Name="lblEndDate" Text="End Date" HorizontalAlignment="Left" Width="88" Margin="8,64,0,45" FontSize="10.667" d:LayoutOverrides="VerticalAlignment"/>
</Grid>
<Grid x:Name="grdData" Margin="0,148,0,0" Height="244" VerticalAlignment="Top" d:LayoutOverrides="VerticalAlignment">
<data:DataGrid x:Name="dgView" ItemsSource="{Binding Mode=OneWay}" FrozenColumnCount="2" Margin="8" RenderTransformOrigin="0.5,0.5" d:LayoutOverrides="VerticalAlignment, GridBox">
<data:DataGrid.DataContext>
<CasinoWebService_wssSycoSL:TTransaction/>
</data:DataGrid.DataContext>
</data:DataGrid>
</Grid>
<controlsToolkit:BusyIndicator x:Name="cntrlBussy" Margin="384,40,392,0" Content="" BusyContent="Loading..." Cursor="Wait" d:LayoutOverrides="VerticalAlignment, GridBox" Height="72" VerticalAlignment="Top"/>
<Canvas x:Name="cnvObject" Height="200" VerticalAlignment="Bottom" Margin="8,0,8,8">
<Border x:Name="cnvBorder" BorderBrush="#FFC16161" CornerRadius="5" BorderThickness="3" Height="200" Width="1112">
<Border.Background>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FF598194" Offset="0.165"/>
<GradientStop Color="#FF303234" Offset="0.961"/>
<GradientStop Color="#FF4A6A71" Offset="0.448"/>
</LinearGradientBrush>
</Border.Background>
</Border>
</Canvas>
<Button x:Name="btnAdd" Height="32" Margin="480,0,552,232" VerticalAlignment="Bottom" Content="Add" Click="btnAdd_Clicked" d:LayoutOverrides="VerticalAlignment"/>
</Grid>
</UserControl>
这是 MainPage 用户控件的代码隐藏。
namespace CasinoWebService
{
public partial class MainPage : UserControl
{
// Create the Proxy of our Silverlight Web Service
CasinoWebServiceClient wsProxy = new CasinoWebServiceClient();
public MainPage()
{
// Required to initialize variables
InitializeComponent();
}
private void btnView_Click(object sender, System.Windows.RoutedEventArgs e)
{
try
{
// Remove the listner; this is debatable on how you want to implement the handlers, you can do it
// in the Constructors or add/remove as needed.
wsProxy.getCasinoDocumentsCompleted += new EventHandler(wsProxy_getCasinoDocumentsCompleted);
// This is to show the service is running; this control is from the Silverlight Toolkit (nice to have)
cntrlBussy.IsBusy = true;
// Start call to the service.
wsProxy.getCasinoDocumentsAsync(txtCompanyCode.Text, getDateTime(txtStartDate.Text), getDateTime(txtEndDate.Text));
}
catch (Exception ex)
{
throw ex;
}
}
private void btnSave_Click(object sender, System.Windows.RoutedEventArgs e)
{
try
{
MessageBox.Show("Feature not implemented", "POC", MessageBoxButton.OK);
}
catch (Exception ex)
{
throw ex;
}
}
private void btnAdd_Clicked(object sender, System.Windows.RoutedEventArgs e)
{
try
{
double left = 0, width = 0;
// This code adds the Carrousel to the Canvas to show the drill down of the grid item.
// Carrousal is another user control that I have build which shows the data in multi-dimension
foreach (object oCarrousel in this.cnvObject.Children)
{
if (oCarrousel.GetType().FullName == "CasinoWebService.PatronCarrousel")
{
PatronCarrousel oItem = (PatronCarrousel)oCarrousel;
width = (double)oItem.GetValue(Canvas.ActualWidthProperty);
left += (double)oItem.GetValue(Canvas.ActualWidthProperty);
}
}
if ((left +width) < (double)this.cnvObject.GetValue(Canvas.ActualWidthProperty))
{
// Cast the SelectedItem into DataContract.TTransaction class.
this.cnvObject.Children.Add(new PatronCarrousel((dgView.SelectedItem as TTransaction)));
Canvas.SetLeft(this.cnvObject.Children[this.cnvObject.Children.Count - 1], left);
}
}
catch (Exception)
{
MessageBox.Show("Select item from the datagrid.", "No Item", MessageBoxButton.OK);
}
}
// Handler for the Call Complete
void wsProxy_getCasinoDocumentsCompleted(object sender, getCasinoDocumentsCompletedEventArgs e)
{
// e Event Args will be Lits<TTransaction>
dgView.DataContext = e.Result;
dgView.Visibility = Visibility.Visible;
cntrlBussy.IsBusy = false;
// Remove the listner; this is debatable on how you want to implement the handlers, you can do it
// in the Constructors or add/remove as needed.
wsProxy.getCasinoDocumentsCompleted -= new EventHandler(wsProxy_getCasinoDocumentsCompleted);
}
private DateTime getDateTime(String inputDateTime)
{
if (inputDateTime == null || inputDateTime == "") return DateTime.Now;
DateTime retDate = Convert.ToDateTime(inputDateTime, new System.Globalization.CultureInfo("en-GB"));
return retDate;
}
}
}
结论
这似乎是很多工作,但另一方面,我相信现在有了 `System.Runtime.Serialization.DataContract`,我们可以真正让我们的 Web 服务通用且易于被任何类型的客户端使用。
另一方面,Silverlight 平台为全新的应用程序开发概念开启了巨大的潜力,而无需在 IIS 服务器上维护会话(Session)来保持状态。