C# 为MVC应用程序模拟HttpRequest和HttpResponse

C# 为MVC应用程序模拟HttpRequest和HttpResponse,c#,asp.net-mvc-2,C#,Asp.net Mvc 2,我目前正在编写一些单元测试,以检查我们所编写的ASP MVC应用程序的功能和正确工作。 在这个MVC应用程序中,我使用了一个特殊的ActionFilterAttribute,它允许在向MVC应用程序发出请求时进行身份验证 此ActionFilterAttribute的代码如下: using System; using System.Security.Authentication; using System.Text; using System.Web.Mvc; using TenForce.Ex

我目前正在编写一些单元测试,以检查我们所编写的ASP MVC应用程序的功能和正确工作。 在这个MVC应用程序中,我使用了一个特殊的ActionFilterAttribute,它允许在向MVC应用程序发出请求时进行身份验证

此ActionFilterAttribute的代码如下:

using System;
using System.Security.Authentication;
using System.Text;
using System.Web.Mvc;
using TenForce.Execution.Framework;
using TenForce.Execution.Api2.Implementation;

namespace TenForce.Execution.Web.Filters
{
     /// <summary>
     /// This class defines a custom Authentication attribute that can be applied on      controllers.
     /// This results in authentication occurring on all actions that are beeing defined in the controller
     /// who implements this filter.
     /// </summary>
     [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
     public class AuthenticationFilter : ActionFilterAttribute
     {
         #region IAuthorizationFilter Members

         /// <summary>
         /// This function get's called by the Mvc framework prior to performing any actions on
         /// the controller. The function will check if a call is authorized by the caller.
    /// The function will extract the username and password from the HTTP headers send by
    /// the caller and will validate these against the database to see if there is a valid
    /// account for the user.
    /// If the user can be found in the database, operations will resume, otherwise the action
    /// is canceled.
    /// </summary>
    /// <param name="filterContext">The context for the filter.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // Call the base operations first.
        base.OnActionExecuting(filterContext);

        // Surround the entire authentication process with a try-catch to prevent errors from
        // breaking the code.
        try
        {
            // Extract the custom authorization header from the HTTP headers.
            string customAuthHeader = Encoding.UTF8.GetString(Convert.FromBase64String(filterContext.RequestContext.HttpContext.Request.Headers["TenForce-Auth"]));

            // Split the header in the subcomponents.
            string[] components = customAuthHeader.Split('|');

            // Check if both components are present.
            if (components.Length >= 2)
            {
                // This header consists of 2 parts, the username and password, seperate by a vertical pipe.
                string username = components[0] ?? string.Empty;
                string password = components[1] ?? string.Empty;
                string databaseId = Authenticator.ConstructDatabaseId(filterContext.HttpContext.Request.RawUrl);

                // Validate the user against the database.
                if (Authenticator.Authenticate(username, password, databaseId))
                {
                    // The request is valid, so add the custom header to inform the request was
                    // authorized.
                    AllowRequest(filterContext);
                    return;
                }

                throw new InvalidCredentialException(@"The provided username & password combination is invalid. Username : " + username);
            }

            // If we reach this point, the authorization request is no longer valid.
            throw new InvalidCredentialException(@"Insufficient parameters supplied for a valid authentication.");
        }
        catch (Exception ex)
        {
            // Log the exception that has occurred.
            Logger.Log(GetType(), ex);

            // Cancel the request, as we could not properly process it.
            CancelRequest(filterContext);
        }
    }

    #endregion

    #region Private Methods

    /// <summary>
    /// Cancels the Athorization and adds the custom tenforce header to the response to
    /// inform the caller that his call has been denied.
    /// </summary>
    /// <param name="authContext">The authorizationContxt that needs to be canceled.</param>        
    private static void CancelRequest(ActionExecutingContext authContext)
    {
        authContext.Result = new HttpUnauthorizedResult();
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"DENIED");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"DENIED");
    }

    /// <summary>
    /// Allows the Authorization and adds the custom tenforce header to the response to
    /// inform the claler that his call has been allowed.
    /// </summary>
    /// <param name="authContext">The authorizationContext that needs to be allowed.</param>
    private static void AllowRequest(ActionExecutingContext authContext)
    {
        authContext.Result = null;
        if (!authContext.RequestContext.HttpContext.Request.ServerVariables[@"SERVER_SOFTWARE"].Contains(@"Microsoft-IIS/7."))
            authContext.HttpContext.Response.AddHeader(@"Tenforce-RAuth", @"OK");
        else
            authContext.HttpContext.Response.Headers.Add(@"Tenforce-RAuth", @"OK");
    }        

    #endregion
    }
}
使用系统;
使用System.Security.Authentication;
使用系统文本;
使用System.Web.Mvc;
使用TenForce.Execution.Framework;
使用TenForce.Execution.Api2.Implementation;
命名空间TenForce.Execution.Web.Filters
{
/// 
///此类定义可应用于控制器的自定义身份验证属性。
///这将导致在控制器中定义的所有操作上发生身份验证
///谁实施这个过滤器。
/// 
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,Inherited=true,AllowMultiple=true)]
公共类AuthenticationFilter:ActionFilterAttribute
{
#区域IAuthorizationFilter成员
/// 
///Mvc框架在对其执行任何操作之前调用此函数
///控制器。该函数将检查调用方是否授权调用。
///该函数将从发送的HTTP头中提取用户名和密码
///调用方,并将根据数据库验证这些,以查看是否存在有效的
///用户的帐户。
///如果可以在数据库中找到该用户,操作将继续,否则操作将停止
///取消了。
/// 
///筛选器的上下文。
公共覆盖无效OnActionExecuting(ActionExecutingContext filterContext)
{
//首先调用基本操作。
base.OnActionExecuting(filterContext);
//使用try-catch环绕整个身份验证过程,以防止出现错误
//破译密码。
尝试
{
//从HTTP头中提取自定义授权头。
string customAuthHeader=Encoding.UTF8.GetString(Convert.FromBase64String(filterContext.RequestContext.HttpContext.Request.Headers[“TenForce Auth”]);
//在子组件中拆分标题。
string[]components=customAuthHeader.Split(“|”);
//检查两个部件是否都存在。
如果(components.Length>=2)
{
//此标题由两部分组成,用户名和密码,由垂直管道分隔。
字符串用户名=组件[0]?字符串.空;
字符串密码=组件[1]??字符串.空;
string databaseId=Authenticator.ConstructDatabaseId(filterContext.HttpContext.Request.RawUrl);
//根据数据库验证用户。
if(Authenticator.Authenticate(用户名、密码、数据库ID))
{
//请求有效,因此添加自定义标头以通知请求已被删除
//授权的。
AllowRequest(filterContext);
返回;
}
抛出新的InvalidCredentialException(@“提供的用户名和密码组合无效。用户名:”+用户名);
}
//如果我们达到这一点,授权请求将不再有效。
抛出新的InvalidCredentialException(@“为有效身份验证提供的参数不足”);
}
捕获(例外情况除外)
{
//记录发生的异常。
Logger.Log(GetType(),ex);
//取消请求,因为我们无法正确处理它。
取消请求(filterContext);
}
}
#端区
#区域私有方法
/// 
///取消Athorization并将自定义tenforce标头添加到对的响应中
///通知打电话的人他的电话被拒绝了。
/// 
///需要取消的AuthorizationContext。
私有静态无效取消请求(ActionExecutingContext authContext)
{
authContext.Result=新的HttpUnauthorizedResult();
如果(!authContext.RequestContext.HttpContext.Request.ServerVariables[@“服务器软件”]包含(@“Microsoft IIS/7”))
authContext.HttpContext.Response.AddHeader(@“Tenforce RAuth”,@“DENIED”);
其他的
authContext.HttpContext.Response.Headers.Add(@“Tenforce RAuth”,@“DENIED”);
}
/// 
///允许授权并将自定义tenforce标头添加到对的响应中
///通知克拉勒他的电话已被允许。
/// 
///需要允许的authorizationContext。
私有静态void AllowRequest(ActionExecutingContext authContext)
{
authContext.Result=null;
如果(!authContext.RequestContext.HttpContext.Request.ServerVariables[@“服务器软件”]包含(@“Microsoft IIS/7”))
authContext.HttpContext.Response.AddHeader(@“Tenforce RAuth”,@“OK”);
其他的
authContext.HttpContext.Response.Headers.Add(@“Tenforce RAuth”,@“OK”);
}        
#端区
}
}
我目前面临的问题是,我似乎无法正确地用响应标题模拟该部分。我编写了一个单元测试,它模拟HttpRequest和HttpResponse对象,并用请求调用属性函数。我可以在IIS7模拟的代码中遵循成功的登录路径分支,因为这取决于属性,但是当我尝试在登录中遵循IIS6分支时,我会遇到空指针异常

