Adding FromServicesAttribute.

This commit is contained in:
Harsh Gupta 2014-11-17 20:09:14 -08:00
parent ff3282827a
commit d2aff42e25
17 changed files with 245 additions and 11 deletions

View File

@ -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
{
/// <summary>
/// An <see cref="IModelBinder"/> which understands <see cref="IServiceActivatorBinderMetadata"/>
/// and activates a given model using <see cref="IServiceProvider"/>.
/// </summary>
public class ServicesModelBinder : MetadataAwareBinder<IServiceActivatorBinderMetadata>
{
/// <inheritdoc />
protected override Task<bool> BindAsync(
[NotNull] ModelBindingContext bindingContext,
[NotNull] IServiceActivatorBinderMetadata metadata)
{
var requestServices = bindingContext.OperationBindingContext.HttpContext.RequestServices;
bindingContext.Model = requestServices.GetRequiredService(bindingContext.ModelType);
return Task.FromResult(true);
}
}
}

View File

@ -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
{
/// <summary>
/// This attribute is used on action parameters or model properties to indicate that
/// they will be bound using service provider.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public class FromServicesAttribute : Attribute, IServiceActivatorBinderMetadata
{
}
}

View File

@ -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
{
/// <summary>
/// Metadata interface that indicates model binding should use the service container to get the value for a model.
/// </summary>
public interface IServiceActivatorBinderMetadata : IBinderMetadata
{
}
}

View File

@ -19,7 +19,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
/// <param name="bindingContext">The binding context which has the object to be bound.</param>
/// <param name="metadata">The <see cref="IBinderMetadata"/> associated with the current binder.</param>
/// <returns>A Task with a bool implying the success or failure of the operation.</returns>
protected abstract Task<bool> BindAsync(ModelBindingContext bindingContext, TBinderMetadata metadata);
protected abstract Task<bool> BindAsync([NotNull] ModelBindingContext bindingContext,
[NotNull] TBinderMetadata metadata);
public Task<bool> BindModelAsync(ModelBindingContext context)
{

View File

@ -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());

View File

@ -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<Customer>(
@ -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()
{

View File

@ -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

View File

@ -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]

View File

@ -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; }
}
}

View File

@ -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
{

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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<ICalculator, DefaultCalculator>();
});
// Add MVC to the request pipeline

View File

@ -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;
}
}
}

View File

@ -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; }
}
}