Adding a context class for ValueProviderFactories

This allows model binding to once again be independent of routing. Sending
RouteContext into model binding was an odd choice from a layering
point-of-view.
This commit is contained in:
Ryan Nowak 2014-06-11 17:15:33 -07:00
parent 44ed23f65e
commit 7f34c94de7
10 changed files with 48 additions and 30 deletions

View File

@ -33,9 +33,11 @@ namespace Microsoft.AspNet.Mvc
public Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext) public Task<ActionBindingContext> GetActionBindingContextAsync(ActionContext actionContext)
{ {
var routeContext = new RouteContext(actionContext.HttpContext); var factoryContext = new ValueProviderFactoryContext(
routeContext.RouteData = actionContext.RouteData; actionContext.HttpContext,
var valueProviders = _valueProviderFactories.Select(factory => factory.GetValueProvider(routeContext)) actionContext.RouteData.Values);
var valueProviders = _valueProviderFactories.Select(factory => factory.GetValueProvider(factoryContext))
.Where(vp => vp != null); .Where(vp => vp != null);
var context = new ActionBindingContext( var context = new ActionBindingContext(
actionContext, actionContext,

View File

@ -115,6 +115,7 @@
<Compile Include="ValueProviders\QueryStringValueProviderFactory.cs" /> <Compile Include="ValueProviders\QueryStringValueProviderFactory.cs" />
<Compile Include="ValueProviders\ReadableStringCollectionValueProvider.cs" /> <Compile Include="ValueProviders\ReadableStringCollectionValueProvider.cs" />
<Compile Include="ValueProviders\RouteValueValueProviderFactory.cs" /> <Compile Include="ValueProviders\RouteValueValueProviderFactory.cs" />
<Compile Include="ValueProviders\ValueProviderFactoryContext.cs" />
<Compile Include="ValueProviders\ValueProviderResult.cs" /> <Compile Include="ValueProviders\ValueProviderResult.cs" />
</ItemGroup> </ItemGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Globalization; using System.Globalization;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
@ -12,9 +11,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private const string FormEncodedContentType = "application/x-www-form-urlencoded"; private const string FormEncodedContentType = "application/x-www-form-urlencoded";
public IValueProvider GetValueProvider([NotNull] RouteContext routeContext) public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{ {
var request = routeContext.HttpContext.Request; var request = context.HttpContext.Request;
if (IsSupportedContentType(request)) if (IsSupportedContentType(request))
{ {

View File

@ -1,8 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public interface IValueProviderFactory public interface IValueProviderFactory
@ -10,8 +8,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <summary> /// <summary>
/// Get a value provider with values from the given <paramref name="requestContext"/>. /// Get a value provider with values from the given <paramref name="requestContext"/>.
/// </summary> /// </summary>
/// <param name="routeContext">RouteContext that value provider will populate from</param> /// <param name="context">ValueProviderFactoryContext that value provider will populate from</param>
/// <returns>a value provider instance or null</returns> /// <returns>a value provider instance or null</returns>
IValueProvider GetValueProvider([NotNull] RouteContext routeContext); IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context);
} }
} }

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Globalization; using System.Globalization;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
@ -10,15 +9,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
private static readonly object _cacheKey = new object(); private static readonly object _cacheKey = new object();
public IValueProvider GetValueProvider([NotNull] RouteContext routeContext) public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{ {
// Process the query collection once-per request. // Process the query collection once-per request.
var storage = routeContext.HttpContext.Items; var storage = context.HttpContext.Items;
object value; object value;
IValueProvider provider; IValueProvider provider;
if (!storage.TryGetValue(_cacheKey, out value)) if (!storage.TryGetValue(_cacheKey, out value))
{ {
var queryCollection = routeContext.HttpContext.Request.Query; var queryCollection = context.HttpContext.Request.Query;
provider = new ReadableStringCollectionValueProvider(queryCollection, CultureInfo.InvariantCulture); provider = new ReadableStringCollectionValueProvider(queryCollection, CultureInfo.InvariantCulture);
storage[_cacheKey] = provider; storage[_cacheKey] = provider;
} }

View File

@ -1,15 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
{ {
public class RouteValueValueProviderFactory : IValueProviderFactory public class RouteValueValueProviderFactory : IValueProviderFactory
{ {
public IValueProvider GetValueProvider([NotNull] RouteContext routeContext) public IValueProvider GetValueProvider([NotNull] ValueProviderFactoryContext context)
{ {
return new DictionaryBasedValueProvider(routeContext.RouteData.Values); return new DictionaryBasedValueProvider(context.RouteValues);
} }
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Http;
namespace Microsoft.AspNet.Mvc.ModelBinding
{
public class ValueProviderFactoryContext
{
public ValueProviderFactoryContext(
[NotNull] HttpContext httpContext,
[NotNull] IDictionary<string, object> routeValues)
{
HttpContext = httpContext;
RouteValues = routeValues;
}
public HttpContext HttpContext { get; private set; }
public IDictionary<string, object> RouteValues { get; private set; }
}
}

View File

@ -6,7 +6,6 @@
"dependencies": { "dependencies": {
"Microsoft.AspNet.Http": "0.1-alpha-*", "Microsoft.AspNet.Http": "0.1-alpha-*",
"Microsoft.AspNet.Mvc.Common": "", "Microsoft.AspNet.Mvc.Common": "",
"Microsoft.AspNet.Routing": "0.1-alpha-*",
"Microsoft.DataAnnotations": "0.1-alpha-*", "Microsoft.DataAnnotations": "0.1-alpha-*",
"Microsoft.Framework.DependencyInjection": "0.1-alpha-*", "Microsoft.Framework.DependencyInjection": "0.1-alpha-*",
"Newtonsoft.Json": "5.0.8" "Newtonsoft.Json": "5.0.8"

View File

@ -19,11 +19,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded() public void GetValueProvider_ReturnsNull_WhenContentTypeIsNotFormUrlEncoded()
{ {
// Arrange // Arrange
var requestContext = CreateRequestContext("some-content-type"); var context = CreateContext("some-content-type");
var factory = new FormValueProviderFactory(); var factory = new FormValueProviderFactory();
// Act // Act
var result = factory.GetValueProvider(requestContext); var result = factory.GetValueProvider(context);
// Assert // Assert
Assert.Null(result); Assert.Null(result);
@ -35,18 +35,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
public void GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType) public void GetValueProvider_ReturnsValueProviderInstaceWithInvariantCulture(string contentType)
{ {
// Arrange // Arrange
var requestContext = CreateRequestContext(contentType); var context = CreateContext(contentType);
var factory = new FormValueProviderFactory(); var factory = new FormValueProviderFactory();
// Act // Act
var result = factory.GetValueProvider(requestContext); var result = factory.GetValueProvider(context);
// Assert // Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result); var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);
Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture); Assert.Equal(CultureInfo.CurrentCulture, valueProvider.Culture);
} }
private static RouteContext CreateRequestContext(string contentType) private static ValueProviderFactoryContext CreateContext(string contentType)
{ {
var collection = Mock.Of<IReadableStringCollection>(); var collection = Mock.Of<IReadableStringCollection>();
var request = new Mock<HttpRequest>(); var request = new Mock<HttpRequest>();
@ -59,9 +59,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var context = new Mock<HttpContext>(); var context = new Mock<HttpContext>();
context.SetupGet(c => c.Request).Returns(request.Object); context.SetupGet(c => c.Request).Returns(request.Object);
var routeContext = new RouteContext(context.Object); return new ValueProviderFactoryContext(
routeContext.RouteData.Values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); context.Object,
return routeContext; new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase));
} }
} }
} }

View File

@ -27,11 +27,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test
var context = new Mock<HttpContext>(); var context = new Mock<HttpContext>();
context.SetupGet(c => c.Items).Returns(new Dictionary<object, object>()); context.SetupGet(c => c.Items).Returns(new Dictionary<object, object>());
context.SetupGet(c => c.Request).Returns(request.Object); context.SetupGet(c => c.Request).Returns(request.Object);
var routeContext = new RouteContext(context.Object); var factoryContext = new ValueProviderFactoryContext(context.Object, new Dictionary<String, object>(StringComparer.OrdinalIgnoreCase));
routeContext.RouteData.Values = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
// Act // Act
var result = _factory.GetValueProvider(routeContext); var result = _factory.GetValueProvider(factoryContext);
// Assert // Assert
var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result); var valueProvider = Assert.IsType<ReadableStringCollectionValueProvider>(result);