Skip to content

Commit bc62072

Browse files
author
henrikn
committed
Added HttpContent.ReadAsFormData helper for reading form data and return a NameValueCollection. The helper is a thin wrapper around existing functionality but saves a couple of lines for the developer to write.
1 parent 2470d13 commit bc62072

File tree

4 files changed

+221
-0
lines changed

4 files changed

+221
-0
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2+
3+
using System.Collections.Specialized;
4+
using System.ComponentModel;
5+
using System.Net.Http.Formatting;
6+
using System.Net.Http.Headers;
7+
using System.Threading.Tasks;
8+
using System.Web.Http;
9+
10+
namespace System.Net.Http
11+
{
12+
/// <summary>
13+
/// Extension methods to allow HTML form URL-encoded data, also known as <c>application/x-www-form-urlencoded</c>,
14+
/// to be read from <see cref="HttpContent"/> instances.
15+
/// </summary>
16+
[EditorBrowsable(EditorBrowsableState.Never)]
17+
public static class HttpContentFormDataExtensions
18+
{
19+
private const string ApplicationFormUrlEncoded = "application/x-www-form-urlencoded";
20+
21+
/// <summary>
22+
/// Determines whether the specified content is HTML form URL-encoded data, also known as <c>application/x-www-form-urlencoded</c> data.
23+
/// </summary>
24+
/// <param name="content">The content.</param>
25+
/// <returns>
26+
/// <c>true</c> if the specified content is HTML form URL-encoded data; otherwise, <c>false</c>.
27+
/// </returns>
28+
public static bool IsFormData(this HttpContent content)
29+
{
30+
if (content == null)
31+
{
32+
throw Error.ArgumentNull("content");
33+
}
34+
35+
MediaTypeHeaderValue contentType = content.Headers.ContentType;
36+
return contentType != null && String.Equals(ApplicationFormUrlEncoded, contentType.MediaType, StringComparison.OrdinalIgnoreCase);
37+
}
38+
39+
/// <summary>
40+
/// Returns a <see cref="Task{T}"/> that will yield a <see cref="NameValueCollection"/> instance containing the form data
41+
/// from the <paramref name="content"/> instance.
42+
/// </summary>
43+
/// <param name="content">The content.</param>
44+
/// <returns>A <see cref="Task{T}"/> which will provide the result.</returns>
45+
public static Task<NameValueCollection> ReadAsFormDataAsync(this HttpContent content)
46+
{
47+
if (content == null)
48+
{
49+
throw Error.ArgumentNull("content");
50+
}
51+
52+
return content.ReadAsAsync<FormDataCollection>()
53+
.Then(formdata => formdata != null ? formdata.ReadAsNameValueCollection() : null);
54+
}
55+
}
56+
}

