ASP.NET
Web Services Tutorial
Silan Liu, February 2004
Content
1.2. Files in
a Visual Studio .NET Web Service Project
1.4. Location
of the Web Service
1.5. Useful
Properties of class WebService
1.6. Storing
Configuration Settings in web.config
1.7. Some Web
Service Design Issues
1.8. Generate
WSDL from Web Service
1.9. Web
Service Round Trip Time
1.10. 1.10.Customizing
Web Service with Web Method Attributes
2.1. Do Not
Transport Customized Types with Behaviour
2.3. Exposing
COM Object As Web Service
2.4. Asynchronous
Invoke of Web Service
2.5. Concurrent
Requests on Web Service
3. 3.
Managing States of Web
Services
3.1. State
Management in ASP.NET
3.3. Implications
of using Application
3.4. Other
Ways to Store State Information
4.2. Expiration
of the Cache Object
4.3. Manually
Removing Cached Items
4.4. Cached
Item’s Priority for Purge and Decay
4.5. Callback
Method for Removed Cached Item
5.1. A Web
Method Request Sent Over HTTP POST
5.5. Making
Objects Serializable
5.8. Using XML
Attributes to Customize the SOAP Message
6. 6.
Web Service Discription
Language (WSDL)
8.1. Remoting
vs. Web Services
8.4. Server
Activated Object (SAO) Code Example
8.5. Client
Accessing MBR Using Interface
8.6. Generate
Interface Assembly of a Given URL
8.7. How To
Invoke MBR’s Non-Default Constructors
8.8. No Need
To Register All Remotable Objects
8.9. Client
Activated Object (CAO)
8.10. 8.10.Using
Configuration File on Server and Client
8.11. 8.11.Hosting
Remoting Object in IIS
8.12. 8.12.Create
and Marshal MBR Objects Youself
8.13. 8.13.Remotable
Objects Can Be Called Asynchronously
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) 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.
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 |
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.
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”.
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 |
|
Context |
HttpContext of the current request |
Server |
HttpServerUtility of current request |
Session |
|
User |
Mainly used for user authentication |
Apart from
the above benefits, classes inheriting from WebService can use .NET remoting.
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) 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;
To generate
WSDL document from a web service:
disco http://www.silan.com.au/wstests/service1.asmx MyWebServices.wsdl
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.
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.
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.
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();
}
}
}
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)
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);
}
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();
}
}
}
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).
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
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;
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.
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.
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.
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;
}
}
}
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.
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.
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
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
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.
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.
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))
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);
Context.Cache.Remove(“ProductsDataSet”);
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
);
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
);
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;
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();
}
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
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 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>
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.
SOAP
encoding rules defines how types such as structures, arrays are encoded in the
SOAP message.
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">
<HelloWorld xmlns="http://www.silan.com.au/test">
<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">
<HelloWorldResponse xmlns="http://www.silan.com.au/test">
<Name>Mr Bob</Name>
<Age>21</Age>
<Salary>65000</Salary>
</HelloWorldResult>
</HelloWorldResponse>
</soap:Body>
</soap:Envelope>
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>
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>
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();
See my
article “SOAP Extensition”.
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
{ … }
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:
[XmlRoot(“MyRootName”)]
public class MyClass
{
...
[XmlAttribute(“UserName”)]
public string mstrUserName;
[XmlIgnore]
public string mstrPassword;
[XmlArray(ElementName = “ArrayOfUsers”)]
public User [] maUsers;
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;
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.
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.
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;
}
}
}
Here we
discusss the WSDL of a web service over SOAP Protocol.
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.
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>
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>
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.
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>
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>
See my
article “Authentication in ASP.NET 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.
The
structure and relation of remoting objects are represented in the following
figure:
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.
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.
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.
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);
}
}
Following
is the simplest implementation of remoting: the MBR object is exposed as a SAO,
and client directly instantiate this MBR object.
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();
}
[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.
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.
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
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
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
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();
}
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.
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.
[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();
}
[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.
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.
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();
}
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();
}
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.
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.
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();
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.
[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();
}
[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();
}
MarshalByRefObject objects can be called
asynchronously. You don’t need to do anything special to the objects.
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".
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
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();
…
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.