From Client-Server to Web-Service based applications with Microsoft.NET
.NET Beta-2 version.
Author: Wim UYTTERSPROT,
wim@u2u.be
Abstract
Compared to the Microsoft Win32/COM+
platform and the Sun JAVA platform, the new Microsoft.NET is a (r)evolution
that offers us an innovative application framework with one single Common
Language Runtime for a range of programming languages and programming-paradigms.
C# and VB.NET are new Microsoft .NET programming languages, both perfectly
suitable to create object- and service-oriented enterprise applications.
This article shows how the .NET framework, including the ADO.NET and
the XML.NET Framework, enables us to switch from the client-server programming
paradigm to the web service-based paradigm, all of this being achieved
by means of the new Microsoft Visual Studio.NET development environment.
What is Microsoft .NET?
Microsoft.NET is the new application
platform where the internet itself
forms the basis of the new operating system.
For the application developer, .NET replaces the Win32 API- and the
COM- platform, in use as application platform since the Windows NT-
and Windows 95-introducton. But to
be precise, the Win32 system is
not replaced as soon as
you install .NET. Temporarily at least, the .NET Framework is installed
as an extra layer above the Win32API of a Windows 2000 or XP operating
system; all the functionality of Windows 2000 remains operational. Only
the new .NET applications will no more communicate directly with the
Win32API: they will have to use the .NET Framework. Installed applications
based on Win32 and COM+ will thus remain operational under the .NET
platform because they will simply continue to address directly the services
from the operating system (Windows 2000 or other).
The coming new API from
.NET is a rupture with the past: the entire functionality of the .NET
System is built object oriented and is only accessible to languages
that conform to the .NET Common Language Specification. The venerable
C interface of the Win32 libraries and the IUnknown-interface from COM+
belong to the past as you upgrade to .NET. Compared to the Win32/COM+,
the .NET Framework is a true revolution; compared to the JAVA platform,
Microsoft makes a jump ahead. The .NET Framework offers us a one and
only Common Language Runtime and a full set of programming languages
and programming paradigms. Among these, C# (spoken as C-sharp) and Visual
Basic.NET are the new .NET programming languages that are both ultimately
well suited for object, component and service oriented programming.
They enable to create enterprise applications easily with the help of
ADO.NET's and XML.NET's Frameworks.
Making the move from
Win32API to .NET reminds many of us the earlier step from DOS to Windows:
at the early stages our applications should still remain client of the
older system services of Win32API and COM+. But as time goes by, our
applications will become 100% dependent from the application interface
of .NET. Especially for developers of distributed enterprise applications,
the step to .NET is attractive, even mandatory, but for sure will become
the cornerstone of their applications. Moreover, the .NET Framework
together with Microsoft Visual Studio.NET makes the development of web
services as easy as the creation of plain classes.
Microsoft Visual Studio.NET
Microsoft Visual Studio.NET
(abbreviated VS.NET) is the new IDE for the development of .NET applications
and replaces Microsoft Visual Basic 6, Visual Studio 6 (including Visual
C++ 6 and Visual InterDev 6). JAVA developers will notice that Microsoft
Visual J++ 6, the Microsoft development environment for JAVA, disappears
to be replaced by VS.NET Beta-2. But it should not be a problem as Visual
C# is an improved version of Visual J++. To ease the move towards C#,
Microsoft has designed the well-named JUMP conversion tool.
The new Visual Studio
is made inviting and pleasant to use: As soon as you start VS.NET you
realize that you are in a programming environment that offers a wide
variety of languages such as C#, VB.NET, C++, script languages, non-Microsoft
languages; and even a variety of technologies as Windows Applications,
Web Applications, Web Services, Win- and Web Controls, all kinds of
libraries, and more.
We should now start
our tour of .NET's programming environment. We want to prepare a distributed
application and thus we ask the Project Wizard of VS.NET to open a new
project for us (figure 1). Our application is baptized ”Products
Distributed App” from which the first project is named “ProductsWindowsApp”.
To choose a Windows Application template for our first project we select
an object of type WinForm. In lieu of a WinForm, we could also have
chosen for a WebForm, as the .NET Framework allows for the development
of WebForms following the exact same programming paradigm as that of
WinForms.

