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"]); + } + } +}