Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Handle Session in ASP.NET MVC

0.00/5 (No votes)
9 Jun 2015 2  
Three different approaches to handle Session access in MVC applications.

Introduction

The proposed solutions solve the problem of accessing Session in controller and therefore allowing testing it.

Background

I have faced the issue with handling Session access in ASP.NET MVC that allows me to write unit test against controller logic. As I am TDD and IOC/DI focused, my first choice was just inject proper dependency. By going through "Pro ASP.NET MVC 5" by Adam Freeman, I have learnt another interesting approach.

Standard Solution

In the moment, I wanted to access Session in ASP.NET I have just called the session of current http request and have the value. Simple and clean. Let's assume that we have standard ASP.NET MVC application. Nothing fancy in it. We have decided to store user preferences regarding page background color and font style. Those data will be stored in the current session.

This is our model:

    public class UserSettingsModel
    {
        public string FontName { get; set; }
 
        public Color Background { get; set; }
    }

and UserSettingsController :

    public class UserSettingsController : Controller
    {
            public ActionResult Index()
            {
                var service = GetService();
                var model = service.GetSettings();
                return View();
            }
 
            public ActionResult Save(UserSettingsModel model)
            {
                var service = GetService();
                service.Update(model);
                return View();
            }
 
            private UserSettingsService GetService()
            {
                var service = (UserSettingsService)Session["Settings"];
                if (service == null)
                {
                    service = new UserSettingsService();
                    Session["Settings"] = service;
                }
                return service;
            } 
        }

In Session, we store our UserSettingsService class:

        public class UserSettingsService
        {
            private string _fontName = "Arial";
            private Color _background = Color.White;
 
            public void Update(UserSettingsModel model)
            {
                _fontName = model.FontName;
                _background = model.Background;
            }
 
            public UserSettingsModel GetSettings()
            {
                var model = new UserSettingsModel()
                {
                    FontName = _fontName,
                    Background = _background
                };
                return model;
            }
        }

Obviously, writing any unit test against Controller will involve struggle with mocking HttpContext. We just need to get rid of dependency from Controller. We can do this in two ways. First, by using Dependency Injection resolved by container. Second introducing Dependency Injection by Model Binder.

Model Binder Solution

In this approach, we force Models Binder to deliver UserSettingsService on Controller level as Action parameter. To do this, we have to implement IModelBinder in our new UserSettingsServiceModelBinder class.

    public class UserSettingsServiceModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            UserSettingsService service = null;
            if (controllerContext.HttpContext.Session != null)
            {
                service = (UserSettingsService)controllerContext.HttpContext.Session["Settings"];
            }
 
            if (service == null && controllerContext.HttpContext.Session != null)
            {
                service = new UserSettingsService();
                controllerContext.HttpContext.Session["Settings"] = service;
            }
            return service;
        }
    }

To make it work, we also have to register it in Global.asax.cs to let it know our application, that if any action will need to bind request to model of type UserSettingsService, just use UserSettingsServiceModelBinder class to handle this:

        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
 
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
 
            ModelBinders.Binders.Add(typeof(UserSettingsService), new UserSettingsServiceModelBinder());
 
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            AuthConfig.RegisterAuth();
        }

Now, we can get rid of direct call to session in actions in UserSettingsController and this way making them testable and not dependent of HttpContext.

   public class UserSettingsController : Controller
     {  
        public ActionResult Index(UserSettingsService service)
        {
            var model = service.GetSettings();
            return View();
        }
 
        public ActionResult Save(UserSettingsService service, UserSettingsModel model)
        {
            service.Update(model);
            return View();
        }
    }

UserSettingsService can be mocked now action methods can be tested in isolation.

Dependency Injection Solution

More convenient for me is injecting dependency into UserSettingsController constructor as below:

    public class UserSettingsController : Controller
    {
        private readonly IUserSettingsService _service;
 
        public UserSettingsController(IUserSettingsService service)
        {
            Contract.Requires(service == null, "User settings service must be injected.");
            _service = service;
        }
 
        public ActionResult Index()
        {
            var model = _service.GetSettings();
            return View();
        }
 
        public ActionResult Save(UserSettingsModel model)
        {
            _service.Update(model);
            return View();
        }
    }
    public interface IUserSettingsService
    {
        UserSettingsModel GetSettings();
 
        void Update(UserSettingsModel model);
    } 

Also, we are not storing in Session entire service class but just model.

 public class UserSettingsService : IUserSettingsService
    {
        private readonly UserSettingsModel _settings;
 
        public UserSettingsService()
        {
            var settings = (UserSettingsModel)HttpContext.Current.Session["Settings"];
            if (settings == null)
            {
                settings = new UserSettingsModel();
                HttpContext.Current.Session["Settings"] = settings;
            }
            _settings = settings;
        }
 
        public UserSettingsModel GetSettings()
        {
            return _settings;
        }
 
        internal void Update(UserSettingsModel model)
        {
            _settings.Background = model.Background;
            _settings.FontName = model.FontName;
        }
    }

Of course, we have to register IUserSettingsService in Dependency Injection container of our choice, but this will not be covered by this tip.

As we can see, we can perform tests on actions in full isolation and mock service on class declaration. One more advantage of this solution is flexibility. If there is request to store user settings information instead of Session in database, we can replace in Dependency Injection container reference to other UserSettingsService that will access database.

public class UserSettingsService : IUserSettingsService
    {
        private readonly IUserSettingsReporitory _repository;
 
        public UserSettingsService(IUserSettingsReporitory repository)
        {
            Contract.Requires(repository == null, "User settings repository must be injected.");
            _repository = repository;
        }
 
        public UserSettingsModel GetSettings()
        {
            return _repository.GetSettings();
        }
 
        internal void Update(UserSettingsModel model)
        {
            _repository.Update(model.Background, model.FontName);
        }
    }

For the sake of simplicity, implementation of IUserSettingsRepository is omitted.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here