diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionExecutor.cs index a2f883098e..64dfdbab7e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; @@ -125,9 +126,19 @@ namespace Microsoft.AspNet.Mvc } else { - value = parameterInfo.ParameterType.GetTypeInfo().IsValueType - ? Activator.CreateInstance(parameterInfo.ParameterType) - : null; + var defaultValueAttribute = + parameterInfo.GetCustomAttribute(inherit: false); + + if (defaultValueAttribute?.Value == null) + { + value = parameterInfo.ParameterType.GetTypeInfo().IsValueType + ? Activator.CreateInstance(parameterInfo.ParameterType) + : null; + } + else + { + value = defaultValueAttribute.Value; + } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionExecutorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionExecutorTests.cs index 1288aabdb8..a437a31a83 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionExecutorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionExecutorTests.cs @@ -193,6 +193,70 @@ namespace Microsoft.AspNet.Mvc.Core.Test new Dictionary() { { "input", inputString } })); } + [Fact] + public async Task ExecuteAsync_WithArgumentDictionary_DefaultValueAttributeUsed() + { + // Arrange + var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue); + + // Act + var result = await ControllerActionExecutor.ExecuteAsync( + syncMethod.GetMethodInfo(), + _controller, + new Dictionary()); + + // Assert + Assert.Equal("hello", result); + } + + [Fact] + public async Task ExecuteAsync_WithArgumentArray_DefaultValueAttributeIgnored() + { + // Arrange + var syncMethod = new SyncMethod(_controller.EchoWithDefaultValue); + + // Act + var result = await ControllerActionExecutor.ExecuteAsync( + syncMethod.GetMethodInfo(), + _controller, + new object[] { null, }); + + // Assert + Assert.Null(result); + } + + [Fact] + public async Task ExecuteAsync_WithArgumentDictionary_DefaultParameterValueUsed() + { + // Arrange + var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute); + + // Act + var result = await ControllerActionExecutor.ExecuteAsync( + syncMethod.GetMethodInfo(), + _controller, + new Dictionary()); + + // Assert + Assert.Equal("world", result); + } + + [Fact] + public async Task ExecuteAsync_WithArgumentDictionary_AnyValue_HasPrecedenceOverDefaults() + { + // Arrange + var syncMethod = new SyncMethod(_controller.EchoWithDefaultValueAndAttribute); + + // Act + var result = await ControllerActionExecutor.ExecuteAsync( + syncMethod.GetMethodInfo(), + _controller, + new Dictionary() { { "input", null } }); + + // Assert + Assert.Null(result); + } + [Fact] public async Task AsyncAction_WithCustomTaskReturnTypeThrows() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/TestController.cs b/test/Microsoft.AspNet.Mvc.Core.Test/TestController.cs index 91f183691e..9278be0c8c 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/TestController.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/TestController.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.ComponentModel; using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.Core.Test @@ -85,6 +86,16 @@ namespace Microsoft.AspNet.Mvc.Core.Test throw new NotImplementedException(); } + public string EchoWithDefaultValue([DefaultValue("hello")] string input) + { + return input; + } + + public string EchoWithDefaultValueAndAttribute([DefaultValue("hello")] string input = "world") + { + return input; + } + public dynamic ReturnTaskAsDynamicValue(int i, string s) { return Task.Factory.StartNew(() => i); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/DefaultValuesTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/DefaultValuesTest.cs new file mode 100644 index 0000000000..ef6c805e2d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/DefaultValuesTest.cs @@ -0,0 +1,90 @@ +// 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. + +using System; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.Framework.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class DefaultValuesTest + { + private const string SiteName = nameof(BasicWebSite); + private readonly Action _app = new BasicWebSite.Startup().Configure; + private readonly Action _configureServices = new BasicWebSite.Startup().ConfigureServices; + + [Fact] + public async Task Controller_WithDefaultValueAttribut_ReturnsDefault() + { + // Arrange + var expected = "hello"; + + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + var url = "http://localhost/DefaultValues/EchoValue_DefaultValueAttribute"; + + // Act + var response = await client.GetStringAsync(url); + + // Assert + Assert.Equal(expected, response); + } + + [Fact] + public async Task Controller_WithDefaultValueAttribute_ReturnsModelBoundValues() + { + // Arrange + var expected = "cool"; + + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + var url = "http://localhost/DefaultValues/EchoValue_DefaultValueAttribute?input=cool"; + + // Act + var response = await client.GetStringAsync(url); + + // Assert + Assert.Equal(expected, response); + } + + [Fact] + public async Task Controller_WithDefaultParameterValue_ReturnsDefault() + { + // Arrange + var expected = "world"; + + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + var url = "http://localhost/DefaultValues/EchoValue_DefaultParameterValue"; + + // Act + var response = await client.GetStringAsync(url); + + // Assert + Assert.Equal(expected, response); + } + + [Fact] + public async Task Controller_WithDefaultParameterValue_ReturnsModelBoundValues() + { + // Arrange + var expected = "cool"; + + var server = TestHelper.CreateServer(_app, SiteName, _configureServices); + var client = server.CreateClient(); + + var url = "http://localhost/DefaultValues/EchoValue_DefaultParameterValue?input=cool"; + + // Act + var response = await client.GetStringAsync(url); + + // Assert + Assert.Equal(expected, response); + } + } +} diff --git a/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs b/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs new file mode 100644 index 0000000000..a93886da2c --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs @@ -0,0 +1,23 @@ +// 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. + +using System.ComponentModel; +using Microsoft.AspNet.Mvc; + +namespace BasicWebSite.Controllers +{ + public class DefaultValuesController : Controller + { + [HttpGet] + public string EchoValue_DefaultValueAttribute([DefaultValue("hello")] string input) + { + return input; + } + + [HttpGet] + public string EchoValue_DefaultParameterValue(string input = "world") + { + return input; + } + } +}