Search code examples
c#asp.net-mvcdependency-injectionhttpcontext

Access HttpContext in constructor for fake DI


I am working on an asp.net mvc application that does not have DI or unit testing yet. So I started to restructure the application to have unit tests by spliting the application into 3 layers: Controllers - Services - DataAccess.

Some of the controllers were using the Session and the Cookies to store and retrieve values. So I create an interface and a class that deals with saving and retrieving values from Session and Cookies.

I did this only by using unit testing and never run the application.

Since the application did not had DI I created on the contructor of the controller the ContextService by giving as an input parameter the HttpContext of the Controller.

However when I run the application the values were not retrieved or saved in the Session or Cookies. It seems that the HttpContext is null on contructor.

Question 1: How should I deal with my ContextService. Should it use the static property HttpContext.Current in order to access the session and cookies (how will it be unit tested) or ...?

Question 2: If you know another solution how should it be adapt in order to have also DI in the future.


Solution

  • I created on the contructor of the controller the ContextService by giving as an input parameter the HttpContext of the Controller.

    By passing the HttpContext from the controller to the service, you make the controller responsible of the creation of that service. This tightly couples the controller with the service, while loose coupling is the goal.

    hould it use the static property HttpContext.Current in order to access the session and cookies

    how will it be unit tested

    It won't. This is an important reason why we create abstractions. Some parts in our system can't be unit tested and we want to be able to replace them with fake implementations that we use under test.

    The trick, however, is to make the replaced part as small as possible, and preferably don't mix it with business logic, since replacing that will also mean you won't be testing that logic.

    You should hide access to the HttpContext.Current behind an abstraction. But when you do that, make sure that you define the abstraction in a way that suits your application best. For instance, take a close look at what it is that your ContextService wants. Does it really want to access cookies? Probably not. Or does it want to the name or ID of the currently logged in user? That's more likely. So you should model your abstractions around that.

    As an example, define an abstraction that allows application code to access information about the logged in user using a IUserContext:

    public interface IUserContext
    {
        string UserName { get; }
    }
    

    One possible implementation of this abstraction is one that retrieves this information from an HTTP cookie:

    public class CookieUserContext : IUserContext
    {
        public string UserName => HttpContext.Current.Cookies["name"];
    }
    

    But you can easily imagine other implementations, for instance when that same application code needs to run outside the context of a web request, for instance as part of a background operation, or an isolated Windows service application. This is another important reason to introduce abstractions—whenever the same code needs to be able to run in different environments.

    If you're interested, the book Dependency Injection in .NET, by Mark Seemann, goes into great detail about these kinds of patterns and principles, such as reasons for applying DI, preventing tight coupling. The second edition of this book, by Seemann and myself, even goes into more detail about the things you are struggling with, such as preventing leaky abstractions, how to separate behavior into classes, and designing applications using the SOLID principles. The book's homepage contains a download link for the first chapter, which is free to download.