Figure 1
We create a “Products Distributed
App” where the first application project named “ProductsWindowsApp”
is based on the WinForm template.
VS.NET prepares for
us a default project of type Windows Application. For the sake of simplicity
we name the freshly generated C# file ProductsWinForm.cs and the corresponding
class ProductsWinForm (Figure 2
and Figure
3).
|

Figure 2:
The “solution” Products Distributed App
with the project ProductsWindowsApp that contains the C# source
code file ProductsWinForm.cs.
|

Figure 3:
This Class View shows us the class ProductsWinForm
in the namespace ProductsWindowsApp.
|
In the ProductsWinForm class we will now
insert two service functions: first a getter named GetProducts that
will ask for the result of a query in a database, and secondly a setter
that must update the data in the same database. To be able to communicate
between the client application and the service functions in a structured
and simple fashion, ADO.NET provides us with a new data type named the
DataSet.
Disconnected DataSets
Before we proceed to use service functions
that exchange disconnected DataSets with each other, it is good
to first review the operation of a DataSet in the light of the following
simple example.
You were cautioned: ADO.NET is no more
an older version of ADO revisited for .NET; on the contrary, it
requires from us to think of data and data access differently. With
his new programming interface, ADO.NET enables us to dissociate
the true data source and the physical format on which it is stored.
So ADO.NET does no more implicitly implement cursors, but enables
to isolate very simply the data from it's data source with the help
of a DataSet object that is loose from the data base or "disconnected".
The DataSet is not to be confused with ADO’s RecordSet: where
the RecordSet is a set (=collection) of records that you travel
with a cursor, the DataSet is a set of data (data table). Access
to a DataSet is also different: it does not use a cursor but instead
an array mechanism. The following example code will send a query
to the Northwind database, the well-known example database of Microsoft
SQL Server 2000.
We begin by opening a connection towards Northwind with the help
of a SqlConnection
object from the assembly System.Data.Sql:
using System.Data.SqlClient;
//...
string connectionstring
= "data source=(local); initial catalog=Northwind; "
+ "integrated security=sspi;";
SqlConnection con = new SqlConnection(connectionstring);
con.Open();
Then we define
our SQL-query as a SqlCommand, and we use this command object to
build a SqlDataAdapter:
SqlCommand cmd = new SqlCommand ("Select * From Products",con);
SqlDataAdapter adapter = new SqlDataAdapter(cmd);
The
result from this query will be accessible in the form of a DataSet
constructed and "filled" by the SqlDataAdapter:
DataSet dataset = new DataSet();
adapter.Fill(dataset,"Products");
The correct interpretation
of this freshly obtained DataSet object is that of an object where
a "piece" of the Northwind database has been copied. Our dataset
is loosely coupled (=disconnected) from the original database. To
check that, we interrogate the data immediately after closing the
connection to the database:
con.close();
So the dataset in
the above example looks like a local and temporarily constructed
database, containing a single table supported as an array of rows:
DataTable table = dataset.Tables[0];
DataRow row = table.Rows[46];
object fieldvalue = row[1];
DataColumn column = table.Columns[1];
string fieldname = column.ColumnName;
string output = fieldname + ": " + fieldvalue;
MessageBox.Show(fieldname + ": " + fieldvalue, table.TableName);
It results in the
following sweet message (Zaanse koeken = cookies from Zaandam):

