ASP.NET Core自定义验证属性注入服务及单元测试方案咨询
Nice question! Let's tackle both parts of your problem step by step:
First off, the approach you're already using—validationContext.GetService(typeof(ILocalizer))—is the right way to go here. Unlike regular services, custom ValidationAttribute instances are often cached by the framework, so you can't inject dependencies directly via the constructor (that would cause lifecycle issues). Instead, leveraging the ValidationContext to pull services from the DI container is the recommended pattern.
To make this work reliably, just ensure two things:
- You've properly registered the
ILocalizerservice in your app's configuration (Program.cs or Startup.cs):builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); - Don't cache the
ILocalizerinstance you retrieve. Fetch it fresh every time theIsValidmethod runs to avoid problems with service scoping.
Your existing code already follows this pattern, so as long as the ILocalizer is registered, it should work as expected in your ASP.NET Core app.
When writing unit tests, you don't have the full ASP.NET Core DI container available, so you need to manually mock the ValidationContext and the services it needs to resolve. Here's how to do it with Moq (a popular mocking library), or with a simple custom implementation if you prefer not to use Moq:
Option 1: Using Moq
using Moq; using Microsoft.Extensions.Localization; using System.ComponentModel.DataAnnotations; // 1. Mock the ILocalizer to return your test error message var mockLocalizer = new Mock<ILocalizer>(); mockLocalizer.Setup(l => l.GetString(It.IsAny<string>())) .Returns("Test error message for invalid age"); // 2. Mock the IServiceProvider to return your mocked ILocalizer var mockServiceProvider = new Mock<IServiceProvider>(); mockServiceProvider.Setup(sp => sp.GetService(typeof(ILocalizer))) .Returns(mockLocalizer.Object); // 3. Create your test model (adjust to match your actual model type) var testModel = new YourModel { DateOfBirth = DateTime.UtcNow.AddYears(-16) }; // Triggers "too young" validation // 4. Build the ValidationContext with your mocked service provider var validationContext = new ValidationContext(testModel, mockServiceProvider.Object, null); // 5. Run the validation var ageValidator = new YourCustomAgeValidationAttribute(); var validationResult = ageValidator.GetValidationResult(testModel.DateOfBirth, validationContext); // 6. Assert the result Assert.NotNull(validationResult); Assert.Equal("Test error message for invalid age", validationResult.ErrorMessage);
Option 2: Custom IServiceProvider Implementation (No Moq)
If you don't want to use a mocking library, you can create a simple test-only service provider:
using Microsoft.Extensions.Localization; using System.ComponentModel.DataAnnotations; // Create a test ILocalizer implementation or use a mock var testLocalizer = new Mock<ILocalizer>().Object; // Or implement a basic ILocalizer for testing // Custom service provider that returns our test ILocalizer public class TestServiceProvider : IServiceProvider { private readonly ILocalizer _localizer; public TestServiceProvider(ILocalizer localizer) => _localizer = localizer; public object GetService(Type serviceType) { return serviceType == typeof(ILocalizer) ? _localizer : null; } } // Then use it in your test: var serviceProvider = new TestServiceProvider(testLocalizer); var validationContext = new ValidationContext(new YourModel(), serviceProvider, null); // ... rest of the test logic as above
Either approach will let you simulate the DI container in your unit tests and verify that your validation logic works as expected, including the localization of error messages.
内容的提问来源于stack exchange,提问作者Coder949




