Fix #2527 - Remove FormCollection use

This removes the dependency on Microsoft.AspNet.Http from the Mvc.Core
code. Added the reference back to tests where needed (DefaultHttpContext).
This commit is contained in:
Ryan Nowak 2015-07-09 12:42:08 -07:00
parent a045324d3a
commit 1596cd9422
10 changed files with 180 additions and 104 deletions

View File

@ -4,7 +4,6 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.WebUtilities;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc

View File

@ -1,12 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. 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 System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.Framework.Internal; using Microsoft.Framework.Internal;
namespace Microsoft.AspNet.Mvc.ModelBinding namespace Microsoft.AspNet.Mvc.ModelBinding
@ -19,8 +19,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <inheritdoc /> /// <inheritdoc />
public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext) public async Task<ModelBindingResult> BindModelAsync([NotNull] ModelBindingContext bindingContext)
{ {
if (bindingContext.ModelType != typeof(IFormCollection) && if (bindingContext.ModelType != typeof(IFormCollection))
bindingContext.ModelType != typeof(FormCollection))
{ {
return null; return null;
} }
@ -30,19 +29,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
if (request.HasFormContentType) if (request.HasFormContentType)
{ {
var form = await request.ReadFormAsync(); var form = await request.ReadFormAsync();
if (bindingContext.ModelType.GetTypeInfo().IsAssignableFrom(form.GetType().GetTypeInfo())) model = form;
{
model = form;
}
else
{
var formValuesLookup = form.ToDictionary(p => p.Key, p => p.Value);
model = new FormCollection(formValuesLookup, form.Files);
}
} }
else else
{ {
model = new FormCollection(new Dictionary<string, string[]>()); model = new EmptyFormCollection();
} }
var validationNode = var validationNode =
@ -57,5 +48,86 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
isModelSet: true, isModelSet: true,
validationNode: validationNode); validationNode: validationNode);
} }
private class EmptyFormCollection : IFormCollection
{
public string this[string key]
{
get
{
return null;
}
}
public int Count
{
get
{
return 0;
}
}
public IFormFileCollection Files
{
get
{
return new EmptyFormFileCollection();
}
}
public ICollection<string> Keys
{
get
{
return new List<string>();
}
}
public bool ContainsKey(string key)
{
return false;
}
public string Get(string key)
{
return null;
}
public IEnumerator<KeyValuePair<string, string[]>> GetEnumerator()
{
return Enumerable.Empty<KeyValuePair<string, string[]>>().GetEnumerator();
}
public IList<string> GetValues(string key)
{
return null;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
private class EmptyFormFileCollection : List<IFormFile>, IFormFileCollection
{
public IFormFile this[string name]
{
get
{
return null;
}
}
public IFormFile GetFile(string name)
{
return null;
}
IReadOnlyList<IFormFile> IFormFileCollection.GetFiles(string name)
{
throw new NotImplementedException();
}
}
} }
} }

View File

@ -12,7 +12,6 @@
"Microsoft.AspNet.Authorization": "1.0.0-*", "Microsoft.AspNet.Authorization": "1.0.0-*",
"Microsoft.AspNet.FileProviders.Abstractions": "1.0.0-*", "Microsoft.AspNet.FileProviders.Abstractions": "1.0.0-*",
"Microsoft.AspNet.Hosting.Abstractions": "1.0.0-*", "Microsoft.AspNet.Hosting.Abstractions": "1.0.0-*",
"Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Mvc.Abstractions": "6.0.0-*", "Microsoft.AspNet.Mvc.Abstractions": "6.0.0-*",
"Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*", "Microsoft.Dnx.Runtime.Abstractions": "1.0.0-*",
"Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" }, "Microsoft.Framework.ClosedGenericMatcher.Sources": { "version": "1.0.0-*", "type": "build" },

View File

