Cristian Capannini
Cristian Capannini

Reputation: 57

Why HttpContext is null?

I've just read more than one and I don't find any solution for my problem. I've create WebApi + MVC and Unit Test project with FrameWork 4.5. I have this HomeController.cs with this method:

using Net.Personal.Authentication.FormAuthentication; 
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Web;
using System.Web.Mvc;

namespace FormAuthenticationProva.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.Title = "Home Page";

            var guidAccount = "xxxxxxxx-xxxx-xxxx-xxxx-422632e0bd95";

            var userData = new CookieUserData(guidAccount) { GuidAccount = guidAccount };

            HttpContextBase httpContextBase = this.HttpContext;

            AuthenticationProvider _authProvider = new AuthenticationProvider(httpContextBase.ApplicationInstance.Context);

            _authProvider.CheckAuthorizationForUrl("http://pippo");

            return View();
        }
    }
}

And the HomeControllerTest.cs with this code:

using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using FormAuthenticationProva.Controllers;
using Moq;
using System.Web;
using System.Collections.Specialized;
using System.Web.Routing;

namespace FormAuthenticationProva.Tests.Controllers
{
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void Index()
        {   // Disposizione

            var formData = new NameValueCollection { { "id", "test" } };
            var request = new Mock<HttpRequestBase>();
            request.SetupGet(r => r.Form).Returns(formData);
            var context = new Mock<HttpContextBase>();
            context.SetupGet(c => c.Request).Returns(request.Object);

            var controller = new HomeController();
            controller.ControllerContext = new ControllerContext(context.Object, new RouteData(), controller);

            // Azione
            ViewResult result = controller.Index() as ViewResult;

            // Asserzione
            Assert.IsNotNull(result);
            Assert.AreEqual("Home Page", result.ViewBag.Title);

        }
    }
}

And the AuthenticationProvider.cs class is here:

/* AuthenticationProvider.cs code */

    using System;
    using System.Security.Principal;
    using System.Threading;
    using System.Web;
    using System.Web.Script.Serialization;
    using System.Web.Security;

    namespace Net.Personal.Authentication.FormAuthentication
    {
        public class AuthenticationProvider : IFormsAuthentication
        {  
            public AuthContextConfiguration AuthContextConfiguration { get; set; }

            public AuthenticationProvider() {}
            public AuthenticationProvider(HttpContext context , AuthContextConfiguration authContextConfiguration = null)
            {
                AuthContextConfiguration = AuthContextConfiguration ?? new AuthContextConfiguration(context);
            }
            private void SetPrincipal(IPrincipal principal)
            {
                Thread.CurrentPrincipal = principal;
                if (HttpContext.Current != null)
                {
                    HttpContext.Current.User = principal;
                }

            }

            public void SignIn(string userName, bool createPersistentCookie)
            {
                FormsAuthentication.SetAuthCookie(userName, createPersistentCookie);
            }

            public void SignIn(string userName, bool createPersistentCookie, ICookieUserData userData)
            {
                this.SetAuthCookie<ICookieUserData>(userName, true, userData);
                HttpContext.Current.Cache.Insert(userName, userData);
            }


            public int SetAuthCookie<T>( string name, bool rememberMe, T userData)
            {
                /// In order to pickup the settings from config, we create a default cookie and use its values to create a 
                /// new one.

                if (string.IsNullOrEmpty(((ICookieUserData)userData).Name)) ((ICookieUserData)userData).Name = name;
                var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
                var ticket = FormsAuthentication.Decrypt(cookie.Value);

                var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
                    ticket.IsPersistent, new JavaScriptSerializer().Serialize(userData), ticket.CookiePath);
                var encTicket = FormsAuthentication.Encrypt(newTicket);

                /// Use existing cookie. Could create new one but would have to copy settings over...
                cookie.Value = encTicket;

                HttpContext.Current.Response.Cookies.Add(cookie);

                return encTicket.Length;
            }


            public void SignOut()
            {
                FormsAuthentication.SignOut();
            }

            public bool IsAuthorized()
            {
                return HttpContext.Current.User != null &&
                       HttpContext.Current.User.Identity != null &&
                       HttpContext.Current.User.Identity.IsAuthenticated;

            }

            public void SetUserOnApplication_AuthenticateRequest<T>(HttpContext context)
            {
                PrincipalUser principal = null;
                ICookieUserData userData = null;
                // Extract the forms authentication cookie
                string cookieName = FormsAuthentication.FormsCookieName;
                HttpCookie authCookie = context.Request.Cookies[cookieName];


                if (null == authCookie)
                {
                    // There is no authentication cookie.
                    return;
                }

                var nameIdentity = HttpContext.Current.User.Identity.Name;
                if(string.IsNullOrEmpty(nameIdentity)) {
                    return;
                }

                if(HttpContext.Current.Cache[nameIdentity] ==null) { 
                    FormsAuthenticationTicket authTicket = null;
                    try {
                        authTicket = FormsAuthentication.Decrypt(authCookie.Value);
                    }
                    catch (Exception ex) {
                        // Log exception details (omitted for simplicity)
                        return;
                    }

                    if (null == authTicket) {
                        // Cookie failed to decrypt.
                        return;
                    }

                    userData = (ICookieUserData)new JavaScriptSerializer().Deserialize<T>(authTicket.UserData);

                    // When the ticket was created, the UserData property was assigned a
                    // pipe delimited string of role names.
                    string[] roles = authTicket.UserData.Split('|');

                    // Create an Identity object
                    //FormsIdentity id = new FormsIdentity(authTicket);


                    // This principal will flow throughout the request.
                    //PrincipalUser principal = new PrincipalUser(id, roles);

                } else {
                    userData = (ICookieUserData)HttpContext.Current.Cache[nameIdentity];
                }
                principal = new PrincipalUser(userData);




                // Attach the new principal object to the current HttpContext object
                context.User = principal;
            }

            public void CheckAuthorization()
            {
                if (!this.IsAuthorized()) throw new Exception("Access not allowed");
            }

            public void CheckAuthorizationForUrl(string url)
            {
                AuthContextConfiguration.CheckAuthorizationForUrl(url);
                if (AuthContextConfiguration.CheckRequiredAuth() && !this.IsAuthorized()) throw new Exception("Access not allowed");
            }
        }

        public class PrincipalUser : IPrincipal
        {
            private ICookieUserData _userData;
            private GenericIdentity _identity;
            public PrincipalUser(ICookieUserData userData)
            {
                _identity = new GenericIdentity(userData.Name);
                _userData = userData;
            }

            public IIdentity Identity
            {
                get
                {
                    return _identity;
                }
            }

            public ICookieUserData UserData
            {
                get
                {
                    return _userData;
                }
            }

            public bool IsInRole(string role)
            {
                return _userData.Role.Contains(role);
            }


        }


        public interface ICookieUserData
        {
            string Name { get; set; }
            string Role { get; set; }
        }


    }

