Recently I was having major problems designing a smart client application because the architecture necessitated a n-tier (or multi tier) paradigm. This "Architecture" was non-negotiable insofar as the smart client being for a relatively successful business. The advantages of this type of architecture are well documented so feel free to Google search on the subject.
The following tutorial will take you through the steps required to create an n-tier application using Visual Studio 2008.
- Create a new windows application called NorthwindTraders
- In the same solution, add a new class library called Northwind.DataAccessLayer (Delete the Class1.cs file in this project)
- Add another class library called Northwind.BusinessEntities (Delete the Class1.cs file in this project)
- Add a WCF Service Application called WcfService1 (the default). Make sure you choose the WCF service under the web options shown below
Your solution explorer should have the following four projects.
In the DataAccessLayer project add a DataSet. To do this, make sure the DataAccessLayer project is selected in Solution Explorer, and from the main menu choose "add new data source". Make sure Database is selected and click “Next”.
Note: I’m assuming you have downloaded and installed the Northwind database, if not you can get the ubiquitous database here.

If you don’t already have a connection to Northwind, click the “New Connection” button and select the SQL database file
You should now be able to browse to the database, and test to make sure the connection is working
Click on “Next”, if you get the following dialog, saying yes makes a copy of the Northwind Database to your project. I usually say no because you ens up with dozens if not hundreds of copies of the same database in your Visual Studio Projects folders, this was you always use the same database in all your projects.
Save the connection as “NorthwindConnectionString” and Click “Next”. Choose the “Customers” and “Orders” tables, and Change the name of the DataSet to “CustomersDataSet” and click on “Finish”.
”Choose the default database option and click next. Select the Northwind database and then choose the customers and orders tables. Change the name of the DataSet to "CustomerOrdersDataSet" and click finish.
Double click the “CustomersDataSet” in Solution Explorer and right click on the dataset and select properties
In the properties pane you can now choose the DataSet project for the data access layer. This is a new feature in Visual Studio 2008 that allows you to separate you data access logic and your business logic into two projects, which was a real pain point in Visual Studio 2005. In this case choose the Northwind.BusinessEntities project and make sure you click “Save”.
If you double click the orders table in the DataSet designer, you should be taken into the business entities project, where you can add some validation logic.
namespace Northwind.BusinessEntities {
public partial class CustomersDataSet {
partial class OrdersDataTable
{
public override void EndInit()
{
base.EndInit();
this.ColumnChanged += new System.Data.DataColumnChangeEventHandler(OrdersDataTable_ColumnChanged);
}
void OrdersDataTable_ColumnChanged(object sender, System.Data.DataColumnChangeEventArgs e)
{
if (e.Column.ColumnName == this.OrderDateColumn.ColumnName
|| e.Column.ColumnName == this.ShippedDateColumn.ColumnName)
{
VaidateDates((OrdersRow)e.Row);
}
}
/// <summary>
/// Simple validation to ensure that the date an order is shipped is not
/// before it has been ordered
/// </summary>
/// <param name="ordersRow"></param>
private void VaidateDates(OrdersRow ordersRow)
{
if (ordersRow.OrderDate > ordersRow.ShippedDate)
{
ordersRow.SetColumnError(this.OrderDateColumn, "Cannot ship before it’s been ordered");
ordersRow.SetColumnError(this.ShippedDateColumn, "Cannot ship before it’s been ordered");
}
else
{
ordersRow.SetColumnError(this.OrderDateColumn, "");
ordersRow.SetColumnError(this.ShippedDateColumn, "");
}
}
}
}
}
We can now complete the setup by adding a class into the data access layer called the NorthwindDataManager that connects to the database, and returns the tables. The NorthwindManager will typically contain all the queries that you create. If you need to know how to create queries you can have a look at this article, or do some research. You typically you change the query to GetOrders and not GetData, but some knowledge of this is assumed.
This is the code that returns a DataTable in the NorthwindDataManager. Make sure you include the Using statements at the top
using System;
using System.Collections.Generic;
using System.Linq;
using Northwind.BusinessEntities;
using System.Text;
using Northwind.DataAccessLayer.CustomersDataSetTableAdapters;
namespace Northwind.DataAccessLayer
{
/// <summary>
/// Typically this class would not return all the data, but specific queries related to a table.
/// For the sake of brevity, we will return all the orders in the northwind database. Also try
/// change the GetData() to GetOrders() in the OrdersTableAdapter by modifying the query in the
/// DataSetDesigner. Knowledge of doing this is assumed.
/// </summary>
public class NorthwindDataManager
{
public static CustomersDataSet.OrdersDataTable GetOrders()
{
OrdersTableAdapter ordersAdapter = new OrdersTableAdapter();
return ordersAdapter.GetData();
}
}
}
Now we will configure the service. Select the WCF service project, right click the “References” node in Solution Explorer, and go “Add References.” choose the projects tab at the top and add the DataAccessLayer and BusinessEntities projects.
You also need to add a reference to System.Data.DataSetExtensions so add a reference as above, but this time choose the .NET tab
So your solution explorer should look like this
In the IService interface file, remove the default entries and add a contract called GetOrders (not forgetting the namespaces at the top)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using Northwind.BusinessEntities;
namespace WcfService1
{
// NOTE: If you change the interface name "IService1" here, you must also update the reference to "IService1" in Web.config.
[ServiceContract]
public interface IService1
{
[OperationContract]
CustomersDataSet.OrdersDataTable GetOrders();
}
}
In the Service1.scv file remove the default data and implement the IServive1 interface. A quick shortcut to do this is to right click the IService1 in Visual Studio shown below (this can save quite a lot of typing when setting up big service layers)
Add the following code (again note the namespaces above)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using Northwind.DataAccessLayer;
using Northwind.BusinessEntities;
namespace WcfService1
{
// NOTE: If you change the class name "Service1" here, you must also update the reference to "Service1" in Web.config and in the associated .svc file.
public class Service1 : IService1
{
#region IService1 Members
public CustomersDataSet.OrdersDataTable GetOrders()
{
return NorthwindDataManager.GetOrders();
}
#endregion
}
}
To complete the tutorial go to the NorthwindTraders project, which typically is your client application and make sure you add a reference to the business entities project. This is important because it allows proxy reuse (another VS 2008 Key feature). You should also see here that at no stage is the data access component ever available or referenced on the client. Make sure you build the entire Solution now by clicking F5.
Right click the references node and add a service reference this time.
Click the discover button and wait till the reference is located and then click OK
Note: A word of warning is that if you use third party software libraries, than you do not want their libraries included here. Be sure to click the advanced button, and choose “Reuse types in specified referenced assemblies” and omit them. Failure to do this may result in some rather nasty Visual Studio crashes.
In the app.config, as a security precaution, the client configuration we generate has a default MaxRecievedMessageSize value of 65536, meaning that an exception will be thrown at runtime if the service sends more data to the client than it expects. Since we know we will be passing large DataSets over the wire, please open up the app.config and set the element <bindings>\<wsHttpBinding>\<binding>’s maxReceivedMessageSize attribute to 5000000. Or, if you feel uncomfortable editing your service’s configuration by hand, you can open up the Microsoft Service Configuration Editor available under Visual Studio’s Tools menu.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IService1" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="5000000"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:2429/Service1.svc" binding="wsHttpBinding"
bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"
name="WSHttpBinding_IService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Now that our service has been added and configured correctly, let’s do some data binding to the data tables returned from our service. Open up the Data Sources window and drag and drop the Orders table onto Form1.
Finally, the last thing we need to do is add code to Form1’s OnLoad event to get data from our WCF service.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace NorthwindTraders
{
public partial class Form1 : Form
{
private ServiceReference1.Service1Client serviceClient;
public Form1()
{
InitializeComponent();
this.serviceClient = new NorthwindTraders.ServiceReference1.Service1Client();
}
private void Form1_Load(object sender, EventArgs e)
{
this.customersDataSet.Merge(this.serviceClient.GetOrders());
}
// Being a good citizen, always close a service connection
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
this.serviceClient.Close();
}
}
}
As you can see when the form is loaded and the dates changed, the validation occurs in a library separate from the client.
