diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
new file mode 100644
index 0000000000..d12a81c8ef
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/ServicesModelBinder.cs
@@ -0,0 +1,27 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNet.Mvc.ModelBinding;
+using Microsoft.Framework.DependencyInjection;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// An which understands
+ /// and activates a given model using .
+ ///
+ public class ServicesModelBinder : MetadataAwareBinder
+ {
+ ///
+ protected override Task BindAsync(
+ [NotNull] ModelBindingContext bindingContext,
+ [NotNull] IServiceActivatorBinderMetadata metadata)
+ {
+ var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
+ bindingContext.Model = requestServices.GetRequiredService(bindingContext.ModelType);
+ return Task.FromResult(true);
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromSericesAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromSericesAttribute.cs
new file mode 100644
index 0000000000..ba84b2fe81
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/FromSericesAttribute.cs
@@ -0,0 +1,17 @@
+// 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 Microsoft.AspNet.Mvc.ModelBinding;
+
+namespace Microsoft.AspNet.Mvc
+{
+ ///
+ /// This attribute is used on action parameters or model properties to indicate that
+ /// they will be bound using service provider.
+ ///
+ [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public class FromServicesAttribute : Attribute, IServiceActivatorBinderMetadata
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.cs
new file mode 100644
index 0000000000..5b18b12fc2
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BinderMetadata/IServiceActivatorBinderMetadata.cs
@@ -0,0 +1,12 @@
+// 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.
+
+namespace Microsoft.AspNet.Mvc.ModelBinding
+{
+ ///
+ /// Metadata interface that indicates model binding should use the service container to get the value for a model.
+ ///
+ public interface IServiceActivatorBinderMetadata : IBinderMetadata
+ {
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs
index 75531e3245..0bf3c4cfbe 100644
--- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs
+++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MetadataAwareBinder.cs
@@ -19,7 +19,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// The binding context which has the object to be bound.
/// The associated with the current binder.
/// A Task with a bool implying the success or failure of the operation.
- protected abstract Task BindAsync(ModelBindingContext bindingContext, TBinderMetadata metadata);
+ protected abstract Task BindAsync([NotNull] ModelBindingContext bindingContext,
+ [NotNull] TBinderMetadata metadata);
public Task BindModelAsync(ModelBindingContext context)
{
diff --git a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
index 2c0e2f4690..9f9e0da1ee 100644
--- a/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcOptionsSetup.cs
@@ -27,6 +27,7 @@ namespace Microsoft.AspNet.Mvc
options.ViewEngines.Add(typeof(RazorViewEngine));
// Set up ModelBinding
+ options.ModelBinders.Add(typeof(ServicesModelBinder));
options.ModelBinders.Add(typeof(BodyModelBinder));
options.ModelBinders.Add(new TypeConverterModelBinder());
options.ModelBinders.Add(new TypeMatchModelBinder());
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs
index 5ec9b55de5..422572019b 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs
@@ -63,7 +63,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
// Act
var response = await client.PostAsync("http://localhost/Home/GetCustomer?Id=1234", content);
-
+
//Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var customer = JsonConvert.DeserializeObject(
@@ -75,6 +75,51 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
Assert.Equal("dummy", customer.Name);
}
+ [Fact]
+ public async Task CanModelBindServiceToAnArgument()
+ {
+ // Arrange
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync("http://localhost/FromServices_Calculator/Add?left=1234&right=1");
+
+ //Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("1235", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task CanModelBindServiceToAProperty()
+ {
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(
+ "http://localhost/FromServices_Calculator/Calculate?Left=10&Right=5&Operator=*");
+
+ //Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("50", await response.Content.ReadAsStringAsync());
+ }
+
+ [Fact]
+ public async Task CanModelBindServiceToAProperty_OnBaseType()
+ {
+ var server = TestServer.Create(_services, _app);
+ var client = server.CreateClient();
+
+ // Act
+ var response = await client.GetAsync(
+ "http://localhost/FromServices_Calculator/CalculateWithPrecision?Left=10&Right=5&Operator=*");
+
+ //Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ Assert.Equal("50", await response.Content.ReadAsStringAsync());
+ }
+
[Fact]
public async Task MultipleParametersMarkedWithFromBody_Throws()
{
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RequestServicesTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RequestServicesTest.cs
index 851b7cb85e..8bf7da227e 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RequestServicesTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RequestServicesTest.cs
@@ -23,6 +23,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
[InlineData("http://localhost/Other/FromFilter")]
[InlineData("http://localhost/Other/FromView")]
[InlineData("http://localhost/Other/FromViewComponent")]
+ [InlineData("http://localhost/Other/FromModelProperty")]
+ [InlineData("http://localhost/Other/FromActionArgument")]
public async Task RequestServices(string url)
{
// Arrange
diff --git a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs
index 714ecdcf17..f6cdc38906 100644
--- a/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Test/MvcOptionSetupTest.cs
@@ -38,15 +38,17 @@ namespace Microsoft.AspNet.Mvc
setup.Configure(mvcOptions);
// Assert
- Assert.Equal(8, mvcOptions.ModelBinders.Count);
- Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[0].OptionType);
- Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[1].OptionType);
- Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[2].OptionType);
- Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[3].OptionType);
- Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[4].OptionType);
- Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[5].OptionType);
- Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[6].OptionType);
- Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[7].OptionType);
+ var i = 0;
+ Assert.Equal(9, mvcOptions.ModelBinders.Count);
+ Assert.Equal(typeof(ServicesModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(BodyModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(TypeConverterModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(TypeMatchModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(CancellationTokenModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(ByteArrayModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(GenericModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(MutableObjectModelBinder), mvcOptions.ModelBinders[i++].OptionType);
+ Assert.Equal(typeof(ComplexModelDtoModelBinder), mvcOptions.ModelBinders[i++].OptionType);
}
[Fact]
diff --git a/test/WebSites/ModelBindingWebSite/CaculatorContext.cs b/test/WebSites/ModelBindingWebSite/CaculatorContext.cs
new file mode 100644
index 0000000000..e7eab38aa0
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/CaculatorContext.cs
@@ -0,0 +1,19 @@
+// 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;
+
+namespace ModelBindingWebSite
+{
+ public class CalculatorContext
+ {
+ [FromServices]
+ public ICalculator Calculator { get; set; }
+
+ public int Left { get; set; }
+
+ public int Right { get; set; }
+
+ public char Operator { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs b/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs
index 3d67be77be..85d9d060a5 100644
--- a/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs
+++ b/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
+using Microsoft.AspNet.Mvc.ModelBinding;
namespace ModelBindingWebSite.Controllers
{
diff --git a/test/WebSites/ModelBindingWebSite/Controllers/FromServices_CalculatorController.cs b/test/WebSites/ModelBindingWebSite/Controllers/FromServices_CalculatorController.cs
new file mode 100644
index 0000000000..e2b1cbeecc
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/Controllers/FromServices_CalculatorController.cs
@@ -0,0 +1,25 @@
+// 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;
+
+namespace ModelBindingWebSite.Controllers
+{
+ public class FromServices_CalculatorController : Controller
+ {
+ public int Calculate(CalculatorContext context)
+ {
+ return context.Calculator.Operation(context.Operator, context.Left, context.Right);
+ }
+
+ public int Add(int left, int right, [FromServices] ICalculator calculator)
+ {
+ return calculator.Operation('+', left, right);
+ }
+
+ public int CalculateWithPrecision(DefaultCalculatorContext context)
+ {
+ return context.Calculator.Operation(context.Operator, context.Left, context.Right);
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/DefaultCalculator.cs b/test/WebSites/ModelBindingWebSite/DefaultCalculator.cs
new file mode 100644
index 0000000000..61cd6f9a89
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/DefaultCalculator.cs
@@ -0,0 +1,32 @@
+// 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 Microsoft.Framework.Logging;
+
+namespace ModelBindingWebSite
+{
+ public class DefaultCalculator : ICalculator
+ {
+ private ILogger _logger;
+
+ public DefaultCalculator(ILoggerFactory loggerFactory)
+ {
+ _logger = loggerFactory.Create(typeof(DefaultCalculator).FullName);
+ }
+
+ public int Operation(char @operator, int left, int right)
+ {
+ switch (@operator)
+ {
+ case '+': return left + right;
+ case '-': return left - right;
+ case '*': return left * right;
+ case '/': return left / right;
+ default:
+ _logger.WriteError("Unrecognized operator:" + @operator);
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/DefaultCalculatorContext.cs b/test/WebSites/ModelBindingWebSite/DefaultCalculatorContext.cs
new file mode 100644
index 0000000000..bf8a9c6114
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/DefaultCalculatorContext.cs
@@ -0,0 +1,12 @@
+// 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;
+
+namespace ModelBindingWebSite
+{
+ public class DefaultCalculatorContext : CalculatorContext
+ {
+ public int Precision { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/ICalculator.cs b/test/WebSites/ModelBindingWebSite/ICalculator.cs
new file mode 100644
index 0000000000..40c1952625
--- /dev/null
+++ b/test/WebSites/ModelBindingWebSite/ICalculator.cs
@@ -0,0 +1,10 @@
+// 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.
+
+namespace ModelBindingWebSite
+{
+ public interface ICalculator
+ {
+ int Operation(char @operator, int left, int right);
+ }
+}
\ No newline at end of file
diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs
index 4649eb800b..0ea1b6182b 100644
--- a/test/WebSites/ModelBindingWebSite/Startup.cs
+++ b/test/WebSites/ModelBindingWebSite/Startup.cs
@@ -5,6 +5,7 @@ using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Routing;
using Microsoft.Framework.DependencyInjection;
+using Microsoft.Framework.Logging;
namespace ModelBindingWebSite
{
@@ -24,6 +25,8 @@ namespace ModelBindingWebSite
m.MaxModelValidationErrors = 8;
m.ModelBinders.Insert(0, typeof(TestMetadataAwareBinder));
});
+
+ services.AddSingleton();
});
// Add MVC to the request pipeline
diff --git a/test/WebSites/RequestServicesWebSite/Controllers/OtherController.cs b/test/WebSites/RequestServicesWebSite/Controllers/OtherController.cs
index e48080c0e5..181d6cac50 100644
--- a/test/WebSites/RequestServicesWebSite/Controllers/OtherController.cs
+++ b/test/WebSites/RequestServicesWebSite/Controllers/OtherController.cs
@@ -39,5 +39,17 @@ namespace RequestServicesWebSite
{
return View("ViewComponent");
}
+
+ [HttpGet]
+ public string FromModelProperty(RequestModel requestContext)
+ {
+ return requestContext.RequestIdService.RequestId;
+ }
+
+ [HttpGet]
+ public string FromActionArgument([FromServices] RequestIdService requestIdService)
+ {
+ return requestIdService.RequestId;
+ }
}
}
\ No newline at end of file
diff --git a/test/WebSites/RequestServicesWebSite/Models/RequestModel.cs b/test/WebSites/RequestServicesWebSite/Models/RequestModel.cs
new file mode 100644
index 0000000000..29444be583
--- /dev/null
+++ b/test/WebSites/RequestServicesWebSite/Models/RequestModel.cs
@@ -0,0 +1,13 @@
+// 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;
+
+namespace RequestServicesWebSite
+{
+ public class RequestModel
+ {
+ [FromServices]
+ public RequestIdService RequestIdService { get; set; }
+ }
+}
\ No newline at end of file