Skip to content

Commit dbba52c

Browse files
committed
[Validation]: add localization tests + docs + DI package for tests
- 3 new tests: default (no localizer), with TestLocalizer, null fallback - Docs: localization section in ASP.NET integration page - Test project gets Microsoft.Extensions.DependencyInjection reference 70 tests pass.
1 parent 864d1bf commit dbba52c

3 files changed

Lines changed: 110 additions & 0 deletions

File tree

docs/src/content/docs/packages/validation/aspnet.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,44 @@ app.MapPost("/api/users", (UserDto dto) =>
8888

8989
> Note: `.WithValidation()` calls `Validate()` with no ruleset (validates all). For selective validation, call manually in the handler.
9090
91+
## Localization (i18n)
92+
93+
Register an `IValidationLocalizer` in DI — all generated messages pass through it automatically:
94+
95+
```csharp
96+
// 1. Implement the interface:
97+
public class PolishLocalizer : IValidationLocalizer
98+
{
99+
private readonly IStringLocalizer<SharedResources> _loc;
100+
101+
public PolishLocalizer(IStringLocalizer<SharedResources> loc) => _loc = loc;
102+
103+
public string? GetMessage(string property, string defaultMessage)
104+
{
105+
var localized = _loc[defaultMessage];
106+
return localized.ResourceNotFound ? null : localized.Value;
107+
}
108+
}
109+
110+
// 2. Register in DI:
111+
builder.Services.AddSingleton<IValidationLocalizer, PolishLocalizer>();
112+
113+
// 3. Call once at startup:
114+
app.Services.ConfigureValidation();
115+
```
116+
117+
That's it. `.WithValidation()` auto-resolves the localizer. Manual `Validate()` also uses it (resolved from the static `ValidationServiceProvider`).
118+
119+
Without `IValidationLocalizer` in DI → default English messages (zero breaking change).
120+
121+
## Setup
122+
123+
```csharp
124+
var app = builder.Build();
125+
app.Services.ConfigureValidation(); // bridges DI → validation runtime (for localization)
126+
app.Services.ConfigureAop(); // if using [Log]/[Trace] etc.
127+
```
128+
91129
## Requirements
92130

93131
`.WithValidation()` is auto-generated when your project references `Microsoft.AspNetCore.Http` (Web SDK projects). Non-web projects (class libraries, test projects) don't get this extension method — it's conditionally emitted.

packages/ZibStack.NET.Validation/tests/ZibStack.NET.Validation.Tests/ValidationTests.cs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Microsoft.Extensions.DependencyInjection;
12
using Xunit;
23
using ZibStack.NET.Validation;
34

@@ -698,4 +699,74 @@ public void RuleSet_ValidForCreate_NoError()
698699
var result = obj.Validate(null, "Create");
699700
Assert.True(result.IsValid);
700701
}
702+
703+
// ── Localization ─────────────────────────────────────────────────
704+
705+
[Fact]
706+
public void Localization_WithoutLocalizer_ReturnsDefaultMessage()
707+
{
708+
// No localizer registered → default English messages
709+
ValidationServiceProvider.ServiceProvider = null;
710+
var obj = new CascadeTest { Name = "" };
711+
var result = obj.Validate();
712+
713+
Assert.Contains(result.Errors, e => e.Contains("is required"));
714+
}
715+
716+
[Fact]
717+
public void Localization_WithLocalizer_ReturnsLocalizedMessage()
718+
{
719+
// Register a test localizer
720+
var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
721+
services.AddSingleton<IValidationLocalizer>(new TestLocalizer());
722+
var sp = services.BuildServiceProvider();
723+
ValidationServiceProvider.ServiceProvider = sp;
724+
725+
try
726+
{
727+
var obj = new CascadeTest { Name = "" };
728+
var result = obj.Validate();
729+
730+
Assert.Contains(result.Errors, e => e.Contains("LOCALIZED:"));
731+
}
732+
finally
733+
{
734+
ValidationServiceProvider.ServiceProvider = null;
735+
}
736+
}
737+
738+
[Fact]
739+
public void Localization_ReturnsNull_FallsBackToDefault()
740+
{
741+
var services = new Microsoft.Extensions.DependencyInjection.ServiceCollection();
742+
services.AddSingleton<IValidationLocalizer>(new NullLocalizer());
743+
var sp = services.BuildServiceProvider();
744+
ValidationServiceProvider.ServiceProvider = sp;
745+
746+
try
747+
{
748+
var obj = new CascadeTest { Name = "" };
749+
var result = obj.Validate();
750+
751+
// NullLocalizer returns null → falls back to default
752+
Assert.Contains(result.Errors, e => e.Contains("is required"));
753+
}
754+
finally
755+
{
756+
ValidationServiceProvider.ServiceProvider = null;
757+
}
758+
}
759+
}
760+
761+
// Test localizers
762+
internal class TestLocalizer : IValidationLocalizer
763+
{
764+
public string? GetMessage(string property, string defaultMessage)
765+
=> $"LOCALIZED: {defaultMessage}";
766+
}
767+
768+
internal class NullLocalizer : IValidationLocalizer
769+
{
770+
public string? GetMessage(string property, string defaultMessage)
771+
=> null; // fall back to default
701772
}

packages/ZibStack.NET.Validation/tests/ZibStack.NET.Validation.Tests/ZibStack.NET.Validation.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
1213
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
1314
<PackageReference Include="xunit" Version="2.9.3" />
1415
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />

0 commit comments

Comments
 (0)