65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WebDev.WebServer、NUnit 和 Salient.Web.HttpLib 测试 Http 端点

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2010年4月16日

GPL3

9分钟阅读

viewsIcon

18768

保持理智!快速轻松地为您的 Ajax/REST/表单/上传/其他 http 端点编写测试套件。

保持理智!快速轻松地为您的 Ajax/REST/表单/上传/其他 http 端点编写测试套件。

在此处下载 HttpLib 和本教程的源代码:Salient.Web.HttpLib.b1.zip

概述

在本文中,我将向您介绍一个小型库,名为 Salient.Web.HttpLib,它主要设计用于支持 Http 端点的测试。

特别关注测试 MS Ajax/WCF/WebServices 端点的问题,测试的主要重点将集中在 Ajax 启用的 WCF 服务、用 [ScriptService][ScriptMethod] 属性装饰的静态 .aspx PageMethods 和 Xml WebServices 上,它们都表现出相同的行为,并将在此后统称为“JSON 端点”。

其他端点/平台可以轻松处理,并且在遵循本教程之后,将上述模式应用于其他目标将是轻而易举的事。

本教程将通过详细描述随附 VS2008 解决方案中包含的实际通过的测试来完成。

本教程测试包含在 ArticleSiteFixture 中,该类继承自 Salient.Web.HttpLib.WebDevFixture

此测试夹具包含模拟客户端脚本预期请求类型的方法。这允许从客户端脚本的角度自动测试我们的端点。

注意:请注意:本着我们正在模拟的 JavaScript 客户端的精神,我将肆无忌惮地滥用闭包、匿名方法和类型。;-)

您可以称之为功能测试、冒烟测试,甚至集成测试,但不要称之为单元测试。

演练

Salient.Web.HttpLib.WebDevFixture

WebDevFixture 封装了 Visual Studio 开发服务器的可控启动。要启动 WebDev 实例,只需提供一个未使用的端口和站点的物理路径。这可以是一个相对路径,从而实现便携式测试。

WebDevFixture 提供了一个便捷方法 .NormalizeUri(),它可以将相对路径和查询字符串规范化或根植到当前站点的根目录。

有关更多信息,请参阅 https://codeproject.org.cn/Articles/72222/A-general-purpose-Visual-Studio-2008-Development-S.aspx

有关更精细地控制测试服务器,您应该查看 http://cassinidev.codeplex.com/

此示例演示了 NUnit 与 WebDevFixture 的用法,但任何测试框架都可以轻松使用。

 

ArticleSiteFixture

[TestFixture]
public class ArticleSiteFixture : WebDevFixture
{
    protected override int Port
    {
        get { return 12334; }
    }

    [TestFixtureSetUp]
    public void TestFixtureSetUp()
    {
        // the relative physical path of the site we want to test
        var path = Path.GetFullPath(@"..\..\..\HttpLibArticleSite");

        StartServer(path);
    }

    /*
     * 
     *  insert interesting tests go here
     * 
     */

}

 

反序列化 MS Ajax 包装的 JSON

JSON 端点响应的默认格式是“d: 包装”对象。有一些配置选项和属性可以修改此行为,但此默认行为也是最坏的情况,因此我们立即处理它。

我们将处理的一个示例是演示站点中定义的简单 Result 类。当返回为 JSON 时,它看起来像这样:

{"d":{"__type":"Result:#HttpLibArticleSite","Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}}

反序列化此格式的 JSON 可能会有问题,但我为 JavaScriptSerializer 提供了一个扩展方法,该方法可以处理此类型的 JSON。您可以在此处详细了解它:Parsing-ClientScript-JSON.aspx

简而言之,ClientScriptUtilities 提供了一个 JavaScriptSerializer 扩展方法 .CleanAndDeserialize<T>(),该方法将提取内部对象并清理 "__type" 属性,然后对其进行反序列化。最终反序列化的 JSON 如下所示:

{"Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}

CleanAndDeserialize 也支持反序列化为匿名类型。

注意:CleanAndDeserialize 不需要丑陋的 MsAjax JSON。它可以处理任何有效的 JSON,因此无论 JSON 格式如何,它都可以安全地替换您代码中的 .Deserialize<T>

  [Test]
  public void Deserializing_MS_Ajax_Wrapped_JSON()
  {
      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();


      // simulate a typical JSON response
      const string responseText =
          "{\"d\":{\"__type\":\"Result:#HttpLibArticleSite\",\"Message\":\"Value pull OK.\",\"Session\":\"rmyykw45zbkxxxzdun0juyfr\",\"Value\":\"foo\"}}";

      Result result = jsSerializer.CleanAndDeserialize<Result>(responseText);

      Assert.AreEqual("rmyykw45zbkxxxzdun0juyfr", result.Session);

      var resultPrototype = new
          {
              Message = default(string),
              Value = default(string)

          };

      var anonymousResultSubset = jsSerializer.CleanAndDeserialize(responseText, resultPrototype);

      Assert.AreEqual("foo", anonymousResultSubset.Value);

  }

异常处理

当任何类型的端点抛出异常时,它最终都会被一个通用的“500 Internal Server Error”WebException 吞没。

发现实际底层异常可能非常繁琐,涉及解析 WebException.Response.GetResponseStream() 并确定它是来自 JSON 端点的 JsonFaultDetail,还是 ASP.Net 页面崩溃(Yellow Screen of death),还是其他多种格式。

WebRequestException 封装了所有这些行为,并在可用时显示相关信息。原始 WebException 可在 InnerException 属性上找到。

  [Test]
  public void Exception_Handling()
  {

      try
      {
          RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")).GetResponse();
          Assert.Fail("Expected WebException");
      }
      catch (WebException ex)
      {
          WebRequestException ex2 = WebRequestException.Create(ex);
          Console.WriteLine("Caught {0}: {1}", ex2.ExceptionDetail.ExceptionType, ex2.Message);
      }
  }

RequestFactory.CreateRequest 

RequestFactory.CreateRequest 是所有专用工厂方法重载最终调用的基础方法。

通过对该方法的基本理解,所有专用重载都将易于理解。

RequestFactory.CreateRequest 构建一个 HttpWebRequest,适当地应用提供的参数并返回它。然后,您可以随意操作请求,然后再执行请求。此模式应用于所有各种重载。

参数

  • requestUri - Uri 或 String
    资源的绝对 URI
  • method - HttpMethod
    请求的 http 方法。当前支持的选项有 HttpMethod.PostHttpMethod.Get
  • contentType - ContentType
    请求的内容类型。当前支持的选项有 ContentType.NoneContentType.ApplicationFormContentType.ApplicationJsonContentType.TextJson
  • postData - object
    可选。一个 NameValueCollectionobject。匿名类型是可接受的。

    对象将被适当地序列化以适应请求方法和内容类型,然后再应用于请求。

    注意:任何形状与目标方法参数相似的对象都可以接受。方便的是,这包括匿名类型。

    对于“Get”或表单“Post”,NameValueCollection 可能更合适,因为它能够接受单个键的多个值,从而完全模拟表单的可能形状。

    在创建匿名类型作为输入参数时,您不必完全原型化目标类型。您必须原型化非可空属性,包括值类型和结构,但可以省略任何可空属性,包括您不需要发送的 Nullable<T> 和引用类型。

    适合方法 public Result PutSessionVar(string input) 的 postData 的匿名类型将是 var postData = new { input = "foo" };

    适合方法 public Result PutSessionVar(Result input) 的 postData 的匿名类型将是
    var postData = new { input = new Result() };
    var postData = new { input = new { Message = "message", Session = "session", Value = "value" } };
    var postData = new { input = new { Message = "message" } };
    • 对于表单“Post”请求,postData 对象将被序列化为 URL 编码的键值字符串,并流式传输到请求正文中。
    • 对于 JSON“Post”,它将被 JSON 序列化并流式传输到请求正文中。
    • 对于“Get”请求,它将被 URL 编码成一个查询字符串,并智能地附加到 Uri。如果 Uri 是裸露的,查询字符串将用“?”附加。如果 Uri 已经有查询字符串,则新查询将用“&”附加。
  • cookies - CookieCollection
    可选。在请求之间共享 CookieCollection 对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
  • headers - NameValueCollection
    可选。要添加到请求的 Http 标头。

StreamExtensions 提供了两个简单的便捷扩展,可与响应流一起使用。

  • Stream.TextStream.Text() 将流提取为 UTF-8 字符串。
  • Stream.Bytes() 将流提取为字节数组。
  [Test]
  public void Using_CreateRequest()
  {

      // Required arguments:
      Uri uri = NormalizeUri("AjaxService.svc/PutSessionVar");
      const HttpMethod method = HttpMethod.Post;
      const ContentType contentType = ContentType.ApplicationJson;
      // Optional arguments:


      // This is the method we will be calling - public Result PutSessionVar(string input)
      // Lets create an anonymous type shaped like the method signature...
      var postData = new { input = "foo" };

      CookieContainer cookies = new CookieContainer();

      // Add a useful header...
      NameValueCollection headers = new NameValueCollection { { "x-foo-header", "bar-value" } };

      // With the arguments defined, create the request
      HttpWebRequest request = RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);

      // If you are not concerned with immediate disposal of the response stream, 
      // you can retrieve the response with a single expression like so
      var responseText = request.GetResponse().GetResponseStream().Text();


      // thats it for the basic usage.





      // we are going to pick up the pace a bit and demonstrate a few usage patterns so lets declare an anonymous 
      // inline factory method to create our requests from this point on.

      Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);


      using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
      {
          var rtext = responseStream.Text();
      }


      // It is recommended that you wrap requests using the exception handling pattern previously described
      // even if you are just going to re-throw

      try
      {
          using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
          {
              var rtext = responseStream.Text();
          }
      }
      catch (WebException ex)
      {
          WebRequestException wrex = WebRequestException.Create(ex);
          // Now either try to handle it or re-throw

          throw wrex;
          // we reset the stack trace but a WebRequestException has but one source so this is not an issue.
      }


      // Now if we throw the deserialization into the mix things start coming together.

      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();

      try
      {
          using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
          {
              Result result1 = jsSerializer.CleanAndDeserialize<Result>(responseStream.Text());
              Assert.AreEqual("foo", result1.Value);
          }
      }
      catch (WebException ex)
      {
          WebRequestException wrex = WebRequestException.Create(ex);
          throw wrex;
      }




      // Using an anonymous prototype for the response

      try
      {
          using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
          {
              var resultPrototype = new
                  {
                      Value = default(string),
                      Session = default(string)
                  };

              var result2 = jsSerializer.CleanAndDeserialize(responseStream.Text(), resultPrototype);

              Assert.AreEqual("foo", result2.Value);
          }
      }
      catch (WebException ex)
      {
          WebRequestException wrex = WebRequestException.Create(ex);
          throw wrex;
      }




      // For those who like to live dangerously the whole process can be executed in one statement 
      // Do not try this at home. ;-)

      CookieContainer existingCookies = new CookieContainer();

      var result3 = jsSerializer.CleanAndDeserialize(
          RequestFactory.CreateRequest(NormalizeUri("AjaxService.svc/PutSessionVar"), HttpMethod.Post, ContentType.ApplicationJson,
          new { input = "foo" }, existingCookies, new NameValueCollection { { "x-foo-header", "bar-value" } }).
          GetResponse().GetResponseStream().Text(), new { Value = default(string) });
      Assert.AreEqual("foo", result3.Value);

      // interesting as this treatment may be, I think you would have to be mad to apply it. 

  }

现在,在对基础 .CreateRequest() 方法有了基本了解之后,让我们重构以减少代码冗余,并将我们的 JSON 响应执行模式集中到几个通用方法中。

   /// <summary>
  /// </summary>
  /// <typeparam name="T"></typeparam>
  /// <param name="request"></param>
  /// <param name="handler">If null, created WebRequestException is thrown when GetReponse craps out.</param>
  /// <returns></returns>
  public T GetJsonResponse<T>(HttpWebRequest request, Action<WebRequestException> handler)
  {
      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
      try
      {
          using (Stream responseStream = request.GetResponse().GetResponseStream())
          {

              return jsSerializer.CleanAndDeserialize<T>(responseStream.Text());
          }
      }
      catch (WebException ex)
      {
          WebRequestException reqEx = WebRequestException.Create(ex);
          if (handler != null)
          {
              handler(reqEx);
              return default(T);
          }

          throw reqEx;
      }
  }

  /// <summary>
  /// And for anonymous response types....
  /// </summary>
  /// <typeparam name="T"></typeparam>
  /// <param name="request"></param>
  /// <param name="prototype"></param>
  /// <param name="handler">If null, created WebRequestException is thrown when GetReponse craps out.</param>
  /// <returns></returns>
  public T GetJsonResponse<T>(HttpWebRequest request, T prototype, Action<WebRequestException> handler)
  {
      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
      try
      {
          using (Stream responseStream = request.GetResponse().GetResponseStream())
          {

              return jsSerializer.CleanAndDeserialize(responseStream.Text(), prototype);
          }
      }
      catch (WebException ex)
      {
          WebRequestException reqEx = WebRequestException.Create(ex);
          if (handler != null)
          {
              handler(reqEx);
              return default(T);
          }

          throw reqEx;
      }
  }



  /// <summary>
  /// Test the utility methods.
  /// </summary>
  [Test]
  public void Using_Utility_Methods()
  {
      // We have already seen how to build up a request from parts, so for the sake of 
      // brevity (and being clever) lets define an inline factory method.

      Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(
          NormalizeUri("AjaxService.svc/PutSessionVar"), HttpMethod.Post, ContentType.TextJson, new { input = "foo" }, null, null);

      // As an added bit of abuse, an anonymous exception handler to pass to the generic utility methods

      Action<WebRequestException> commonExceptionHandler = up =>
                      {

                          Console.WriteLine("Inside inline shared exception handler");
                          // handle ex if possible, and return, otherwise throw 


                          throw up; // ;-O~~
                      };


      // Call a valid method with referenced response type. commonExceptionHandler will not be executed
      Result result = GetJsonResponse<Result>(createRequest(), commonExceptionHandler);

      Assert.AreEqual("foo", result.Value);


      // Call a valid method with an anonymous response type. commonExceptionHandler will not be executed
      var anonResult = GetJsonResponse(createRequest(), new { Value = default(string) }, commonExceptionHandler);

      Assert.AreEqual("foo", anonResult.Value);

      // Test the generic GetJsonResponse with an exception and a handler, . commonExceptionHandler WILL executed
      try
      {
          GetJsonResponse(RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")), new { Value = default(string) }, commonExceptionHandler);
          Assert.Fail("Expected WebRequestException");
      }
      catch (WebRequestException)
      {

          Console.WriteLine("Handler works");
      }

      // test the generic GetJsonResponse with an exception and no handler. commonExceptionHandler is null and will NOT be executed
      try
      {
          GetJsonResponse(RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")), new { Value = default(string) }, null);
          Assert.Fail("Expected WebRequestException");
      }
      catch (WebRequestException)
      {

          Console.WriteLine("WebRequestException was thrown from GetJsonResponse as no handler was specified");
      }
  } 

重载

现在,让我们花点时间简要回顾一下 CreateRequest 的可用重载,然后再深入研究 .CreatePostJsonApp()

所有重载都接受 stringUri 作为 requestUri

有一个专用的 RequestFactory.CreateFilePost() 方法用于上传文件,它具有自己独特但类似 API,将在本教程的最后进行介绍。

  /// <summary>
  /// Creates a request using http 'Get' and ContentType.None. PostData is Url-encoded and appended to the Uri.
  /// </summary>
  [Test]
  public void CreateGet_Overloads()
  {
      Uri requestUri = NormalizeUri("Default.aspx");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or anonymous type
      NameValueCollection postData = new NameValueCollection();


      // get overloads
      RequestFactory.CreateGet(requestUri)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGet(requestUri, postData)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGet(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGet(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

  /// <summary>
  /// Creates a request using http 'Get' and ContentType.ApplicationJson. PostData is Url-encoded and appended to the Uri.
  /// </summary>
  [Test]
  public void CreateGetJsonApp_Overloads()
  {
      Uri requestUri = NormalizeUri("Default.aspx");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or anonymous type
      NameValueCollection postData = new NameValueCollection();

      RequestFactory.CreateGetJsonApp(requestUri)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonApp(requestUri, postData)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonApp(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonApp(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

  /// <summary>
  /// Creates a request using http 'Get' and ContentType.TextJson. PostData is Url-encoded and appended to the Uri.
  /// </summary>
  [Test]
  public void CreateGetJsonText_Overloads()
  {
      Uri requestUri = NormalizeUri("Default.aspx");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or anonymous type
      NameValueCollection postData = new NameValueCollection();

      RequestFactory.CreateGetJsonText(requestUri)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonText(requestUri, postData)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonText(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreateGetJsonText(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

  /// <summary>
  /// Creates a request using http 'Post' and ContentType.ApplicationForm. PostData is Url-encoded and written to the request body.
  /// </summary>
  [Test]
  public void CreatePostForm_Overloads()
  {
      Uri requestUri = NormalizeUri("Default.aspx");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or anonymous type
      NameValueCollection postData = new NameValueCollection();

      RequestFactory.CreatePostForm(requestUri)
          .GetResponse().GetResponseStream().Text(); ;

      RequestFactory.CreatePostForm(requestUri, postData)
          .GetResponse().GetResponseStream().Text(); ;

      RequestFactory.CreatePostForm(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostForm(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

  /// <summary>
  /// Creates a request using http 'Post' and ContentType.ApplicationJson. PostData is JSON-encoded and written to the request body.
  /// This overload is most appropriate for posting to MS JSON endpoints an the rest of the functional examples will use
  /// this overload.
  /// </summary>
  [Test]
  public void CreatePostJsonApp_Overloads()
  {
      Uri requestUriNoArgs = NormalizeUri("AjaxService.svc/Noop");
      Uri requestUri = NormalizeUri("AjaxService.svc/PutSessionVar");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or a NameValueCollection
      object postData = new { input = "foo" };

      RequestFactory.CreatePostJsonApp(requestUriNoArgs)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonApp(requestUri, postData)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonApp(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonApp(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

  /// <summary>
  /// Creates a request using http 'Post' and ContentType.TextJson. PostData is JSON-encoded and written to the request body.
  /// </summary>
  [Test]
  public void CreatePostJsonText_Overloads()
  {
      Uri requestUriNoArgs = NormalizeUri("AjaxService.svc/Noop");
      Uri requestUri = NormalizeUri("AjaxService.svc/PutSessionVar");

      CookieContainer cookies = new CookieContainer();
      NameValueCollection headers = new NameValueCollection();

      // postData could also be an object or a NameValueCollection
      object postData = new { input = "foo" };

      RequestFactory.CreatePostJsonText(requestUriNoArgs)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonText(requestUri, postData)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonApp(requestUri, postData, cookies)
          .GetResponse().GetResponseStream().Text();

      RequestFactory.CreatePostJsonText(requestUri, postData, cookies, headers)
          .GetResponse().GetResponseStream().Text();
  }

使用 CreatePostJsonApp 处理 JSON 端点

所有重载都解析到基础 .CreateRequest() 方法,因此遵循相同的模式。本教程的重点是测试 MS JSON 端点,因此我们将专注于 .CreatePostJsonApp() 方法。

大多数其他场景,例如发布到标准表单和消耗非 MS REST 服务,都可以使用其他重载以类似的方式处理。

因此,使用 CreatePostJsonApp() 可以模拟用于消耗 ms JSON 端点的 XMLHttpRequest

在此示例中,为了演示三种 JSON 端点之间的直接对等性,我们创建了 3 个端点:一个 Ajax 启用的 WCF 服务、一个用 [ScriptService] 装饰的 WebServices,以及一个用 [WebMethod] (PageMethods) 装饰的静态 .aspx 方法,每个端点都有类似的 API。

我们将使用相同的代码在循环中调用其中每个端点,并观察相同的行为。

注意:在之前的列表中,已经展示了各种通用实用方法和内联工厂策略。您可能会发现这些模式很有用,但为了清晰起见,本教程的其余部分将使用展开的语法。

  [Test]
    public void Using_RequestFactory_CreatePostJsonApp_With_Json_Endpoints()
    {
        JavaScriptSerializer jsSerializer = new JavaScriptSerializer();

        // the methods will be calling looks like this:

        // public string PutSessionVar(string input)
        // public Result GetSessionVar()


        // parameters can be defined with anonymous types. 
        var postData = new { input = "foo" };

        // we will simply call API equivalent instances of each endpoint with the same code.
        string[] jsonEndpoints = new[] { "AjaxService.svc", "ScriptService.asmx", "Default.aspx" };

        foreach (string address in jsonEndpoints)
        {
            try
            {
                using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/PutSessionVar"), postData).GetResponse().GetResponseStream())
                {
                    Result result = jsSerializer.CleanAndDeserialize(response.Text());
                    Assert.AreEqual("foo", result.Value);
                }

                // now lets get the value

                try // this try/catch is a bit of a spoiler but bear with me
                {
                    RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/GetSessionVar")).GetResponse().GetResponseStream();
                    Assert.Fail("Expected WebException");
                }
                catch (WebException ex)
                {
                    // as you can see, the value was not found in the session because we don't have a cookie
                    // container to share between requests
                    WebRequestException ex2 = WebRequestException.Create(ex);
                    Console.WriteLine("Caught {0}: {1}", ex2.ExceptionDetail.ExceptionType, ex2.Message);

                }
            }
            catch (Exception ex)
            {
                Assert.Fail("Endpoint {0} failed with {1}", address, ex.Message);
            }
        }

        // Which brings us to the overload of .CreatePostJsonApp that accepts a CookieContainer as an argument....

    }

为了在请求之间维护会话状态、FormsAuthentication 和其他 Cookie,我们需要将一个通用的 CookieContainer 传递给每个请求。

还可以使用另一个重载将 Http 标头添加到请求中,但这在此处不进行处理。那些需要这样做的人将很容易理解实现。

 [Test]
  public void Using_CookieContainer_With_CreatePostJsonApp()
  {

      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();

      string[] jsonEndpoints = new[] { "AjaxService.svc", "ScriptService.asmx", "Default.aspx" };
      List<string> failures = new List<string>();
      foreach (string address in jsonEndpoints)
      {
          try
          {

              // a container to store our cookies
              CookieContainer cookies = new CookieContainer();

              // the SessionId to verify cookies/session are working as expected
              string sessionId;

              // pass the CookieContainer in to catch any cookies set by the server
              using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/PutSessionVar"), new { input = "foo" }, cookies).GetResponse().GetResponseStream())
              {
                  Result result = jsSerializer.CleanAndDeserialize<Result>(response.Text());
                  Assert.AreEqual("foo", result.Value);

                  sessionId = result.Session;
              }

              // get the var
              // pass the same CookieContainer in with the next request as it now contains the session cookie 
              using (var response = RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/GetSessionVar"), null, cookies).GetResponse().GetResponseStream())
              {
                  Result result = jsSerializer.CleanAndDeserialize<Result>(response.Text());
                  Assert.AreEqual("foo", result.Value);
                  Assert.AreEqual(sessionId, result.Session);
              }
          }
          catch (Exception ex)
          {
              failures.Add(string.Format("Endpoint {0} failed {1} with {2}", address, "CreatePostJsonApp", ex.Message));
          }
      }

      if (failures.Count > 0)
      {
          Assert.Fail(string.Join("\r\n", failures.ToArray()));
      }
  }

使用 CreateFilePost 上传流

主要的 CreateFilePost 方法将一个 Stream 上传到 Http 表单处理程序。

参数

  • requestUri - string 或 Uri
    资源的绝对 URI
  • postData - NameValueCollection
    可选。一个包含要与文件数据一起发布的表单字段的 NameValueCollection
  • fileData - Stream
    一个包含文件数据的已打开、已定位的流。
  • fileName - string
    要分配给文件数据并从中推断文件内容的名称(如果需要)。
  • fileContentType - string
    可选。如果省略,则使用 fileName 查询注册表。如果从注册表中找不到内容类型,则提交 application/octet-stream。
  • fileFieldName - string
    可选。用于表示提供数据的输入元素的名称的标识符。如果省略,则提交值 file。
  • cookies - CookieCollection
    可选。在请求之间共享 CookieCollection 对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
  • headers - NameValueCollection
    可选。要添加到请求的 Http 标头。
  [Test]
  public void Using_CreateFilePost_To_Upload_Data_From_Memory()
  {
      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();

      var uploadResultPrototype = new
          {
              postData = default(string),
              fileFieldName = default(string),
              fileName = default(string),
              fileContentType = default(string),
              fileContentLength = default(int),
          };

      Uri requestUri = NormalizeUri("UploadHandler.ashx");
      NameValueCollection postData = new NameValueCollection
          {
              {"field1", "field1Value"},
              {"chkBoxGrp1", "a"},
              {"chkBoxGrp1", "b"}
          };


      const string content = "some text";
      Stream fileData = new MemoryStream(Encoding.UTF8.GetBytes(content));
      const string fileName = "TextFileFromMemory.txt";
      const string fileContentType = "text/plain";
      const string fileFieldName = "fileField";

      // am glossing over the use of cookies and headers as this has already been covered.

      HttpWebRequest request = RequestFactory.CreateFilePost(requestUri, postData, fileData, fileName, fileContentType, fileFieldName, null, null);

      try
      {
          using (Stream stream = request.GetResponse().GetResponseStream())
          {
              var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
              Assert.AreEqual(9, response.fileContentLength);
              Assert.AreEqual("text/plain", response.fileContentType);
              Assert.AreEqual("fileField", response.fileFieldName);
              Assert.AreEqual("TextFileFromMemory.txt", response.fileName);
              Assert.AreEqual("field1=field1Value\r\nchkBoxGrp1=a,b\r\n", response.postData);


          }
      }
      catch (WebException ex)
      {
          throw WebRequestException.Create(ex);
      }


      // just send data and filename

      fileData.Position = 0;
      HttpWebRequest request2 = RequestFactory.CreateFilePost(requestUri, fileData, fileName);

      try
      {
          using (Stream stream = request2.GetResponse().GetResponseStream())
          {
              var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
              Assert.AreEqual(9, response.fileContentLength);
              Assert.AreEqual("text/plain", response.fileContentType);
              Assert.AreEqual("file", response.fileFieldName);
              Assert.AreEqual("TextFileFromMemory.txt", response.fileName);
          }
      }
      catch (WebException ex)
      {
          throw WebRequestException.Create(ex);
      }

      
  }

使用 CreateFilePost 上传文件

第二个基础 CreateFilePost 方法接受一个物理文件路径,打开文件流并调用主 CreateFilePost 方法。

参数

  • requestUri - string 或 Uri
    资源的绝对 URI
  • postData - NameValueCollection
    可选。一个包含要与文件数据一起发布的表单字段的 NameValueCollection
  • fileName - string
    要上传的文件的物理路径
  • fileContentType - fileContentType - string
    可选。如果省略,则使用 fileName 查询注册表。如果从注册表中找不到内容类型,则提交 application/octet-stream。
  • fileFieldName - string
    可选。用于表示提供数据的输入元素的名称的标识符。如果省略,则提交值 file。
  • cookies - CookieCollection
    可选。在请求之间共享 CookieCollection 对于维护会话状态、FormsAuthentication 票证和其他 Cookie 是必需的。
  • headers - NameValueCollection
    可选。要添加到请求的 Http 标头。
  [Test]
  public void Using_CreateFilePost_To_Upload_File_From_Disk()
  {
      JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
      var uploadResultPrototype = new
      {
          postData = default(string),
          fileFieldName = default(string),
          fileName = default(string),
          fileContentType = default(string),
          fileContentLength = default(int),
      };


      Uri requestUri = NormalizeUri("UploadHandler.ashx");
      NameValueCollection postData = new NameValueCollection
          {
              {"field1", "field1Value"},
              {"chkBoxGrp1", "a"},
              {"chkBoxGrp1", "b"}
          };

      string fileName = Path.GetFullPath("TextFileFromDisk.txt");
      const string fileContentType = "text/plain";
      const string fileFieldName = "fileField";


      var request = RequestFactory.CreateFilePost(requestUri, postData, fileName, fileContentType, fileFieldName, null, null);


      try
      {
          using (Stream stream = request.GetResponse().GetResponseStream())
          {
              var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
              Assert.AreEqual(12, response.fileContentLength); // content length is text length + BOM
              Assert.AreEqual("text/plain", response.fileContentType);
              Assert.AreEqual("fileField", response.fileFieldName);
              Assert.AreEqual("TextFileFromDisk.txt", response.fileName);
              Assert.AreEqual("field1=field1Value\r\nchkBoxGrp1=a,b\r\n", response.postData);


          }
      }
      catch (WebException ex)
      {
          throw WebRequestException.Create(ex);
      }


      // just send data and filename


      HttpWebRequest request2 = RequestFactory.CreateFilePost(requestUri, fileName);

      try
      {
          using (Stream stream = request2.GetResponse().GetResponseStream())
          {
              var response = jsSerializer.CleanAndDeserialize(stream.Text(), uploadResultPrototype);
              Assert.AreEqual(12, response.fileContentLength);// content length is text length + BOM
              Assert.AreEqual("text/plain", response.fileContentType);
              Assert.AreEqual("file", response.fileFieldName);
              Assert.AreEqual("TextFileFromDisk.txt", response.fileName);
          }
      }
      catch (WebException ex)
      {
          throw WebRequestException.Create(ex);
      }

  }

结论

通过上面提供的信息和模式,您现在应该能够快速轻松地为您的项目添加测试,以证明您的端点运行正常。

在主 HttpLib 测试中,您将找到对库中每个类和方法的更全面的介绍。

最新源代码和测试可在 http://salient.codeplex.com 找到

历史

  • 2010/04/15 - 初始帖子 

 相关帖子 

© . All rights reserved.