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 [1].

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 [2].

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[3] 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.

 

[1] Warning: this article is based on version Beta-2 of Microsoft.NET, Microsoft . NET SDK and Microsoft Visual Studio.NET. We want to advise the reader that the issued subjects and more specifically the sample code are subject to modifications

[2] The code examples in C# and also in VB.NET are available on www.u2u.be; also the version with WebForms instead of WinForms is available.

[3] If the datasource is not a MS SQL Server database, we have to use the OleDbConnection and OleDBCommand from the System.Data.OleDb assembly. The rest of the code remains the same.

Contact me Contact


Contact me Receive U2U Newsletter.
Looking for a challenging job Download Brochure On Site Training Looking for a challenging job
Favorites Favorites

Copyright © 1999-2010 by U2U