@ -1,20 +1,27 @@
{ {
"description": "Provides compatibility in ASP.NET MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations.", "description": "Provides compatibility in ASP.NET MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations.",
"version": "6.0.0-*", "version": "6.0.0-*",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/aspnet/mvc" "url": "git://github.com/aspnet/mvc"
}, },
"compilationOptions": { "compilationOptions": {
"warningsAsErrors": false "warningsAsErrors": false
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.WebUtilities": "1.0.0-*",
"Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-*", "Microsoft.AspNet.Mvc.Core": "6.0.0-*",
"Microsoft.AspNet.WebApi.Client": "5.2.2", "Microsoft.AspNet.Mvc.Formatters.Json": "6.0.0-*",
"Microsoft.Framework.PropertyHelper.Sources": { "version": "1.0.0-*", "type": "build" }, "Microsoft.AspNet.WebApi.Client": "5.2.2",
"Microsoft.Framework.NotNullAttribute.Sources": { "version": "1.0.0-*", "type": "build" } "Microsoft.Framework.PropertyHelper.Sources": {
"version": "1.0.0-*",
"type": "build"
}, },
"Microsoft.Framework.NotNullAttribute.Sources": {
"version": "1.0.0-*",
"type": "build"
}
},
"frameworks": { "frameworks": {
"dnx451": { "dnx451": {
"frameworkAssemblies": { "frameworkAssemblies": {

View File

@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{ "field2", new string[] { "value2" } } { "field2", new string[] { "value2" } }
}); });
var httpContext = GetMockHttpContext(formCollection); var httpContext = GetMockHttpContext(formCollection);
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext); var bindingContext = GetBindingContext(typeof(IFormCollection), httpContext);
var binder = new FormCollectionModelBinder(); var binder = new FormCollectionModelBinder();
// Act // Act
@ -64,6 +64,27 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
Assert.Null(result); Assert.Null(result);
} }
// We only support IFormCollection here. Using the concrete type won't work.
[Fact]
public async Task FormCollectionModelBinder_FormCollectionConcreteType_BindFails()
{
// Arrange
var formCollection = new FormCollection(new Dictionary<string, string[]>
{
{ "field1", new string[] { "value1" } },
{ "field2", new string[] { "value2" } }
});
var httpContext = GetMockHttpContext(formCollection);
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext);
var binder = new FormCollectionModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.Null(result);
}
[Fact] [Fact]
public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection() public async Task FormCollectionModelBinder_NoForm_BindSuccessful_ReturnsEmptyFormCollection()
{ {
@ -75,34 +96,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
// Act // Act
var result = await binder.BindModelAsync(bindingContext); var result = await binder.BindModelAsync(bindingContext);
// Assert
Assert.NotNull(result);
Assert.IsType(typeof(FormCollection), result.Model);
Assert.Empty((FormCollection)result.Model);
}
[Fact]
public async Task FormCollectionModelBinder_CustomFormCollection_BindSuccessful()
{
// Arrange
var formCollection = new MyFormCollection(new Dictionary<string, string[]>
{
{ "field1", new string[] { "value1" } },
{ "field2", new string[] { "value2" } }
});
var httpContext = GetMockHttpContext(formCollection);
var bindingContext = GetBindingContext(typeof(FormCollection), httpContext);
var binder = new FormCollectionModelBinder();
// Act
var result = await binder.BindModelAsync(bindingContext);
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
var form = Assert.IsAssignableFrom<IFormCollection>(result.Model); var form = Assert.IsAssignableFrom<IFormCollection>(result.Model);
Assert.Equal(2, form.Count); Assert.Empty(form);
Assert.Equal("value1", form["field1"]);
Assert.Equal("value2", form["field2"]);
} }
private static HttpContext GetMockHttpContext(IFormCollection formCollection, bool hasForm = true) private static HttpContext GetMockHttpContext(IFormCollection formCollection, bool hasForm = true)

View File

@ -24,7 +24,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{ {
public int Zip { get; set; } public int Zip { get; set; }
public FormCollection FileCollection { get; set; } public IFormCollection FileCollection { get; set; }
} }
[Fact] [Fact]
@ -61,7 +61,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Model // Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model); var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson.Address); Assert.NotNull(boundPerson.Address);
var formCollection = Assert.IsAssignableFrom<FormCollection>(boundPerson.Address.FileCollection); var formCollection = Assert.IsAssignableFrom<IFormCollection>(boundPerson.Address.FileCollection);
var file = Assert.Single(formCollection.Files); var file = Assert.Single(formCollection.Files);
Assert.Equal("form-data; name=Address.File; filename=text.txt", file.ContentDisposition); Assert.Equal("form-data; name=Address.File; filename=text.txt", file.ContentDisposition);
var reader = new StreamReader(file.OpenReadStream()); var reader = new StreamReader(file.OpenReadStream());
@ -88,7 +88,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// Setting a custom parameter prevents it from falling back to an empty prefix. // Setting a custom parameter prevents it from falling back to an empty prefix.
BinderModelName = "CustomParameter", BinderModelName = "CustomParameter",
}, },
ParameterType = typeof(FormCollection) ParameterType = typeof(IFormCollection)
}; };
var data = "Some Data Is Better Than No Data."; var data = "Some Data Is Better Than No Data.";
@ -109,7 +109,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelBindingResult.IsModelSet); Assert.True(modelBindingResult.IsModelSet);
// Model // Model
var formCollection = Assert.IsType<FormCollection>(modelBindingResult.Model); var formCollection = Assert.IsAssignableFrom<IFormCollection>(modelBindingResult.Model);
var file = Assert.Single(formCollection.Files); var file = Assert.Single(formCollection.Files);
Assert.NotNull(file); Assert.NotNull(file);
Assert.Equal("form-data; name=CustomParameter; filename=text.txt", file.ContentDisposition); Assert.Equal("form-data; name=CustomParameter; filename=text.txt", file.ContentDisposition);
@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{ {
BinderModelName = "CustomParameter", BinderModelName = "CustomParameter",
}, },
ParameterType = typeof(FormCollection) ParameterType = typeof(IFormCollection)
}; };
// No data is passed. // No data is passed.
@ -148,7 +148,7 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
// ModelBindingResult // ModelBindingResult
Assert.NotNull(modelBindingResult); Assert.NotNull(modelBindingResult);
var collection = Assert.IsType<FormCollection>(modelBindingResult.Model); var collection = Assert.IsAssignableFrom<IFormCollection>(modelBindingResult.Model);
// ModelState // ModelState
Assert.True(modelState.IsValid); Assert.True(modelState.IsValid);