我使用以下代码来构造MoQ对象:

/// <summary>
    /// This function is called before running each test and configures the various properties
    /// of the test class so that each test will run with the same settings initialy.
    /// The function will configure the Mock Framework object so that they simulate a proper
    /// web request on the ActionFilter of a Controller.
    /// </summary>
    [SetUp]
    protected void TestSetup()
    {
        // Construct the Mock object required for the test.
        HttpRequest = new Mock<HttpRequestBase>();
        HttpResponse = new Mock<HttpResponseBase>();
        HttpContext = new Mock<HttpContextBase>();
        ActionContext = new Mock<ActionExecutingContext>();
        Filter = new Web.Filters.AuthenticationFilter();

        // Configure the properties to modify the headers, request and response
        // objects starting from the HttpContext base object.
        // Also create the custom header collection and set the test URI.
        ActionContext.SetupGet(c => c.HttpContext).Returns(HttpContext.Object);
        HttpContext.SetupGet(r => r.Request).Returns(HttpRequest.Object);
        HttpContext.SetupGet(r => r.Response).Returns(HttpResponse.Object);
        HttpResponse.SetupGet(x => x.Headers).Returns(new System.Net.WebHeaderCollection());
        HttpRequest.SetupGet(r => r.RawUrl).Returns(@"http://test.tenforce.tst");
    }
