Limit [FromServices] to apply only to parameters

Fixes #3507
This commit is contained in:
Pranav K 2015-11-16 13:37:45 -08:00
parent db94a8ed19
commit b520b0cb22
28 changed files with 113 additions and 439 deletions

View File

@ -93,13 +93,12 @@ namespace MvcSample.Web
return View("MyView", user);
}
[FromServices]
public IHostingEnvironment HostingEnvironment { get; set; }
/// <summary>
/// Action that shows multiple file upload.
/// </summary>
public async Task<ActionResult> PostFile(IList<IFormFile> files)
public async Task<ActionResult> PostFile(
[FromServices] IHostingEnvironment hostingEnvironment,
IList<IFormFile> files)
{
if (!ModelState.IsValid)
{
@ -108,7 +107,7 @@ namespace MvcSample.Web
foreach (var f in files)
{
await f.SaveAsAsync(Path.Combine(HostingEnvironment.WebRootPath, "test-file" + files.IndexOf(f)));
await f.SaveAsAsync(Path.Combine(hostingEnvironment.WebRootPath, "test-file" + files.IndexOf(f)));
}
return View();
}

View File

@ -7,25 +7,9 @@ using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Specifies that a parameter or property should be bound using the request services.
/// Specifies that an action parameter should be bound using the request services.
/// </summary>
/// <example>
/// In this example, the LocationService property on the VehicleWithDealerViewModel class
/// will be bound to the value resolved for the ILocationService service from the request services.
///
/// <code>
/// public class VehicleWithDealerViewModel
/// {
/// [FromServices]
/// public ILocationService LocationService { get; set; }
///
/// public void Update()
/// {
/// LocationService.Update();
/// }
/// }
/// </code>
///
/// In this example an implementation of IProductModelRequestService is registered as a service.
/// Then in the GetProduct action, the parameter is bound to an instance of IProductModelRequestService
/// which is resolved from the the request services.
@ -38,10 +22,10 @@ namespace Microsoft.AspNet.Mvc
/// }
/// </code>
/// </example>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class FromServicesAttribute : Attribute, IBindingSourceMetadata
{
/// <inheritdoc />
public BindingSource BindingSource { get { return BindingSource.Services; } }
public BindingSource BindingSource => BindingSource.Services;
}
}

View File

@ -10,6 +10,7 @@ using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Mvc.ModelBinding.Validation;
using Microsoft.AspNet.Mvc.WebApiCompatShim;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers;
using Newtonsoft.Json;
@ -22,6 +23,9 @@ namespace System.Web.Http
public abstract class ApiController : IDisposable
{
private HttpRequestMessage _request;
private IModelMetadataProvider _metadataProvider;
private IObjectModelValidator _objectValidator;
private IUrlHelper _urlHelper;
/// <summary>
/// Gets the action context.
@ -52,12 +56,42 @@ namespace System.Web.Http
/// Gets the <see cref="IModelMetadataProvider"/>.
/// </summary>
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
[FromServices]
public IModelMetadataProvider MetadataProvider { get; set; }
public IModelMetadataProvider MetadataProvider
{
get
{
if (_metadataProvider == null)
{
_metadataProvider = Context?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
}
return _metadataProvider;
}
set
{
_metadataProvider = value;
}
}
[FromServices]
public IObjectModelValidator ObjectValidator { get; set; }
/// <summary>
/// Gets or sets the <see cref="IObjectModelValidator"/>.
/// </summary>
public IObjectModelValidator ObjectValidator
{
get
{
if (_objectValidator == null)
{
_objectValidator = Context?.RequestServices?.GetRequiredService<IObjectModelValidator>();
}
return _objectValidator;
}
set
{
_objectValidator = value;
}
}
/// <summary>
/// Gets model state after the model binding process. This ModelState will be empty before model binding
@ -96,8 +130,22 @@ namespace System.Web.Http
/// Gets a factory used to generate URLs to other APIs.
/// </summary>
/// <remarks>The setter is intended for unit testing purposes only.</remarks>
[FromServices]
public IUrlHelper Url { get; set; }
public IUrlHelper Url
{
get
{
if (_urlHelper == null)
{
_urlHelper = Context?.RequestServices?.GetRequiredService<IUrlHelper>();
}
return _urlHelper;
}
set
{
_urlHelper = value;
}
}
/// <summary>
/// Gets or sets the current principal associated with this request.

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(BaseViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "BaseProperty");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.BaseProperty));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
@ -34,7 +34,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(BaseViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "TestProperty");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.TestProperty));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(DerivedViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "BaseProperty");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.BaseProperty));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(DerivedViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "TestProperty");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.TestProperty));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(DerivedViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "VirtualProperty");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.VirtualProperty));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
@ -120,17 +120,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
{
// Arrange
var modelType = typeof(DerivedViewModel);
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == "Calculator");
var property = modelType.GetRuntimeProperties().FirstOrDefault(p => p.Name == nameof(BaseModel.RouteValue));
// Act
var attributes = ModelAttributes.GetAttributesForProperty(modelType, property);
// Assert
Assert.Single(attributes.Attributes.OfType<RequiredAttribute>());
Assert.Single(attributes.Attributes.OfType<FromServicesAttribute>());
Assert.Single(attributes.Attributes.OfType<FromRouteAttribute>());
Assert.Single(attributes.PropertyAttributes.OfType<RequiredAttribute>());
Assert.Single(attributes.PropertyAttributes.OfType<FromServicesAttribute>());
Assert.Single(attributes.PropertyAttributes.OfType<FromRouteAttribute>());
}
[Fact]
@ -191,8 +191,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
[Required]
public virtual int VirtualProperty { get; set; }
[FromServices]
public ICalculator Calculator { get; set; }
[FromRoute]
public string RouteValue { get; set; }
}
private class DerivedModel : BaseModel
@ -218,7 +218,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding
public string BaseProperty { get; set; }
[Required]
public ICalculator Calculator { get; set; }
public string RouteValue { get; set; }
}
[ModelMetadataType(typeof(DerivedModel))]

