ASP.NET Web Services Tutorial

Silan Liu, February 2004

 

Content

1.       1.        Basics.. 3

1.1.     A Simplest Web Service 3

1.2.     Files in a Visual Studio .NET Web Service Project 3

1.3.     Naming Convention 3

1.4.     Location of the Web Service 3

1.5.     Useful Properties of class WebService 4

1.6.     Storing Configuration Settings in web.config 4

1.7.     Some Web Service Design Issues 4

1.8.     Generate WSDL from Web Service 4

1.9.     Web Service Round Trip Time 4

1.10.  1.10.Customizing Web Service with Web Method Attributes 4

2.       2.        Individual Topics.. 6

2.1.     Do Not Transport Customized Types with Behaviour 6

2.2.     Invoking COM Objects 7

2.3.     Exposing COM Object As Web Service 9

2.4.     Asynchronous Invoke of Web Service 11

2.5.     Concurrent Requests on Web Service 11

2.6.     Web Service Proxy Class 12

3.       3.        Managing States of Web Services.. 15

3.1.     State Management in ASP.NET 15

3.2.     Session 15

3.3.     Implications of using Application 17

3.4.     Other Ways to Store State Information 17

4.       4.        Caching.. 18

4.1.     Output Caching 18

4.2.     Expiration of the Cache Object 18

4.3.     Manually Removing Cached Items 19

4.4.     Cached Item’s Priority for Purge and Decay 19

4.5.     Callback Method for Removed Cached Item 19

5.       5.        SOAP and Serialization.. 20

5.1.     A Web Method Request Sent Over HTTP POST 20

5.2.     SOAP Header 20

5.3.     SOAP Body 21

5.4.     SOAP Encoding 22

5.5.     Making Objects Serializable 23

5.6.     SOAP Extension 23

5.7.     SOAP Message Encoding 23

5.8.     Using XML Attributes to Customize the SOAP Message 24

6.       6.        Web Service Discription Language (WSDL) 26

6.1.     WSDL 26

6.2.     A Web Service Example 26

6.3.     WSDL On SOAP 27

6.4.     import Element 30

7.       7.        Authentication.. 31

8.       8.        Remoting.. 32

8.1.     Remoting vs. Web Services 32

8.2.     Type of Remoting Objects 32

8.3.     MBV & MBR Code Example 33

8.4.     Server Activated Object (SAO) Code Example 34

8.5.     Client Accessing MBR Using Interface 34

8.6.     Generate Interface Assembly of a Given URL 35

8.7.     How To Invoke MBR’s Non-Default Constructors 35

8.8.     No Need To Register All Remotable Objects 36

8.9.     Client Activated Object (CAO) 36

8.10.  8.10.Using Configuration File on Server and Client 36

8.11.  8.11.Hosting Remoting Object in IIS 38

8.12.  8.12.Create and Marshal MBR Objects Youself 38

8.13.  8.13.Remotable Objects Can Be Called Asynchronously 39

 

1.       Basics

1.1.        A Simplest Web Service

This example demonstrates how easy it can be to create a web service.

1.       Create a directory with any name, say “TestDir”.

2.       Create a virtual directory named “TestWeb” pointing to “TestDir”.

3.       Create a file as follow with your notepad named “Hello.asmx” in “TestDir”.

<%@ WebService Language="C#" class="HelloClass" %>

 

using System;

using System.Web;

using System.Web.Services;

 

public class HelloClass

{

    [WebMethod]

    public String GetGreeting()

    {

        return "Hello!";

    }

}

That’s all you need to do. To test it, open a web page and key in URL “http://localhost/TestWeb/Hello.asmx”.

1.2.        Files in a Visual Studio .NET Web Service Project

1)    ProjectName.csproj
Stores info about the project, including files included in the project and references, root namespace, etc.

2)    ProjectName.csproj.webinfo
Stores the URL of the project file (e.g. http://localhost/test/DataAccess.csproj). If you changed the URL i.e. virtual directory name of your web service, you must change this URL accordingly.

1.3.        Naming Convention

There are so many entities in a Visual Studio .NET web service project. If you don’t follow a clear naming convention it can be confusing and sometimes even frustrating if you run into weird bugs caused by the framework being confused by names. Suppose we want to create a web service whick sits on top of a data access tier, I usually name the following entities in the following way:

Physical folder

WSDataAccess

Virtual directory

WSDataAccess

Project

WSDataAccess

Name space

NsDataAccess

Class

DataAccess

asmx file

DataAccess

1.4.        Location of the Web Service

¨      URL location of the project

A Visual Studio .NET web services project has a file named “ProjectName.csproj.webinfo”, which stores the URL of the web service project. If you change the virtual directory name, you must change this URL correspondingly. Otherwise you wouldn’t be able to open the project.

¨    Physical location of the Web Service folder

By default, when you create a web service say “MyBankService” with Visual Studio .NET, all the files including the project file “MyBankService.csproj” were under directory “C:\Inetpub\wwwroot\MyBankService”, while all other non-web-service projects of the same solution are under a directory of your chosen, say  c:\Visual Studio Projects\MyBankSolution”. For the sake of file management, you may want all projects of the solution to be under the same directory. You can move the web service directory from under “C:\Inetpub\wwwroot” to under “c:\Visual Studio Projects\MyBankSolution”, and you must  change the virtual directory of the IIS to point to the web service new location “c:\Visual Studio Projects\MyBankSolution\ MyBankService”.

1.5.        Useful Properties of class WebService

Web service classes generated by Visual Studio .NET inherits from class System.Web.Services.WebService. This class has following properties (not all of them) that are convenient:

Property

Description

Application

 See Enabling Session in a Web Service

Context

HttpContext of the current request

Server

HttpServerUtility of current request

Session

See Enabling Session in a Web Service

User

Mainly used for user authentication

Apart from the above benefits, classes inheriting from WebService can use .NET remoting.

1.6.        Storing Configuration Settings in web.config

In ASP.NET we typically store configuration settings in the web.config file in its “appSettings” element. Suppose we want to store a key named “ConnectionString”, the web.config will look like

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

 

  <appSettings>

    <add key="ConnectionString" value="Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=False;Data Source=FLIU2000\NetSDK"/>

  </appSettings>

 

  ...

 

</configuration>

Then, in the code, when we want to retrieve the key’s value:

public string HelloWorld()

{

    OleDbConnection cn = new OleDbConnection();

    cn.ConnectionString = ConfigurationSettings.AppSettings["SqlConnectionString"];

    cn.Open();

    cn.Close();

    return "Hello World";

}

ConfigurationSettings class is defined in System.Configuration.

1.7.        Some Web Service Design Issues

1)    With both client and server on the same machine, a Web service call is almost 5 times slower than a call to a DLL or a call using .NET remoting TCP channels;

2)    .NET advanced data types such as DataSet may not be correctly parsed by a non-.NET server. So always use basic data types if you want to build a cross-platform web service;

1.8.        Generate WSDL from Web Service

To generate WSDL document from a web service:

disco http://www.silan.com.au/wstests/service1.asmx MyWebServices.wsdl

1.9.        Web Service Round Trip Time

The round trip time of a web method call transmitting neglectable amount of data across Internet:

First call: 5-10 seconds, due to the JIT complilation.

Subsequent calls: 1-2 seconds.

1.10.     Customizing Web Service with Web Method Attributes

You can use the following web method attributes to customize your web services:

1.    BufferResponse – if you are returning large chunk of data more than 16K and the data is serialized on the fly, the framework will return the data in multiple deliveries. Setting this attribute to true will force the framework to buffer the response until the message is completely serialized;

2.    CacheDuration – sets the caching time for the output. When set to 0 disables the caching. See section Output Caching for details.

3.    Description;

4.    EnableSession – see section Session for details.

5.    MessageName – use if if you want the web method to appear to have different name as the method name.

2.       Individual Topics

2.1.          Do Not Transport Customized Types with Behaviour

In the following web service, we define a class Employee which has both data and behaviour. It appears both in the parameters and return type of a web method.

using System;

using System.Collections;

using System.ComponentModel;

using System.Diagnostics;

using System.Web;

using System.Web.Services;

 

namespace WsSimpleTests

{

    [Serializable]

    public class Employee

    {

        private string mstrName = "";

        private int miAge = 0;

        private string mstrAddress = "";

 

        public Employee()

        {}

 

        public Employee(string name, int age, string address)

        {

            mstrName = name;

            miAge = age;

            mstrAddress = address;

        }

 

        public string Name

        {

            get { return mstrName; }

            set { mstrName = value; }

        }

 

        public int Age

        {

            get { return miAge; }

            set { miAge = value; }

        }

 

        public string Address

        {

            get { return mstrAddress; }

            set { mstrAddress = value; }

        }

 

        public string GetInfo()

        {

            return mstrName + ", " + miAge + ", " + mstrAddress;

        }

    }

 

    public class Service1 : System.Web.Services.WebService

    {

        public Service1()

        {

            InitializeComponent();

        }

 

        [WebMethod]

        public Employee HelloWorld(Employee emp)

        {

            emp.Name = "Mr " + emp.Name;

            emp.Age += 1;

            emp.Address = emp.Address + " Australia";

            return emp;

        }

    }

}

Now generate the proxy file of this web service by using Microsoft’s proxy generation tool wsdl.exe. Open the proxy file, and look at the definition of class Employee:

    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]

    public class Employee {

        public string Name;

        public int Age;

        public string Address;

    }

Alas! All behaviour is deprived from the class, only data left. This isn’t what we want!

If we define the original Employee class in a separate class library, then have both the web service and the client refer to it, what will happen? The client code looks like

static void Main(string[] args)

{

    ClassLibrary.Employee emp1 = new ClassLibrary.Employee();

    emp1.Name = "Bob";

    emp1.Age = 20;

    emp1.Address = "679 Springvale Rd, Mulgrave, vic";

    Service1 s = new Service1();

    ClassLibrary.Employee emp2 = s.HelloWorld(emp1);

    Console.WriteLine(emp2.Name + ", " + emp2.Age + ", " + emp2.Address);

    Console.ReadLine();

}

