August 21, 2018 11:24 by
Peter
Last Friday, while working on a web application based on ASP.Net Core 2.1, I came across a scenario where I had to put some data into memory. While that's not a problem, the catch is that the data has to be unique for each HTTP Session. Consider it as keeping a key that is to be used across different views to display some information related to that particular session.
The only possible solution satisfying my needs was to keep the "key" in session. However, the last point in the code that has access to the key is a simple helper class and not an MVC Controller. And we had no intentions to expose the "key" to our controller. So, the question remains: how do we save the key in session without exposing it to the controller?
Sample Code
In the following code, we have a very basic MVC application with our HomeController. We also have a RequestHandler that takes care of all the background logic making our controller clean and light.
using HttpContextProject.Helpers;
using HttpContextProject.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace HttpContextProject.Controllers
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
public IActionResult About()
{
// handle the request and do something
var requestHandler = new RequestHandler();
requestHandler.HandleAboutRequest();
ViewData["Message"] = "This is our default message for About Page!";
return View();
}
}
}
namespace HttpContextProject.Helpers
{
public class RequestHandler
{
internal void HandleAboutRequest()
{
// do something here
}
}
}
As it can be seen in the above code, we are simply setting a message in the ViewData and rendering it on the view. Nothing fancy so far. Now, let's see how we can set our message in Http Session from RequestHandler and later access it inside the controller.
Using HttpContext in a Helper Class
With .Net Core 2.1 we can not access the HttpContext outside a controller, however, we can use the IHttpContextAccessor to access the current session outside a controller. In order to do so, we need to add the Session and HttpContextAccessor middle-ware to ConfigureServices method of our Startup class as shown in the code below,
using HttpContextProject.Helpers;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace HttpContextProject
{
public class Startup
{
public IConfiguration Configuration { get; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options => {
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSession();
services.AddSingleton<RequestHandler>();
services.AddHttpContextAccessor();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
else {
app.UseExceptionHandler("/Home/Error");
}
app.UseSession();
app.UseMvc(routes => {
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
}
The next thing we need to do is to add a dependency of IHttpContextAccessor in the RequestHandler. This allows us to access the HttpContext inside the request handler. After we have done required processing for the request, we can now set the message in session, using the Session.SetString(key, value) method. Please refer to the code below,
using Microsoft.AspNetCore.Http;
namespace HttpContextProject.Helpers
{
public class RequestHandler
{
IHttpContextAccessor _httpContextAccessor;
public RequestHandler(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
internal void HandleAboutRequest()
{
// handle the request
var message = "The HttpContextAccessor seems to be working!!";
_httpContextAccessor.HttpContext.Session.SetString("message", message);
}
}
}
Now, that we have our RequestHandler all set, it's time to make some changes in the HomeController. Currently, the "new" RequestHandler is inside the action method, which is not a good practice. So, I will decouple the handler from the controller and rather inject it as a dependency in the constructor. Next thing we do is to set the message in ViewData from the session, as shown in the code below,
using HttpContextProject.Helpers;
using HttpContextProject.Models;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
namespace HttpContextProject.Controllers
{
public class HomeController : Controller
{
private readonly RequestHandler _requestHandler;
public HomeController(RequestHandler requestHandler)
{
_requestHandler = requestHandler;
}
public IActionResult About()
{
_requestHandler.HandleAboutRequest();
ViewData["Message"] = HttpContext.Session.GetStringValue("message");
return View();
}
}
}
Note that I'm using Sesseion.GetStringValue(key) which is an extension method that I have added to retrieve data from the session, however, it's not really required. You can simply use the Session.TryGetValue(key, value) as well.
In case you have not figured it out already, I must tell you that we need to register our RequestHandler in the ConfigureServices method of the Startup class so that the dependency for our controller can be resolved.
Summary
With the above changes in place, we can now access the HttpContext.Session inside our request handler and the same can be done for any other class as well. However, there is one thing that I don't like about this approach. For every single component where we need to access the session, we have to inject a dependency of IHttpContextAccessor.
While for one or two components it's not a problem, it can be very daunting if we have to do the same over and over again. There is a way to achieve the same accessibility without having to inject any dependency, but that's a story for another day and I will write about that in my next post.