And now the problem is when I debug from [TestMethod] and I go into method with debugging (F10) and putted breakpoint in the method in HomeController.cs I see this System.Web.HttpContext.Current is every time null! What's the problem? I use Moq.

Upvotes: 3

Views: 13183

Answers (2)

Rodrigo Werlang
Rodrigo Werlang

Reputation: 2176

I will scope my answer only to System.Web.HttpContext.Current. It is set automatically by IIS.

In order to use it you could use Microsoft fakes (https://learn.microsoft.com/en-us/visualstudio/test/isolating-code-under-test-with-microsoft-fakes).

The article above explains how to implement it. You would then have access to Current without getting null error.

Upvotes: 1

Nkosi
Nkosi

Reputation: 247018

System.Web.HttpContext.Current is populated by IIS which is not present/active during unit testing. Hence null. Do not tightly couple your code to HttpContext for that very reason. Instead encapsulate that behind abstractions that can be mocked when testing in isolation.

Bad design aside, for your specific design you are trying to access a static dependency external to the controller. If faking the controller's context as demonstrated in your current test, then access the controller's context instead of calling the static context.

//... code removed for brevity

var _authProvider = new AuthenticationProvider(this.HttpContext);

//... code removed for brevity

With that out of the way, the design of the controller should be refactored to explicitly depend on abstractions instead of concretions.

for example, here is an abstraction for the provider

public interface IAuthenticationProvider : IFormsAuthentication {
    void CheckAuthorizationForUrl(string url);

    //...other members
}

public class AuthenticationProvider : IAuthenticationProvider {

    //...

}

The controller should explicitly depend on this via constructor injection

public class HomeController : Controller {
    private readonly IAuthenticationProvider authProvider;

    public HomeController(IAuthenticationProvider authProvider) {
        this.authProvider = authProvider;
    }

    public ActionResult Index() {
        ViewBag.Title = "Home Page";

        var guidAccount = "xxxxxxxx-xxxx-xxxx-xxxx-422632e0bd95";

        var userData = new CookieUserData(guidAccount) { GuidAccount = guidAccount };

        authProvider.CheckAuthorizationForUrl("http://pippo");

        return View();
    }
}

The IAuthenticationProvider implementation should be configured to be injected into the controller at run time using the framework's DependencyResolver if using dependency injection, but can now be replaced when testing the controller in isolation so as not to be coupled to framework implementation concerns.

[TestClass]
public class HomeControllerTest {
    [TestMethod]
    public void Index(){
        // Disposizione
        var authMock = new Mock<IAuthenticationProvider>();
        var controller = new HomeController(authMock.Object);

        // Azione
        ViewResult result = controller.Index() as ViewResult;

        // Asserzione
        Assert.IsNotNull(result);
        Assert.AreEqual("Home Page", result.ViewBag.Title);
    }
}

Upvotes: 4

Related Questions