Introduction
This article demonstrates how to maintain "Http Session State" in "WCF" "REST" Services when consumed from desktop applications using the "HttpWebRequest" object.
Background
"REST" has been a recent popular topic when developing light-weight service-oriented applications. Because of the origination of the "Representational state transfer" theory, it is a natural choice to host the services in a web server, such as an "IIS" server. It is also a natural choice to use the "Http Session State" to keep the state of the services with each individual client. For example, it is ideal to keep the user's log-in information in the web session, so the service client does not need to send the user's credential every time when a call is made. Generally speaking, there are two ways to consume the services:
- "REST" services can be consumed from a web browser in Javascript using "Ajax" calls. The "jQuery" is one of the most popular Javascript libraries that supports "Ajax" calls;
- "REST" services can be also consumed from a desktop application using "Http" client libraries, The "HttpWebRequest" class is a popular choice in the Microsoft world.
To maintain a web session between the service and client, the client needs to send the cookies to the service. When the service is called in the "Ajax" fashion, sending cookies to the service is the web browser's responsibility. But when the service is called from a desktop application, most of the client libraries do not send cookies by default. To maintain the "Http Session State", we need to make some additional effort at the client side.
The purpose of this article is to show you an example on how to maintain the web session when the service is called from desktop applications with a simple extension of the "HttpWebRequest" class.
The attached Visual Studio 2010 solution has three projects:
- The "SharedLibraries" project is a class library. It defines some classes shared by the "REST" service and the client to send data to each other. This project also implements a simple "class factory" to create "HttpWebRequest" objects. The "Http Session State" will be automatically maintained if we use the "HttpWebRequest" objects created by this class factory.
- The "Service" project is an "ASP.Net" project. A simple REST service is implemented here.
- The "Client" project is an "WPF" "MVVM" application. I will show you how to make calls to the REST service in this project. I will make two service calls. In one call, I will use an "HttpWebRequest" object created by the default "WebRequest" class factory. In the other call, I will use an "HttpWebRequest" object created by the class factory implemented in the "SharedLibraries" project. You will see that the session is lost in one call but maintained in the other.
I will first introduce the "SharedLibraries" project to show you the class factory to create the session enabled "
HttpWebRequest" object and the shared classes used by both the service and the client. I will then introduce the "Service" project and the "Client" project to demonstrate how to use the class factory.
The Shared Libraries