src/System.Net.Http.Formatting/System.Net.Http.Formatting.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
<Link>Properties\TransparentCommonAssemblyInfo.cs</Link>
8989
</Compile>
9090
<Compile Include="CloneableExtensions.cs" />
91+
<Compile Include="HttpContentFormDataExtensions.cs" />
9192
<Compile Include="MultipartRelatedStreamProvider.cs" />
9293
<Compile Include="MultipartFileData.cs" />
9394
<Compile Include="MultipartStreamProvider.cs" />
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2+
3+
using System.Collections.Specialized;
4+
using System.Net.Http.Formatting;
5+
using System.Net.Http.Headers;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Microsoft.TestCommon;
9+
using Xunit;
10+
using Xunit.Extensions;
11+
using Assert = Microsoft.TestCommon.AssertEx;
12+
13+
namespace System.Net.Http
14+
{
15+
public class HttpContentFormDataExtensionsTest
16+
{
17+
public static TheoryDataSet<string> FormDataContentTypes
18+
{
19+
get
20+
{
21+
return new TheoryDataSet<string>
22+
{
23+
"application/x-www-form-urlencoded",
24+
"APPLICATION/x-www-form-urlencoded",
25+
"application/X-WWW-FORM-URLENCODED",
26+
"application/x-www-form-urlencoded; charset=utf-8",
27+
"application/x-www-form-urlencoded; parameter=value",
28+
};
29+
}
30+
}
31+
32+
public static TheoryDataSet<string> NonFormDataContentTypes
33+
{
34+
get
35+
{
36+
return new TheoryDataSet<string>
37+
{
38+
"application/xml",
39+
"APPLICATION/json",
40+
"text/xml",
41+
"text/xml; charset=utf-8",
42+
"text/xml; parameter=value",
43+
};
44+
}
45+
}
46+
47+
public static TheoryDataSet<string> FormData
48+
{
49+
get
50+
{
51+
return new TheoryDataSet<string>
52+
{
53+
"a=b",
54+
"a+c=d+e",
55+
"n1=v1&n2=v2",
56+
"n1=v1a+v1b&n2=v2a+v2b",
57+
"N=%c3%a6%c3%b8%c3%a5",
58+
};
59+
}
60+
}
61+
62+
public static TheoryDataSet<string> IrregularFormData
63+
{
64+
get
65+
{
66+
return new TheoryDataSet<string>
67+
{
68+
"?data",
69+
Char.ConvertFromUtf32(0x0D),
70+
"Hello World",
71+
"<string>Hello World</string>",
72+
"{ \"Message\" : \"Hello World\"",
73+
};
74+
}
75+
}
76+
77+
[Fact]
78+
public void IsFormData_ThrowsOnNull()
79+
{
80+
Assert.ThrowsArgumentNull(() => HttpContentFormDataExtensions.IsFormData(null), "content");
81+
}
82+
83+
[Fact]
84+
public void IsFormData_HandlesNullContentType()
85+
{
86+
HttpContent content = new StringContent(String.Empty);
87+
content.Headers.ContentType = null;
88+
Assert.False(content.IsFormData());
89+
}
90+
91+
[Theory]
92+
[PropertyData("FormDataContentTypes")]
93+
public void IsFormData_AcceptsFormDataMediaTypes(string mediaType)
94+
{
95+
HttpContent content = new StringContent(String.Empty);
96+
content.Headers.ContentType = MediaTypeHeaderValue.Parse(mediaType);
97+
Assert.True(content.IsFormData());
98+
}
99+
100+
[Theory]
101+
[PropertyData("NonFormDataContentTypes")]
102+
public void IsFormData_RejectsNonFormDataMediaTypes(string mediaType)
103+
{
104+
HttpContent content = new StringContent(String.Empty);
105+
content.Headers.ContentType = MediaTypeHeaderValue.Parse(mediaType);
106+
Assert.False(content.IsFormData());
107+
}
108+
109+
[Fact]
110+
public void ReadAsFormDataAsync_ThrowsOnNull()
111+
{
112+
Assert.ThrowsArgumentNull(() => HttpContentFormDataExtensions.ReadAsFormDataAsync(null), "content");
113+
}
114+
115+
[Theory]
116+
[PropertyData("FormData")]
117+
public Task ReadAsFormDataAsync_HandlesFormData(string formData)
118+
{
119+
// Arrange
120+
HttpContent content = new StringContent(formData);
121+
content.Headers.ContentType = MediaTypeConstants.ApplicationFormUrlEncodedMediaType;
122+
123+
// Act
124+
return content.ReadAsFormDataAsync().ContinueWith(
125+
readTask =>
126+
{
127+
NameValueCollection data = readTask.Result;
128+
129+
// Assert
130+
Assert.Equal(TaskStatus.RanToCompletion, readTask.Status);
131+
Assert.Equal(formData, data.ToString());
132+
});
133+
}
134+
135+
[Theory]
136+
[PropertyData("IrregularFormData")]
137+
public Task ReadAsFormDataAsync_HandlesIrregularFormData(string irregularFormData)
138+
{
139+
// Arrange
140+
HttpContent content = new StringContent(irregularFormData);
141+
content.Headers.ContentType = MediaTypeConstants.ApplicationFormUrlEncodedMediaType;
142+
143+
// Act
144+
return content.ReadAsFormDataAsync().ContinueWith(
145+
readTask =>
146+
{
147+
NameValueCollection data = readTask.Result;
148+
149+
// Assert
150+
Assert.Equal(TaskStatus.RanToCompletion, readTask.Status);
151+
Assert.Equal(1, data.Count);
152+
Assert.Equal(irregularFormData, data.AllKeys[0]);
153+
});
154+
}
155+
156+
[Fact]
157+
public void ReadAsFormDataAsync_HandlesNonFormData()
158+
{
159+
HttpContent content = new StringContent(String.Empty, Encoding.UTF8, "test/unknown");
160+
Assert.Throws<InvalidOperationException>(() => content.ReadAsFormDataAsync());
161+
}
162+
}
163+
}

test/System.Net.Http.Formatting.Test.Unit/System.Net.Http.Formatting.Test.Unit.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
<Compile Include="..\Common\UriQueryUtilityTest.cs">
8787
<Link>Common\UriQueryUtilityTest.cs</Link>
8888
</Compile>
89+
<Compile Include="HttpContentFormDataExtensionsTest.cs" />
8990
<Compile Include="PushStreamContentTest.cs" />
9091
<Compile Include="HttpClientFactoryTest.cs" />
9192
<Compile Include="Handlers\ProgressContentTest.cs" />

0 commit comments

Comments
 (0)