View File

@ -25,7 +25,6 @@ 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)
{
@ -40,6 +39,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
var response = await Client.SendAsync(request);
// Assert
response.EnsureSuccessStatusCode();
var body = (await response.Content.ReadAsStringAsync()).Trim();
Assert.Equal(requestId, body);
}

View File

@ -258,176 +258,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
Assert.True(modelState.IsValid);
}
private class Order2
{
public int ProductId { get; set; }
public Person2 Customer { get; set; }
}
private class Person2
{
public string Name { get; set; }
[FromServices]
public IActionBindingContextAccessor BindingContext { get; set; }
}
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_Success()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Order2)
};
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter.Customer.Name=bill");
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Order2>(modelBindingResult.Model);
Assert.NotNull(model.Customer);
Assert.Equal("bill", model.Customer.Name);
Assert.NotNull(model.Customer.BindingContext);
Assert.Equal(1, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter.Customer.Name").Value;
Assert.Equal("bill", entry.AttemptedValue);
Assert.Equal("bill", entry.RawValue);
}
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithEmptyPrefix_Success()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Order2)
};
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?Customer.Name=bill");
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Order2>(modelBindingResult.Model);
Assert.NotNull(model.Customer);
Assert.Equal("bill", model.Customer.Name);
Assert.NotNull(model.Customer.BindingContext);
Assert.Equal(1, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "Customer.Name").Value;
Assert.Equal("bill", entry.AttemptedValue);
Assert.Equal("bill", entry.RawValue);
}
// We don't provide enough data in this test for the 'Person' model to be created. So even though there is
// a [FromServices], it won't be used.
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_PartialData()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Order2)
};
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?parameter.ProductId=10");
SetJsonBodyContent(request, AddressBodyContent);
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Order2>(modelBindingResult.Model);
Assert.Null(model.Customer);
Assert.Equal(10, model.ProductId);
Assert.Equal(1, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
var entry = Assert.Single(modelState, e => e.Key == "parameter.ProductId").Value;
Assert.Equal("10", entry.AttemptedValue);
Assert.Equal("10", entry.RawValue);
}
// We don't provide enough data in this test for the 'Person' model to be created. So even though there is
// a [FromServices], it won't be used.
[Fact]
public async Task MutableObjectModelBinder_BindsNestedPOCO_WithServicesModelBinder_WithPrefix_NoData()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "parameter",
ParameterType = typeof(Order2)
};
// Need to have a key here so that the MutableObjectModelBinder will recurse to bind elements.
var operationContext = ModelBindingTestHelper.GetOperationBindingContext(request =>
{
request.QueryString = new QueryString("?");
SetJsonBodyContent(request, AddressBodyContent);
});
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
Assert.True(modelBindingResult.IsModelSet);
var model = Assert.IsType<Order2>(modelBindingResult.Model);
Assert.Null(model.Customer);
Assert.Equal(0, modelState.Count);
Assert.Equal(0, modelState.ErrorCount);
Assert.True(modelState.IsValid);
}
private class Order3
{
public int ProductId { get; set; }
@ -1489,9 +1319,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
[FromBody]
public Address1 Address { get; set; }
[FromServices]
public IActionBindingContextAccessor BindingContext { get; set; }
}
// If a nested POCO object has all properties bound from a greedy source, then it should be populated
@ -1524,7 +1351,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
var model = Assert.IsType<Order9>(modelBindingResult.Model);
Assert.NotNull(model.Customer);
Assert.NotNull(model.Customer.BindingContext);
Assert.NotNull(model.Customer.Address);
Assert.Equal(AddressStreetContent, model.Customer.Address.Street);

