Use FluentValidation rules instead of ComponentModel attributes to define swagger schema.
Note: For WebApi see: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation.WebApi
<PackageReference Include="FluentValidation.AspNetCore" Version="8.5.0" />
<PackageReference Include="MicroElements.Swashbuckle.FluentValidation" Version="3.1.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.2.0" />
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// HttpContextServiceProviderValidatorFactory requires access to HttpContext
services.AddHttpContextAccessor();
services
.AddControllers()
// Adds fluent validators to Asp.net
.AddFluentValidation(c =>
{
c.RegisterValidatorsFromAssemblyContaining<Startup>();
// Optionally set validator factory if you have problems with scope resolve inside validators.
c.ValidatorFactoryType = typeof(HttpContextServiceProviderValidatorFactory);
})
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
// Adds fluent validation rules to swagger
c.AddFluentValidationRules();
});
// Adds logging
services.AddLogging(builder => builder.AddConsole());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
// Adds swagger
app.UseSwagger();
// Adds swagger UI
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
MicroElements.Swashbuckle.FluentValidation | Swashbuckle.AspNetCore | FluentValidation |
---|---|---|
[1.1.0, 2.0.0) | [3.0.0, 4.0.0) | >=7.2.0 |
[2.0.0, 3.0.0) | [4.0.0, 5.0.0) | >=8.1.3 |
[3.0.0, 3.1.0) | [5.0.0, 5.2.0) | >=8.3.0 |
[3.1.0, 4.0.0) | [5.2.0, 6.0.0) | >=8.3.0 |
See sample project: https://github.com/micro-elements/MicroElements.Swashbuckle.FluentValidation/tree/master/src/SampleWebApi
- INotNullValidator (NotNull)
- INotEmptyValidator (NotEmpty)
- ILengthValidator (Length, MinimumLength, MaximumLength, ExactLength)
- IRegularExpressionValidator (Email, Matches)
- IComparisonValidator (GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual)
- IBetweenValidator (InclusiveBetween, ExclusiveBetween)
You can register FluentValidationRule in ServiceCollection.
User defined rule name replaces default rule with the same.
Full list of default rules can be get by FluentValidationRules.CreateDefaultRules()
List or default rules:
- Required
- NotEmpty
- Length
- Pattern
- Comparison
- Between
Example of rule:
new FluentValidationRule("Pattern")
{
Matches = propertyValidator => propertyValidator is IRegularExpressionValidator,
Apply = context =>
{
var regularExpressionValidator = (IRegularExpressionValidator)context.PropertyValidator;
context.Schema.Properties[context.PropertyKey].Pattern = regularExpressionValidator.Expression;
}
},
public class Sample
{
public string PropertyWithNoRules { get; set; }
public string NotNull { get; set; }
public string NotEmpty { get; set; }
public string EmailAddress { get; set; }
public string RegexField { get; set; }
public int ValueInRange { get; set; }
public int ValueInRangeExclusive { get; set; }
public float ValueInRangeFloat { get; set; }
public double ValueInRangeDouble { get; set; }
}
public class SampleValidator : AbstractValidator<Sample>
{
public SampleValidator()
{
RuleFor(sample => sample.NotNull).NotNull();
RuleFor(sample => sample.NotEmpty).NotEmpty();
RuleFor(sample => sample.EmailAddress).EmailAddress();
RuleFor(sample => sample.RegexField).Matches(@"(\d{4})-(\d{2})-(\d{2})");
RuleFor(sample => sample.ValueInRange).GreaterThanOrEqualTo(5).LessThanOrEqualTo(10);
RuleFor(sample => sample.ValueInRangeExclusive).GreaterThan(5).LessThan(10);
// WARNING: Swashbuckle implements minimum and maximim as int so you will loss fraction part of float and double numbers
RuleFor(sample => sample.ValueInRangeFloat).InclusiveBetween(1.1f, 5.3f);
RuleFor(sample => sample.ValueInRangeDouble).ExclusiveBetween(2.2, 7.5f);
}
}
public class CustomerValidator : AbstractValidator<Customer>
{
public CustomerValidator()
{
RuleFor(customer => customer.Surname).NotEmpty();
RuleFor(customer => customer.Forename).NotEmpty().WithMessage("Please specify a first name");
Include(new CustomerAddressValidator());
}
}
internal class CustomerAddressValidator : AbstractValidator<Customer>
{
public CustomerAddressValidator()
{
RuleFor(customer => customer.Address).Length(20, 250);
}
}
MicroElements.Swashbuckle.FluentValidation updates swagger schema for operation parameters bounded to validatable models.
See BlogValidator in sample.
Error: System.InvalidOperationException: 'Cannot resolve 'IValidator<T>' from root provider because it requires scoped service 'TDependency'
Workarounds in order or preference:
public void ConfigureServices(IServiceCollection services)
{
// HttpContextServiceProviderValidatorFactory requires access to HttpContext
services.AddHttpContextAccessor();
services
.AddMvc()
// Adds fluent validators to Asp.net
.AddFluentValidation(c =>
{
c.RegisterValidatorsFromAssemblyContaining<Startup>();
// Optionally set validator factory if you have problems with scope resolve inside validators.
c.ValidatorFactoryType = typeof(HttpContextServiceProviderValidatorFactory);
});
Replace UseSwagger
for UseScopedSwagger
:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app
.UseMvc()
// Use scoped swagger if you have problems with scoped services in validators
.UseScopedSwagger();
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
// Needed for using scoped services (for example DbContext) in validators
.UseDefaultServiceProvider(options => options.ValidateScopes = false)
.UseStartup<Startup>()
.Build();
Example: You split validator into several small validators but AspNetCore uses only one of them.
Workaround: Hide dependent validators with internal
and use Include
to include other validation rules to one "Main" validator.
Startup.cs:
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.PropertyNamingPolicy = new NewtonsoftJsonNamingPolicy(new SnakeCaseNamingStrategy());
//options.JsonSerializerOptions.DictionaryKeyPolicy = new NewtonsoftJsonNamingPolicy(new SnakeCaseNamingStrategy());
})
/// <summary>
/// Allows use Newtonsoft <see cref="NamingStrategy"/> as System.Text <see cref="JsonNamingPolicy"/>.
/// </summary>
public class NewtonsoftJsonNamingPolicy : JsonNamingPolicy
{
private readonly NamingStrategy _namingStrategy;
/// <summary>
/// Creates new instance of <see cref="NewtonsoftJsonNamingPolicy"/>.
/// </summary>
/// <param name="namingStrategy">Newtonsoft naming strategy.</param>
public NewtonsoftJsonNamingPolicy(NamingStrategy namingStrategy)
{
_namingStrategy = namingStrategy;
}
/// <inheritdoc />
public override string ConvertName(string name)
{
return _namingStrategy.GetPropertyName(name, false);
}
}
Initial version of this project was based on Mujahid Daud Khan answer on StackOverflow: https://stackoverflow.com/questions/44638195/fluent-validation-with-swagger-in-asp-net-core/49477995#49477995