First the disclaimer. Yes, there are a lot of problems with using session in your ASP.NET applications. Moreover, by default, HTTP (and by extension, REST) is stateless - and as a result each HTTP request should carry enough information by itself for its recipient to process it to be in complete harmony with the stateless nature of HTTP.
So if you are designing a proper API, if you are a REST purist, or if you are Darrel Miller, you definitely do not want to continue reading this article. But if not, and you find yourself in a scenario requiring session - perhaps you are using Web API to facilitate your JS or MVC application, and you want to sync the state of Web API with the state of MVC easily, or you simply want a quick and easy way to persist state on the server side - this article is for you.
More after the jump.
Adding session support with IRequiresSessionState π
Let’s start by exploring how to extend Routes with Session state.
Web API routing, is operating on the same underlying ASP.NET RouteCollection, and therefore similar principles apply. ASP.NET has a concept of IRouteHandler which is a property on a System.Web.Routing.Route class and is responsible for processing HTTP requests for a route. By default, all Web API routes use HttpControllerRouteHandler which doesn’t do much, except handing over the HttpContexBase to HttpControllerHandler.
In order to provide session support, we will need to create 2 custom components:
- IRouteHandler, to replace the default HttpControllerRouteHandler and to tell the ASP.NET pipeline to go to our custom HttpControllerHandler.
- customized HttpControllerHandler, which will mark the route as being session enabled and then go back to the Web API execution pipeline
So what now? That’s surprisingly easy.
In order to enforce session on the IHttpHandler, we simply implement the interface IRequiresSessionState (more about which you can read here). This is a so-called marker interface - so all you need to do is make the handler use it, and you don’t have to implement any methods.
public class SessionControllerHandler : HttpControllerHandler, IRequiresSessionState
{
public SessionControllerHandler(RouteData routeData)
: base(routeData)
{}
}
This class does nothing more than introduce the marker interface, and hands the rest of the work to the default HttpControllerHandler which then continues with Web API execution pipeline.
By the way - this solution is inspired by the original proposal by Imran Baloch, who is doing great job for the ASP.NET community so make sure to follow his blog.
Plugging this in π
So now we have session enabled, but how to plug this in in the first place?
Well, two steps:
- create IRouteHandler that points to our new SessionControllerHandler
- add it to the route when we register routes, so that a given route would use it
public class SessionRouteHandler : IRouteHandler
{
IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext)
{
return new SessionControllerHandler(requestContext.RouteData);
}
}
And now, in the route registration, we add our route handler:
RouteTable.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
).RouteHandler = new SessionRouteHandler();
Note, that I’m registering this against RouteTable.Routes directly, rather than GlobalConfiguration.Configuration.Routes. The reason for this is that the first collection is of type System.Web.Routing.Route (same as MVC) while the latter is of type System.Web.Http.Routing.IHttpRoute, and it does not allow you to set RouteHandler on it. Basically, this is us taking leverage on the fact that I mentioned in the beginning - that Web API routes and MVC routes are ultimately part of the same route collection.
Putting it to use π
Now we are ready to go, and let’s create a simple Session wrapper to test our solution - you can use whatever you want, any session abstraction you like, this is purely for demonstration:
public interface IDataPersistance<T>
{
T ObjectValue { get; set; }
}
public class SessionDataPersistance<T> : IDataPersistance<T>
where T : class
{
private static string key = typeof(T).FullName.ToString();
public T ObjectValue
{
get
{
return HttpContext.Current.Session[key] as T;
}
set
{
HttpContext.Current.Session[key] = value;
}
}
}
Now we can inject this into the controller, and use session in the GET action.
public class TeamsController : ApiController
{
private IDataPersistance<Team> _teamStorage;
private ITeamService _teamService;
public TeamsController()
: this(new SessionDataPersistance<Team>(), new TeamService())
{}
//THIS IS TO AVOID WIRING DI IN THE DEMO
public TeamsController(IDataPersistance<Team> teamStorage, ITeamService service)
{
_teamStorage = teamStorage;
_teamService = service;
}
public Team Get(int id)
{
//GET FROM SESSION
Team t = _teamStorage.ObjectValue;
//IF NULL, SET IN SESSION
if (t == null)
{
t = _teamService.Get(id);
_teamStorage.ObjectValue = t;
}
return t;
}
}
That’s it! Quick, easy and simple. We can test - on the 1st request, the object from session is obviously null.
Then, on the next one, it’s pulled from session:
Summary π
There are probably many reasons why you DON’T want to use session.
At the same time, there are many scenarios when using session is the most viable solution and makes most business sense, especially as you don’t design pure API but rather use Web API to facilitate your MVC app - and you need i.e. a quick way to share artefacts between MVC and Web API.
While it’s not supported out of the box, with this approach you can quickly enable it without any problems.