You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Add `IValidationContext` as scoped serviceservices.AddValidation(options => ...);// Get or injectvarvalidationContext=serviceProvider.GetRequiredService<IValidationContext>();// Validation key will be `Name` using default validation part providervalidationContext.When(person, p =>p.Name).Is(name =>name==null).AddValidationDetail("Name must be set");// Validation key will be `Address.Locations[0].Latitude` using default validation part providervalidationContext.When(person, p =>p.Address.Locations[0].Latitude).Is(latitude => ...custom check...).AddValidationDetail("Some custom check failed");
Validation conditions
Monads are not composable, so Is and IsNot, IsNull and IsNotNull... duplication
// Check for Phema.Validation.Conditions namespacevalidationContext.When(person, p =>p.Name).IsNullOrWhitespace().AddValidationDetail("Name must be set");// Use multiple conditions (joined with AND)validationContext.When(person, p =>p.Name).IsNotNull()// AND.HasLengthGreater(20)// .IsNull()// .IsEqual()// .IsNotUrl()// .IsNotEmail()// .IsMatch(regex).AddValidationDetail("Name should be less than 20");// DateTime conditionsvalidationContext.When(task, t =>t.DueDate).IsNotUtc().AddValidationDetail("Due date must be in Utc");// Type checksvalidationContext.When(person, p =>p.Car).IsType<Ferrari>(typed =>typed.Is(ferarriCar => ...Some Ferrari specific checks...)).AddValidationDetail("You have Ferrari car, but ...");
Validation details
varvalidationDetails=validationContext.When(person, p =>p.Age)// Validation check is failed, validation condition is valid.Is(()=>false).AddValidationDetail("Age must be set");// Use deconstructionvar(key,message)=validationContext.When(person, p =>p.Age).IsNull().AddValidationDetail("Age must be set");// More deconstructionvar(key,message,isValid)=validationContext.When(person, p =>p.Age).IsNull().AddValidationDetail("Age must be set");// Even more deconstruction!var(key,message,isValid,severity)=validationContext.When(person, p =>p.Age).IsNull().AddValidationDetail("Age must be set");
Check validation
// Throw exception when details severity greater than ValidationContext.ValidationSeverityvalidationContext.When(person, p =>p.Address).IsNull().AddValidationFatal("Address is not presented!!!");// If invalid throw ValidationConditionException// Check if context is validvalidationContext.IsValid();validationContext.EnsureIsValid();// If invalid throw ValidationContextException// Check concrete validation detailsvalidationContext.IsValid(person, p =>p.Age);validationContext.IsNotValid(person, p =>p.Age);validationContext.EnsureIsValid(person, p =>p.Age);
Compose and reuse validation rules with extensions
Call is allocation free
Static checks
// ExtensionspublicstaticvoidValidateCustomer(thisIValidationContextvalidationContext,Customercustomer){// Some checks}validationContext.ValidateCustomer(customer);
Write your own middleware or validation components/validators on top of IValidationContext
Validation part resolvers
ValidationPartResolver is a delegate, trying to get string valdiation part from MemberInfo
Use built in resolvers with ValidationPartResolvers static class: Default, DataMember, PascalCase, CamelCase
// Configure DataMember validation part resolverservices.AddValidation(options =>options.WithValidationPartResolver(ValidationPartResolvers.DataMember));// Override validation parts with `DataMemberAttribute`[DataMember(Name="name")]publicstringName{get;set;}
Validation scopes
Use scopes when you need to have:
Same nested validation path multiple times
Empty validation details collection (syncing with parent context/scope)
ValidationSeverity override
// Validation key will be `Child.*ValidationPart*`ValidateChild(validationContext.CreateScope(parent, p =>p.Child))// Validation key will be `Address.Locations[0].*ValidationPart*`
ValidateLocation(validationContext.CreateScope(person, p =>p.Address.Locations[0]))// Override local scope ValidationSeverityusing(varscope=validationContext.CreateScope(person, p =>p.Address,ValidationSeverity.Warning)){// Some scope validation checks syncing with validationContext}
High performance with non-expression constructions
validationContext.When("key",value).IsNull().AddValidationDetail("Value is null");validationContext.CreateScope("key",ValidationSeverity.Warning);validationContext.IsValid("key");validationContext.IsNotValid("key");validationContext.EnsureIsValid("key");
Benchmarks (i7 9700k 3.60 GHz, 16Gb 3400 MHz)
Simpler expression = less costs
Try to use non-expression extensions in hot paths
Use CreateScope to not to repeat chained member calls (x => x.Property1.Property2[0].Property3)
Expression-based When with Is(value => ...) extensions use lazy expression compilation to get value (Invoke)