Silverlight Web Service





3.00/5 (3 votes)
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.
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.
|
|
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>
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.