Compile it and guess what will happen. Compiler will complain that it can not convert ClassLibrary.Employee to the dummy behaviourless Employee definition in the proxy file. Because the web method in the proxy class expects this dummy Employee class instead of the real one.

So how can we pass an object of the original Employee class between the client and the web service? The answer is: you can’t

Web service is designed to be interoperable across different platforms. The method definition of a class at this end of the wire may mean something else at the other end. It does not make sense to pass an object with a defined behaviour. The simpler the types being passed are, the better the interoperability.

Therefore, as a rule of thumb, only pass standard data types that are defined in SOAP encoding specification, such as simple types int, string, etc. and some compound types including structures and arrays. Then, if necessary, at both ends, create objects from the passed data using locally defined types. This results in a loss-coupling between the two ends.

There is one exception. You can pass a System.Data.DataSet between a client and a web service which are both sitting on Microsoft .NET framework. This functionality is provided by wsdl.exe tool to support ADO.NET. You can even pass a generated typed dataset which inherits from System.Data.DataSet. You should of course define it on the server side. The generated proxy file will contain a valid definition of the typed dataset, which client can use.

2.2.        Invoking COM Objects

The IDL file of a COM object “Calculator.idl” is as follow:

import "oaidl.idl";

import "ocidl.idl";

    [

        object,

        uuid(F7973031-8192-4575-8810-9F58887A4B81),

        dual,

        helpstring("ICalc Interface"),

        pointer_default(unique)

    ]

    interface ICalc : IDispatch

    {

        [id(1), helpstring("method Add")] HRESULT Add([in] int a1, [in] int a2, [out, retval]int  * b);

    };

 

[

    uuid(1CEAA1D9-567C-4D67-8DAA-AA498CC10648),

    version(1.0),

    helpstring("Calculator 1.0 Type Library")

]

library CALCULATORLib

{

    importlib("stdole32.tlb");

    importlib("stdole2.tlb");

 

    [

        uuid(23AE171E-9FCF-46A8-96D5-983244BDD2A7),

        helpstring("Calc Class")

    ]

    coclass Calc

    {

        [default] interface ICalc;

    };

};

The coclass declaration “Calc.h” is as follow:

#ifndef __CALC_H_

#define __CALC_H_

 

#include "resource.h"       // main symbols

 

class ATL_NO_VTABLE CCalc :

    public CComObjectRootEx<CComSingleThreadModel>,

    public CComCoClass<CCalc, &CLSID_Calc>,

    public IDispatchImpl<ICalc, &IID_ICalc, &LIBID_CALCULATORLib>

{

public:

    CCalc()

    {

    }

 

DECLARE_REGISTRY_RESOURCEID(IDR_CALC)

 

DECLARE_PROTECT_FINAL_CONSTRUCT()

 

BEGIN_COM_MAP(CCalc)

    COM_INTERFACE_ENTRY(ICalc)

    COM_INTERFACE_ENTRY(IDispatch)

END_COM_MAP()

 

// ICalc

public:

    STDMETHOD(Add)(/*[in]*/ int a1, /*[in]*/ int a2, /*[out, retval]*/int  * b);

};

 

#endif //__CALC_H_

The implementation “Calc.cpp” is as follow:

#include "stdafx.h"

#include "Calculator.h"

#include "Calc.h"

 

STDMETHODIMP CCalc::Add(int a1, int a2, int *b)

{

    *b = a1 + a2;

    return S_OK;

}

To reference it in a .NET project, choose “Add reference”, in that page, choose the “COM” tab, and browse for the DLL file or type library file. The .NET code which invokes the COM service is as follow:

using System;

using CALCULATORLib;

 

namespace CalcClient3

{

    class Class1

    {

        [STAThread]

        static void Main(string[] args)

        {

            CalcClass calc = new CalcClass();

            int result;

            result = calc.Add(3, 5);

            String s = String.Format("3 + 5 = {0:###}", result);

            Console.WriteLine(s);

            s = Console.ReadLine();

        }

    }

}

2.3.        Exposing COM Object As Web Service

The previous section Invoking COM Objects shows how to invoke a COM object. You can create a web service application which exposes the COM object’s services. But there is a much easier way. Microsoft provides a SOAP Toolkit, which can create a web service for a COM object. All you have to do is:

1.Start the SOAP Toolkit’s “WSDL Generator”;

2.Choose a name for the exposed service (say “Calculator”);

3.Browse to the COM dll (could be anywhere in the same machine, must be registered);

4.Select the coclass method to expose;