We could have had
the exact same result with the following fieldvalue:
fieldvalue = dataset.Tables[0].Rows[46]["ProductName"];
We will now go a step ahead by replacing the price of these biscuits
in the database. We first restore the connection to the database
and then call for the update of the dataset:
dataset.Tables[0].Rows[0]["UnitPrice"] = 10.0;
con.Open();
SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
adapter.Update(dataset,"Products");
con.Close();
|
From a Client-Server based WinForm application...
Such
a disconnected DataSet is an object having its data in its own management,
exactly this simplifying the way we can exchange data between client
and server. More specifically ADO.NET enables us to define datasets
as function parameters and return values.
Figure
4:
In
a client-server version of our application we find the
GetProducts and UpdateProducts as functions in the WinForm itself.
GetProducts
How simple it is to exchange data is illustrated
in the source code of the GetProducts function (Figure 4):
first open a connection to a database via an OleDbConnection object,
then perform a query via the OleDbCommand and OleDbDataAdapter, and
to terminate send the result as a return value of type "disconnected
DataSet object":
public DataSet GetProducts()
{
OleDbConnection con = new OleDbConnection(connectionstring);
con.Open();
OleDbCommand cmd = new OleDbCommand("Select * From Products",con);
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
DataSet dataset = new DataSet();
adapter.Fill(dataset,"Products");
con.Close();
return dataset;
}
To visualise rapidly
the effect of the GetProducts function we use a DataGrid: we link the
DataSource property of the datagrid with the dataset that was received
as return value from the GetProducts function. This link in .NET Beta-2
is still performed via a DataView object. As a user clicks on the button
“Get Products” the following code is executed:
protected void buttonGetProducts_Click (object sender, System.EventArgs e)
{
DataSet dataset = GetProducts();
DataView dataview = new DataView(dataset.Tables[0]);
dataGridProducts.DataSource = dataview;
dataview.AllowEdit = true;
}
UpdateProducts
Similarly, we prepare an UpdateProducts function
(Figure
4)
that accepts a DataSet as input parameter and updates its contents in
the Products table from the data source with the help of
OleDbCommand, OleDbCommand, OleDbDataAdapter
and OleDbCommandBuilders.
public bool UpdateProducts(DataSet dataset)
{
try
{
OleDbConnection con = new OleDbConnection(connectionstring);
con.Open();
OleDbCommand cmd = new OleDbCommand("SELECT * FROM Products",con);
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
OleDbCommandBuilder builder = new OleDbCommandBuilder(adapter);
adapter.Update(dataset,"Products");
con.Close();
return true;
}
catch
{
return false;
}
}
We will use this function to update the database
with the data that was modified in the datagrid. On the ProductsWinForm
we place an “Update Products” button with an event handler that looks
as follows:
protected void buttonUpdateProducts_Click(object sender, EventArgs e)
{
DataView dataview = (DataView) dataGridProducts.DataSource;
DataSet dataset = dataview.Table.DataSet;
UpdateProducts(dataset);
}
The result of our code is a .NET WinForm
application (Figure 5)
following the rules of the client-server programming paradigm.

5:
Press the Get Products button; modify
the data in this DataGrid (for example the price of your beloved “Zaanse
koeken”); press on the Update Products button: the modified data
has been updated in the database. Everything happens with the help of
disconnected DataSets.
...to a Web-Service based WinForm application
In this second part we will
modify our example in a way that GetProducts and UpdateProducts are
no longer "common" service functions, but will be so-called "web" service
functions.
In the object-oriented paradigm, functions
are known under the name of methods; in the service oriented
paradigm, public functions are named service-functions or
services. If so a function is made accessible via internet (thus
also for non-Microsoft applications), than we speak of a web service
function. Strictly speaking, a web service is a class, a
group of web service functions.
As the objective of this article is only
to point out the logics of the web service programming paradigm, we
will not cover the underneath protocols as UDDI, WSDL, SOAP etc., that
are used to drive the remote message calls from web services. Articles
over these subjects can be found by a motivated reader in other editions
of this magazine.
The .NET Framework allows us to migrate
towards web services such that the functionality of the service functions
is maintained: both functions can use the same ADO.NET protocol to read
data in a dataset, and to update the database: neither the implementation
nor the signature of the service functions is affected.
Creating a Web Service component
Microsoft
makes our life very easy: in Visual Studio.NET we have the possibility
to prepare a WebService project and add our Solution to it:

Figure 6:
We add a second project where we define
our WebService in the ProductBusinessComponent.
After a few simple manipulation our web
service named ProductService is added to the project ProductBusinessComponent
(Figure 6).
The web service is made up of 2 files: The first file ProductsService.asmx,
in it's compiled form, has the duty of sending all the web service requests
that arrive to the web server to the compiled file ProductsService.cs.
This second file contains the ProductsService class.
The Web Service functions
To move the functions GetProducts and
UpdateProducts from the presentation tier to the business tier, we simply
cut them out the ProductWinForm class and paste them in the ProductsService
class (Figure
7)
and give both of them the attribute [WebMethod].

Figure 7:
The GetProducts and UpdateProducts service
functions are moved to the business component to now act as webservices.
Apart from the attribute, there is no
difference between a basic function and a webservice function:
public class ProductsService : System.Web.Services.WebService
{...
[WebMethod]
public DataSet GetProducts()
{
OleDbConnection con = new OleDbConnection(connectionstring);
con.Open();
OleDbCommand cmd = new OleDbCommand("Select * From Products",con);
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
DataSet dataset = new DataSet();
adapter.Fill(dataset,"Products");
con.Close();
return dataset;
}
[WebMethod]
public bool UpdateProducts(DataSet dataset)
{
try
{
OleDbConnection con = new OleDbConnection(connectionstring);
con.Open();
OleDbCommand cmd = new OleDbCommand("SELECT * FROM Products",con);
OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
OleDbCommandBuilder builder = new OleDbCommandBuilder(adapter);
adapter.Update(dataset,"Products");
con.Close();
return true;
}
catch
{
return false;
}
}
}
The above code illustrates that a web
service is truly a class that is instantiated by the web server. To
be even more precise: ProductsService is a class that is isolated from
the class System.Web.Services.WebService.
All the required auxiliary
components and the plumbing code required to link the web service component
and the component to the web server are generated by VS.NET with the
support of ASP.NET. Without going too deep, it is good to know that
a web service is hosted in the Internet Information Service of a website.
For each web service an .asmx file is generated and linked to the web
service-component with extension .dll. Here, ProductService.asmx is
linked to the ProductServiceComponent.dll.
Consuming a Web Service
Once the web service is prepared and active
over a web server, one can use the asmx file to invoke the web service
functions. VS.NET makes it also very simple. We can first ask for the
list of all available web services offered by the web server
(Figure 8):

Figure 8:
Addition of a webreference in VS.NET is entirely
Wizard driven.
As we add a reference, VS.NET will register
the webservice in the form of a local class in the project of the client
application:
|

Figure 9:
The Web Reference u2u_wim_net is automatically
generated by VS.NET and represented as a namespace referencing the
web server.
|

Figure 10:
VS.NET generates a Web Reference and even a proxy-class that offers
the proxy-functions GetProducts and UpdateProducts to the consumer
of the web service.
|
protected void buttonUpdateProducts_Click (object sender, System.EventArgs e)
{
DataView dataview = (DataView) dataGridProducts.DataSource;
DataSet dataset = dataview.Table.DataSet;
new u2u_wim_net.ProductsService().UpdateProducts(dataset);
}
protected void buttonGetProducts_Click (object sender, System.EventArgs e)
{
DataSet dataset = new u2u_wim_net.ProductsService().GetProducts();
DataView dataview = new DataView(dataset.Tables[0]);
dataGridProducts.DataSource = dataview;
dataview.AllowEdit = true;
}
In the above code u2u_wim_net
is a namespace that represents a reference to the webserver. ProductsService
is the proxy-class generated by VS.NET on the client side (Figure 9 and
Figure
10).
The name of this class comes from the WSDL file (Web Service Description
Language) that contains the "contract" in XML format from the webservice.
From this namespace we instance the ProductsService class with the
help of the new operator;
from now on we can call the GetProducts and the UpdateProducts
functions.
After these changes, the client may
operate as before: products may be asked for and updated. The major
difference is that the data transfer is now done under the http protocol
(Figure 11).

