Retain your sanity! Quickly and easily write test suites for your Ajax/REST/Form/Upload/other http endpoints.
download source code for HttpLib and this walkthrough here: Salient.Web.HttpLib.b1.zip
Overview
In this post I will introduce you to a small library, Salient.Web.HttpLib
, that is designed primarily to support the testing of Http endpoints.
Special attention is paid to the concerns of testing MS Ajax/WCF/WebServices endpoints and the primary focus of the tests will be centered on Ajax-enabled WCF service, static .aspx PageMethods and Xml WebServices that have been decorated with [ScriptService]
or [ScriptMethod]
attributes all demonstrate the same behavior and will hereafter referred to collectively as 'JSON endpoints'.
Other endpoints/platforms can easily be consumed and following the walkthrough, applying the patterns demonstrated against other targets will be academic.
The walkthrough will be accomplished by detailed description of actual passing tests contained in the attached VS2008 solution.
The walkthough tests are contained in ArticleSiteFixture
which is derived from Salient.Web.HttpLib.WebDevFixture
.
This test fixture contains methods that simulate the type of requests that are to be expected from our client script. This allows for automated testing of our endpoints from the perspective of client script.
Note: consider yourself warned: In the spirit of the JavaScript client we are going to be simulating, I will be abusing closures, anonymous methods and types with impunity and glee. ;-)
You can call this functional testing or smoke testing or maybe even integration testing, just don't call it unit testing.
Walkthrough
Salient.Web.HttpLib.WebDevFixture
WebDevFixture
encapsulates a controlled startup of Visual Studio's development server. To spin up an instance of WebDev, simply supply an unused port and the physical path to the site. This can be a relative path enabling portable tests.
WebDevFixture
provides a convenience method, .NormalizeUri(), that will normalize, or root, a relative path and query string to the root of the current site.
For more information see
http://www.codeproject.com/Articles/72222/A-general-purpose-Visual-Studio-2008-Development-S.aspx.
For more finely tuned control of a testing server you should check out
http://cassinidev.codeplex.com/
This example demonstrates use of WebDevFixture
with NUnit but any testing framework can be used with ease.
ArticleSiteFixture
[TestFixture]
public class ArticleSiteFixture : WebDevFixture
{
protected override int Port
{
get { return 12334; }
}
[TestFixtureSetUp]
public void TestFixtureSetUp()
{
var path = Path.GetFullPath(@"..\..\..\HttpLibArticleSite");
StartServer(path);
}
}
Deserializing MS Ajax Wrapped JSON
The default format for a JSON endpoint response JSON is a 'd: wrapped' object. There are some configuration options and attributes that can modify this behavior, but this default behavior is also the worst case scenario so lets deal with it straight away.
An example that we will be dealing with is the simple Result
class defined in the demo site. When returned as JSON it looks like this:
{"d":{"__type":"Result:#HttpLibArticleSite","Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}}
Deserializing JSON of this format can be problematic but I have provided an extension method for JavaScriptSerializer
that will handle this type of JSON. You can read about it in detail here: Parsing-ClientScript-JSON.aspx
The short story is that ClientScriptUtilities
provides a JavaScriptSerializer
extension method, .CleanAndDeserialize<T>()
, that will extract the inner object and clean the "__type"
properties before deserializing it. The JSON that is ultimately deserialized looks like this:
{"Message":"Value pull OK.","Session":"rmyykw45zbkxxxzdun0juyfr","Value":"foo"}
CleanAndDeserialize
also supports deserialization to anonymous types.
NOTE: CleanAndDeserialize
does not require ugly MsAjax JSON. It will consume any valid JSON so this is a safe replacement for .Deserialize<T>
throughout your code regardless of the JSON format.
[Test]
public void Deserializing_MS_Ajax_Wrapped_JSON()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
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);
}
Exception Handling
When an endpoint of any kind throws an exception it is ultimately swallowed by a generic '500 Internal Server Error' WebException.
Discovering the actual underlying exception can be quite tedious, involving parsing of the WebException.Response.GetResponseStream()
and determining whether it is a JsonFaultDetail from a JSON endpoint, an ASP.Net Yellow Screen of death or a number of other formats.
WebRequestException
encapsulates all of this behavior and surfaces the salient information, if available. The original WebException
is availabe on the InnerException
property.
[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
is the base method that all of the specialized factory method overloads ultimately call.
With a basic understanding of this method, all of the specialized overloads will be readily understandable.
RequestFactory.CreateRequest
builds up an HttpWebRequest
, applying the supplied arguments appropriately and returns it. You are then free to manipulate the request as you choose before executing the request. This pattern is applied over all of the various overloads.
Arguments
requestUri - Uri or String
Absolute Uri of resource
method - HttpMethod
The http method of the request. Currently supported options are HttpMethod.Post
and HttpMethod.Get
contentType - ContentType
The content-type of the request. Currently supported options are ContentType.None
, ContentType.ApplicationForm
, ContentType.ApplicationJson
and ContentType.TextJson
postData - object
Optional. A NameValueCollection
or object
. Anonymous types are acceptable.
The object will be serialized appropriately for the request method and content-type before being applied to the request.
Note: any object that is shaped like the target method's arguments is acceptable. Conveniently this includes anonymous types.
For a 'Get' or form 'Post' a NameValueCollection
may be appropriate as it is able to accept multiple values for a single key to fully simulate the possible shape of a form.
When creating anonymous types as input parameters, you are not required to prototype the target type exactly. You MUST prototype non-nullable properties, including value types and structs, but you may omit any nullable properties, including Nullable<T>
and reference types, that you do not need to send.
An anonymous type suitable as postData for the method public Result PutSessionVar(string input)
would be var postData = new { input = "foo" };
An anonymous type suitable as postData for the method public Result PutSessionVar(Result input)
would be
var postData = new { input = new Result() };
or
var postData = new { input = new { Message = "message", Session = "session", Value = "value" } };
or
var postData = new { input = new { Message = "message" } };
- For form 'Post' requests, the postData object will be serialized into a Url encoded key-value string an streamed into the request body.
- For a JSON 'Post', it will be JSON serialized and streamed into the request body.
- For a 'Get' request it will be Url encoded into a query string and intelligently appended to the Uri. If the Uri is bare, the query string will be appended with a '?'. If the Uri already has a query string the new query will be appended with a '&'.
cookies - CookieCollection
Optional. Sharing a CookieCollection
between requests is required to maintain session state, FormsAuthentication tickets and other cookies.
headers - NameValueCollection
Optional. Http headers to be added to the request.
StreamExtensions
provide two simple convenience extensions for use with the response stream.
Stream.TextStream.Text()
will extract the stream into an UTF-8 string.
Stream.Bytes()
will extract the stream into a byte array.
[Test]
public void Using_CreateRequest()
{
Uri uri = NormalizeUri("AjaxService.svc/PutSessionVar");
const HttpMethod method = HttpMethod.Post;
const ContentType contentType = ContentType.ApplicationJson;
var postData = new { input = "foo" };
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection { { "x-foo-header", "bar-value" } };
HttpWebRequest request = RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);
var responseText = request.GetResponse().GetResponseStream().Text();
Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(uri, method, contentType, postData, cookies, headers);
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
var rtext = responseStream.Text();
}
try
{
using (Stream responseStream = createRequest().GetResponse().GetResponseStream())
{
var rtext = responseStream.Text();
}
}
catch (WebException ex)
{
WebRequestException wrex = WebRequestException.Create(ex);
throw wrex;
}
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;
}
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;
}
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);
}
Now, with a basic understanding of the base .CreateRequest()
method, lets refactor to reduce code bloat and centralize our JSON response execution pattern in a few generic methods.
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;
}
}
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;
}
}
[Test]
public void Using_Utility_Methods()
{
Func<HttpWebRequest> createRequest = () => RequestFactory.CreateRequest(
NormalizeUri("AjaxService.svc/PutSessionVar"), HttpMethod.Post, ContentType.TextJson, new { input = "foo" }, null, null);
Action<WebRequestException> commonExceptionHandler = up =>
{
Console.WriteLine("Inside inline shared exception handler");
throw up; };
Result result = GetJsonResponse<Result>(createRequest(), commonExceptionHandler);
Assert.AreEqual("foo", result.Value);
var anonResult = GetJsonResponse(createRequest(), new { Value = default(string) }, commonExceptionHandler);
Assert.AreEqual("foo", anonResult.Value);
try
{
GetJsonResponse(RequestFactory.CreatePostJsonApp(NormalizeUri("AjaxService.svc/ThrowException")), new { Value = default(string) }, commonExceptionHandler);
Assert.Fail("Expected WebRequestException");
}
catch (WebRequestException)
{
Console.WriteLine("Handler works");
}
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");
}
}
Overloads
Now lets take the time for a brief overview of the available overloads of CreateRequest
before we start drilling down into .CreatePostJsonApp()
.
All overloads accept a string
or Uri
for requestUri
.
There is a specialized RequestFactory.CreateFilePost()
method for uploading files that has it's own unique but similar API that will be covered at the end of this walkthrough.
[Test]
public void CreateGet_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
NameValueCollection postData = new NameValueCollection();
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();
}
[Test]
public void CreateGetJsonApp_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
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();
}
[Test]
public void CreateGetJsonText_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
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();
}
[Test]
public void CreatePostForm_Overloads()
{
Uri requestUri = NormalizeUri("Default.aspx");
CookieContainer cookies = new CookieContainer();
NameValueCollection headers = new NameValueCollection();
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();
}
[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();
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();
}
[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();
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();
}
Using CreatePostJsonApp With JSON Endpoints
All of the overloads resolve to the base .CreateRequest()
methods thus follow the same pattern. The focus of this walkthrough is testing MS JSON endpoints, so we are going to focus on the .CreatePostJsonApp()
method.
Most other scenarios such as posting to a standard form and consuming a non MS REST service can be serviced in like fashion using the other overloads.
So, simulating an XMLHttpRequest
that would be used to consume an ms JSON endpoint is accomplished using CreatePostJsonApp()
.
In this example, in order to demonstrate the direct parity between the three types of JSON endpoints we have created 3 endpoints, an Ajax-enabled WCF service, A WebServices decorated with [ScriptService] and static .aspx methods decorated with [WebMethod] (PageMethods), each with a similar API.
We will call each of these endpoints using identical code from within a loop and witness identical behavior.
Note: in previous listings, various generic utility methods and inline factory strategies have been presented. You may find these patterns useful but, for the sake of clarity, the rest of this walk through will use expanded syntax.
[Test]
public void Using_RequestFactory_CreatePostJsonApp_With_Json_Endpoints()
{
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
var postData = new { input = "foo" };
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);
}
try {
RequestFactory.CreatePostJsonApp(NormalizeUri(address + "/GetSessionVar")).GetResponse().GetResponseStream();
Assert.Fail("Expected WebException");
}
catch (WebException ex)
{
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);
}
}
}
To maintain Session State, FormsAuthentication and other cookies between requests we need to pass a common CookieContainer
to each request.
Http headers may also be added to the request using another overload but this will not be handled here. Those that need to do such a thing will easily understand the implementation.
[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
{
CookieContainer cookies = new CookieContainer();
string sessionId;
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;
}
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()));
}
}
Using CreateFilePost To Upload Stream
The primary CreateFilePost
method uploads a Stream
to an Http form handler.
Arguments:
requestUri - string or Uri
Absolute Uri of resource
postData - NameValueCollection
Optional. A NameValueCollection containing form fields to post with file data
fileData - Stream
An open, positioned stream containing the file data.
fileName - string
A name to assign to the file data and from which to infer fileContentType if necessary.
fileContentType - string
Optional. If omitted, the registry is queried using fileName. If content type is not available from registry, application/octet-stream will be submitted.
fileFieldName - string
Optional. A identifier to represent the name of the input element supplying the data. If ommited the value file will be submitted.
cookies - CookieCollection
Optional. Sharing a CookieCollection between requests is required to maintain session state, FormsAuthentication tickets and other cookies.
headers - NameValueCollection
Optional. Http headers to be added to the request.
[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";
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);
}
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);
}
}
Using CreateFilePost To Upload File
The secondary base CreateFilePost
method accepts a physical file path, open the file stream and calls the primary CreateFilePost
method.
Arguments:
requestUri - string or Uri
Absolute Uri of resource
postData - NameValueCollection
Optional. A NameValueCollection
containing form fields to post with file data
fileName - string
The physical path of the file to upload
fileContentType - fileContentType - string
Optional. If omitted, the registry is queried using fileName
. If content type is not available from registry, application/octet-stream will be submitted.
fileFieldName - string
Optional. A identifier to represent the name of the input element supplying the data. If ommited the value file will be submitted.
cookies - CookieCollection
Optional. Sharing a CookieCollection
between requests is required to maintain session state, FormsAuthentication tickets and other cookies.
headers - NameValueCollection
Optional. Http headers to be added to the request.
[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); 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);
}
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); Assert.AreEqual("text/plain", response.fileContentType);
Assert.AreEqual("file", response.fileFieldName);
Assert.AreEqual("TextFileFromDisk.txt", response.fileName);
}
}
catch (WebException ex)
{
throw WebRequestException.Create(ex);
}
}
Conclusion
With the information and patterns presented above you should now be able to quickly and easily bolster your projects with tests that prove that your endpoints are behaving themselves.
In the main HttpLib
tests you will find a more thorough treatment of each class and method in the library.
The latest source and tests can be found @ http://salient.codeplex.com
History
- 04/15/2010 - Initial post
Related Posts