//
///在运行每个测试之前调用此函数,并配置各种属性
///使每个测试都以相同的初始设置运行。
///该功能将配置模拟Fra
/// <summary>
    /// <para>This test will call the ActionFilter and perform a standard authorization request against the
    /// database using the credentials of the system administrator account. The test relies on the MoQ
    /// framework to mock several of the key components in the MVC Framework such as the HttpRequest, 
    /// HttpResponse and HttpContext objects.</para>
    /// <para>The test expects the authentication to succeed, and relies on the IIS6 implementation.</para>
    /// </summary>
    [Test, MaxDuration]
    public void SuccessfullAuthenticationOnIis6()
    {
        // Configure the Authentication header of the request, so that a valid authentication
        // can take place. We want valid login credentials when the filter requests the header.
        HttpRequest.SetupGet(r => r.Headers).Returns(new System.Net.WebHeaderCollection { { @"TenForce-Auth", CorrectAuthToken } });
        HttpRequest.SetupGet(r => r.ServerVariables).Returns(
            new System.Collections.Specialized.NameValueCollection { { @"SERVER_SOFTWARE", @"Microsoft-IIS/6.0" } });
        HttpResponse.SetupGet(r => r.Headers).Returns(new System.Collections.Specialized.NameValueCollection());
        HttpResponse.Setup(r => r.AddHeader(@"TenForce-RAuth", @"OK"));

        // Call the action on the filter and check the response.
        Filter.OnActionExecuting(ActionContext.Object);

        // Check the ActionResult to null and that the response header contains the correct value.
        Assert.IsTrue(ActionContext.Object.Result == null);
        Assert.IsTrue(ActionContext.Object.HttpContext.Response.Headers["TenForce-RAuth"].Equals(@"OK"));
    }
HttpResponse.Setup(r => r.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((x,y) => HttpResponse.Object.Headers.Add(x, y));