Figuur
11:
The end-user clicks the GetProducts button, looking
for the GetProducts proxy function in the proxy class. The proxy function
requests via the ProductsService.asmx for the GetProducts web service
function. The ProductBusinessComponent handles this request by first
examining the data source, then making a DataSet object and send this
dataset back in XML-format as a result.
Testing a Web Service manually
We can test the function GetProducts of the
ProductsService web service manually by typing the following url
in Internet Explorer:
http://u2u-wim-net/ProductBusinessComponent/ProductsService.asmx/GetProducts?
The result of this webservice function
call is a xml file where
1) it's schema reads as follows:
<?xml version="1.0" encoding="utf-8" ?>
<DataSet
xmlns="http://tempuri.org/">
<xsd:schema
id="NewDataSet"
targetNamespace=""
xmlns=""
xmlns:xsd=
"http://www.w3.org/2001/XMLSchema"
xmlns:msdata
= "urn:schemas-microsoft-com:xml-msdata">
<xsd:element
name="NewDataSet"
msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice
maxOccurs="unbounded">
<xsd:element
name="Products">
<xsd:complexType>
<xsd:sequence>
<xsd:element
name="ProductID"
msdata:DefaultValue="NULL"
type="xsd:int"
minOccurs="0"
msdata:Ordinal="0"
/>
<xsd:element
name="ProductName"
msdata:DefaultValue="NULL"
type="xsd:string"
minOccurs="0"
msdata:Ordinal="1"
/>
<xsd:element
name="SupplierID"
msdata:DefaultValue="NULL"
type="xsd:int"
minOccurs="0"
msdata:Ordinal="2"
/>
<xsd:element
name="CategoryID"
msdata:DefaultValue="NULL"
type="xsd:int"
minOccurs="0"
msdata:Ordinal="3"
/>
<xsd:element
name="QuantityPerUnit"
msdata:DefaultValue="NULL"
type="xsd:string"
minOccurs="0"
msdata:Ordinal="4"
/>
<xsd:element
name="UnitPrice"
msdata:DefaultValue="NULL"
type="xsd:number"
minOccurs="0"
msdata:Ordinal="5"
/>
<xsd:element
name="UnitsInStock"
msdata:DefaultValue="NULL"
type="xsd:short"
minOccurs="0"
msdata:Ordinal="6"
/>
<xsd:element
name="UnitsOnOrder"
msdata:DefaultValue="NULL"
type="xsd:short"
minOccurs="0"
msdata:Ordinal="7"
/>
<xsd:element
name="ReorderLevel"
msdata:DefaultValue="NULL"
type="xsd:short"
minOccurs="0"
msdata:Ordinal="8"
/>
<xsd:element
name="Discontinued"
msdata:DefaultValue="NULL"
type="xsd:boolean"
minOccurs="0"
msdata:Ordinal="9"
/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
2)
The dataset contains a Products table with, in 47th position, our
cookies "Zaanse koeken":
<NewDataSet
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"...>
...
<msdata:unchanged>
<Products updg:id="728de675-92bb-4d3b-a55a-ca5ed7a94307"
xmlns="">
<ProductID>47</ProductID>
<ProductName>Zaanse
koeken</ProductName>
<SupplierID>22</SupplierID>
<CategoryID>3</CategoryID>
<QuantityPerUnit>10
- 4 oz boxes</QuantityPerUnit>
<UnitPrice>9.5</UnitPrice>
<UnitsInStock>36</UnitsInStock>
<UnitsOnOrder>0</UnitsOnOrder>
<ReorderLevel>0</ReorderLevel>
<Discontinued>false</Discontinued>
</Products>
</msdata:unchanged>
...
</DataSet>
We test the contract with the following url:
http://u2u-wim-net/ProductBusinessComponent/ProductsService.asmx?sdl
|
From Web Services to Web Properties
With
a little good will you could consider GetProducts and the UpdateProducts
together as one .NET property, resulting in the following renewing source
code called Web Properties:
public DataSet Products
{
[WebMethod]
get
{
...
adapter.Fill(dataset,"Products");
con.Close();
return dataset;
}
[WebMethod]
set
{
try
{
...
adapter.Update(value,"Products");
...
}
catch
{
}
}
}
Figure 12: Products is a Web Property
|
Figure 13: A Web Property seen in the proxy
class
|
We
hope that this last example gave you a new insight on the Web of tomorrow.
|