diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs
new file mode 100644
index 0000000000..8c900ce4a7
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoundTripTests.cs
@@ -0,0 +1,141 @@
+// 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;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Builder;
+using Microsoft.AspNet.TestHost;
+using ModelBindingWebSite;
+using Newtonsoft.Json;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.FunctionalTests
+{
+ ///
+ /// Functional tests that verify if names returned by HtmlHelper.NameFor get model bound correctly.
+ /// Each test works in three steps -
+ /// 1) The result of an HtmlHelper.NameFor invocation for a specific expression is retrieved.
+ /// 2) A form URL encoded value is posted for the retrieved name.
+ /// 3) The server returns the bound object. We verify if the property specified by the expression in step 1
+ /// has the expected value.
+ ///
+ public class RoundTripTests
+ {
+ private readonly IServiceProvider _services = TestHelper.CreateServices("ModelBindingWebSite");
+ private readonly Action _app = new Startup().Configure;
+
+ // Uses the expression p => p.Name
+ [Fact]
+ public async Task RoundTrippedValues_GetsModelBound_ForSimpleExpressions()
+ {
+ // Arrange
+ var expected = "test-name";
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPerson");
+ var keyValuePairs = new[]
+ {
+ new KeyValuePair(expression, expected)
+ };
+ var result = await GetPerson(client, keyValuePairs);
+
+ // Assert
+ Assert.Equal("Name", expression);
+ Assert.Equal(expected, result.Name);
+ }
+
+ // Uses the expression p => p.Parent.Age
+ [Fact]
+ public async Task RoundTrippedValues_GetsModelBound_ForSubPropertyExpressions()
+ {
+ // Arrange
+ var expected = 40;
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonParentAge");
+ var keyValuePairs = new[]
+ {
+ new KeyValuePair(expression, expected.ToString())
+ };
+ var result = await GetPerson(client, keyValuePairs);
+
+ // Assert
+ Assert.Equal("Parent.Age", expression);
+ Assert.Equal(expected, result.Parent.Age);
+ }
+
+ // Uses the expression p => p.Dependents[0].Age
+ [Fact]
+ public async Task RoundTrippedValues_GetsModelBound_ForNumericIndexedProperties()
+ {
+ // Arrange
+ var expected = 12;
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonDependentAge");
+ var keyValuePairs = new[]
+ {
+ new KeyValuePair(expression, expected.ToString())
+ };
+ var result = await GetPerson(client, keyValuePairs);
+
+ // Assert
+ Assert.Equal("Dependents[0].Age", expression);
+ Assert.Equal(expected, result.Dependents[0].Age);
+ }
+
+ // Uses the expression p => p.Parent.Attributes["height"]
+ [Fact]
+ public async Task RoundTrippedValues_GetsModelBound_ForStringIndexedProperties()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetPersonParentHeightAttribute");
+
+ // Assert
+ Assert.Equal("Parent.Attributes[height]", expression);
+ // TODO Requires resolution in model binding as part of #1418
+ }
+
+ // Uses the expression p => p.Dependents[0].Dependents[0].Name
+ [Fact]
+ public async Task RoundTrippedValues_GetsModelBound_ForNestedNumericIndexedProperties()
+ {
+ // Arrange
+ var expected = "test-nested-name";
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var expression = await client.GetStringAsync("http://localhost/RoundTrip/GetDependentPersonName");
+ var keyValuePairs = new[]
+ {
+ new KeyValuePair(expression, expected.ToString())
+ };
+ var result = await GetPerson(client, keyValuePairs);
+
+ // Assert
+ Assert.Equal("Dependents[0].Dependents[0].Name", expression);
+ Assert.Equal(expected, result.Dependents[0].Dependents[0].Name);
+ }
+
+ private static async Task GetPerson(HttpClient client, KeyValuePair[] keyValuePairs)
+ {
+ var content = new FormUrlEncodedContent(keyValuePairs);
+ var response = await client.PostAsync("http://localhost/RoundTrip/Person", content);
+ var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/Controllers/RoundtripController.cs b/test/WebSites/ModelBindingWebSite/Controllers/RoundtripController.cs
new file mode 100644
index 0000000000..8f30225bcf
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/Controllers/RoundtripController.cs
@@ -0,0 +1,77 @@
+// 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;
+using System.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.Rendering;
+
+namespace ModelBindingWebSite.Controllers
+{
+ public class RoundtripController : Controller
+ {
+ private IHtmlHelper _personHelper;
+ private bool _activated;
+
+ [Activate]
+ public IHtmlHelper PersonHelper
+ {
+ get
+ {
+ if (!_activated)
+ {
+ _activated = true;
+ var viewData = new ViewDataDictionary(ViewData);
+ var context = new ViewContext(ActionContext, new TestView(), viewData, TextWriter.Null);
+ ((ICanHasViewContext)PersonHelper).Contextualize(context);
+ }
+
+ return _personHelper;
+ }
+ set
+ {
+ _personHelper = value;
+ }
+ }
+
+ public string GetPerson()
+ {
+ return PersonHelper.NameFor(p => p.Name);
+ }
+
+ public string GetPersonParentAge()
+ {
+ return PersonHelper.NameFor(p => p.Parent.Age);
+ }
+
+ public string GetPersonDependentAge()
+ {
+ return PersonHelper.NameFor(p => p.Dependents[0].Age);
+ }
+
+ public string GetDependentPersonName()
+ {
+ return PersonHelper.NameFor(p => p.Dependents[0].Dependents[0].Name);
+ }
+
+ public string GetPersonParentHeightAttribute()
+ {
+ return PersonHelper.NameFor(p => p.Parent.Attributes["height"]);
+ }
+
+ [HttpPost]
+ public Person Person(Person boundPerson)
+ {
+ return boundPerson;
+ }
+
+ private sealed class TestView : IView
+ {
+ public Task RenderAsync(ViewContext context)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/test/WebSites/ModelBindingWebSite/Model/Person.cs b/test/WebSites/ModelBindingWebSite/Model/Person.cs
index 7f9f4de3b8..cfbeb02dcc 100644
--- a/test/WebSites/ModelBindingWebSite/Model/Person.cs
+++ b/test/WebSites/ModelBindingWebSite/Model/Person.cs
@@ -1,7 +1,7 @@
// 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 Microsoft.AspNet.Mvc;
+using System.Collections.Generic;
namespace ModelBindingWebSite
{
@@ -12,5 +12,9 @@ namespace ModelBindingWebSite
public int Age { get; set; }
public Person Parent { get; set; }
+
+ public List Dependents { get; set; }
+
+ public Dictionary Attributes { get; set; }
}
}
\ No newline at end of file