Introduction
Below is a guide to constructing an application using Expression Blend and Silverlight for the front-end which obtains data from a WCF service and SQL Server back-end.
This guide does not discuss using Silverlight with WCF RIA Services, because at the time of writing of this article, Microsoft has discontinued support of this technology. If you still desire to implement RIA Services, please consider this open source implementation: Open RIA Services.
Should you use WCF SOAP services or ASMX ASP.NET Web Services? They both are almost identical, except that WCF has more support for marshaling support. WCF services can support more types for input and return from the service.
Please note that I have also written another article similar to this one, except that I go into detail about implementing the Model-View-ViewModel pattern here which I hope you will find helpful. The MVVM pattern is targeted towards UI platforms that support event-driven programming, and it more clearly separates the UI from the business layer or back-end data layer.
Table of Contents
1. Designing in Expression Blend
Expression Blend ships as a standalone tool with Visual Studio 2013. At the time of writing of this article, you can download Blend for free with Visual Studio Express for Windows. It allows UX designers to create an attractive and intuitive user interface without having to write any source code.
When you first start Visual Studio 2013, you are presented with this page where can choose the project type. Select Silverlight Application, and do not check the "Enable WCF RIA Services" checkbox. We'll go into more detail on this topic in sections 3 and 4 below.
After clicking OK, then right-click MainPage.xaml and choose "Open in Blend..." The following page is displayed:
Drag and drop a button to the lower left, and a couple Border items from Blend assets window, and choose a background color for each if desired. Then put a text block item in the larger border, and a text box in the smaller border item.
Next put a text block item in the upper left portion of the display:
You can get more tutorials and videos for Blend here.
2. Data Binding with Expression Blend and Visual Studio
Data binding is necessary to connect your data source to the user interface elements:
There are three main forms of data binding:
OneTime
: Items that rarely change, such as a store name, or payment options.
OneWay
: On items that change often, but are essentially read only.
TwoWay
: Much the same as OneWay
, except that the changes the user makes here are applied to the business layer, such as allowing a user to select their address from a previously used list, and edit that address.
The previous section described how to design the user interface in Blend, and next we will update the data context and complete the data binding in Visual Studio, so open the solution file in that application:
Next, add the following Product
class to the application:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
namespace WcfEntitiesSample.Model
{
public class Product
{
private string _name;
public string Name
{
get { return _name; }
set
{
NotifyPropertyChanged("Name");
_name = value;
}
}
private string _description;
public string Description
{
get { return _description; }
set
{
NotifyPropertyChanged("Description");
_description = value;
}
}
private int _inventoryCount = 0;
public int InventoryCount
{
get { return _inventoryCount; }
set
{
_inventoryCount = value;
NotifyPropertyChanged("InventoryCount");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public Product()
{
}
public Product(string name, string description, int inventory)
{
_name = name;
_description = description;
_inventoryCount = inventory;
}
private void NotifyPropertyChanged(String propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The PropertyChangedEventHandler
is used with data binding to ensure hta updates to the source data pass to the bound user interface properties. Creating the PropertyChangedEventHandler
identifies the method that will handle the event.
Then open the MainPage.xaml designer, click on the button, and create an event handler for you button by double-clicking on the "Click" item in the Properties window:
Next, open the MainPage.xaml.cs file, and add the following code:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace WcfEntitiesSample
{
public partial class MainPage : UserControl
{
private ObservableCollection<Product> Inventory;
private int inventoryIndex = 0;
public MainPage()
{
InitializeComponent();
Inventory = new ObservableCollection<Product>();
Inventory.Add(new Product
("Stapler", "A hinged device that binds soft things together.", 32));
Inventory.Add(new Product
("Tape", "Sticky strips with impressive binding.", 16));
Inventory.Add(new Product
("Glue", "Unimpressive while wet, but can bind materials together.", 8));
this.Resources.Add("items", Inventory);
this.DataContext = Inventory[0];
}
private void NextProduct_Click(object sender, RoutedEventArgs e)
{
inventoryIndex++;
inventoryIndex = inventoryIndex % Inventory.Count;
this.DataContext = Inventory[inventoryIndex];
}
}
}
The ObservableCollection
allows the data items in the user interface to remain synchronized with the data items in the underlying data source. It raises the CollectionChanged
event whenever you add, remove, move, refresh or replace an item in the collection. Note that although ObservableCollection
watches for changes to its elements, it does not care about changes to the properties of its elements.
The this.DataContext
object gets or sets the data context for a user interface element when it participates in data binding.
Next, open MainPage.xaml, and update the XAML source code to the following. You use the Binding
keyword to actually implement the data binding for use with the ObservableCollection
and DataContext
:
If you click start in Visual Studio to debug the application, you should notice the user interface elements changing each time you click the "Next Product" button.
3. Setting Up Database
Download and install SQL Server Express with Advanced Services from MSDN.
Download and install the appropriate version of the AdventureWorks
database from Codeplex.
Add this view to the database and call it vProductProductInventory
:
SELECT Production.Product.ProductID, Production.Product.Name,
SUM(Production.ProductInventory.Quantity) AS InventoryCount,
'Color = ' + Production.Product.Color + ',
Size = ' + Production.Product.Size + ',
Weight = ' + CAST(Production.Product.Weight AS NVARCHAR) +
', Style = ' + Production.Product.Style AS Description
FROM Production.Product LEFT OUTER JOIN
Production.ProductInventory ON Production.Product.ProductID = Production.ProductInventory.ProductID
GROUP BY Production.Product.Name, Production.Product.Color, Production.Product.Size, Production.Product.Weight, Production.Product.Style, Production.Product.ProductID
4. Creating WCF Service Component
WCF services is a server-side technology that is different from XML Web Services in that it is an interface-driven technology. The interface has a ServiceContract
attribute, and each of the class methods has an OperationContract
attribute. WCF may use XML or JSON.
The data contract which is also in the interface file annotates any class that is going to be serialized over the internet.
Right-click your *.Web project in Visual Studio in the solution you have created (I've called my WcfEntitiesSample.Web
) and choose to add a folder called "Services".
Then right-click this folder you have created and choose "Add New Item" and select "WCF Service."
Depending on what you named your service, this is what your code should look like:
namespace WcfEntitiesSample.Web.Services
{
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AdventureWorksService : IAdventureWorksService
{
public void DoWork()
{
}
}
}
As well, your web.config should contain the following:
The binary message encoding is a feature that saves bandwidth.
Next, change the DoWork
into something that applies to the Adventure Works database:
[ServiceContract]
public interface IAdventureWorksService
{
[OperationContract]
Product GetProductByID(int id);
}
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class AdventureWorksService : IAdventureWorksService
{
[OperationContract]
public Product GetProductByID(int id)
{
return new Product()
{
ProductID = id,
Name = "BB Ball Bearing",
InventoryCount = 1109,
Description = ""
};
}
}
For now, we are just creating a dummy constructor. Later, in the LINQ to Entities section, we will actually get this data from the AdventureWorks
database.
To get your service method to show up in the generated proxy, it must be public
and marked with the [OperationContract]
attribute.
Since business logic today is becoming increasingly complex, it is important to separate out various components of your application into a presentation layer, a business logic layer, and a data layer. The Web service is merely a means of communication between the business logic layer and the presentation layer. Separating out the application into different components helps reduce complexity and makes unit testing easier.
Silverlight does not know anything about the ADO.NET types DataRow
or DataTable
, so we need to create a type to represent our data. Add the following class into a separate Silverlight Class 5 Library and link that file into another .NET 4 Class Library, which helps provide a better separation of concerns. If you choose to do this, it is important that both the Silverlight Class Library and the .NET Class Library have the same root namespace. This lets you share the files between the client and the server without having to be concerned about different namespaces.
public class Product
{
public int ProductID { get; set; }
public string Name { get; set; }
public int? InventoryCount { get; set; }
public string Description { get; set; }
}
The compiler will keep it in the service description when generating the client proxy. This is what the [DataContract]
attribute does if you decide instead to keep the Product
class in the same file as the AdventureWorksService
.
Build the solution before starting the next step.
4.1 Adding the Service Reference
Now you should have a web project pointing to the full .NET model and a Silverlight project pointing to the Silverlight model, add a service reference to the WcfEntitiesSample
project and generate the proxy:
The reason you want to have both the main project and the Silverlight project referencing their respective Silverlight Class Library and the .NET Class Library is so that the client proxy generator doesn't generate a copy of the model class on the client.
So the next step is to right-click the Silverlight client project and choose "Add Service Reference." On the next dialog that comes up, click the Discover button.
After you click OK, the client configuration file will have been generated.
4.2 Updating the UI to Test the Service
Add a new textbox
where you can enter the ID and a search button:
Next, update the MainPage.xaml.cs file with this code:
using System.Windows;
using System.Windows.Controls;
using WcfEntitiesSample.ServiceRefs;
namespace WcfEntitiesSample
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
private void btnSearch_Click(object sender, RoutedEventArgs e)
{
int searchId;
if (int.TryParse(txtID.Text, out searchId))
{
var client = new AdventureWorksServiceClient();
client.GetProductByIDCompleted += (s, ea) =>
{
DataContext = ea.Result;
};
client.GetProductByIDAsync(searchId);
}
else
{
MessageBox.Show("Invalid customer ID. Please enter a number.");
}
}
}
}
Now test your application by selecting the WcfEntitiesSample.Web
as the startup project and start debugging. Try clicking the search button--it should return the values that you hard-coded into your dummy constructor above.
5. Adding LINQ to Entities
It is possible to have many different types of data layers, including LINQ-to-SQL, CLR object, a Web service, or LINQ-to-Enties. We are going to use the latter.
Create a new Class Library project and call it WcfEntitiesSample.Data
. Then choose to add new item. In the dialog that is displayed, choose the ADO.NET Entity Data Model:
Then select "EF Designer from database:"
Next, we need to create a connection to our database, so type "ConnecctionString
" in the textbox
, and click on the new connection button.
In the following dialog, select your SQL Server machine name, and choose the AdventureWorks
database, using Windows Authentication:
After this, you need to check all tables, views and stored procedures. Also, ensure that "Include foreign key columns in the model" is checked, and then click Finish:
You should then see a *.edmx file in your solution, with a display showing the tables you selected:
Now, we need to add the EntityFramework.dll reference the WcfEntitiesSample.Data
project by browsing to this directory to select the file: ~\WcfEntitiesSample\packages\EntityFramework.5.0.0\lib\net40\EntityFramework.dll.
Next add a ProductManager
class to the WcfEntitiesSample.Model.Net
Class Library:
using System.Collections.Generic;
using System.Linq;
using WcfEntitiesSample.Data;
namespace WcfEntitiesSample.Model
{
public class ProductManager
{
public Product GetProductById(int productID)
{
IEnumerable<Product> products = null;
var db = new AdventureWorksEntities();
products = db.vProductProductInventories
.Where(prod => prod.ProductID == productID)
.Select(p => new Product
{
ProductID = p.ProductID,
Name = p.Name,
InventoryCount = p.InventoryCount,
Description = p.Description
});
return products.ToList()[0];
}
Now go to the AdventureWorksService.svc.cs file and change this code:
[OperationContract]
public Product GetProductByID(int id)
{
return new Product()
{
ProductID = id,
Name = "BB Ball Bearing",
InventoryCount = 1109,
Description = ""
};
}
To this:
[OperationContract]
public Product GetProductByID(int id)
{
var mgr = new ProductManager();
return mgr.GetProductById(id);
}
Next, in your WcfEntitiesSample.Web
project, ensure you put the connection string into the web.config file:
Run your project to verify that the product changes when entering in different ProductID
values:
References
History
- 19th December, 2014: Initial version
- 20th December, 2014: Article updated:
- Corrected instructions for VS 2013 and Blend and updated reference to sections 3 and 4 and corrected code
- Also added link to details on Microsoft's end of support for WCF RIA Services
- Added explanation of why n-tier applications are needed
- Added hyper-linked Table of Contents
- Added information about MVVM