c# - 为什么 HttpContext 为空?

标签 c# asp.net-mvc unit-testing moq

我刚刚阅读了不止一篇,但没有找到解决我问题的方法。 我已经使用 FrameWork 4.5 创建了 WebApi + MVC 和单元测试项目。 我有这个 HomeController.cs 用这个方法:

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();
        }
    }
}

HomeControllerTest.cs 的代码如下:

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);

        }
    }
}

AuthenticationProvider.cs 类在这里:

/* 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; }
        }


    }

现在的问题是,当我从 [TestMethod] 调试并进入调试方法 (F10) 并在 HomeController.cs 的方法中放置断点时,我看到了这个 System.Web.HttpContext.Current 每次都是空的!有什么问题?我使用最小起订量。

最佳答案

System.Web.HttpContext.Current 由在单元测试期间不存在/不活动的 IIS 填充。因此 null。出于这个原因,不要将您的代码与 HttpContext 紧密耦合。而是将其封装在可以在隔离测试时模拟的抽象背后。

撇开糟糕的设计不谈,对于您的特定设计,您正试图访问 Controller 外部的静态依赖项。如果像当前测试中演示的那样伪造 Controller 的上下文,则访问 Controller 的上下文而不是调用静态上下文。

//... code removed for brevity

var _authProvider = new AuthenticationProvider(this.HttpContext);

//... code removed for brevity

除此之外, Controller 的设计应该重构为明确依赖于抽象而不是具体化。

例如,这里是提供者的抽象

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

    //...other members
}

public class AuthenticationProvider : IAuthenticationProvider {

    //...

}

Controller 应该通过构造函数注入(inject)明确地依赖于此

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();
    }
}

如果使用依赖注入(inject),IAuthenticationProvider 实现应该配置为在运行时使用框架的 DependencyResolver 注入(inject) Controller ,但现在可以在测试 Controller 时替换隔离,以免与框架实现问题耦合。

[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);
    }
}

关于c# - 为什么 HttpContext 为空?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49687708/

相关文章:

java - 如何离线使用 RestTemplate 对类进行单元测试?

java - spring-boot 应用程序中的缓慢单元测试

c# - 双败赛数据结构

c# - LinkedList 和 ArrayList 有什么区别,什么时候使用哪一个?

c# - 连接字符串时如何插入空格 ("")?

.net - $.connection.hub.start().done() 上出现错误 500

c# - 如何用 Spark 返回部分 View ?

unit-testing - 是否可以通过电子邮件发送 JUnit 测试报告?

c# - 为树状结构设计界面

c# - Entity Framework 检查数据库表是否添加了新记录