From d9200031943a747d3063bf41fe4d9f99dfe2ee39 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 25 Feb 2014 17:50:39 -0800 Subject: [PATCH] Introduce FormValueProviderFactory to expose form data as a value provider --- .../FormValueProviderFactory.cs | 40 +++++++++++++ .../ValueProviders/IValueProviderFactory.cs | 5 +- .../QueryStringValueProvider.cs | 13 ----- .../QueryStringValueProviderFactory.cs | 22 ++++--- ... ReadableStringCollectionValueProvider.cs} | 4 +- .../RouteValueValueProviderFactory.cs | 7 ++- .../DefaultActionSelector.cs | 10 ++-- src/Microsoft.AspNet.Mvc/IActionSelector.cs | 6 +- .../Routing/RouteEndpoint.cs | 2 +- .../FormValueProviderFactoryTests.cs | 58 +++++++++++++++++++ .../QueryStringValueProviderFactoryTest.cs | 9 +-- ...ableStringCollectionValueProviderTests.cs} | 34 +++++------ 12 files changed, 154 insertions(+), 56 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs delete mode 100644 src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProvider.cs rename src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/{NameValuePairsValueProvider.cs => ReadableStringCollectionValueProvider.cs} (93%) create mode 100644 test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs rename test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/{NameValuePairsValueProviderTest.cs => ReadableStringCollectionValueProviderTests.cs} (80%) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs new file mode 100644 index 0000000000..c16765e150 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + public class FormValueProviderFactory : IValueProviderFactory + { + private const string FormEncodedContentType = "application/x-www-form-urlencoded"; + + public async Task GetValueProviderAsync(RequestContext requestContext) + { + var request = requestContext.HttpContext.Request; + + if (IsSupportedContentType(request)) + { + var queryCollection = await request.GetFormAsync(); + var culture = GetCultureInfo(request); + return new ReadableStringCollectionValueProvider(queryCollection, culture); + } + + return null; + } + + private bool IsSupportedContentType(HttpRequest request) + { + var contentType = request.Headers["Content-Type"]; + return !String.IsNullOrEmpty(contentType) && + contentType.Equals(FormEncodedContentType, StringComparison.OrdinalIgnoreCase); + } + + private static CultureInfo GetCultureInfo(HttpRequest request) + { + // TODO: Tracked via https://github.com/aspnet/HttpAbstractions/issues/10. Determine what's the right way to + // map Accept-Language to culture. + return CultureInfo.CurrentCulture; + } + } +} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs index 539720d121..c488ebce85 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/IValueProviderFactory.cs @@ -1,4 +1,5 @@ - +using System.Threading.Tasks; + namespace Microsoft.AspNet.Mvc.ModelBinding { public interface IValueProviderFactory @@ -8,6 +9,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// RequestContext that value provider will populate from /// a value provider instance or null - IValueProvider GetValueProvider(RequestContext requestContext); + Task GetValueProviderAsync(RequestContext requestContext); } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProvider.cs deleted file mode 100644 index b14e05522e..0000000000 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Globalization; -using Microsoft.AspNet.Abstractions; - -namespace Microsoft.AspNet.Mvc.ModelBinding -{ - public class QueryStringValueProvider : NameValuePairsValueProvider - { - public QueryStringValueProvider(HttpContext context, CultureInfo culture) - : base(context.Request.Query, culture) - { - } - } -} diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs index c42249d7c4..d33c9c07e2 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/QueryStringValueProviderFactory.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.Globalization; +using System.Globalization; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding @@ -8,24 +8,28 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { private static readonly object _cacheKey = new object(); - public IValueProvider GetValueProvider(RequestContext requestContext) + public Task GetValueProviderAsync(RequestContext requestContext) { if (requestContext == null) { throw Error.ArgumentNull("requestContext"); } - // Process the query string once-per request. - IDictionary storage = requestContext.HttpContext.Items; + // Process the query collection once-per request. + var storage = requestContext.HttpContext.Items; object value; + IValueProvider provider; if (!storage.TryGetValue(_cacheKey, out value)) { - var provider = new QueryStringValueProvider(requestContext.HttpContext, CultureInfo.InvariantCulture); + var queryCollection = requestContext.HttpContext.Request.Query; + provider = new ReadableStringCollectionValueProvider(queryCollection, CultureInfo.InvariantCulture); storage[_cacheKey] = provider; - return provider; } - - return (QueryStringValueProvider)value; + else + { + provider = (ReadableStringCollectionValueProvider)value; + } + return Task.FromResult(provider); } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/NameValuePairsValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs similarity index 93% rename from src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/NameValuePairsValueProvider.cs rename to src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs index b217141d07..5ed9f2e6b9 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/NameValuePairsValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs @@ -6,7 +6,7 @@ using Microsoft.AspNet.Mvc.ModelBinding.Internal; namespace Microsoft.AspNet.Mvc.ModelBinding { - public class NameValuePairsValueProvider : IEnumerableValueProvider + public class ReadableStringCollectionValueProvider : IEnumerableValueProvider { private readonly CultureInfo _culture; private PrefixContainer _prefixContainer; @@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// The key value pairs to wrap. /// The culture to return with ValueProviderResult instances. - public NameValuePairsValueProvider(IReadableStringCollection values, CultureInfo culture) + public ReadableStringCollectionValueProvider(IReadableStringCollection values, CultureInfo culture) { if (values == null) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs index fb7c1aa0b3..1378f441d0 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/RouteValueValueProviderFactory.cs @@ -1,11 +1,14 @@  +using System.Threading.Tasks; + namespace Microsoft.AspNet.Mvc.ModelBinding { public class RouteValueValueProviderFactory : IValueProviderFactory { - public IValueProvider GetValueProvider(RequestContext requestContext) + public Task GetValueProviderAsync(RequestContext requestContext) { - return new DictionaryBasedValueProvider(requestContext.RouteValues); + var valueProvider = new DictionaryBasedValueProvider(requestContext.RouteValues); + return Task.FromResult(valueProvider); } } } diff --git a/src/Microsoft.AspNet.Mvc/DefaultActionSelector.cs b/src/Microsoft.AspNet.Mvc/DefaultActionSelector.cs index f60bb2ca4c..011a109371 100644 --- a/src/Microsoft.AspNet.Mvc/DefaultActionSelector.cs +++ b/src/Microsoft.AspNet.Mvc/DefaultActionSelector.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; namespace Microsoft.AspNet.Mvc @@ -16,7 +17,7 @@ namespace Microsoft.AspNet.Mvc _valueProviderFactory = valueProviderFactories; } - public ActionDescriptor Select(RequestContext context) + public async Task SelectAsync(RequestContext context) { if (context == null) { @@ -36,7 +37,7 @@ namespace Microsoft.AspNet.Mvc } else { - return SelectBestCandidate(context, matching); + return await SelectBestCandidate(context, matching); } } @@ -52,9 +53,10 @@ namespace Microsoft.AspNet.Mvc (descriptor.DynamicConstraints == null || descriptor.DynamicConstraints.All(c => c.Accept(context))); } - protected virtual ActionDescriptor SelectBestCandidate(RequestContext context, List candidates) + protected virtual async Task SelectBestCandidate(RequestContext context, List candidates) { - var valueProviders = _valueProviderFactory.Select(vpf => vpf.GetValueProvider(context)).ToArray(); + var valueProviders = await Task.WhenAll(_valueProviderFactory.Select(vpf => vpf.GetValueProviderAsync(context))); + valueProviders = valueProviders.Where(vp => vp != null).ToArray(); var applicableCandiates = new List(); foreach (var action in candidates) diff --git a/src/Microsoft.AspNet.Mvc/IActionSelector.cs b/src/Microsoft.AspNet.Mvc/IActionSelector.cs index 7ffaf9b77a..6b1e07ded9 100644 --- a/src/Microsoft.AspNet.Mvc/IActionSelector.cs +++ b/src/Microsoft.AspNet.Mvc/IActionSelector.cs @@ -1,8 +1,10 @@ -namespace Microsoft.AspNet.Mvc +using System.Threading.Tasks; + +namespace Microsoft.AspNet.Mvc { public interface IActionSelector { - ActionDescriptor Select(RequestContext context); + Task SelectAsync(RequestContext context); bool Match(ActionDescriptor descriptor, RequestContext context); } diff --git a/src/Microsoft.AspNet.Mvc/Routing/RouteEndpoint.cs b/src/Microsoft.AspNet.Mvc/Routing/RouteEndpoint.cs index 70cf70cb6d..731e88ce0d 100644 --- a/src/Microsoft.AspNet.Mvc/Routing/RouteEndpoint.cs +++ b/src/Microsoft.AspNet.Mvc/Routing/RouteEndpoint.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.Routing var routeValues = context.GetFeature(); var requestContext = new RequestContext(context, routeValues.Values); - var actionDescriptor = ActionSelector.Select(requestContext); + var actionDescriptor = await ActionSelector.SelectAsync(requestContext); if (actionDescriptor == null) { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs new file mode 100644 index 0000000000..7b90136124 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNet.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.ModelBinding.Test +{ + public class FormValueProviderFactoryTests + { + [Fact] + public async Task GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded() + { + // Arrange + var requestContext = CreateRequestContext("some-content-type"); + var factory = new FormValueProviderFactory(); + + // Act + var result = await factory.GetValueProviderAsync(requestContext); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture() + { + // Arrange + var requestContext = CreateRequestContext("application/x-www-form-urlencoded"); + var factory = new FormValueProviderFactory(); + + // Act + var result = await factory.GetValueProviderAsync(requestContext); + + // Assert + var valueProvider = Assert.IsType(result); + Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture); + } + + private static RequestContext CreateRequestContext(string contentType) + { + var collection = Mock.Of(); + var request = new Mock(); + request.Setup(f => f.GetFormAsync()).Returns(Task.FromResult(collection)); + + var mockHeader = new Mock(); + mockHeader.Setup(h => h["Content-Type"]).Returns(contentType); + request.SetupGet(r => r.Headers).Returns(mockHeader.Object); + + var context = new Mock(); + context.SetupGet(c => c.Request).Returns(request.Object); + + var requestContext = new RequestContext(context.Object, new Dictionary()); + return requestContext; + } + } +} diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs index e643a096ee..d59ab70055 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/QueryStringValueProviderFactoryTest.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Globalization; +using System.Threading.Tasks; using Microsoft.AspNet.Abstractions; using Moq; using Xunit; @@ -14,11 +15,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetValueProvider_WhenrequestContextParameterIsNull_Throws() { // Act and Assert - ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProvider(requestContext: null), "requestContext"); + ExceptionAssert.ThrowsArgumentNull(() => _factory.GetValueProviderAsync(requestContext: null), "requestContext"); } [Fact] - public void GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() + public async Task GetValueProvider_ReturnsQueryStringValueProviderInstaceWithInvariantCulture() { // Arrange var request = new Mock(); @@ -29,10 +30,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var requestContext = new RequestContext(context.Object, new Dictionary()); // Act - IValueProvider result = _factory.GetValueProvider(requestContext); + var result = await _factory.GetValueProviderAsync(requestContext); // Assert - var valueProvider = Assert.IsType(result); + var valueProvider = Assert.IsType(result); Assert.Equal(CultureInfo.InvariantCulture, valueProvider.Culture); } } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/NameValuePairsValueProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs similarity index 80% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/NameValuePairsValueProviderTest.cs rename to test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs index a3b5272036..33cec193c2 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/NameValuePairsValueProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTests.cs @@ -7,7 +7,7 @@ using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Test { - public class NameValuePairsValueProviderTest + public class ReadableStringCollectionValueProviderTest { private static readonly IReadableStringCollection _backingStore = new ReadableStringCollection( new Dictionary @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Act & assert ExceptionAssert.ThrowsArgumentNull( - () => new NameValuePairsValueProvider(values: null, culture: CultureInfo.InvariantCulture), + () => new ReadableStringCollectionValueProvider(values: null, culture: CultureInfo.InvariantCulture), "values"); } @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void ContainsPrefix_GuardClauses() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act & assert ExceptionAssert.ThrowsArgumentNull( @@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var backingStore = new ReadableStringCollection(new Dictionary()); - var valueProvider = new NameValuePairsValueProvider(backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(backingStore, null); // Act bool result = valueProvider.ContainsPrefix(""); @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForEmptyPrefix() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act bool result = valueProvider.ContainsPrefix(""); @@ -70,7 +70,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void ContainsPrefix_WithNonEmptyCollection_ReturnsTrueForKnownPrefixes() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act & Assert Assert.True(valueProvider.ContainsPrefix("foo")); @@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void ContainsPrefix_WithNonEmptyCollection_ReturnsFalseForUnknownPrefix() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act bool result = valueProvider.ContainsPrefix("biff"); @@ -95,7 +95,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetKeysFromPrefix_GuardClauses() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act & assert ExceptionAssert.ThrowsArgumentNull( @@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetKeysFromPrefix_EmptyPrefix_ReturnsAllPrefixes() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act IDictionary result = valueProvider.GetKeysFromPrefix(""); @@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetKeysFromPrefix_UnknownPrefix_ReturnsEmptyDictionary() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act IDictionary result = valueProvider.GetKeysFromPrefix("abc"); @@ -135,7 +135,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetKeysFromPrefix_KnownPrefix_ReturnsMatchingItems() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act IDictionary result = valueProvider.GetKeysFromPrefix("bar"); @@ -150,7 +150,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetValue_GuardClauses() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act & assert ExceptionAssert.ThrowsArgumentNull( @@ -163,7 +163,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var culture = CultureInfo.GetCultureInfo("fr-FR"); - var valueProvider = new NameValuePairsValueProvider(_backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); // Act ValueProviderResult vpResult = valueProvider.GetValue("bar.baz"); @@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { // Arrange var culture = CultureInfo.GetCultureInfo("fr-FR"); - var valueProvider = new NameValuePairsValueProvider(_backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); // Act ValueProviderResult vpResult = valueProvider.GetValue("foo"); @@ -201,7 +201,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test //{ // // Arrange // var culture = CultureInfo.GetCultureInfo("fr-FR"); - // var valueProvider = new NameValuePairsValueProvider(_backingStore, culture); + // var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, culture); // // Act // ValueProviderResult vpResult = valueProvider.GetValue(key); @@ -223,7 +223,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test { "key", new string[] { null, null, "value" } } }); var culture = CultureInfo.GetCultureInfo("fr-FR"); - var valueProvider = new NameValuePairsValueProvider(backingStore, culture); + var valueProvider = new ReadableStringCollectionValueProvider(backingStore, culture); // Act ValueProviderResult vpResult = valueProvider.GetValue("key"); @@ -237,7 +237,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test public void GetValue_ReturnsNullIfKeyNotFound() { // Arrange - var valueProvider = new NameValuePairsValueProvider(_backingStore, null); + var valueProvider = new ReadableStringCollectionValueProvider(_backingStore, null); // Act ValueProviderResult vpResult = valueProvider.GetValue("bar");