View File

@ -1,23 +1,24 @@
{ {
"compile": [ "compile": [
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs", "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs",
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs", "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs",
"../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs" "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileTrigger.cs"
], ],
"dependencies": { "dependencies": {
"Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*", "Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*", "Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*",
"Microsoft.AspNet.Mvc.Razor": "6.0.0-*", "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestCommon": { "Microsoft.AspNet.Mvc.Razor": "6.0.0-*",
"version": "6.0.0-*", "Microsoft.AspNet.Mvc.TestCommon": {
"type": "build" "version": "6.0.0-*",
}, "type": "build"
"Microsoft.AspNet.Testing": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.WebEncoders.Testing": "1.0.0-*",
"Microsoft.Dnx.Runtime": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
}, },
"Microsoft.AspNet.Testing": "1.0.0-*",
"Microsoft.Framework.DependencyInjection": "1.0.0-*",
"Microsoft.Framework.WebEncoders.Testing": "1.0.0-*",
"Microsoft.Dnx.Runtime": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": { "commands": {
"test": "xunit.runner.aspnet" "test": "xunit.runner.aspnet"
}, },

View File

@ -1,17 +1,18 @@
{ {
"dependencies": { "dependencies": {
"Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*", "Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*", "Microsoft.AspNet.Mvc.DataAnnotations": "6.0.0-*",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*", "Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-*",
"Microsoft.AspNet.Mvc.TestCommon": { "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
"version": "6.0.0-*", "Microsoft.AspNet.Mvc.TestCommon": {
"type": "build" "version": "6.0.0-*",
}, "type": "build"
"Microsoft.AspNet.Testing": "1.0.0-*",
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.WebEncoders.Testing": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
}, },
"Microsoft.AspNet.Testing": "1.0.0-*",
"Microsoft.Framework.Logging.Abstractions": "1.0.0-*",
"Microsoft.Framework.WebEncoders.Testing": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": { "commands": {
"test": "xunit.runner.aspnet" "test": "xunit.runner.aspnet"
}, },

View File

@ -1,13 +1,14 @@
{ {
"compilationOptions": { "compilationOptions": {
"warningsAsErrors": "false" "warningsAsErrors": "false"
}, },
"dependencies": { "dependencies": {
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*", "Microsoft.AspNet.Http": "1.0.0-*",
"Microsoft.AspNet.Testing": "1.0.0-*", "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
"Microsoft.Framework.Logging.Testing": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*",
"xunit.runner.aspnet": "2.0.0-aspnet-*" "Microsoft.Framework.Logging.Testing": "1.0.0-*",
}, "xunit.runner.aspnet": "2.0.0-aspnet-*"
},
"commands": { "commands": {
"test": "xunit.runner.aspnet" "test": "xunit.runner.aspnet"
}, },

View File

@ -4,7 +4,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Internal;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
namespace ModelBindingWebSite.Controllers namespace ModelBindingWebSite.Controllers
@ -26,7 +25,7 @@ namespace ModelBindingWebSite.Controllers
return form.Count; return form.Count;
} }
public ActionResult ReturnFileContent(FormCollection form) public ActionResult ReturnFileContent(IFormCollection form)
{ {
var file = form.Files.GetFile("File"); var file = form.Files.GetFile("File");
using (var reader = new StreamReader(file.OpenReadStream())) using (var reader = new StreamReader(file.OpenReadStream()))