Thursday, April 19, 2007

For a project I'm currently working on, we need a service that send on regular base a signal to a certain system, this service can't be hosted in IIS and we need to use a windows service because the Windows Service can continuously send that signal. This service uses an httplistener to receive the soap messages from clients and parses the incoming SOAP to get the data. This simulates the IIS so soap calls can be send through HTTP, clients are not aware of the fact that they are not talking to IIS. This concept allows keeping sending the heartbeats while processing the soaprequest.

But what about the WSDL ? Normally developers can ask the WSDL so they can create the proxies for their clients.
This is an issue, as the service is not an IIS service, we need another way to solve this problem. You can do this using this technology :

The contract is developed as webservice without implementation. This results in a .cs file which declares the webmethods, but holds no functionality and an .asmx file. The HTTP listener can forward the call for the WSDL to the ASP.NET environment which generates the WSDL. The ASP.NET environment processes the .asmx?WSDL query and generates the WSDL. This WSDL is send to the client development environment so proxies can be generated.

This whole description is fine, but practical ?
OK, this is my best practice. In my solution I create a webservice project. In this project I write all the code I so that my WS will work.

public Service () {

//Uncomment the following line if using designed components
//InitializeComponent();
}

[WebMethod]
public OperationAnswer Add2Numbers(int number1, int number2)
{
OperationAnswer a = new OperationAnswer();
a.ErrorCode = 0;
a.STBAnswer = "Het Antwoord" ;

return a;
}

#region Answer Classes
//Temp class
public class OperationAnswer
{
private int _ErrorCode;
public int ErrorCode
{
get { return _ErrorCode; }
set { _ErrorCode = value; }
}
private String _STBAnswer;
public String STBAnswer
{
get { return _STBAnswer; }
set { _STBAnswer = value; }
}
}
#endregion

After that I go to the bin directory of my windows service. In here I copy the asmx file and create a directory named App_Code. In this directory I copy the .cs file of the webservice. Now that we have done this, our webservice is hosted in our windows service without using IIS.

But what about the WSDL ? Here for I have created a new project called processingHost. In here we have a method that will create a stringwriter and return the value of this. The content contains the WSDL.

[Serializable]
public class ProcessingHost : MarshalByRefObject
{
public string ProcessRequest(string page, string query)
{
System.IO.StringWriter sw = new System.IO.StringWriter();

HttpRuntime.ProcessRequest(new SimpleWorkerRequest(page, query, sw));

sw.Close();
return sw.ToString();
}

This DLL must also copied to your service. So in the bin dir of your service create a new bin directory and copy the DLL in here.
When using this in your service the moment the client ask the WSDL, you can use this code :

static ProcessingHost SOAPProcessingHost;
static HttpListener SoapHTTPListener;
static HttpListenerContext SoapHTTPListenerContext;

SOAPProcessingHost = (ProcessingHost)ApplicationHost.CreateApplicationHost(typeof(ProcessingHost),"/",Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().GetModules(false)[0].FullyQualifiedName));

//Create a new HTTPListener
SoapHTTPListener = new HttpListener();

string SoapServer = "http://localhost:9999/"

SoapHTTPListener.Prefixes.Add(SoapServer);

//Start the listener
SoapHTTPListener.Start();

while (SoapHTTPListener.IsListening)
{

//Get the SOAP Enveloppe
SoapHTTPListenerContext = SoapHTTPListener.GetContext();
string page = SoapHTTPListenerContext.Request.Url.LocalPath.Replace("/", "");
string query = SoapHTTPListenerContext.Request.Url.Query.Replace("?", "");

if (query.ToUpper() == "WSDL")
{
string Answer = SOAPProcessingHost.ProcessRequest(page, query);
if (Answer != null && SoapHTTPListenerContext.Response.OutputStream.CanWrite)
{
byte[] AnswerAsByteArray = System.Text.UTF8Encoding.UTF8.GetBytes(Answer);
SoapHTTPListenerContext.Response.OutputStream.Write(AnswerAsByteArray,0, AnswerAsByteArray.Length);
}
}
else
{
//Do your normal SOAP processing here
}
SoapHTTPListenerContext.Response.Close();

}

Now when your service is running, the developers of the client can create a webreference to the service by using following statement :

http://localhost:9999/Service.asmx?WSDL

You can download here a zipfile which contains a small example project (quick and dirty coding). This project has a Soap client where you can enter 2 numbers, after pressing the "add" button, you get the result back. This soapclient calls the webservice hosted under a winservice without using IIS. The webservice code is also included. The windows service is sending a "heartbeat" (Text to the console) while waiting for SOAP input. This Heartbeat simulates another process that the windows service is doing. You can use this as an example for your project.

4/19/2007 9:25:48 PM (Romance Standard Time, UTC+01:00)  #     |