View File

@ -13,90 +13,6 @@ namespace Microsoft.AspNet.Mvc.IntegrationTests
{
public class ServicesModelBinderIntegrationTest
{
private class Person
{
public Address Address { get; set; }
}
private class Address
{
// Using a service type already in defaults.
[FromServices]
public JsonOutputFormatter OutputFormatter { get; set; }
}
[Fact]
public async Task BindPropertyFromService_WithData_WithPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo()
{
BinderModelName = "CustomParameter",
},
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address.OutputFormatter);
// ModelState
Assert.True(modelState.IsValid);
Assert.Empty(modelState.Keys);
}
[Fact]
public async Task BindPropertyFromService_WithData_WithEmptyPrefix_GetsBound()
{
// Arrange
var argumentBinder = ModelBindingTestHelper.GetArgumentBinder();
var parameter = new ParameterDescriptor()
{
Name = "Parameter1",
BindingInfo = new BindingInfo(),
ParameterType = typeof(Person)
};
var operationContext = ModelBindingTestHelper.GetOperationBindingContext();
var modelState = new ModelStateDictionary();
// Act
var modelBindingResult = await argumentBinder.BindModelAsync(parameter, modelState, operationContext);
// Assert
// ModelBindingResult
Assert.True(modelBindingResult.IsModelSet);
// Model
var boundPerson = Assert.IsType<Person>(modelBindingResult.Model);
Assert.NotNull(boundPerson);
Assert.NotNull(boundPerson.Address.OutputFormatter);
// ModelState
Assert.True(modelState.IsValid);
// For non user bound models there should be no entry in model state.
Assert.Empty(modelState);
}
[Fact]
public async Task BindParameterFromService_WithData_GetsBound()
{

View File

@ -10,9 +10,12 @@ namespace ActionResultsWebSite
{
public class ActionResultsVerificationController : Controller
{
public ActionResultsVerificationController(GuidLookupService guidLookupService)
{
GuidLookupService = guidLookupService;
}
[FromServices]
public GuidLookupService Service { get; set; }
public GuidLookupService GuidLookupService { get; }
public IActionResult Index([FromBody]DummyClass test)
{
@ -107,7 +110,7 @@ namespace ActionResultsWebSite
public bool GetDisposeCalled(string guid)
{
bool value;
if (Service.IsDisposed.TryGetValue(guid, out value))
if (GuidLookupService.IsDisposed.TryGetValue(guid, out value))
{
return value;
}
@ -131,7 +134,7 @@ namespace ActionResultsWebSite
private DisposableType CreateDisposableType(string guid)
{
return new DisposableType(Service, guid);
return new DisposableType(GuidLookupService, guid);
}
private class DisposableType : IDisposable

View File

@ -8,9 +8,6 @@ namespace ActivatorWebSite
{
public class PlainController
{
[FromServices]
public MyService Service { get; set; }
[ActionContext]
public ActionContext ActionContext { get; set; }
@ -18,12 +15,12 @@ namespace ActivatorWebSite
public HttpResponse Response => ActionContext.HttpContext.Response;
public IActionResult Index()
public IActionResult Index([FromServices] MyService service)
{
Response.Headers["X-Fake-Header"] = "Fake-Value";
var value = Request.Query["foo"];
return new ContentResult { Content = Service.Random + "|" + value };
return new ContentResult { Content = service.Random + "|" + value };
}
}
}

View File

@ -1,9 +0,0 @@
// 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.
namespace ApiExplorerWebSite
{
public interface IOrderRepository
{
}
}

View File

@ -7,9 +7,6 @@ namespace ApiExplorerWebSite
{
public class OrderDTO
{
[FromServices]
public IOrderRepository Repository { get; set; }
public string CustomerId { get; set; }
[FromHeader(Name = "Referrer")]

View File

@ -8,9 +8,6 @@ namespace HtmlGenerationWebSite.Controllers
{
public class Catalog_CacheTagHelperController : Controller
{
[FromServices]
public ProductsService ProductsService { get; set; }
[HttpGet("/catalog")]
public IActionResult Splash(int categoryId, int correlationId, [FromHeader] string locale)
{
@ -75,9 +72,9 @@ namespace HtmlGenerationWebSite.Controllers
}
[HttpPost("/categories/update-products")]
public void UpdateCategories()
public void UpdateCategories([FromServices] ProductsService productsService)
{
ProductsService.UpdateProducts();
productsService.UpdateProducts();
}
[HttpGet("/catalog/GetDealPercentage/{dealPercentage}")]

View File

@ -7,9 +7,6 @@ namespace ModelBindingWebSite
{
public class CalculatorContext
{
[FromServices]
public ICalculator Calculator { get; set; }
public int Left { get; set; }
public int Right { get; set; }

View File

@ -7,9 +7,9 @@ namespace ModelBindingWebSite.Controllers
{
public class FromServices_CalculatorController : Controller
{
public int Calculate(CalculatorContext context)
public int Calculate(CalculatorContext context, [FromServices] ICalculator calculator)
{
return context.Calculator.Operation(context.Operator, context.Left, context.Right);
return calculator.Operation(context.Operator, context.Left, context.Right);
}
public int Add(int left, int right, [FromServices] ICalculator calculator)
@ -17,9 +17,9 @@ namespace ModelBindingWebSite.Controllers
return calculator.Operation('+', left, right);
}
public int CalculateWithPrecision(DefaultCalculatorContext context)
public int CalculateWithPrecision(DefaultCalculatorContext context, [FromServices] ICalculator calculator)
{
return context.Calculator.Operation(context.Operator, context.Left, context.Right);
return calculator.Operation(context.Operator, context.Left, context.Right);
}
}
}

View File

@ -18,7 +18,11 @@ namespace ModelBindingWebSite.Controllers
private IHtmlHelper<Person> _personHelper;
private bool _activated;
[FromServices]
public RoundtripController(IHtmlHelper<Person> personHelper)
{
_personHelper = personHelper;
}
public IHtmlHelper<Person> PersonHelper
{
get
@ -35,15 +39,11 @@ namespace ModelBindingWebSite.Controllers
TextWriter.Null,
new HtmlHelperOptions());
((ICanHasViewContext)PersonHelper).Contextualize(context);
((ICanHasViewContext)_personHelper).Contextualize(context);
}
return _personHelper;
}
set
{
_personHelper = value;
}
}
public string GetPerson()

View File

@ -8,9 +8,6 @@ namespace ModelBindingWebSite.Controllers
[Route("Validation/[Action]")]
public class ValidationController : Controller
{
[FromServices]
public ITestService ControllerService { get; set; }
public object AvoidRecursive(SelfishPerson selfishPerson)
{
return ModelState.IsValid;

View File

@ -59,7 +59,6 @@ namespace ModelBindingWebSite
return PartialView("UpdateVehicle", model);
}
model.Update();
return PartialView("UpdateSuccessful", model);
}

View File

@ -1,15 +1,10 @@
// 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.DataAnnotations;
namespace ModelBindingWebSite
{
public interface ITestService
{
[Required]
string NeverBound { get; set; }
bool Test();
}
}

View File

@ -1,14 +0,0 @@
// 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 ModelBindingWebSite.ViewModels;
namespace ModelBindingWebSite.Services
{
public interface ILocationService
{
bool IsValidMakeForRegion(string make, string region);
bool Update(VehicleWithDealerViewModel model);
}
}

View File

@ -1,28 +0,0 @@
// 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 ModelBindingWebSite.ViewModels;
namespace ModelBindingWebSite.Services
{
public class LocationService : ILocationService
{
public bool Update(VehicleWithDealerViewModel viewModel)
{
return true;
}
public bool IsValidMakeForRegion(string make, string region)
{
switch (make)
{
case "Acme":
return region == "NW" || "region" == "South Central";
case "FastCars":
return region != "Central";
}
return true;
}
}
}

View File

@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.Extensions.DependencyInjection;
using ModelBindingWebSite.Models;
using ModelBindingWebSite.Services;
@ -32,7 +31,6 @@ namespace ModelBindingWebSite
services.AddSingleton<ITestService, TestService>();
services.AddTransient<IVehicleService, VehicleService>();
services.AddTransient<ILocationService, LocationService>();
}
public void Configure(IApplicationBuilder app)

View File

@ -1,18 +1,10 @@
// 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.DataAnnotations;
namespace ModelBindingWebSite
{
public class TestService : ITestService
{
[Required]
public string NeverBound { get; set; }
public bool Test()
{
return true;
}
public bool Test() => true;
}
}

View File

@ -38,8 +38,9 @@ namespace ModelBindingWebSite.ViewModels
{
if (InspectedDates.Any(d => d.Year > Year))
{
yield return new ValidationResult("Inspection date cannot be later than year of manufacture.",
new[] { nameof(InspectedDates) });
yield return new ValidationResult(
"Inspection date cannot be later than year of manufacture.",
new[] { nameof(InspectedDates) });
}
}

View File

@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNet.Mvc;
using ModelBindingWebSite.Services;
namespace ModelBindingWebSite.ViewModels
{
@ -17,23 +16,28 @@ namespace ModelBindingWebSite.ViewModels
[FromBody]
public VehicleViewModel Vehicle { get; set; }
[FromServices]
public ILocationService LocationService { get; set; }
[FromHeader(Name = "X-TrackingId")]
public string TrackingId { get; set; } = "default-tracking-id";
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (!LocationService.IsValidMakeForRegion(Vehicle.Make, Dealer.Location))
if (!IsValidMakeForRegion(Vehicle.Make, Dealer.Location))
{
yield return new ValidationResult("Make is invalid for region.");
}
}
public void Update()
public bool IsValidMakeForRegion(string make, string region)
{
LocationService.Update(this);
switch (make)
{
case "Acme":
return region == "NW" || "region" == "South Central";
case "FastCars":
return region != "Central";
}
return true;
}
}
}

View File

@ -40,12 +40,6 @@ namespace RequestServicesWebSite
return View("ViewComponent");
}
[HttpGet]
public string FromModelProperty(RequestModel requestContext)
{
return requestContext.RequestIdService.RequestId;
}
[HttpGet]
public string FromActionArgument([FromServices] RequestIdService requestIdService)
{

View File

@ -8,13 +8,10 @@ namespace RequestServicesWebSite
[Route("RequestScoped/[action]")]
public class RequestScopedServiceController
{
[FromServices]
public RequestIdService RequestIdService { get; set; }
[HttpGet]
public string FromController()
public string FromController([FromServices] RequestIdService requestIdService)
{
return RequestIdService.RequestId;
return requestIdService.RequestId;
}
}
}

View File

@ -1,13 +0,0 @@
// 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 Microsoft.AspNet.Mvc;
namespace RequestServicesWebSite
{
public class RequestModel
{
[FromServices]
public RequestIdService RequestIdService { get; set; }
}
}

View File

@ -15,9 +15,6 @@ namespace WebApiCompatShimWebSite
{
public class BasicApiController : ApiController
{
[FromServices]
public IOptions<WebApiCompatShimOptions> OptionsAccessor { get; set; }
// Verifies property activation
[HttpGet]
public async Task<IActionResult> WriteToHttpContext()
@ -43,9 +40,9 @@ namespace WebApiCompatShimWebSite
// Verifies the default options configure formatters correctly.
[HttpGet]
public string[] GetFormatters()
public string[] GetFormatters([FromServices] IOptions<WebApiCompatShimOptions> optionsAccessor)
{
return OptionsAccessor.Value.Formatters.Select(f => f.GetType().FullName).ToArray();
return optionsAccessor.Value.Formatters.Select(f => f.GetType().FullName).ToArray();
}
[HttpGet]