Wednesday, January 13, 2010

Identifying whether execution context is win or web

When writing components which targets both web and desktop applications, there might be instances where there is need to check under which context the code is running.

Checking System.Web.HttpContext.Current won't work always. If the code is running under the context of main thread this always works. But if a thread is spawn, in the new thread System.Web.HttpContext.Current will return null even if it is running under web context.

Some debugging showed me that System.Web.HttpRuntime.AppDomainId is the right candidate for checking this. System.Web.HttpRuntime.AppDomainId returns null when the code is running under windows context, but always returns a non-null string value while running under web context regardless of whether the code is running under main thread or a spawned thread.

So the below piece of code can be used to check whether the code is running under web context.
/// <summary>
/// Specifies whether the current execution context is Web.
/// </summary>
/// <returns>True if current context is Web.</returns>
public bool IsWebApplicationContext()
{
    return !string.IsNullOrEmpty(System.Web.HttpRuntime.AppDomainId);
}

Tuesday, January 12, 2010

Simple WCF Service, Host and Client

We will create a simple WCF service which will be hosted in a custom console application. A win form application is used as the WCF client.

WCF Service
We will start with a ServiceContract interface which will be implemented by a ServiceBehavior class.

[ServiceContract()]
public interface IUserService
{
    [OperationContract]
    void AddUser(UserDataContract user);
    [OperationContract]
    void RemoveUser(int userId);
    [OperationContract]
    List<UserDataContract> GetUsers();
    [OperationContract]
    string Echo(string msg);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class UserService : IUserService
{
    List<UserDataContract> users = new List<UserDataContract>();

    public void AddUser(UserDataContract user)
    {
        users.Add(user);
        Console.WriteLine(string.Format("User {0} {1} added", user.FirstName, user.LastName));
    }
    public void RemoveUser(int userId)
    {
        for (int i = 0; i < users.Count; i++)
        {
            if (users[i].Id == userId)
            {
                Console.WriteLine(string.Format("User {0} {1} removed", users[i].FirstName, users[i].LastName));
                users.Remove(users[i]);
                return;
            }
        }
        Console.WriteLine(string.Format("User {0} does not exist", userId));
    }
    public List<UserDataContract> GetUsers()
    {
        Console.WriteLine(string.Format("User list returned. {0} users", users.Count));
        return users;
    }
    public string Echo(string msg)
    {
        Console.WriteLine("Invoked with " + msg);
        return "Haaalo, " + msg;
    }
}

Here the service InstanceContextMode is declared as InstanceContextMode.Single to have a singleton behavior.
Please click here for more details for InstanceContextMode in msdn.

Now a DataContract can be created which is the agreement between service and client that abstractly describes the data to be exchanged.

[DataContract]
public class UserDataContract
{
    int id;
    string firstName;
    string lastName;

    [DataMember]
    public int Id
    {
        get { return id; }
        set { id = value; }
    }

    [DataMember]
    public string FirstName
    {
        get { return firstName; }
        set { firstName = value; }
    }

    [DataMember]
    public string LastName
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

WCF Service Host
A Service Host is needed to host the service just created. A simple console application is used as a host here. The below configuration specifies the end points exposed by the service. Different endpoint can be created for different binding.

<configuration>
    <system.serviceModel>
        <services>
            <service name="UserServiceLibrary.UserService">
                <endpoint address="http://localhost:8080/userservice/svc" binding="basicHttpBinding"
                    bindingConfiguration="" contract="UserServiceLibrary.IUserService" />
    <endpoint address="net.tcp://localhost:8081/userservice/svc" binding="netTcpBinding"
                    bindingConfiguration="" contract="UserServiceLibrary.IUserService" />
            </service>
        </services>
    </system.serviceModel>
</configuration>

class Program
{
    static void Main(string[] args)
    {
        using (ServiceHost host = new ServiceHost(
        typeof(UserService), new Uri("http://localhost:8085/userservice")))
        {
            host.Open();

            Console.WriteLine("{0} is open and has the following endpoints:\n",
                host.Description.ServiceType);

            int i = 1;
            foreach (ServiceEndpoint end in host.Description.Endpoints)
            {
                Console.WriteLine("Endpoint #{0}", i++);
                Console.WriteLine("Address: {0}", end.Address.Uri.AbsoluteUri);
                Console.WriteLine("Binding: {0}", end.Binding.Name);
                Console.WriteLine("Contract: {0}\n", end.Contract.Name);
            }
            Console.ReadLine();
        }
    }
}

WCF Win Client
An interactive win form application is used as the client. The below configuration defines the named endpoint configurations used by the ChannelFactory. These addresses should match with the addresses specified earlier with the host configuration.

<configuration>
 <system.serviceModel>
  <client>
   <endpoint name="usHttpEndpoint"
     address="http://localhost:8080/userservice/svc"
     binding="basicHttpBinding"
     contract="UserServiceLibrary.IUserService"/>
   <endpoint name="usTcpEndpoint"
     address="net.tcp://localhost:8081/userservice/svc"
     binding="netTcpBinding"
     contract="UserServiceLibrary.IUserService"/>
  </client>
 </system.serviceModel>
</configuration>

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

    private void btnAdd_Click(object sender, EventArgs e)
    {
        try
        {
            using (ChannelFactory<IUserService> httpFactory = new ChannelFactory<IUserService>("usHttpEndpoint"))
            {
                IUserService svc = httpFactory.CreateChannel();
                UserDataContract user = new UserDataContract();
                user.Id = int.Parse(userId.Text);
                user.FirstName = firstName.Text;
                user.LastName = lastName.Text;
                svc.AddUser(user);
            }
        }
        catch (Exception ex)
        {
            output.Text = ex.Message;
        }
    }

    private void btnDelete_Click(object sender, EventArgs e)
    {
        try
        {
            using (ChannelFactory<IUserService> httpFactory = new ChannelFactory<IUserService>("usHttpEndpoint"))
            {
                IUserService svc = httpFactory.CreateChannel();
                svc.RemoveUser(int.Parse(userId.Text));
            }
        }
        catch (Exception ex)
        {
            output.Text = ex.Message;
        }
    }

    private void btnShowAll_Click(object sender, EventArgs e)
    {
        try
        {
            using (ChannelFactory<IUserService> httpFactory = new ChannelFactory<IUserService>("usHttpEndpoint"))
            {
                IUserService svc = httpFactory.CreateChannel();
                List<UserDataContract> users = svc.GetUsers();
                StringBuilder sb = new StringBuilder();
                foreach (UserDataContract user in users)
                {
                    sb.AppendFormat("{0} {1}/{2}\r\n", user.FirstName, user.LastName, user.Id);
                }
                output.Text = sb.ToString();
            }
        }
        catch (Exception ex)
        {
            output.Text = ex.Message;
        }
    }
}

Below is a snapshot of the client form where in we can add, delete and list the users.


Now the service is ready to be tested. Start the host console application and once ready, start the client to communicate with the service.

Output
After adding 2 users and retrieving the list of users the output will look as below.