Sunday, September 16, 2007

Logging serialization and de-serialization

To implement serialization derive your class from SoapExtension, and override all methods. ProcessMessage is the method that acts as a nevous system for our functionality. This method is called on a variety of scenarios. Before start f serialization, during serialization, after serialization etc….

If we want logging to be done, then we implement our functionality in the method ProcessMessage. If you want to achieve encryption then we need to have our functionality defined in the section BeforeSerialize and BeforeDeSerialize. Encrypt message before serialize operations and before deserialize operations.

Sample code is appended for your reference

using System;
using System.Web;
using System.Collections;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.IO;

[AttributeUsage(AttributeTargets.Method)]
public class TraceExtensionAttribute : SoapExtensionAttribute
{

private string filename = "c:\\SOAPTraceLog.txt";
private int priority;

public override Type ExtensionType
{
get { return typeof(TraceExtension); }
}

public override int Priority
{
get { return priority; }
set { priority = value; }
}

public string Filename
{
get
{
return filename;
}
set
{
filename = value;
}
}
}

public class TraceExtension : SoapExtension
{
Stream oldStream;
Stream newStream;
string filename;

public override object GetInitializer(LogicalMethodInfo methodInfo,
SoapExtensionAttribute attribute) {
return ((TraceExtensionAttribute) attribute).Filename;
}

public override object GetInitializer(Type serviceType) {
return typeof(TraceExtension);
}

public override void Initialize (object initializer) {
filename = (string) initializer;
}

public override void ProcessMessage(SoapMessage message) {
switch (message.Stage) {

case SoapMessageStage.BeforeSerialize:
break;

case SoapMessageStage.AfterSerialize:
//WriteOutput();// message );
WriteOutput(message );
break;

case SoapMessageStage.BeforeDeserialize:
WriteInput ( message );
break;

case SoapMessageStage.AfterDeserialize:
break;

default:
throw new Exception("invalid stage");
}
}

public override Stream ChainStream( Stream stream ){
oldStream = stream;
newStream = new MemoryStream();
return newStream;
}

//public void WriteOutput()//SoapMessage message)
public void WriteOutput(SoapMessage message)
{
newStream.Position = 0;
FileStream fs = new FileStream(filename, FileMode.Append,
FileAccess.Write);

StreamWriter w = new StreamWriter(fs);

w.WriteLine("---------------------------------- Response at " + message.ToString() +
DateTime.Now);
w.Flush();
Copy (newStream, fs);
fs.Close();
newStream.Position = 0;
Copy (newStream, oldStream);
}

public void WriteInput ( SoapMessage message )
{
Copy(oldStream, newStream);
FileStream fs = new FileStream(filename, FileMode.Append);
StreamWriter w = new StreamWriter(fs);

w.WriteLine("============================== Request at " +
DateTime.Now);
w.Flush();
newStream.Position = 0;
Copy(newStream, fs);
fs.Close();
newStream.Position = 0;
}

void Copy(Stream from, Stream to) {
TextReader reader = new StreamReader(from);
TextWriter writer = new StreamWriter(to);
writer.WriteLine (reader .ReadToEnd());
writer.Flush();
}
}


///
/// Summary description for TraceSerialize
///

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class TraceSerialize : System.Web.Services.WebService {

public TraceSerialize () {

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

[WebMethod]
public string HelloWorld() {
return "Hello World";
}

[WebMethod, TraceExtension(Filename = "C:\\WebServiceTrace.Log")]
public string StartTracing()
{
return "Start Tracing ....";
}

}

Tuesday, September 4, 2007

Manage .NET webservices

WSDL based web service
A simple Helloworld example with WSDL

Create a simple web service and name it HelloWorld, rename service1.asmx as HelloService
Create a new WebMethod HelloUser with string as a parameter
Feel free to add any functionality to this function. As a starter, keep this simple. May be this function just returns a concatenated string “Hello” when user provides his name. Once we get a simple solution out of the box, then we could add any complex operation to the web service. This just to reinstate that our focus should be in getting the web service working in WSDL form first
Your web service code should look something as simple as the following

using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class HelloService : System.Web.Services.WebService
{
public HelloService () {

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

[WebMethod]
public string HelloWorld() {
return "Hello World";
}

[WebMethod]
public string HelloUser(string sUserName)
{
return "Hello " + sUserName;
}

}

Execute this code and get the WSDL file
Generation of WSDL file
Let’s say on executeion of the web service code results in the following link http://localhost:4461/HelloWorld/HelloService.asmx
All that we need to do is add “wsdl” term at the end to generate WSDL code
Your sample URL for generation of WSDL for HelloService would look like http://localhost:4461/HelloWorld/HelloService.asmx?wsdl
There you go. Your first WSDL code is ready, store this in the same directory as you have your web service (outside App_Code directory), so that all web service files are in the same location.

Web service consumer application

Create a simple windows application
Add wsdl file generated in the previous step to windows application. You can do that by clicking on the windows application project, right-click and select “Add existing Item”, point to the location containing .WSDL file and add it
Now add a new web reference, instead of picking up .asmx file(as always), pick up service that contains .wsdl extension (sample URL can be like http://localhost/HelloWorld/HelloService.wsdl
Now proxy code is also generated, all that remains is the invocation of the function
Create instance of the Web service class and call web methods of you choice.
There ends invocation of web service methods through WSDL

Sample code of windows application is appended for your reference
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace SampleWSConsumer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e)
{
MessageBox.Show("Started");
HelloServiceWsdl.HelloService wService = new SampleWSConsumer.HelloServiceWsdl.HelloService();
MessageBox.Show(wService.HelloWorld());
MessageBox.Show(wService.HelloUser("Krishnan"));
}
}
}

