From 328f4d648b8d921d2e9e9e3849d8afa88d56694b Mon Sep 17 00:00:00 2001 From: Derek Gray Date: Tue, 15 Dec 2015 13:35:58 -0600 Subject: [PATCH] Allow ValidationAttributes and IValidatableObjects to resolve services from the RequestServices provider by injecting it into the ValidationContext. --- .../DataAnnotationsModelValidator.cs | 5 +- .../ValidatableObjectAdapter.cs | 6 ++- .../Validation/DefaultObjectValidatorTests.cs | 50 +++++++++++++++++ .../DataAnnotationsModelValidatorTest.cs | 53 ++++++++++++++++++- 4 files changed, 111 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs index c29cd8e5f7..11c7320141 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/DataAnnotationsModelValidator.cs @@ -82,7 +82,10 @@ public IEnumerable Validate(ModelValidationContext valida var memberName = metadata.PropertyName ?? metadata.ModelType.Name; var container = validationContext.Container; - var context = new ValidationContext(container ?? validationContext.Model) + var context = new ValidationContext( + instance: container ?? validationContext.Model, + serviceProvider: validationContext.ActionContext?.HttpContext?.RequestServices, + items: null) { DisplayName = metadata.GetDisplayName(), MemberName = memberName diff --git a/src/Microsoft.AspNet.Mvc.DataAnnotations/ValidatableObjectAdapter.cs b/src/Microsoft.AspNet.Mvc.DataAnnotations/ValidatableObjectAdapter.cs index fad8f98f7e..c6a4dd9b3a 100644 --- a/src/Microsoft.AspNet.Mvc.DataAnnotations/ValidatableObjectAdapter.cs +++ b/src/Microsoft.AspNet.Mvc.DataAnnotations/ValidatableObjectAdapter.cs @@ -29,7 +29,11 @@ public IEnumerable Validate(ModelValidationContext contex throw new InvalidOperationException(message); } - var validationContext = new ValidationContext(validatable, serviceProvider: null, items: null); + var validationContext = new ValidationContext( + instance: validatable, + serviceProvider: context.ActionContext?.HttpContext?.RequestServices, + items: null); + return ConvertResults(validatable.Validate(validationContext)); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Validation/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Validation/DefaultObjectValidatorTests.cs index 7cdf56a6fd..f7dca4eb22 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Validation/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ModelBinding/Validation/DefaultObjectValidatorTests.cs @@ -8,8 +8,10 @@ #if DNXCORE50 using System.Reflection; #endif +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.ModelBinding.Metadata; using Microsoft.AspNet.Testing; +using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; @@ -431,6 +433,49 @@ public void Validate_ComplexType_IValidatableObject_Invalid() Assert.Equal("Error3", error.ErrorMessage); } + [Fact] + public void Validate_ComplexType_IValidatableObject_CanUseRequestServices() + { + // Arrange + var service = new Mock(); + service.Setup(x => x.DoSomething()).Verifiable(); + + var provider = new ServiceCollection().AddSingleton(service.Object).BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(x => x.RequestServices).Returns(provider); + + var actionContext = new ActionContext { HttpContext = httpContext.Object }; + + var validatorProvider = CreateValidatorProvider(); + var modelState = actionContext.ModelState; + var validationState = new ValidationStateDictionary(); + + var validator = CreateValidator(); + + var model = new Mock(); + model + .Setup(x => x.Validate(It.IsAny())) + .Callback((ValidationContext context) => + { + var receivedService = context.GetService(); + Assert.Equal(service.Object, receivedService); + receivedService.DoSomething(); + }) + .Returns(new List()); + + // Act + validator.Validate( + actionContext, + validatorProvider, + validationState, + null, + model.Object); + + // Assert + service.Verify(); + } + [Fact] [ReplaceCulture] public void Validate_ComplexType_FieldsAreIgnored_Valid() @@ -1071,5 +1116,10 @@ public IEnumerable Validate(ValidationContext validationContex } } } + + public interface IExampleService + { + void DoSomething(); + } } } diff --git a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs index 1e4323a882..b3653936b7 100644 --- a/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorTest.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.DataAnnotations; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Moq; using Xunit; @@ -152,7 +154,7 @@ public void Validate_Invalid() } [Fact] - public void Validatate_ValidationResultSuccess() + public void Validate_ValidationResultSuccess() { // Arrange var metadata = _metadataProvider.GetMetadataForType(typeof(string)); @@ -315,6 +317,50 @@ public void Validate_IsValidFalse_StringLocalizerReturnsLocalizerErrorMessage() Assert.Equal("Longueur est invalide : 4", validationResult.Message); } + [Fact] + public void Validate_CanUseRequestServices_WithinValidationAttribute() + { + // Arrange + var service = new Mock(); + service.Setup(x => x.DoSomething()).Verifiable(); + + var provider = new ServiceCollection().AddSingleton(service.Object).BuildServiceProvider(); + + var httpContext = new Mock(); + httpContext.SetupGet(x => x.RequestServices).Returns(provider); + + var attribute = new Mock { CallBase = true }; + attribute + .Setup(p => p.IsValidPublic(It.IsAny(), It.IsAny())) + .Callback((object o, ValidationContext context) => + { + var receivedService = context.GetService(); + Assert.Equal(service.Object, receivedService); + receivedService.DoSomething(); + }); + + var validator = new DataAnnotationsModelValidator( + new ValidationAttributeAdapterProvider(), + attribute.Object, + stringLocalizer: null); + + var validationContext = new ModelValidationContext( + actionContext: new ActionContext + { + HttpContext = httpContext.Object + }, + modelMetadata: _metadataProvider.GetMetadataForType(typeof(object)), + metadataProvider: _metadataProvider, + container: null, + model: new object()); + + // Act + var results = validator.Validate(validationContext); + + // Assert + service.Verify(); + } + private const string LocalizationKey = "LocalizeIt"; public static TheoryData Validate_AttributesIncludeValues @@ -430,5 +476,10 @@ private class SampleModel { public string Name { get; set; } } + + public interface IExampleService + { + void DoSomething(); + } } }