Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Silverlight Web Service

0.00/5 (No votes)
23 Sep 2010 1  
Data binding in Silverlight Web Services

Introduction

I am sure there is a lot discussion about this topic and many people writing about it, however I find the most of the examples too complex or too simple like Hello World. So, here is my take on this from a simplistic point of view.

Silverlight_POC.JPG

Problem

Silverlight is a very good platform which truly blends as a rich web experience application platform; however the problem is integration with data using web-services, now that can be a bit challenging for people like me. Here I am trying to demonstrate how to implement custom Lists to achieve the same data-binding capabilities as that of a conventional DataSet web services.

Pre-requisite

Understanding of Web Services in general and System.Collections.Generic.Lists; when I say Web Services I mean creating and hosting of standard ASP.NET web services using Visual Studio. Here is a nice article Creating and consuming a Web Service using Visual Studio .NET on how to create Web Services

Understanding of creating Silverligh Applications,WPF and Microsoft Expression Blend, optionally you can get the Silverlight ToolKit SDK.

Why we need System.Collections.Generic.Lists, is because SilverLight Applications do not understand System.Data.DataSet objects and there is a very good reason of not supporting this. Instead of System.Data.DataSet you can bind SilverLight Objects to a System.Collections which is much organized, strongled typed and can implemented additional element level protection.

Patron

I am sure my company’s security team will be raising their ears / eye and all possible things when they see “Patron” on the web, and again there is a good reason as this is the core business object. However, what I am trying to establish here is to give a background on few of the data elements that we can look at to see how we can write Silverlight web service and at the same time protect individual sensitive data based on the request; like element masking.

Patron Master
Data Column Column Name Data Type
CompanyCode Primary Property character
PatronID Patron Number. character
PatronName Patron Name. character
 
Transaction
Data Column Column Name Data Type
ChipPurchaseVoucher Chip Purchase Voucher character
CPVSequence CPV Sequence integer
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)

As Silverlight does not understand System.Data.DataSet we have to define Data Contracts for all our data that will be passed back to the Silverlight Application. If you are not keen on diving deep into the ServiceContract / OperationContract / DataContract then on a very simplistic view you can look at DataContract as a Agreed Data Exchange Contract between Consumer & Publisher for strong data binding; on the other hand ServiceContract is the Entry Point into your service, typically called as EndPointAddress (strange but true, as this is the place where the consumer request for Operations), and that is what is OperationsContract or the Method() the consumer will call.

DataContract - Patron

So, lets define a class and mark it with the System.Runtime.Serialization.DataContract class.

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()

Let's define the web method that will be used by SilverLight client side to consume.

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;
            }
        }
}        

This is pretty much for the Web Service Side; if you want to host this on a standard IIS environment then go ahead and publish it, Visual Studio will put all the necessary standard serviceModel behaviours and bindings in the Web.config. However you have to understand the System.serviceModel if you are hosting it in complex environment where the clients are behind Proxy or FireWalls etc.

 <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 Client

Create a new Silverlight Application Project in VS (I strongly recommend get Expression Blend), right click on the project "Add Service References"; enter the URL where you have published the web service and add the WebReference. Here is a nice article on how to...; VS will create the ServiceReferences.ClientConfig file.

We will keep the default System.Collections.Generic.Dictionary for the data type; as we will be returning 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="http://localhost/POC/Documents.svc"
                binding="customBinding" bindingConfiguration="CustomBinding_Documents"
                contract="ServiceReference1.Documents" name="CustomBinding_Documents" />
        </client>
    </system.serviceModel>
</configuration>

Putting it together

Following is the XAML for the MainPage.XAML, this is the user control that is created by VS as default; bind the DataContext of DataGrid to the DataContract as defined by the service.

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

This is the code-behind of the MainPage User Control.

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;
        }
	}
}

Conclusion

This seems to be a lot of work, but on the other hand I believe that now with System.Runtime.Serialization.DataContract we can truly make our web services universal and easy to consume with any type of client.

On the other hand the Silverlight platform opens up huge potentials of a totally new application development concepts without cranking Session(s) on IIS server to maintain state.

History

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here