diff --git a/src/Microsoft.AspNet.Abstractions/HttpRequest.cs b/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
index f7b85de29d..9e8f53a110 100644
--- a/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
+++ b/src/Microsoft.AspNet.Abstractions/HttpRequest.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading;
+using System.Threading.Tasks;
namespace Microsoft.AspNet.Abstractions
{
@@ -58,6 +59,12 @@ namespace Microsoft.AspNet.Abstractions
/// The query value collection parsed from owin.RequestQueryString.
public abstract IReadableStringCollection Query { get; }
+ ///
+ /// Gets the query value collection form collection.
+ ///
+ /// The form collection parsed from the request body.
+ public abstract Task GetFormAsync();
+
///
/// Gets or set the owin.RequestProtocol.
///
diff --git a/src/Microsoft.AspNet.PipelineCore/DefaultCanHasForm.cs b/src/Microsoft.AspNet.PipelineCore/DefaultCanHasForm.cs
new file mode 100644
index 0000000000..d7547eab2a
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/DefaultCanHasForm.cs
@@ -0,0 +1,42 @@
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Abstractions;
+using Microsoft.AspNet.FeatureModel;
+using Microsoft.AspNet.HttpFeature;
+using Microsoft.AspNet.PipelineCore.Collections;
+using Microsoft.AspNet.PipelineCore.Infrastructure;
+
+namespace Microsoft.AspNet.PipelineCore
+{
+ public class DefaultCanHasForm : ICanHasForm
+ {
+ private readonly IFeatureCollection _features;
+ private readonly FeatureReference _request = FeatureReference.Default;
+ private Stream _bodyStream;
+ private IReadableStringCollection _form;
+
+ public DefaultCanHasForm(IFeatureCollection features)
+ {
+ _features = features;
+ }
+
+ public async Task GetFormAsync()
+ {
+ var body = _request.Fetch(_features).Body;
+
+ if (_bodyStream == null || _bodyStream != body)
+ {
+ _bodyStream = body;
+ using (var streamReader = new StreamReader(body, Encoding.UTF8,
+ detectEncodingFromByteOrderMarks: true,
+ bufferSize: 1024, leaveOpen: true))
+ {
+ string formQuery = await streamReader.ReadToEndAsync();
+ _form = new ReadableStringCollection(ParsingHelpers.GetQuery(formQuery));
+ }
+ }
+ return _form;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs b/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
index 0f98a561f1..7afd73181d 100644
--- a/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
+++ b/src/Microsoft.AspNet.PipelineCore/DefaultHttpRequest.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Threading;
+using System.Threading.Tasks;
using Microsoft.AspNet.Abstractions;
using Microsoft.AspNet.FeatureModel;
using Microsoft.AspNet.HttpFeature;
@@ -18,6 +19,7 @@ namespace Microsoft.AspNet.PipelineCore
private FeatureReference _connection = FeatureReference.Default;
private FeatureReference _transportLayerSecurity = FeatureReference.Default;
private FeatureReference _canHasQuery = FeatureReference.Default;
+ private FeatureReference _canHasForm = FeatureReference.Default;
private FeatureReference _canHasCookies = FeatureReference.Default;
public DefaultHttpRequest(DefaultHttpContext context, IFeatureCollection features)
@@ -46,6 +48,11 @@ namespace Microsoft.AspNet.PipelineCore
get { return _canHasQuery.Fetch(_features) ?? _canHasQuery.Update(_features, new DefaultCanHasQuery(_features)); }
}
+ private ICanHasForm CanHasForm
+ {
+ get { return _canHasForm.Fetch(_features) ?? _canHasForm.Update(_features, new DefaultCanHasForm(_features)); }
+ }
+
private ICanHasRequestCookies CanHasRequestCookies
{
get { return _canHasCookies.Fetch(_features) ?? _canHasCookies.Update(_features, new DefaultCanHasRequestCookies(_features)); }
@@ -112,6 +119,11 @@ namespace Microsoft.AspNet.PipelineCore
get { return CanHasQuery.Query; }
}
+ public override Task GetFormAsync()
+ {
+ return CanHasForm.GetFormAsync();
+ }
+
public override string Protocol
{
get { return HttpRequestInformation.Protocol; }
diff --git a/src/Microsoft.AspNet.PipelineCore/ICanHasForm.cs b/src/Microsoft.AspNet.PipelineCore/ICanHasForm.cs
new file mode 100644
index 0000000000..aba500a592
--- /dev/null
+++ b/src/Microsoft.AspNet.PipelineCore/ICanHasForm.cs
@@ -0,0 +1,10 @@
+using System.Threading.Tasks;
+using Microsoft.AspNet.Abstractions;
+
+namespace Microsoft.AspNet.PipelineCore
+{
+ public interface ICanHasForm
+ {
+ Task GetFormAsync();
+ }
+}
diff --git a/test/Microsoft.AspNet.PipelineCore.Tests/DefaultCanHasFormTests.cs b/test/Microsoft.AspNet.PipelineCore.Tests/DefaultCanHasFormTests.cs
new file mode 100644
index 0000000000..967839793d
--- /dev/null
+++ b/test/Microsoft.AspNet.PipelineCore.Tests/DefaultCanHasFormTests.cs
@@ -0,0 +1,68 @@
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNet.FeatureModel;
+using Microsoft.AspNet.HttpFeature;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNet.PipelineCore.Tests
+{
+ public class DefaultCanHasFormTests
+ {
+ [Fact]
+ public async Task GetFormAsync_ReturnsParsedFormCollection()
+ {
+ // Arrange
+ var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2");
+ var features = new Mock();
+ var request = new Mock();
+ request.SetupGet(r => r.Body).Returns(new MemoryStream(formContent));
+
+ object value = request.Object;
+ features.Setup(f => f.TryGetValue(typeof(IHttpRequestInformation), out value))
+ .Returns(true);
+
+ var provider = new DefaultCanHasForm(features.Object);
+
+ // Act
+ var formCollection = await provider.GetFormAsync();
+
+ // Assert
+ Assert.Equal("bar", formCollection["foo"]);
+ Assert.Equal("2", formCollection["baz"]);
+ }
+
+ [Fact]
+ public async Task GetFormAsync_CachesFormCollectionPerBodyStream()
+ {
+ // Arrange
+ var formContent1 = Encoding.UTF8.GetBytes("foo=bar&baz=2");
+ var formContent2 = Encoding.UTF8.GetBytes("collection2=value");
+ var features = new Mock();
+ var request = new Mock();
+ request.SetupGet(r => r.Body).Returns(new MemoryStream(formContent1));
+
+ object value = request.Object;
+ features.Setup(f => f.TryGetValue(typeof(IHttpRequestInformation), out value))
+ .Returns(true);
+
+ var provider = new DefaultCanHasForm(features.Object);
+
+ // Act - 1
+ var formCollection = await provider.GetFormAsync();
+
+ // Assert - 1
+ Assert.Equal("bar", formCollection["foo"]);
+ Assert.Equal("2", formCollection["baz"]);
+ Assert.Same(formCollection, await provider.GetFormAsync());
+
+ // Act - 2
+ request.SetupGet(r => r.Body).Returns(new MemoryStream(formContent2));
+ formCollection = await provider.GetFormAsync();
+
+ // Assert - 2
+ Assert.Equal("value", formCollection["collection2"]);
+ }
+ }
+}