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