Simple Cache operations

Use Cache object to store and retrieve data from cache.

This cache can store data with particular or sliding expiration date.

Here’s a sample of a default insert into Cache object
Context.Cache.Insert("CitiesList", sCountries)

If you want to store it in Cache with an particular date, then here’s the same you can try

Context.Cache.Insert("CitiesList", sCountries, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero);

This suggests cache to retain data for 10 minutes, starting from now.

If you want to be alerted when cache data is removed from cache, follow this syntax
Context.Cache.Insert("CitiesList", sCountries, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero, CacheItemPriority.High, OnRemove);

public void RemovedCallback(String k, Object v, CacheItemRemovedReason r)
{
ArrayList sCountries = new ArrayList();
//itemRemoved = true;
//reason = r;
//write the same code to get itemss back into cache
//open connection to SQL Server
DBEngine dbEngine = new DBEngine();
sCountries = dbEngine.GetCities();
//update dataset in cache
Context.Cache.Insert("CitiesList", sCountries, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero, CacheItemPriority.High, null);
}

[WebMethod]
public object GetCitiesList()
{
ArrayList sCountries = new ArrayList();
//check if cache object conatins cities dataset
if (Context.Cache["CitiesList"] == null)
{
//if not
//open connection to SQL Server
DBEngine dbEngine = new DBEngine();
sCountries = dbEngine.GetCities();
//update dataset in cache
Context.Cache.Insert("CitiesList", sCountries, null, DateTime.Now.AddMinutes(10), TimeSpan.Zero, CacheItemPriority.High, null);
//Context.Cache["CitiesList"] = sCountries;
}
else
{
sCountries = (ArrayList)Context.Cache["CitiesList"];
}
//return array list
return sCountries;
}


Transaction support in web service
Inheriting Support from the WebMethodAttribute
Throughout the .NET documentation, Microsoft recommends that you inherit from WebMethodAttribute as part of your ASP.NET Web Services, but notes that it is not a required reference. It is inherited by default. WebMethodAttribute gives you a number of powerful features that are not always necessary, including:
1. BufferResponse – gives you the ability to buffer the Web Service response.
2. CacheDuration – gets and sets the length of time the response is held in the cache.
3. Description – an easy way to set the description in the WSDL file returned by ?wsdl in the URL. It is used by the default page IIS shows for Web Services, and UDDI.
4. EnableSession – turns on and off the Session State support for the method.
5. MessageName – more easy access to the WSDL, setting the name used in the SOAP header.
6. TransactionOption – turns on automatic transaction support.
So, we have finally found what we are looking for – the TransactionOption. What is great about this is the ‘automatic’ part. Transaction support in Windows (or any other system) is a complicated task, requiring Just-In-Time activation, synchronous method handling, and several other services component features handled by COM+. Since we don't have an actual DLL file here, how do we support transactions? Automatically – thanks to the WebMethodAttribute.
As part of our [WebMethod()] statement, then, we'll include the TransactionOption attribute, like so: [WebMethod(TransactionOption=TransactionOption.RequiresNew)]
With this, all of the features we need are enabled automatically for us, added to the configuration of the Web Service in the compiled MSIL. Even more, there are all of the COM+ style types of transactional options, including:
1. Disabled – executes the Web Service without a transaction
2. NotSupported – executes the Web Service without a transaction
(These two are the same because the TransactionOption values are inherited from the COM+ EnterpriseServices, which has more comprehensive transactional support).
1. Supported – participates in a transaction, but does not create a new transaction when invoked.
2. Required – works the same as RequiresNew, since Web Services can't create a new transaction. We'll cover this in the next section.
3. RequiresNew – the important one – fires a new transaction up when invoked.
So, you can see that these look a lot like the attributes in COM+ that tell a consumer how an object is be treated in a transaction. Strangely, that isn't really what these attributes are used for in the WebMethod statement. In this environment, the TransactionOption attribute tells the WebMethod how it should treat those calls within itself – more or less the opposite of the way COM+ works.
This works exactly how you would imagine it would. If an exception is thrown by any of the code in the WebMethod, the transaction is automatically aborted. Its exceptions are handled by a try…catch block, then the transaction is automatically committed unless explicitly aborted by the SetAbort method. We can access SetAbort from the ContextUtil object, which is in the EnterpriseServices namespace

