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

Silverlight Web 服务

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (3投票s)

2010 年 9 月 24 日

CPOL

4分钟阅读

viewsIcon

25156

Silverlight Web 服务中的数据绑定。

引言

我确信关于这个话题已经有很多讨论,也有很多人写过文章,但我发现大多数例子要么太复杂,要么过于简单,比如“Hello World”。所以,从一个简化的角度来看,这是我的观点。

Silverlight_POC.JPG

问题

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 服务,同时保护基于请求的单个敏感数据,例如元素掩码。

Patron Master
Data Column 列名 数据类型
CompanyCode Primary Property character
PatronID Patron Number. character
PatronName Patron Name. character
 
事务
Data Column 列名 数据类型
ChipPurchaseVoucher Chip Purchase Voucher character
CPVSequence CPV Sequence 整数
GameDate Gaming Date. date
PAccount Patron Account. character
PAccBal Patron Account Balance. decimal
DocumentAmount Document Amount. decimal
BalanceDue Balance Due. decimal
CompanyCode Company Code. character

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>`。

Observable.JPG

 

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)来保持状态。

历史

© . All rights reserved.