The "SharedLibraries" project is a simple class library project. It implements a class factory for us to create session enabled "HttpWebRequest" objects. It also implements some shared classes used by both the REST service and the client to send data to each other. Let us first take a look at the class library implemented in the "CookiedRequestFactory.cs" file.
using System;
using System.Net;
using System.Collections.Generic;
namespace SharedLibraries.ClientUtilities
{
public class CookiedRequestFactory
{
private static Dictionary<string, CookieContainer> containers
= new Dictionary<string, CookieContainer>();
public static HttpWebRequest CreateHttpWebRequest(string url)
{
var request = (HttpWebRequest)WebRequest.Create(url);
string domain = (new Uri(url)).GetLeftPart(UriPartial.Authority);
CookieContainer container;
if (!containers.TryGetValue(domain, out container))
{
container = new CookieContainer();
containers[domain] = container;
}
request.CookieContainer = container;
return request;
}
}
}- The static "factory method" "CreateHttpWebRequest" is for us to create the session enabled "HttpWebRequest" object.
- The static "Dictionary" named "containers" keeps a "CookieContainer" for each domain that the application has sent some web request to. Given an url, say "http://domainb.foo:8080/image.jpg", the domain of the url is identified by "http://domainb.foo:8080", which includes the "port number".
When an "HttpWebRequest" object is created, the factory method looks into the "containers" "Dictionary" to see if there is a corresponding "CookieContainer". If there is one, the "CookieContainer" is associated to the "HttpWebRequest" object. Otherwise, a new "CookieContainer" is created. This "CookieContainer" is then associated to the "HttpWebRequest" object and added into the "Dictionary". By doing this, the "HttpWebRequest" objects created by the factory method always have the same "CookieContainer" when calling the services in the same domain. This "CookieContainer" will be storing all the cookies received from the server. When a REST service call is made, it will also send all the cookies to the service. Upon receiving the cookies, the service can then maintain the "Http Session State" with the client.
Before seeing how to use the "CookiedRequestFactory" class, let us first take a look at the classes shared by the service and the client. The "ServiceResult.cs" file defines the format of the data that the service send to the client:
namespace SharedLibraries.ShareTypes
{
public class ServiceStatus
{
public ServiceStatus()
{
Success = false;
Message = "Service Call failed";
}
public bool Success { get; set; }
public string Message { get; set; }
}
public class ServiceResult<T>
{
public ServiceResult()
{
Status = new ServiceStatus();
}
public ServiceStatus Status { get; set; }
public T Result { get; set; }
}
} - If the service only sends the success/fail status to the client, an instance of the "ServiceStatus" class is use. The "Message" property is a free text field. We can put the detailed descriptions about the service status.
- If the service needs to send some data to the client, an instance of the "ServiceResult" class is used. If the "Status" field indicates the service call is successful, the "Result" field then contains the requested data.
The data types that the service and the client use to change data is defined in the "ServiceTypes.cs" file:
using System;
namespace SharedLibraries.ShareTypes
{
public class AppUserCredentail
{
public string UserName { get; set; }
public string Password { get; set; }
}
public class Student
{
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentTime { get; set; }
public int Score { get; set; }
}
}- The "AppUserCredentail" class is used for the client to send the service the user's access credential. Upon a successful login, the service will keep the user's login status in the web session.
- The "Student" class is used for the server to send data to the client. If the service finds the expected user's login status in the web session, it will send a list of students to the client upon request.
Now we can take a look at how the REST service is created.
The Rest Service
There are a couple of ways to create WCF "REST" services in the Microsoft world. The popular ones include:
- We can create "REST" services using "MVC" controllers. I like this approach and I feel it is a natural choice. If you are interested, you can take a look at this article.
- We can also use the "WCF Web API" and you can find some good tutorials from here and here.
But in this article, I will use a different approach. I learned this approach from
here and I have used it
here. I feel this is a simple way to create "
REST" services and It comes a bonus help page that you will see later. In this project, the service is implemented in the "StudentService.cs" file:
using System;
using System.Collections.Generic;
using System.Web;
using System.ServiceModel;
using System.ServiceModel.Activation;
using System.ServiceModel.Web;
using SharedLibraries.ShareTypes;
namespace Service.Services
{
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode
= AspNetCompatibilityRequirementsMode.Allowed)]
public class StudentService
{
private readonly HttpContext context;
public StudentService()
{
context = HttpContext.Current;
}
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "StudentService/Login")]
public ServiceStatus Login(AppUserCredentail credentail)
{
var status = new ServiceStatus()
{ Success = false, Message = "Wrong user name and/or password" };
if ((credentail.UserName == "user") && (credentail.Password == "password"))
{
status.Success = true;
status.Message = "Login success";
}
context.Session["USERLOGGEDIN"] = status.Success? "YES": null;
return status;
}
[OperationContract]
[WebGet(UriTemplate = "StudentService/GetStudents")]
public ServiceResult<List<Student>> GetStudents()
{
var result = new ServiceResult<List<Student>>();
if ((string) context.Session["USERLOGGEDIN"] != "YES")
{
result.Status.Success = false;
result.Status.Message = "Not logged in or session is over";
return result;
}
var students = new List<Student>();
var rand = new Random();
for (int i = 1; i <= 20; i++)
{
var student = new Student();
student.Id = i;
student.LastName = "LName - " + i.ToString();
student.FirstName = "FName - " + i.ToString();
student.EnrollmentTime = DateTime.Now.AddYears(-4);
student.Score = 60 + (int)(rand.NextDouble() * 40);
students.Add(student);
}
result.Result = students;
result.Status.Success = true;
result.Status.Message = "Success";
return result;
}
}
}
The "StudentService" class implements two "OperationContracts":
- The "OperationContract" "Login" takes the user access credential to authorize the user to access the service. For simplicity, we have only one user whose user name/password pair is "user/password". Upon a successful login, the login status is saved in the web session and the client is informed if the user is authorized to access the service.
- The "OperationContract" "GetStudents" checks the user's login status in the web session. If the user is logged in, it will send a list of randomly generated students to the client. If no login information is found, the "Status" field will be marked as fail to tell the client that the user needs to login to make the service call.
To make this WCF REST Service work, we will need to make changes to the "Global.asax.cs" file and the "Web.config" file. We will need to add the following code to the "Application_Start" event in "Global.asax.cs":
void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.Add(new ServiceRoute("",
new WebServiceHostFactory(),
typeof(StudentService)));
} We will also need to add the following configuration into the "Web.config" file:
<system.serviceModel>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true"
multipleSiteBindingsEnabled="true" />
<standardEndpoints>
<webHttpEndpoint>
<standardEndpoint name="" helpEnabled="true"
automaticFormatSelectionEnabled="true" />
</webHttpEndpoint>
</standardEndpoints>
</system.serviceModel> Yes, as simple as it is. Without an "svc" file and without the "Endpoint" configuration, the WCF REST Service is completed and works. If you launch this "ASP.Net" application and type in the url "http://localhost:2742/help" in the web browser, you can see the help page of this "REST" service.
If you click the links on the help page, you can find the detailed instructions on how to consume this service. Most importantly, the urls and methods to access the two "OperationContracts" in the debug mode are the following:
- "Login" - "http://localhost:2742/StudentService/Login", "POST"
- "GetStudents" - "http://localhost:2742/StudentService/GetStudents", "GET".
We now finish the simple "
REST" service, let us take a look at the client.
The Service Client
The "Client" project is a "WPF" "MVVM" application. I am not going to the details on how the "MVVM" is implemented. If you are interested, you can download the attached solution and take a look at it yourself. I made some effort to make sure this application follows the "MVVM" practices to "separate the concerns". In this application, the service is called in the "StudentServiceProxy.cs" file:
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Script.Serialization;
using SharedLibraries.ClientUtilities;
using SharedLibraries.ShareTypes;
namespace Client.ClientProxies
{
public static class StudentServiceProxy
{
private static string LoginUrl;
private static string GetStudentsUrl;
static StudentServiceProxy()
{
LoginUrl = ConfigurationManager.AppSettings["LoginUrl"];
GetStudentsUrl = ConfigurationManager.AppSettings["GetStudentsUrl"];
}
public static ServiceStatus Login(AppUserCredentail credentail)
{
var serializer = new JavaScriptSerializer();
var jsonRequestString = serializer.Serialize(credentail);
var bytes = Encoding.UTF8.GetBytes(jsonRequestString);
var request = CookiedRequestFactory.CreateHttpWebRequest(LoginUrl);
request.Method = "POST";
request.ContentType = "application/json";
request.Accept = "application/json";
var postStream = request.GetRequestStream();
postStream.Write(bytes, 0, bytes.Length);
postStream.Close();
var response = (HttpWebResponse) request.GetResponse();
var reader = new StreamReader(response.GetResponseStream());
var jsonResponseString = reader.ReadToEnd();
reader.Close();
response.Close();
return serializer.Deserialize<ServiceStatus>(jsonResponseString);
}
public static ServiceResult<List<Student>> GetStudentsWithCookie()
{
var request = CookiedRequestFactory.CreateHttpWebRequest(GetStudentsUrl);
return GetStudents(request);
}
public static ServiceResult<List<Student>> GetStudentsWithoutCookie()
{
var request = (HttpWebRequest)WebRequest.Create(GetStudentsUrl);
return GetStudents(request);
}
private static ServiceResult<List<Student>> GetStudents(HttpWebRequest request)
{
request.Method = "GET";
request.Accept = "application/json";
var response = (HttpWebResponse)request.GetResponse();
var reader = new StreamReader(response.GetResponseStream());
var jsonResponseString = reader.ReadToEnd();
reader.Close();
response.Close();
var serializer = new JavaScriptSerializer();
return serializer.Deserialize<ServiceResult<List<Student>>>(jsonResponseString);
}
}
}In this "StudentServiceProxy" class, I made three calls to the "REST" service.
- In the "Login" method, a "POST" service call is made to the
"OperationContract" "Login". In this call the "HttpWebRequest" object is created by the "CookiedRequestFactory" class factory.
- In the "GetStudentsWithCookie" method, a "GET" service call is made to the "OperationContract" "GetStudents". the "HttpWebRequest" object used in this call is created by the "CookiedRequestFactory" class factory.
- The "GetStudentsWithoutCookie" method also makes a "GET" service call to the
"OperationContract" "GetStudents". The difference is that the "HttpWebRequest" object is directly created by the "WebRequest" class.
For simplicity reasons, all the service calls are implemented in "synchronous" fashion. The urls of the services are kept in the "App.config" file in the "appSettings" section:
<appSettings>
<add key="LoginUrl" value="http://localhost:2742/StudentService/Login"/>
<add key="GetStudentsUrl" value="http://localhost:2742/StudentService/GetStudents"/>
</appSettings> These methods are triggered by the application UI through the view model of the application. To save some space for "CodeProject", I want to skip the view model. If you are interested, you can download the attachment and take a look at it yourself. The UI controls are implemented in the following "XAML" section in the "MainWindow.xaml" file:
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Command="{Binding Path=GetStudentWithCookieCommand}">
Get Students with Cookie</Button>
<Button Grid.Column="1" Command="{Binding Path=GetStudentsNoCookieCommand}">
Get Students without Cookie</Button>
</Grid>
<DataGrid Grid.Row="1" Margin="0, 5, 0, 0"
ItemsSource="{Binding Path=Students, Mode=OneWay}">
</DataGrid>
</Grid>
<!---->
<Grid Visibility="{Binding Path=LoginVisibility}">
<Rectangle Fill="Black" Opacity="0.08" />
<Border BorderBrush="blue"
BorderThickness="1" CornerRadius="10"
Background="White"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid Margin="10,10,10,25">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
<ColumnDefinition Width="110" />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Margin="0,0,0,10"
FontWeight="Bold" Foreground="Gray"
Grid.ColumnSpan="3">
Please login to the application
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0">User Name</TextBlock>
<TextBox Grid.Row="1" Grid.Column="1" Width="100"
Text="{Binding Path=UserCredentail.UserName, Mode=TwoWay}" />
<TextBlock Margin="0,5,0,0" Grid.Row="2" Grid.Column="0">Password</TextBlock>
<PasswordBox Margin="0,5,0,0" Grid.Row="2" Grid.Column="1" Width="100"
ff:PasswordBoxAssistant.BindPassword="true"
ff:PasswordBoxAssistant.BoundPassword
="{Binding Path=UserCredentail.Password, Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"/>
<Button Margin="5,5,0,0" Content="Login" Grid.Row="2" Grid.Column="2" Width="80"
Command="{Binding Path=LoginCommand}"/>
</Grid>
</Border>
</Grid>
- The "XAML" code in the "Login Section" has the text inputs for the user name and the password. It also has a button to trigger the "Login" service call.
- There are two additional buttons in the code. Both of them will trigger the calls to the
"OperationContract" "GetStudents". One of them uses the "CookiedRequestFactory" class to create the "HttpWebRequest" object and the other uses the "WebRequest" class.
We now complete the demo application, we can run it in the Visual Studio in the debug mode.
Run the Example Application
If you set the "Client" project as the start up project, you can debug run the application. When the client application first launches, you can see a pop-up window asking you to login to the service.
If you put in the user name/password pair as "user/password" and click on the "Login" button, you can login to the service. Upon a successful login, you can click on the "Get Students with Cookie" button, you can see a list of students are successfully received from the service.
If you click on the "Get Students without Cookie", you can see that we are unable to receive the requested student list, and a message pop-up shows to tell us that we are not logged in.
This example shows us that we need to use the "CreateHttpWebRequest" factory class to create the "HttpWebRequest" object, if we want to maintain the web session with the "REST" service.
Points of Interest
- This article demonstrated how to maintain "Http Session State" in "WCF" "REST" Services when consumed from desktop applications using the "HttpWebRequest" object.
- Although I have only tested the "CookiedRequestFactory" against one way to implement "REST" Services, I believe it should be applicable to all possible "REST" implementations, since the "HttpWebRequest" object is a general purpose Http client library. If you encounter any problems with other types of "REST" implementations, please let me know. I feel we should meet your requirements with minimal modifications to the "CookiedRequestFactory" class.
- I did not expect that I would write this article so long and I hope you are not bored. If you do get bored, you can simply take a look at the "CookiedRequestFactory" class and try to use it yourself. It should be very simple to use.
- I hope you like my postings and I hope this article can help you one way or the other.
History
First revision - 2/2/2012.