By specifying TransactionOption to the WebMethod , we bring n transaction operation. Even if one of the insert operation fails (insert a tourist record or a log), entire insert operation is dropped
Sample Webservice with transaction support
[WebMethod(TransactionOption = TransactionOption.RequiresNew)]
public void InsertInToTouristGuide(string sPlace, string sSpeciality, string sTravel,
string sOtherInfo)
{
DBUpdates dbOp = new DBUpdates();
int nId = 0;
nId = dbOp.InsertIntoTouristGuide(sPlace, sSpeciality, sTravel, sOtherInfo);
dbOp.InsertLog(nId);
}

DBUpdate.cs

using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
using System.Data.Common;
using System.Configuration;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

///
/// Summary description for DBUpdates
///

public class DBUpdates
{
public DBUpdates()
{

}
public int InsertIntoTouristGuide(string sPlace, string sSpeciality, string sTravel,
string sOtherInfo)
{
DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection dbCon;
DbCommand dbCmd;
int nReturnId = 0;

using (dbCon = factory.CreateConnection())
{
dbCon.ConnectionString = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
dbCon.Open();

dbCmd = factory.CreateCommand();
dbCmd.Connection = dbCon;
dbCmd.CommandType = CommandType.Text;
dbCmd.CommandText = "INSERT INTO TOURISTGUIDE VALUES(@place, @speciality, @travel,@otherinfo)";
//Parameter 1
DbParameter dbParam = factory.CreateParameter();
dbParam.Direction = ParameterDirection.Input;
dbParam.ParameterName = "place";
dbParam.Value = sPlace;
dbParam.SourceColumn = "place";
dbCmd.Parameters.Add(dbParam);
//Parameter 2
dbParam = factory.CreateParameter();
dbParam.Direction = ParameterDirection.Input;
dbParam.ParameterName = "speciality";
dbParam.SourceColumn = "speciality";
dbParam.Value = sSpeciality;
dbCmd.Parameters.Add(dbParam);
//Parameter 3
dbParam = factory.CreateParameter();
dbParam.Direction = ParameterDirection.Input;
dbParam.ParameterName = "travel";
dbParam.SourceColumn = "modeoftravel";
dbParam.Value = sTravel;
dbCmd.Parameters.Add(dbParam);
//Parameter 4
dbParam = factory.CreateParameter();
dbParam.Direction = ParameterDirection.Input;
dbParam.ParameterName = "otherinfo";
dbParam.SourceColumn = "otherinfo";
dbParam.Value = sOtherInfo;
dbCmd.Parameters.Add(dbParam);

dbCmd.ExecuteNonQuery();

//obtain the latest ID value for inserting it in log
dbCmd = factory.CreateCommand();
dbCmd.CommandType = CommandType.Text;
dbCmd.CommandText = "SELECT @@IDENTITY";
dbCmd.Connection = dbCon;
nReturnId = System.Convert.ToInt32(dbCmd.ExecuteScalar());
}

//dbCon.Close();
return nReturnId;
}

public void InsertLog(int nInsertID)
{
string sMessage = ConfigurationManager.AppSettings["defaultMessage"].ToString();
sMessage = sMessage + nInsertID.ToString();

DbProviderFactory factory = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection dbCon;
DbCommand dbCmd;
//int nReturnId = 0;

using (dbCon = factory.CreateConnection())
{
dbCon.ConnectionString = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
dbCon.Open();

dbCmd = factory.CreateCommand();
dbCmd.Connection = dbCon;
dbCmd.CommandType = CommandType.Text;
dbCmd.CommandText = "INSERT INTO LOGTABLE VALUES(@logmessage)";
//Parameter 1
DbParameter dbParam = factory.CreateParameter();
dbParam.Direction = ParameterDirection.Input;
dbParam.ParameterName = "logmessage";
dbParam.Value = sMessage;
dbParam.SourceColumn = "logMessage";
dbCmd.Parameters.Add(dbParam);

throw new Exception("playful excecption");
dbCmd.ExecuteNonQuery();
}
}
}