5.Provide the URL and type of the SOAP listener (say “http://localhost/wscalc” and ASP listener);

6.Choose a virtual directory (say “wscalc”) to dump all generated files.

Then a web service for the COM object is generated.

Using the same COM object used in previous section Referencing COM Objects, the WSDL file generated by the SOAP Toolkit is as follow:

<?xml version='1.0' encoding='UTF-8' ?>

<!-- Generated 08/03/03 by Microsoft SOAP Toolkit WSDL File Generator, Version 3.00.1325.0 -->

<definitions

    name='Calculator'

    targetNamespace='http://tempuri.org/Calculator/wsdl/'

    xmlns:wsdlns='http://tempuri.org/Calculator/wsdl/'

    xmlns:typens='http://tempuri.org/Calculator/type/'

    xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'

    xmlns:xsd='http://www.w3.org/2001/XMLSchema'

    xmlns:stk='http://schemas.microsoft.com/soap-toolkit/wsdl-extension'

    xmlns:dime='http://schemas.xmlsoap.org/ws/2002/04/dime/wsdl/'

    xmlns:ref='http://schemas.xmlsoap.org/ws/2002/04/reference/'

    xmlns:content='http://schemas.xmlsoap.org/ws/2002/04/content-type/'

    xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'

    xmlns='http://schemas.xmlsoap.org/wsdl/'>

 

    <types>

        <schema

            targetNamespace='http://tempuri.org/Calculator/type/'

            xmlns='http://www.w3.org/2001/XMLSchema'

            xmlns:SOAP-ENC='http://schemas.xmlsoap.org/soap/encoding/'

            xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'

            elementFormDefault='qualified'>

 

            <import namespace='http://schemas.xmlsoap.org/soap/encoding/'/>

            <import namespace='http://schemas.xmlsoap.org/wsdl/'/>

            <import namespace='http://schemas.xmlsoap.org/ws/2002/04/reference/'/>

            <import namespace='http://schemas.xmlsoap.org/ws/2002/04/content-type/'/>

 

        </schema>

    </types>

 

    <message name='Calc.Add'>

        <part name='a1' type='xsd:int'/>

        <part name='a2' type='xsd:int'/>

    </message>

 

    <message name='Calc.AddResponse'>

        <part name='Result' type='xsd:int'/>

    </message>

 

    <portType name='CalcSoapPort'>

 

        <operation name='Add' parameterOrder='a1 a2'>

            <input message='wsdlns:Calc.Add'/>

            <output message='wsdlns:Calc.AddResponse'/>

        </operation>

 

    </portType>

 

    <binding name='CalcSoapBinding' type='wsdlns:CalcSoapPort' >

 

        <stk:binding preferredEncoding='UTF-8'/>

        <soap:binding style='rpc' transport='http://schemas.xmlsoap.org/soap/http'/>

 

        <operation name='Add'>

            <soap:operation soapAction='http://tempuri.org/Calculator/action/Calc.Add'/>

            <input>

                <soap:body

                    use='encoded'

                    namespace='http://tempuri.org/Calculator/message/'

                    encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'

                    parts='a1 a2'/>

            </input>

            <output>

                <soap:body

                    use='encoded'

                    namespace='http://tempuri.org/Calculator/message/'

                    encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'

                    parts='Result'/>

            </output>

        </operation>

 

    </binding>

 

    <service name='Calculator' >

        <port name='CalcSoapPort' binding='wsdlns:CalcSoapBinding' >

            <soap:address location='http://localhost/wscalc/Calculator.ASP'/>

        </port>

    </service>

 

</definitions>

You can use the same way as in section  Invoking COM Objects to invoke the generated web method, or simply write a VB script:

Option Explicit

 

Dim soapClient

set soapClient = CreateObject("MSSOAP.SoapClient")

On Error Resume Next

Call soapClient.mssoapinit("http://localhost/wscalc/calculator.wsdl", "Calculator", "CalcSoapPort")

if err <> 0 then

wscript.echo "initialization failed " + err.description

wscript.echo 1111

end if

 

wscript.echo soapclient.Add(21, 37)

2.4.        Asynchronous Invoke of Web Service

The principle behind the asynchronous invoking of a web service is pretty similar to the asynchronous invoking of a normal method (see my C# Tutorial – section “Asynchronous Programming with Delegates”). The only defference is: you do not need to create a delegate yourself, bevcause the web service proxy provides this functionality. Instead of calling the delegate’s BeginInvoke and EndInvoke methods, now you call the web service proxy’s Begin… and End… methods. Suppose a web method is called GetRandomNumber, the web service proxy class will provide three methods: GetRandomNumber, BeginGetRandomNumber, EndGetRandomNumber. GetRandomNumber is for synchronous invoke, while the other two are for asynchronous invoke.

The rest of the process is identical. To invoke the web method asynchronously, you first call the Begin... method with all necessary parameters, a AsyncCallback object wrapping the call back method, and a context object. When the result comes back, the delegate (inside the proxy) will call the call back method with an IAsyncResult object. Then your callback method is supposed to call the same web service’s EndGetCount method (instead of a delegate’s EndInvoke method) with the IAsyncResult object to get the result.

The sample RandomNumberService web service is as follow:

[WebMethod]

public int GetRandomNumber(int iSeed)

{

    System.Threading.Thread.Sleep(new System.TimeSpan(0, 0, 0, 3, 0));

    Random random = new Random(iSeed);

    return random.Next(100);

}

The Ui is as follow:

The button handler method which calls the Begin... method and the callback method are shown below:

Service1 mService = new Service1();

 

private void MyCallBack(IAsyncResult ar)

{

    tb.Text = mService.EndHelloWorld(ar);

 

    int i = (int)ar.AsyncState;

    MessageBox.Show("The ar.AsyncState is " + i);

}

 

private void Form1_Load(object sender, System.EventArgs e)

{

    AsyncCallback callback = new AsyncCallback(MyCallBack);

    mService.BeginHelloWorld(callback, 123456789);

}

2.5.        Concurrent Requests on Web Service

A web service’s ability to handle concurrent requests is provided by the server (such as IIS) in which the web service is hosted. As the designer of the web service, you only need to consider the concurrency issue when the web service is accessing some resources such as a file or a database connection.

If you are testing the web service on Windows 2000 or XP, remember that it is limited to up to 10 concurrent connections.

The following code tests the concurrent ability of a web service.

Web service:

[WebMethod]

public string HelloWorld(int i)

{

    System.Threading.Thread.Sleep(new TimeSpan(0, 0, 0, 10, 0));

    return "Thread No. " + i;

}

Client:

using System;

using Client.WSConcurrentRequest;

 

namespace Client

{

 

    class Class1

    {

        private DateTime mStartTime;

 

        private delegate string HelloDelegate(int i);  

 

        private void HelloCallback(IAsyncResult ar)

        {

            HelloDelegate deleg = (HelloDelegate)((System.Runtime.Remoting.Messaging.AsyncResult)ar).AsyncDelegate;

            string strMsg = deleg.EndInvoke(ar);

            TimeSpan delay = (TimeSpan)(DateTime.Now - mStartTime);

            strMsg += ": " + (delay.Minutes * 60 + delay.Seconds) + " seconds";

            Console.WriteLine(strMsg);

        }

 

        private string CallWebService(int i)

        {

            Service1 s = new Service1();

            return s.HelloWorld(i);

        }

 

        private void Test()

        {

            mStartTime = DateTime.Now;

 

            for (int i = 0; i < 10; i++)

            {

                //System.Threading.Thread.Sleep(new TimeSpan(0, 0, 0, 0, 500));

                HelloDelegate deleg = new HelloDelegate(CallWebService);

                AsyncCallback callback = new AsyncCallback(HelloCallback);

                deleg.BeginInvoke(i, callback, new object());

            }

        }

 

        [STAThread]

        static void Main(string[] args)

        {

            Class1 c = new Class1();

            c.Test();

 

            Console.ReadLine();

        }

    }

}

2.6.        Web Service Proxy Class

A proxy class is placed in the same machine (normally same directory) of the client of the web service to represent the web service. Its interface provides the same method signatures as the web service. Behind the hood it uses networking techniques to communicate with the real web service through the net.

The proxy class is generated from the WSDL file of a web service. You can either generate it manually using the .NET  command line tool wsdl.exe, or automatically in Visual Studio .NET by adding a reference to the web service (asmx file).

¨    Generating proxy with wsdl.exe

You simply provide to wsdl.exe as command-line arguments the URL of the web service’s WSDL file (or asmx file if WSDL file is not yet generated), plus other parameters such as desired language of the proxy class, the namespace, the file name, etc., and it will communicate with the web service through the net, acquire its WSDL, and generate the proxy class for you. The client can then include this proxy class in his project and invoke it. He can forget about the web service, for this proxy class is all he needs.

To create a proxy class for a web service on “http://localhost/testsession/service1.asmx” in C# and dump the code in file “ProxyTestSession.cs”:

wsdl http://silanliu.com/testsession/service1.asmx  /out:ProxyTestSession.cs  /language:cs  /namespace:silanliu

Then, you can simply add this class into project, create an instance of the proxy class, and invoke its methods:

[STAThread]

static void Main(string[] args)

{

    Service1 ws = new Service1();

    ws.CookieContainer = new CookieContainer();

    Console.WriteLine(ws.GetCount());

    String s = Console.ReadLine();

    Console.WriteLine(ws.GetCount());

    s = Console.ReadLine();

}

Instead of adding the proxy class source file into your project, you can also build the proxy class into a class library and invoke it:

csc  /out:ProxyTestSession.dll  /t:library  /r:System.Web.dll,System.Web.Services.dll  ProxyTestSession.cs

¨    Generating proxy with Visual Studio .NET

Right-click the “References” item in the solution explorer and choose “Add web reference”, then type in the location of the WSDL or asmx file, such as “http:// silanliu.com /testsession/service1.asmx?wsdl” in the URL window, and click “Add reference” button. The namespace of the proxy will become “TestProxy.localhost”, and thus the “using silanliu” in the above code should be changed to

using TestProxy.localhost;

¨      Proxy Class Properties

Properties of a web service proxy class are used mainly for configuration purpose. They can either be accessed internally in the proxy class, by the designer of the proxy class, or from outside of the proxy class by its clients. If you want to changed the automatically generated proxy class, remember that once you regenerate the proxy class, the change you made will be lost.

A proxy class has the following properties.

1.    Url, of type String

The URL of the web service. You can change this URL to achieve load balancing.

2.    Timeout, of type int

When accessing the web service synchronously, the call will throw an exception after timeout. Conbined with property Url, you can achieve same-client-multiple-service type load balancing with the following logic: first set the approprite time out in the constructor of the proxy class, then in the proxy method that invokes the remote web service, which receive exception due to the delay, catch the timeout exception, change the URL, and recursively call this proxy method.

3.    Proxy, of type IWebProxy

The proxy class can find out client’s proxy server by looking up the proxy server URL setting of client machine’s web browser. If you do not want to use this URL and want to use a different one, set this property:

IWebProxy proxyObj = new WebProxy(“http://proxy.blar.com:80”, true);

Proxy = proxyObj;

4.    AllowAutoRedirection, of tyep boolean

When the proxy tries to invoke a web service, the web service may return in HTTP header a redirection URL, saying “Sorry I can’t serve you but you can try this URL”. This redirection URL is contained in Response.Redirect. By defaut, a proxy class’s AllowAutoRedirection property is set to false, disallowing redirection. You can turn it on to allow it. Then the proxy class will automatically redirect to the new URL.

5.    RequestEncoding

Decides the content type of the HTTP message that the proxy sends out.

6.    UserAgent, of type String

When a web browser sends a request to a server, it includes a user agent string to identify itself. A proxy can set this string to convey some ID information to the web service.

3.      Managing States of Web Services

3.1.        State Management in ASP.NET

In ASP.NET, there are two places to store state information: Application and Session.

Application stores data for the server application. The data is stored in memory all through the life time of the server application (not the server), starting from the the first page being requested. Each server application has its own Application. If the server application terminates, this Application data is lost.

Session stores data for each web user. The data about a user is also stored at server side, but it can only be retrieved with a user GUID, which is stored at client side using cookie or modified URL. If the client application does not store the GUID persistently, when client application terminates, this state data is lost.

3.2.        Session

¨    Three ways to store session data

There are three ways to store this session data:

1.    InProc: default way, session data is stored in memory within the IIS process. Such an application does not scale – all following user requests must be sent to the same server where the session data is stored. But it is the fastest.

2.    StateServer: session data is stored in memory by a Windows service separate from IIS. Can be on a different server. This way the application is scalable. Second fastest.

3.    SqlServer: store the session data in SQL server. Slowest but most reliable.

The way to store session data is set in web.config:

    <sessionState

            mode="InProc"

            stateConnectionString="tcpip=127.0.0.1:42424"

            sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"

            cookieless="false"

            timeout="10"

    />

The mode can be InProc, StateServer or SqlServer. stateConnectionString is the URL of the machine running the state server, and sqlConnectionString is only used when you choose SqlServer option for mode. timeout is the life span of the session data.

¨    How to store and retrieve state data

Unlike in ASP.NET, session is disabled in Web Services by default, while Application is enabled by default for web services. The WebMethod attribute which is placed in front of a web method has a property EnableSession. You have to set it to true to enable session. In the following example, if you invoke the GetCount web method repeatedly within the same session, you will get number 1, 2, 3, …

using System.ComponentModel;

using System.Web;

using System.Web.SessionState;

using System.Web.Services;

 

namespace TestSession

{

    public class Service1 : System.Web.Services.WebService

    {

        public Service1()

        {

            InitializeComponent();

        }

 

        [WebMethod(EnableSession=true)]

        public int GetPerUserClickCount()

        {

            int count;

           

            if (Session["ConnectCount"] == null)

                count = 1;

            else

                count = (int)Session["ConnectCount"] + 1;

 

            Session["ConnectCount"] = count;

            return count;

        }

 

        [WebMethod]

        public int GetTotalClickCount()

        {

            int count;

           

            if (Application["ConnectCount"] == null)

                count = 1;

            else

                count = (int)Application["ConnectCount"] + 1;

 

            Application["ConnectCount"] = count;

            return count;

        }

    }

}

¨    CookieContainer for Web Service Client

As we said before, to use session, server must be able to store the user IDs as cookies at client’s side. If the client is a web browser, even if it disable cookies, server can still send modified URL to client. But if the client is an application, server is talking to the proxy class. Proxy class does not store cookie by default, because not all web services need to use state. So it provides a property CookieContainer. If the web service uses state and client wants to enable it, client can simply create a cookie container and assign it to this property. This cookie container is able to store the cookies sent by the web service. CookieContainer stores the session ID cookies in memory. When the client application terminates, the cookies are lost.

Following is a windows form client of the web service of last section.

using System;

using System.Drawing;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

using System.Net;

 

using FTestSession.WSTestSession; // WSTestSession is the name of the web reference

 

namespace FTestSession

{

    public class Form1 : System.Windows.Forms.Form

    {

        private System.Windows.Forms.Button button1;

        private System.ComponentModel.Container components = null;

        private Service1 ws = new Service1();  // Service1 is the web service class

 

        public Form1()

        {

            InitializeComponent();

            ws.CookieContainer = new CookieContainer();

        }

 

        ...

 

        private void button1_Click(object sender, System.EventArgs e)

        {

            MessageBox.Show(ws.GetCount().ToString());

        }

    }

}

In this example, if the client itself is a ASP.NET page or web service which may be accessed by many users, then the click count returned by the web service is the total click count of all users. If you want the end web service to return the click count of the current user, then you have to have separate CookieContainers for each user. Just store each CookieContainer in Session and retrieve it and assign it to the web service proxy before invoking the web service.

3.3.        Implications of using Application

6.    Memory implication: the stored data is not deleted until the end of the application;

7.    Concurrency implication: if the application is multithreaded, you may need to call the HttpApplicationState.Lock to lock the Application before accessing it. The lock is freed after the same thread calls Unlock or the request ends.

8.    Durability: application data is stored in memory and lost after application ends. If you need the data to be persistent, store it in database.

9.    Scalability: application data is not shared across a web farm or web garden.

3.4.        Other Ways to Store State Information

¨    Cookies

A cookie is basically a name/value string pair. The server sends a cookie to a browser, the browser, if cookie enabled, will store this cookie by the server’s URL. In all following requests that the browser sends to this URL, all cookies under this URL will be attached.

Because a cookie is stored persistently, it enables the server to store state info for a user across sessions. You can config the HttpCookie to expire after a certain time by setting its Expires property.

Cookies are not guaranteed to be accepted by all browsers. They are not designed to store secured or large amount of data.

If Request.Cookies("Name") Is Nothing Then

    tbMsg.Text = "Welcome"

    Dim cookie As New HttpCookie("Name", "Silan Liu")

    Dim dt As DateTime = DateTime.Now()

    Dim ts As New TimeSpan(0, 0, 0, 20) ' a life span of 20 seconds

    cookie.Expires = dt.Add(ts)

    Response.AppendCookie(cookie)

Else

    tbMsg.Text = Request.Cookies("Name").Value

End If

¨    Page.ViewState

ViewState property is of type StateBag, which contains a collection of name/value pairs. It is achieve through HTML hidden forms, so the data is stored in user’s end. All browsers accepts hidden forms. Its content is hashed. Not designed to store large amount of data.

If ViewState("Name") Is Nothing Then

tbMsg.Text = "Welcome"

ViewState.Add("Name", "Silan Liu")

'or ViewState("Name") = "Silan Liu"

Else

tbMsg.Text = ViewState("Name")

End If

4.      Caching

4.1.        Output Caching

Output caching for web service is very simple: set the CacheDuration property of the WebMethodAttribute to the number of seconds. This property is by default set to 0, which disables output caching. Output caching items are indexed by the combination of the parameters of the web method. If the number of combinations is very big, the cached items can consume too much memory and bring down the server.

[WebMethod(CacheDuration=60)]

public string MyMethod(int i)

{

    return DateTime.ToLongTimeString();

}

If you provide parameter i as 1 and you get 09:07:32, in next call if you provide another integer you will get the new time. But if you provide 1 again you will get 09:07:32 again.

4.2.        Expiration of the Cache Object

Cached items are stored in the Cache object in key-value pairs. To access one item with a string key:

if (Context.Cache[“ProductsDataSet”] == null)

To add one item, call the Cache object’s Add or Insert method.

Each cached object can have its own expire scheme. There are different types of expire schemes represented by the overloaded Insert methods.

¨    Time-based expiration

There are two types of time-based expiration:

1.    Absolute expiration: the cached item is valid only for a certain length of time. After this time it should be removed from the Cache object. The following code adds an item of a life span of 10 minutes:

Context.Cache.Insert(“ProductsDataSet”, dsProducts, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero)

2.    Sliding expiration: if within the set length of time the cached item is not accessed then remove it.

Context.Cache.Insert(“ProductsDataSet”, dsProducts, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(10))

¨    Dependency-based expiration

There are three types of dependencies that may cause a cached item to be removed from the Cache object. To set any of these dependencies, you should first create a CacheDependency object and then pass it as a parameter to the Cache.Insert method.

1.    File-based dependency: if a file is changed, the cached item should be removed – typically the content of the cached item comes from this file. If this file is changed then the cached item becomes invalid.

CacheDependency dependency = new CacheDependency(Server.MapPath(“ProductInfo.xml”));

Context.Cache.Insert(“ProductsDataSet”, dsProducts, dependency);

The dependency can involve multiple files:

string [] astrFilePaths = new string [2];

astrFilePaths[0] = Server.MapPath(“ProductInfo.xml”);

astrFilePaths[1] = Server.MapPath(“ExchangeRateInfo.xml”);

CacheDependency dependency = new CacheDependency(astrFilePaths);

Context.Cache.Insert(“ProductsDataSet”, dsProducts, dependency);

2.    Time-based + File-based dependency:

Context.Cache.Insert(“ProductsDataSet”, dsProducts, dependency, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(10))

3.    Key-based dependency: if the cached items identified by the given keys is removed, also remove this item:

string [] astrKeys = new string [2];

astrKeys[0] = “dsExchangeRate”;

astrKeys[1] = “dsSalesStaff”;

CacheDependency dependency = new CacheDependency(null, astrKeys);

Context.Cache.Insert(“ProductsDataSet”, dsProducts, dependency);

4.3.        Manually Removing Cached Items

Context.Cache.Remove(“ProductsDataSet”);

4.4.        Cached Item’s Priority for Purge and Decay

A cached item with a lower CacheItemPriority will be deleted first. A cached item with a lower CacheItemPriorityDecay will decay slower after the same length of unused time and therefore be removed later. These priorities can also be passed as parameters into the Insert method:

Context.Cache.Insert(

    “ProductsDataSet”,

    dsProducts,

    dependency,

    Cache.NoAbsoluteExpiration,

    TimeSpan.FromMinutes(10),

    CacheItemPriority.AboveNormal,

    CacheItemPriorityCache.Slow

);

4.5.        Callback Method for Removed Cached Item

System.Web.Caching defines the following delegate for callback for removed cached item:

public delegate void CacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason);

To define a method to be called back:

public void MyCallback(string key, object value, CacheItemRemovedReason reason);

To create a delegate wrapping this callback method:

CacheItemRemovedCallback deleg = new CacheItemRemovedCallback(this.MyCallback);

To insert the item into the Cache with the callback:

Context.Cache.Insert(

    “ProductsDataSet”,

    dsProducts,

    dependency,

    Cache.NoAbsoluteExpiration,

    TimeSpan.FromMinutes(10),

    CacheItemPriority.AboveNormal,

    CacheItemPriorityCache.Slow,

    deleg

);

5.      SOAP and Serialization

5.1.       A Web Method Request Sent Over HTTP POST

The content of a web method request with a customized SOAP header looks like:

POST /http://www.silan.com.au/TestService/service1.asmx HTTP/1.1

Content-Type: text/xml

SOAPAction: "http://www.silan.com.au/Calculator/Add"

Content-Length: 513

Host: sshort3

 

 

<?xml version="1.0" encoding="utf-8" ?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

    xmlns:xsd="http://www.w3.org/2001/XMLSchema">

 

    <soap:Header>

        <MySoapHeader xmlns="http://tempuri.org/">

            <UserName soap:mustUnderstand=”1”>fliu</UserName>

            <Password soap:mustUnderstand=”1”>thepassword</Password>

        </MySoapHeader>

    </soap:Header>

 

    <soap:Body>

        <GetTicketPrice xmlns="http://tempuri.org/">

            <FlightNumber>CA135</FlightNumber>

            <Class>Business</Class>

            <Date>

                <Year>2004</Year>

                <Month>10</Month>

                <Day>04</Day>

            </Date>

        </GetTicketPrice>

    </soap:Body>

</soap:Envelope>

HTTP POST is the most commonly used protocol to convey SOAP. As you can see from above, a HTTP header for a SOAP message is similar to one for a HTML page, with the following differences:

1.    Content-Type is always “text/xml”;

2.    There must be a SOAPAction entry. It is used to convey the intent of the SOAP message. Can be blank or “”. HTTP-aware firewalls can choose to filter SOAP messages using this entry;

5.2.        SOAP Header

SOAP header provides a way to pass information outside the soap body, such as information about authentication, security keys, routing, transaction and payment.

An element in the SOAP header may have following attributes:

1.    mustUnderstand: if it is 1 and the receipient can’t understand it then it should generate an error.

2.    actor: indicates the desired receipient of the header element. Could be any URI or http://schemas.xmlsoap.org/soap/actor/next indicating the next intermediatary.

To use a SOAP header, following things needs to be done:

1.    The web service defines a header class that inherits from SoapHeader:

    public class MySoapHeader : SoapHeader

    {

        public string UserName;

        public string Password;

    }

2.    The web service holds a public member instance of the this header class;

3.    Add a SoapHeader attribute in front of the method which is expecting the header, indicating to the framework that this method is expecting a SoapHeader and the fields of the pass header should be filled into this public member:

    public class Service1 : System.Web.Services.WebService

    {

        public MySoapHeader mSoapHeader = null;

 

        public Service1()

        {

            InitializeComponent();

        }

        [WebMethod]

        [SoapHeader("mSoapHeader", Direction = SoapHeaderDirection.InOut)]

        public string HelloWorld()

        {

            if (mSoapHeader == null) // if client didn’t provide a header ...

                return null;

            else if ((mSoapHeader.UserName == "fliu") && (mSoapHeader.Password == "thepassword"))

                return "Hello World";

            else

                return null;

        }

    }

4.    The client gets the definition of the header class along with the proxy class. It creates an instance of the header class, assign values to its attributes, and assign it to the corresponding property of the proxy class. Then the proxy will create the soap envelop with the corresponding soap header.

[STAThread]

static void Main(string[] args)

{

    Service1 service = new Service1();

    MySoapHeader header = new MySoapHeader();

    header.UserName = "fliu";

    header.Password = "thepassword";

    service.MySoapHeaderValue = header;

    Console.WriteLine(service.HelloWorld());

    Console.ReadLine();

}

5.3.         SOAP Body

SOAP body is represented by a <SOAP-ENV: Body> element. There are three types of soap messages containing three types of soap bodies respectively: call body, response body and fault body. Suppose there is a web method as follow:

public float GetBalance(int iCustomerId)

¨    Call body

Call body is used to send a message call. The method call is serialized into an element with the same name of the called method, and all arguments of the method become child elements:

<SOAP-ENV:Envelop xmlns:xsi=...>

    <SOAP-ENV:Body xmlns:addr=...>

        <addr:GetBalance>

            <iCustomerId xsi:type=”int”>10357</OperandA>

        </addr:GetBalance>

    </SOAP-ENV:Body>

</SOAP-ENV:Envelop>

¨    Response body

Response body is used to return the response of a method call. The method call is serialized into an element with the same name of the called method plus a suffix of “Response”:

<SOAP-ENV:Envelop xmlns:xsi=...>

    <SOAP-ENV:Body xmlns:addr=...>

        <addr:GetBalanceResponse>

            <GetBalanceResult>2353.75</GetBalanceResult>

        </addr:GetBalanceResponse>

    </SOAP-ENV:Body>

</SOAP-ENV:Envelop>

¨    Fault body

Used to return the error info. Represented by a “Fault” element, which contains child element “faultcode”, “faultstring”, “detail”. Fault soap messages are converted by the ASP.NET framework into a SoapException.

5.4.        SOAP Encoding

SOAP encoding rules defines how types such as structures, arrays are encoded in the SOAP message.

¨    Structure

For a structure defined in the web service

public struct Employee

{

    public string Name;

    public int Age;

    public float Salary;

}

 

[WebMethod]

public Employee HelloWorld(Employee emp)

{

    emp.Name = "Mr " + emp.Name;

    emp.Age += 1;

    emp.Salary += 5000.0f;

   return emp;

}

The request message is

<?xml version="1.0" encoding="utf8" ?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>

        <HelloWorld xmlns="http://www.silan.com.au/test">

            <emp>

                       <Name>Bob</Name>

                       <Age>20</Age>

                       <Salary>60000</Salary>

                 </emp>

           </HelloWorld>

      </soap:Body>

</soap:Envelope>

The response message is

<?xml version="1.0" encoding="utf8" ?>

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <soap:Body>

        <HelloWorldResponse xmlns="http://www.silan.com.au/test">

            <HelloWorldResult>

                       <Name>Mr Bob</Name>

                       <Age>21</Age>

                       <Salary>65000</Salary>

                 </HelloWorldResult>

           </HelloWorldResponse>

      </soap:Body>

</soap:Envelope>

¨    Arrays

For a typed array passed as parameter:

int [] ia = {1, 2, 3};

The request message is

<soap:Body>

      <HelloWorld xmlns="http://www.silan.com.au/test">

           <ia>

                 <int>1</int>

                 <int>2</int>

                 <int>3</int>

           </ia>

      </HelloWorld>

</soap:Body>

For an object array:

object [] ia = {(int)1, (string)"Hello", (float)3.3};

Message:

<soap:Body>

      <HelloWorld xmlns="http://www.silan.com.au/test">

           <ia>

                 <anyType xsi:type="xsd:int">1</anyType>

                 <anyType xsi:type="xsd:string">Hello</anyType>

                 <anyType xsi:type="xsd:float">3.3</anyType>

           </ia>

      </HelloWorld>

</soap:Body>

¨    Pass by reference

int i = 1;

float f = 1.0f;

Service1 s = new Service1();

s.HelloWorld(ref i, ref f);

Request:

<soap:Body>

      <HelloWorld xmlns="http://www.silan.com.au/test">

           <i>1</i>

           <f>1</f>

      </HelloWorld>

</soap:Body>

Response:

<soap:Body>

      <HelloWorldResponse xmlns="http://www.silan.com.au/test">

           <i>2</i>

           <f>2</f>

      </HelloWorldResponse>

</soap:Body>

 

5.5.         Making Objects Serializable

To make a class serializable, you only need to put the [Serializable()] attribute in front of the class.

It is the data contained in an object not the schema that is serialized. In other words, to deserialize an object, you sill need the class definition. That is why the WSDL of a web service always includes the class definitions of all classes that appear in the method signatures.

To manually serialize an object into a soap envelop:

SoapFormatter sf = new SoapFormatter();

FileStream fs = new FileStream(“test.soap”, FileMode.Create, FileAccess.ReadWrite);

sf.Serialize(fs, theObj);

fs.Close();

5.6.         SOAP Extension

See my article “SOAP Extensition”.

5.7.         SOAP Message Encoding

As we can see in the example of a SOAP message, parameters are enclosed inside the SOAP body.

There are two ways of encoding the parameters:

1.    Literal: it is assumed that both the client and the server has a XSD schema, and therefore the SOAP message doesn’t contain any type information. It is the default way of encoding parameters of .NET Web Services. The  example of a SOAP message used this encoding.

2.    Encoded: extra type information is added into the SOAP message, so that it conforms to “SOAP Section 5 encoding rules”, and even if the receiver doesn’t have the corresponding schema file it can still understand it. The cost is a larger message size.

There are two types of encodings for the SOAP body: Document or RPC. They are the same except that Document allows you to customize the encoding of the parameters by specifying either Literal or Encoded, while RPC doesn’t have this flexibility – it is fixed with Encoded. RPC is sometimes known as “SOAP section 7 formatting”.

Therefore, there are three ways of SOAP message encoding:

1.    Document body encoding + Literal parameter encoding:

[SoapDocumentService(Use=SoapBindingUse.Literal), WebService(NameSpace=http://silan.com/)]

public class AirlineTicketServices: System.Web.Services.WebService

{ … }

2.    Document body encoding + Encoded parameter encoding:

[SoapDocumentService(Use=SoapBindingUse.Encoded), WebService(NameSpace=http://silan.com/)]

public class AirlineTicketServices: System.Web.Services.WebService

{ … }

3.    RPC body encoding + Encoded parameter encoding:

[SoapRpcService(), WebService(NameSpace=http://silan.com/)]

public class AirlineTicketServices: System.Web.Services.WebService

{ … }

As you can see in the  example of a SOAP message, all the parameters are wrapped in a single element that corresponds to the web method. You can choose to let the parameter elements become directly under the <soap:body> element. To do this:

[SoapDocumentService(Use=SoapBindingUse.Literal, ParameterStyle=SoapParameterStyle.Bare), WebService(NameSpace=http://silan.com/)]

public class AirlineTicketServices: System.Web.Services.WebService

{ … }

5.8.         Using XML Attributes to Customize the SOAP Message

There are two sets of attributes: Xml...Attributes are for Literal style parameters, and SOAP...Attributes for Encoded style parameters. Here are some use examples:

¨    Specify root element name

[XmlRoot(“MyRootName”)]

public class MyClass

{

    ...

¨    Specify that a field becomes an attribute instead of an element

[XmlAttribute(“UserName”)]

public string mstrUserName;

¨    Igore a field

[XmlIgnore]

public string mstrPassword;

¨    Rename an array

[XmlArray(ElementName = “ArrayOfUsers”)]

public User [] maUsers;

¨    Omit empty array

By default, if an array is empty, it will become the following XML entry:

< ArrayOfUsers xsi:nil=”true”/>

To specify to omit an empty array:

[XmlArray(ElementName=”ArrayOfUsers”, IsNullable=false)]

public User [] maUsers;

¨    XmlElement attribute

The following use of XmlElement attribute changes the name of the parameters in the SOAP message:

[WebMethod()]

public decimal GetTicketPrice(

    [XmlElement(“Flight_Number”)]string flightNumber,

    [XmlElement(“Class”)]int seatingClass)

{ … }

ElementName is the default property of this attribute. Other attributes include: DataType, Form, IsNullable, NameSpace, Type.

6.       Web Service Discription Language (WSDL)

6.1.         WSDL

WSDL is a XML-compatible language used to describe a web service in a programatically way so that a software tool such as wsdl.exe could automatically generate from it the proxy for a client. It contains five layers, each one built on top of the previous one: types, message, portType, binding and service.

The WSDL document is marked by a root element definitions.

The types, message, portType layers describe the web service in all necessary details and serve as the interface definition of the web service. But they do not bind the web service to any protocol. All the elements used in these three layers are defined within the WSDL namespace (http://schemas.xmlsoap.org/wsdl/). The binding and service layer then binds the interface definition to a particular protocol and provide the location/address of the web service, using elements defined for this protocol, which are outside the WSDL namespace. These elements are called extension elements. By using extension elements, WSDL can describe a web service bound to virtually any protocol.

The WSDL specification defines three sets of extension elements for three protocols: SOAP, HTTP (GET/POST) and MIME.

<?xml version=”1.0” encoding=”utf-8”>

 

<definitions

    xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"

    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"

    xmlns:s="http://www.w3.org/2001/XMLSchema"

    xmlns:s0="http://www.silan.com.au/test"

    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"

    xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"

    xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"

    targetNamespace="http://www.silan.com.au/test"

    xmlns="http://schemas.xmlsoap.org/wsdl/">

 

    <types>

        <schema ...>

            ...

        </schema>

    </types>

 

    <message ...>...</message>

    <message ...>...</message>

    ...

 

    <portType ...>

        <operation ...>...</operation>

        <operation ...>...</operation>

        ...

    </portType>

 

    <binding ...>

        <operation ...>...</operation>

        <operation ...>...</operation>

        ...

    </binding>

 

    <service ...>

        <port ...>...</port>

        <port ...>...</port>

        ...

    </service>

 

</definitions>

To learn about basic XML concepts needed to understand this section, see my article XML Basics.

6.2.         A Web Service Example

The following web service example is used all through this chapter:

namespace WsSimpleTests

{

    public struct Employee

    {

        public string Name;

        public int Age;

        public float Salary;

        public string Department;

    }

 

    [WebService(Namespace="http://www.silan.com.au/test")]

    public class ModifyEmployee : System.Web.Services.WebService

    {

        public ModifyEmployee()

        {

            //CODEGEN: This call is required by the ASP.NET Web Services Designer

            InitializeComponent();

        }

 

        [WebMethod]

        public Employee ChangeSalary(Employee emp, float fSalary)

        {

            emp.Salary += fSalary;

            return emp;

        }

 

        [WebMethod]

        public Employee ChangeDepartment(Employee emp, string strDept)

        {

            emp.Department = strDept;

            return emp;

        }

    }

}

6.3.         WSDL On SOAP

Here we discusss the WSDL of a web service over SOAP Protocol.

¨    types element

There is only one types element in a WSDL document. It contains a XML schema element, which contains the definition of all elements (and all the custom types that are used to define these elements) that are going to appear in the web service method calls:

  <types>

    <s:schema elementFormDefault="qualified" targetNamespace="http://www.silan.com.au/test">

 

      <s:element name="ChangeSalary">

        <s:complexType>

          <s:sequence>

            <s:element minOccurs="1" maxOccurs="1" name="emp" type="s0:Employee" />

            <s:element minOccurs="1" maxOccurs="1" name="fSalary" type="s:float" />

          </s:sequence>

        </s:complexType>

      </s:element>

 

      <s:complexType name="Employee">

        <s:sequence>

          <s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string" />

          <s:element minOccurs="1" maxOccurs="1" name="Age" type="s:int" />

          <s:element minOccurs="1" maxOccurs="1" name="Salary" type="s:float" />

          <s:element minOccurs="0" maxOccurs="1" name="Department" type="s:string" />

        </s:sequence>

      </s:complexType>

 

      <s:element name="ChangeSalaryResponse">

        <s:complexType>

          <s:sequence>

            <s:element minOccurs="1" maxOccurs="1" name="ChangeSalaryResult" type="s0:Employee" />

          </s:sequence>

        </s:complexType>

      </s:element>

 

      <s:element name="ChangeDepartment">

        <s:complexType>

          <s:sequence>

            <s:element minOccurs="1" maxOccurs="1" name="emp" type="s0:Employee" />

            <s:element minOccurs="0" maxOccurs="1" name="strDept" type="s:string" />

          </s:sequence>

        </s:complexType>

      </s:element>

 

      <s:element name="ChangeDepartmentResponse">

        <s:complexType>

          <s:sequence>

            <s:element minOccurs="1" maxOccurs="1" name="ChangeDepartmentResult" type="s0:Employee" />

          </s:sequence>

        </s:complexType>

      </s:element>

 

    </s:schema>

  </types>

As you can see, for each web method, the schema defines a call body format and a response body format, as discussed in previous section.

¨    message elements

There can be multiple message elements in a WSDL document, each representing a single message being passed between the client and the server. Although elements ChangeSalary and ChangeSalaryResponse defined in the types layer appear to the reader to be obviously related to message ChangeSalary, programmatically they are no more than two XML elements. We need to explicitly correlate these two elements with the message.

Each message contains one or several part elements, each correlates one element such as the ChangeSalary element defined in the types layer with the message.

  <message name="ChangeSalarySoapIn">

    <part name="parameters" element="s0:ChangeSalary" />

  </message>

 

  <message name="ChangeSalarySoapOut">

    <part name="parameters" element="s0:ChangeSalaryResponse" />

  </message>

 

  <message name="ChangeDepartmentSoapIn">

    <part name="parameters" element="s0:ChangeDepartment" />

  </message>

 

  <message name="ChangeDepartmentSoapOut">

    <part name="parameters" element="s0:ChangeDepartmentResponse" />

  </message>

¨    portType element

There is only one portType element in a WSDL document. It contains one or several operation elements, each representing one web method. Each operation can have 1~3 elements, being input, output and fault, which reference the request, response and fault messages defined in the lower message layer. Therefore, the portType element serves as an interface definition of the web service, describing all the web methods the web service provides in all necessary details using elements defined in all lower layers.

  <portType name="ModifyEmployeeSoap">

 

    <operation name="ChangeSalary">

      <input message="s0:ChangeSalarySoapIn" />

      <output message="s0:ChangeSalarySoapOut" />

    </operation>

 

    <operation name="ChangeDepartment">

      <input message="s0:ChangeDepartmentSoapIn" />

      <output message="s0:ChangeDepartmentSoapOut" />

    </operation>

 

  </portType>

¨    binding element

The binding element binds the interface definition of the web service represented by the portType element to a particular protocol.

  <binding name="ModifyEmployeeSoap" type="s0:ModifyEmployeeSoap">

 

    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document" />

 

    <operation name="ChangeSalary">

      <soap:operation soapAction="http://tempuri.org/ChangeSalary" style="document" />

      <input>

        <soap:body use="literal" />

      </input>

      <output>

        <soap:body use="literal" />

      </output>

    </operation>

 

    <operation name="ChangeDepartment">

      <soap:operation soapAction="http://tempuri.org/ChangeDepartment" style="document" />

      <input>

        <soap:body use="literal" />

      </input>

      <output>

        <soap:body use="literal" />

      </output>

    </operation>

  </binding>

Some annotations:

1.    The type attribute of binding element references the portType defined in the lower layer.

2.    The soap:binding extension element indicates that the portType is bound to SOAP protocol, the underlying transport is HTTP (identified by URI http://schemas.xmlsoap.org/soap/http), and the default message style is document. The other option is rpc.

3.    The operation element references an operation defined in lower portType layer;

4.    The soap:operation extension element specifies the soapAction HTTP header and the message style of the operation.

5.    The soap:body extension element and its use=”literal” attribute specifies that the containing part of an operation (such as a request message) must appear in the SOAP body and encoded using literal method – which means that parts in the SOAP body must comply to the schema.

¨      service element

The service element provides the location/address of the web service in the term of the bound protocol using the protocol’s extension element. It contains one or several port elements. Each represents one location. If there are more than one ports, it means that there are multiple alternative locations of the same web service. In the following example, the location provided is the actual URL of the web service.

  <service name="ModifyEmployee">

    <port name="ModifyEmployeeSoap" binding="s0:ModifyEmployeeSoap">

      <soap:address location="http://localhost/WsModifyEmployee /ModifyEmployee.asmx" />

    </port>

  </service>

6.4.         import Element

Instead of having all layers in one WSDL document, we could for example have the types, message and portType layer in one document, and the binding and service layer in a second one, and import the first one into the second one:

<definitions ...>

    <import namespace="http://www.silan.com.au/test" location="http://www.silan.com.au/test/lowerLayers.wsdl”>

 

    <binding ...

 

    <service ...

 

</definitions>

7.      Authentication

See my article “Authentication in ASP.NET Web Services”.

8.      Remoting

8.1.         Remoting vs. Web Services

ASP.NET Web services are actually implemeted using Remoting. Web services offers higher level of abstraction and is easier to use than Remoting, but Remoting is more efficient. So if both ends of the communication are within your control, use Remoting. If the other end is written in other platform, for the sake of interoperability, use web services.

8.2.        Type of Remoting Objects

The structure and relation of remoting objects are represented in the following figure:

¨    MBV Objects vs. MBR Objects

There are two types of remotable objects:

1.    Marshal-By-Value (MBV) objects are copied and sent to the client’s local application domain. When it is accessed, it already becomes a local object. They are also called serializable objects.

2.    Marshal-By-Reference (MBR) objects are not sent to the client’s domain. Instead the client creates a proxy of the MBR object, and communicates with it through the proxy.

MBV objects are often objects representing data, while MBR objects often represent behaviours or services over data. Clients often has the exact definition of the MBV object, so what is transferred from server to client is the data encapsulated in the MBV object. In comparison, clients often do not have the exact definition of the MBR object, instead they often only have its interface definition, because the behaviour of the MBR object may be the business logic that the server does not want the client know.

A MBR object can represent a business logic middle tier sitting on top of a database, providing services to the client applications. Such a service may not only send to the client primitive data such as integers, decimals and arrays. It may also send to the client some complex data structure, which may also have some behaviour attached, which can be defined as a MBV.

Note: only the non-static public methods, properties and members of the remotable object participates in remoting.

¨    CAO vs. SAO

MBV objects are sent to the client’s domain and treated as normal local objects. But MBR objects stayed at server side. When are they created and when do they end? There are two types of MBR objects:

1.    Server Activated Objects (SAO) – their life time are controlled by the server. Only the default constructor can be used to instantiate the object.

2.    Client Activated Objects (CAO) – their life time are controlled by clients. All constructors can be used.

Whether a MBR object is a SAO or a CAO isn’t decided by the MBR object itself. It is decided by how the MBR is registered by the server program.

¨    SingleCall vs. Singleton

There are two types of SAOs:

1.    SingleCall objects – each time an object is invoked a new instance is created, and after the invoke the instance is freed;

2.    Singleton objects – the first time the object is invoked, an instance is created, which will serve all other client requests.

NOTE:

1.    The MBR object itself DOES NOT know whether it is client activated or server activated, or if server activated SingleCall or Singleton. It is the server program (which can be a console program or IIS or Windows service) which registers this MBR object to the remoting framwork that decides all these.

2.    When the MBR object is server activated, client DOES NOT know whether it is SingleCall or Singleton.

8.3.        MBV & MBR Code Example

To mark a class serializable, simply put Serializable attribute in front of it:

[Serializable]

public class TicketDetails

{ … }

In fact all classes in managed code are serializable because the framework can use their metadata to serialize them automatically. So the Serializable attribute does not “enable” but instead “allows” the class to be serialized. If you forget to use this attribute on a class that you want to send over network, a runtime exception will be thrown indicating that you haven’t made this class serializable. 

To make a class a MBR object, make it inherit from class MarshalByRefObject. The following MBR object and other assisting interfaces and classes will be used across this chapter:

public interface IMyMBR

{

    int Sum(int a, int b);

    ILease GetLease();

}

 

public class MyMBR: MarshalByRefObject, IMyMBR  

{

    private int miDivider = 1;

 

    public MyMBR()

    {

        Console.WriteLine("MyMBR: default constructor called!\n");

    }

 

    public MyMBR(int a)

    {

        miDivider = a;

        Console.WriteLine("MyMBR: 2nd constructor called!\n");

    }

 

    public int Sum(int a, int b)

    {

        return (a + b)/miDivider;

    }

 

    public ILease GetLease()

    {

        return (ILease)RemotingServices.GetLifetimeService(this);

    }

}

 

public interface IMyMBRFactory

{

    NsIMyMBR.IMyMBR CreateMBR();

    NsIMyMBR.IMyMBR CreateMBR(int a);

}

 

public class MyMBRFactory: MarshalByRefObject, IMyMBRFactory

{

    public NsIMyMBR.IMyMBR CreateMBR()

    {

        return new NsMyMBR.MyMBR();

    }

 

    public NsIMyMBR.IMyMBR CreateMBR(int a)

    {

        return new NsMyMBR.MyMBR(a);

    }

}

8.4.        Server Activated Object (SAO) Code Example

Following is the simplest implementation of remoting: the MBR object is exposed as a SAO, and client directly instantiate this MBR object.

¨    Server

Server is the piece of code that registers the MBR to the remoting services:

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel(50000);

    ChannelServices.RegisterChannel(channel);

 

    RemotingConfiguration.RegisterWellKnownServiceType(

        typeof(NsMyMBR.MyMBR),              // the MBR class

        "SilansMBR",                        // URI

        WellKnownObjectMode.SingleCall);    // Service activation mode

 

    Console.WriteLine("MyMBR registered as Single Call.  Press [Enter] to terminate");

    Console.ReadLine();

}

¨    Client

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel();

    ChannelServices.RegisterChannel(channel);

 

    RemotingConfiguration.RegisterWellKnownClientType(

        typeof(NsMyMBR.MyMBR),

        "tcp://localhost:50000/SilansMBR");

 

    MyMBR myMbr = new MyMBR();

    Console.WriteLine("13 + 37 = " + myMbr.Sum(13, 37));

    Console.ReadLine();

}

Now keep the same server program running. Run the client program, shut it down, then restart. Go back to server. You will see that the message printed by the MBR’s default constructor will appear every time the client is restarted. This is because the MBR is registered as a SingleCall object.

Change the SingleCall to Singleton in the server program and do the test again. The MBR’s default constructor will no longer be called when client restarts.

An important limitation of this implementation is: because client program directly accesses type MyMBR, client needs the class library of the MBR. In many cases the MBR represents your business logic and you have many reasons not to give it to the clients.

8.5.        Client Accessing MBR Using Interface

You don’t need to give out your MBR to clients. Instead you can create a class library containing an interface representing all the services that you want the MBR to provide, and let the MBR implement the interface. Then you only need to give out the interface class library to clients.

The server program doesn’t need any change from the previous example.

Client code:

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel();

    ChannelServices.RegisterChannel(channel);

 

    IMyMBR myMbr = (IMyMBR)Activator.GetObject(

        typeof(NsIMyMBR.IMyMBR),

        "tcp://localhost:50000/SilansMBR");

 

    Console.WriteLine("13 + 37 = " + myMbr.Sum(13, 37));

    Console.ReadLine();

}

As you can see in the client code, only the interface type IMyMBR is accessed.

8.6.        Generate Interface Assembly of a Given URL

Utility soapsuds.exe can be used to generate metadata for SAOs on HTTP channel:

soapsuds

nowrappedproxy

urltoschema:http://theserver.com/TheService?wsdl

outputassemblyfile:MyInterface.dll

8.7.        How To Invoke MBR’s Non-Default Constructors

Client can only create SAOs using their default constructors. If you want client to create your MBR objects using non-default constructors, you can simply provide a class factory MBR object, so that client can create this class factory using its default constructor, while the methods of this class factory creates your MBR for service purposes using its non-default constructors.

¨    Server

Server program registers the class factory MBR object, which also inherits from MarshalByRefObject, which has methods to create your MBR for service purposes using its non-default constructors:

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel(50000);

    ChannelServices.RegisterChannel(channel);

 

    RemotingConfiguration.RegisterWellKnownServiceType(

        typeof(MyMBRFactory),              // the MBR class

        "SilansMBRFactory",                        // URI

        WellKnownObjectMode.SingleCall);    // Service activation mode

 

    Console.WriteLine("MyMBRFactory registered as Single Call.  Press [Enter] to terminate");

    Console.ReadLine();

}

¨    Client

Client creates an instance of the class factory, and calls its method to create an instance of the MBR for service purpose:

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel();

    ChannelServices.RegisterChannel(channel);

 

    MyMBRFactory myMbrFactory = (MyMBRFactory)Activator.GetObject(

        typeof(NsMyMBRFactory.MyMBRFactory),

        "tcp://localhost:50000/SilansMBRFactory");

 

    IMyMBR myMbr = myMbrFactory.CreateMBR(10);

    Console.WriteLine("13 + 37 = " + myMbr.Sum(13, 37));

    Console.ReadLine();

}

Of course if you do not want to give out the implementation of the class factory to client, you can again let the class factory implement an interface and only give out this interface to client. Then client code will become:

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel();

    ChannelServices.RegisterChannel(channel);

 

    IMyMBRFactory myMbrFactory = (IMyMBRFactory)Activator.GetObject(

        typeof(NsIMyMBRFactory.IMyMBRFactory),

        "tcp://localhost:50000/SilansMBRFactory");

 

    IMyMBR myMbr = myMbrFactory.CreateMBR(10);

    Console.WriteLine("13 + 37 = " + myMbr.Sum(13, 37));

    Console.ReadLine();

}

8.8.        No Need To Register All Remotable Objects

In the code example in previous sections, we only registered to the remoting framework the factory class MyMBRFactory, not the MyMBR. Yet client can still invoke MyMBR’s service. You only need to register one object in the remoting framework as an entry point, through which you can access all other remoting objects. All other remoting objects need to do is to inherit from MarshalByRefObject and thus be remotable.

8.9.         Client Activated Object (CAO)

CAOs are created for each client. So CAOs are suitable when clients want to maintain their private sessions or if they want to instantiate the remotable object with non-default properties.

¨    Server

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel(50000);

    ChannelServices.RegisterChannel(channel);

 

    RemotingConfiguration.RegisterActivatedServiceType(

        typeof(MyMBR));    // Service activation mode

 

    Console.WriteLine("MyMBR registered as client activated.  Press [Enter] to terminate");

    Console.ReadLine();

}

¨    Client

[STAThread]

static void Main(string[] args)

{

    TcpChannel channel = new TcpChannel();

    ChannelServices.RegisterChannel(channel);

 

    RemotingConfiguration.RegisterActivatedClientType(

        typeof(MyMBR),

        "tcp://localhost:50000");

 

    MyMBR myMbr = new MyMBR(10);

    Console.WriteLine("13 + 37 = " + myMbr.Sum(13, 37));

    Console.ReadLine();

}

As you can see in the client code, you can create the MBR using its non-default constructor.

8.10.     Using Configuration File on Server and Client

Configuration files are used to configure how an application (normal application or web application) runs, so that the client can change how the application runs without having to ask you to change the code and recompile.

For a web application, it would be a file named “web.config”, and it will be created when Visual Studio .NET creates the web project.

For a normal application (.exe), it would be a file with the same name of the output file of the application plus “.config”. For example, if the output file is “MyApplication.exe”, the configuration file would be “MyApplication.exe.config”. It won’t be created automatically with the project by Visual Studio .NET, as web.config for a web service project. You can create a new empty file or copy from existing projects. You can always name the file “App.config”. When the project is compiled this file would be copied into the “application.exe.config” file.

¨    Server

The “App.config” file is:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

 

  <system.runtime.remoting>

    <application>

   

        <service>

            <wellknown mode="Singleton" type="NsMyMBR.MyMBR, MyMBR" objectUri="SilansMBR" />

        </service>

       

        <channels>

            <channel ref="http server" port="50000" />

        </channels>

       

    </application>

 </system.runtime.remoting>

 

</configuration>

The “type” attribute has two strings separated by “,”. First one “NsMyMBR.MyMBR is the qualified type of the remotable object, second one “MyMBR" is the assembly name of the remotable object.

The server code becomes:

[STAThread]

static void Main(string[] args)

{

    RemotingConfiguration.Configure("ServerConfig.exe.config");

    Console.WriteLine("MyMBR registered as Singleton.  Press [Enter] to terminate");

    Console.ReadLine();

}

¨    Client

The “App.config” file is:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

 

  <system.runtime.remoting>

    <application>

        <client>

            <wellknown type="NsMyMBR.MyMBR, MyMBR" url="http://localhost:50000/SilansMBR" />

        </client>

    </application>

 </system.runtime.remoting>

 

</configuration>

Client code becomes:

[STAThread]

static void Main(string[] args)

{

    RemotingConfiguration.Configure("ClientConfig.exe.config");

    MyMBR mbr = new MyMBR();

    Console.WriteLine("13 + 37 = " + mbr.Sum(13, 37));

    Console.ReadLine();

}

8.11.     Hosting Remoting Object in IIS

Hosting a remoting object in IIS has the following characteristics:

1.    You do not need to provide and run a server program to register the remoting object to remoting framework. IIS will automatically load it as SAO using HTTP channel, and the remoting object will be hosted at port 80.

2.    Your remotable object will enjoy some of IIS’s services such as authentication and SSL. In fact IIS is the only remoting host that can provide authentication services.

¨    Server

All server need to do is to put the assembly of your remotable object into the GAC or any virtual directory which is known by the IIS. Then client would specify the virtual directory and the URI of the remotable object. Then IIS will go to this virtual directory, load the web.config file, and discover from it how to load the remotable object.

Therefore, you need to do the following steps:

1.    Create an empty ASP.NET web application’;

2.    Add reference to the remotable object’s assembly;

3.    Put the following section into the web.config file of the project:

<system.runtime.remoting>

    <application>

        <service>

            <wellknown mode="Singleton" type="NsMyMBR.MyMBR, MyMBR" objectUri="SilansMBR.rem" />

        </service>

    </application>

 </system.runtime.remoting>

The “objectUri” must have a “.rem” or “.soap” surfix, which are two extensitions registered with IIS.

¨    Client

HttpChannel channel = new HttpChannel();

ChannelServices.RegisterChannel(channel);

IMyMBR mbr = (IMyMBR)Activator.GetObject(typeof(NsIMyMBR.IMyMBR), "http://localhost/WARemoting/SilansMBR.rem");

IDictionary props = ChannelServices.GetChannelSinkProperties(mbr);

props["Credentials"] = System.Net.CredentialCache.DefaultCredentials;

Console.WriteLine("13 + 37 = " + mbr.Sum(13, 37));

Console.ReadLine();

8.12.     Create and Marshal MBR Objects Youself

In all previous sections in this chapter, we always register the remotable object to the remoting framework at server side and let client instantiate the object remotely. This imposes some restrictions such as client can not instantiate the SAO using its non-default constructor.

If you don’t want to give client the flexibility of choosing constructor, an alternative is to instantiate the MBR object at server side, then marshal the object to make it available across the application domain boundary at a certain URI.

¨    Server

[STAThread]

static void Main(string[] args)

{

    MyMBR mbr = new MyMBR(10);

 

    TcpChannel channel = new TcpChannel(50000);

    ChannelServices.RegisterChannel(channel);

 

    RemotingServices.Marshal(mbr, "SilansMBR");

 

    Console.WriteLine("MyMBR marshalled as \"SilansMBR\". Press [Enter] to terminate...");

    Console.ReadLine();

}

¨    Client

[STAThread]

static void Main(string[] args)

{

    IMyMBR mbr = (IMyMBR)Activator.GetObject(typeof(NsIMyMBR.IMyMBR), "tcp://localhost:50000/SilansMBR");

    Console.WriteLine("13 + 37 = " + mbr.Sum(13, 37));

    Console.ReadLine();

}

8.13.     Remotable Objects Can Be Called Asynchronously

MarshalByRefObject objects can be called asynchronously. You don’t need to do anything special to the objects.

9.      Windows Services

9.1.         Windows Services Overview

A Windows service is an executable that has the following characteristics:

1.         It is maintained (started, stopped, paused, etc.) by a windows component called Service Control Manager (SCM). To be used by SCM, it has to inherit from a ServiceBase class, which provides the interface and necessary functionalities.

2.         How SCM should maintain it – for SCM to know the existence of a certain Windows service, its location, when to start it, under what account it is run, etc., are stored in the system registry under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services.

3.         The executable normally has not user interface and runs at the background.

To run a Windows service, three parties are involved:

1.         The Windows service executable;

2.          SCM;

3.         System registry database.

The code of a Windows service application is shown below:

namespace NsTestWindowService

{

    public class TestWindowService : System.ServiceProcess.ServiceBase

    {

        private System.IO.FileSystemWatcher fws;

        private System.ComponentModel.Container components = null;

 

        public TestWindowService()

        {

            InitializeComponent();

        }

 

        // The main entry point for the process

        static void Main()

        {

            ServiceBase[] ServicesToRun;

            ServicesToRun = new ServiceBase[] { new TestWindowService() };

            ServiceBase.Run(ServicesToRun);

        }

 

        private void InitializeComponent()

        {

            this.fws = new System.IO.FileSystemWatcher();

            ((System.ComponentModel.ISupportInitialize)(this.fws)).BeginInit();

 

            this.fws.EnableRaisingEvents = true;

            this.fws.Filter = "*.txt";

            this.fws.Path = "c:\\temp";

            this.fws.Changed += new System.IO.FileSystemEventHandler(this.fws_Changed);

            this.fws.Created += new System.IO.FileSystemEventHandler(this.fws_Created);

 

            this.CanPauseAndContinue = true;

            this.ServiceName = "TestWindowService";

            ((System.ComponentModel.ISupportInitialize)(this.fws)).EndInit();

 

        }

 

        protected override void Dispose( bool disposing )

        {

            ...

        }

 

        protected override void OnStart(string[] args)

        {

            if (args.Length == 2)

            {

                fws.Filter = args[0];

                fws.Path = args[1];

            }

        }

 

        protected override void OnStop()

        {

        }

 

        protected override void OnPause()

        {

        }

 

        protected override void OnContinue()

        {

        }

 

        private void fws_Created(object sender, System.IO.FileSystemEventArgs e)

        {

            EventLog.WriteEntry(e.FullPath + " created!");

        }

    }

}

As you can see from the sample code, a Windows service class inherits from ServiceBase. A Windows service application can contain multiple such classes, but only one of them can have a Main method. At startup, SCM will search the registry to find all Windows service application that should be run at startup, and run each of them. It starts with calling the Main method. In this method, you should create the Windows services contained in this application and pass them to the static ServiceBase.Run method. This is how you pass to SCM the instances of the Windows services contained in this application. Then SCM will call the Start method of all these ServiceBase instances and to start the services.

Of course a service has to be able to be started, so you must implement the OnStart method of your ServiceBase. Whether it can be paused/continued, shut down or stopped is optional. For example, if you want it to be able to be stopped, you must set the CanStop property of the ServiceBase to true, and implement its OnStop method.

Note that OnStart is different from the other three in that it takes an array of arguments just like normal Main methods. When using Service MMC Snap-in, these arguments can be passed in in the “Start parameters:” text box on the Properties page.

If you set the AutoLog property of ServiceBase to true, it will automatically log and error and state change into application event log. To write to the event log yourself, you can directly call the WriteEntry method of the EventLog property of ServiceBase class.

To create a Windows service using Visual Studio .NET is very easy: create a Windows service project, Visual Studio .NET will create the skeleton of the ServiceBase-derived class for you, similar to the above sample code. All you need to do is to set its properties (CanHandlePowerEvent, CanPauseAndContinue, CanShutdown, CanStop, ServiceName), and provide your implementation of the OnStart, OnStop, OnPause, OnContinue methods.

The Windows service in the sample code uses FileSystemWatcher to watch for any newly created text file in “c:\temp” directory, and write their full path into file "silanslog.txt".

9.2.        Installing Windows Service

As said before, for a Windows service to be successfully run, we need SCM, data entries in the system registry, and the service executable itself. We already have a SCM and the executable, now we need to put relevant entries into system registry. Here there are three parties involved in this installation process.

There are two types of info we want to put in: those regarding the whole application/process, such as the account used to run the application, and those regarding each individual Windows service contained in this application, such as the user-friendly and system-unique name of the service, other services that this service depends on, and how this service should be started – automatically at system startup or manually. We use a ServiceProcessInstaller to install the process-related info, and one ServiceInstaller for each service to install the service-related info.

Finally, we have a Installer to inform the SCM the existence of all the ServiceProcessInstaller and ServiceInstaller instances. In the Installer, you should create the instances of all the installer classes, and add them into its Installers collection property.

When you run an installation tool (such as installutil.exe), it will look for an Installer class in the application with a RunInstaller attribute set to true, and create an instance for it. Then it gets its Installers collection, and call the Install method of each installer contained in it, to insert all necessary info into the system registry.

Visual Studio .NET allows you to create all these installing classes at one hit. Click the designer pane of the Windows service, at the bottom of the Properties window, there is an Add Installer link. Click it, an Installer-derived class will be created in the project, which holds a ServiceProcessInstaller instance and one ServiceInstaller instance for each Windows service contained in the application. All you need to do is to set their properties and then build the whole application. Now the application is finished and ready to be installed.

To install the service using installutil.exe, simply type in the following command at command line:

installutil myservice.exe

To uninstall:

installutil /u myservice.exe

9.3.        Manipulating Windows Services

In the previous sections we have learnt how to create and install a Windows service. Now we need to manipulate the service – start, stop, pause, continue. We have the following tools to do it:

1.       Service MMC Snap-in: Control Panel | Administrative Tools | Services;

2.       Visual Studio .NET Server Explorer;

3.       net.exe (type in following command at command line: net start/pause/continue/stop servicename);

4.       Service Control utility sc.exe

Alternatively, you can use the System.ServiceProcess.ServiceController class to programmatically manipulate the Windows services.

To get an array of ServiceController objects representing all the Windows services installed on this computer:

ServiceController [] controllers = ServiceController.GetServices();

To check a Windows service’s settings:

if (controllers[i].CanStop) …

if (controllers[i].CanPauseAndContinue) …

To check a Windows service’s status:

if (controller[i].Status = ServiceControllerStatus.ContinuePending)…

if (controller[i].Status = ServiceControllerStatus.Paused)…

if (controller[i].Status = ServiceControllerStatus.PausePending)…

To actually change a Windows service’s status:

controller[i].Start();

controller[i].Stop();

9.4.         Security Issues of Windows Service

Windows services can not be run in the context of the logged-in user, because most services will be running across different user logins. It is run in the context of the account specified at the installation time. You assign the Account property of the ServiceProcessInstaller instance held by the Installer object one of the following enumerations: LocalService, LocalSystem, NetworkService, User.