|
1 | 1 | # CSF Validation |
2 | | -Validation library, currently undergoing a rewrite from scratch. |
| 2 | +CSF.Validation is a business rule validation framework. |
| 3 | +It is intended for use within the business logic layer of your application. |
3 | 4 |
|
4 | | -The previous version is not particularly fit for use, and this rewrite is not yet ready for use. |
| 5 | +## How this differs from other frameworks |
| 6 | +Many validation frameworks (such as [ASP.NET validation controls] or [.NET validation attributes]) are intended for validation which lies close to the user interface. |
| 7 | +This is evident in their design, for example the validation failure messages are defined along with the validation rule itself. |
| 8 | +The human-readable failure message is a presentation/UI concern and not directly related to the the pass/fail of the rule logic. |
5 | 9 |
|
6 | | -### Continuous integration builds |
| 10 | +[ASP.NET validation controls]: https://msdn.microsoft.com/en-us/library/debza5t0.aspx |
| 11 | +[.NET validation attributes]: https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.validationattribute(v=vs.110).aspx |
| 12 | + |
| 13 | +CSF.Validation separates concerns appropriately so that each aspect of the validation process may be controlled separately. |
| 14 | +If you wanted a simple validator which you will (architecturally speaking) keep close to your UI, then this framework is probably not for you. |
| 15 | + |
| 16 | +If you want a shared validation framework which will live in your business logic layer, validating requests which come from a wide variety of other layers then this might be what you are looking for. |
| 17 | + |
| 18 | +## Usage |
| 19 | +The process of validating an object is: |
| 20 | + |
| 21 | +1. Create a validation manifest, which lists the rules which are desired, along with their configurations |
| 22 | +2. Use a validator factory to create a validator from that manifest |
| 23 | +3. Use the validator to validate your object |
| 24 | + |
| 25 | +At the first stage, there is a static type named `ManifestBuilder` which can help you with the creation of a validation manifest. |
| 26 | +It provides a fluent interface to add and configure rules in the manifest. |
| 27 | + |
| 28 | +## Example |
| 29 | +Here is an example of usage, including the creation of your own custom validation rule: |
| 30 | + |
| 31 | +```csharp |
| 32 | +// A simple object model which we are going to validate |
| 33 | +
|
| 34 | +public interface IDessert |
| 35 | +{ |
| 36 | + string Name { get; } |
| 37 | + decimal UnitPrice { get; } |
| 38 | +} |
| 39 | + |
| 40 | +public interface IPurchaseDessertRequest |
| 41 | +{ |
| 42 | + IDessert DessertToPurchase { get; } |
| 43 | + decimal MoneyInWallet { get; } |
| 44 | + int DesiredQuantity { get; |
| 45 | +} |
| 46 | + |
| 47 | +// A custom validator type |
| 48 | +
|
| 49 | +// using CSF.Validation.Rules; |
| 50 | +public class CanAffordDessertRule : Rule<IPurchaseDessertRequest> |
| 51 | +{ |
| 52 | + protected override RuleOutcome GetOutcome(IPurchaseDessertRequest request) |
| 53 | + { |
| 54 | + if(request == null) |
| 55 | + return Success; |
| 56 | + |
| 57 | + var totalCost = request.DessertToPurchase.UnitPrice * request.DesiredQuantity; |
| 58 | + if(totalCost > request.MoneyInWallet) |
| 59 | + return Failure; |
| 60 | + |
| 61 | + return Success; |
| 62 | + } |
| 63 | +} |
| 64 | + |
| 65 | +// Code to construct the validator for this |
| 66 | +
|
| 67 | +// using CSF.Validation; |
| 68 | +// using CSF.Validation.Manifest; |
| 69 | +// using CSF.Validation.Manifest.Fluent; |
| 70 | +// using CSF.Validation.Manifest.StockRules; |
| 71 | +public IValidator CreatePurchaseDessertRequestValidator() |
| 72 | +{ |
| 73 | + var builder = ManifestBuilder.Create<IPurchaseDessertRequest>(); |
| 74 | + |
| 75 | + builder.AddRule<NotNullRule>(); |
| 76 | + |
| 77 | + builder.AddMemberRule<NotNullValueRule>(x => x.DessertToPurchase, c => { |
| 78 | + c.AddDependency<NotNullRule,IPurchaseDessertRequest>(); |
| 79 | + }); |
| 80 | + |
| 81 | + builder.AddMemberRule<NumericRangeValueRule>(x => x.DesiredQuantity, c => { |
| 82 | + c.Configure(r => { |
| 83 | + r.Min = 1; |
| 84 | + }); |
| 85 | + c.AddDependency<NotNullRule,IPurchaseDessertRequest>(); |
| 86 | + }); |
| 87 | + |
| 88 | + builder.AddMemberRule<CanAffordDessertRule>(c => { |
| 89 | + c.AddDependency<NotNullValueRule,IPurchaseDessertRequest>(x => x.DessertToPurchase); |
| 90 | + c.AddDependency<NumericRangeValueRule,IPurchaseDessertRequest>(x => x.DesiredQuantity); |
| 91 | + }); |
| 92 | + |
| 93 | + var factory = new ValidatorFactory(); |
| 94 | + return factory.GetValidator(builder.GetManifest()); |
| 95 | +} |
| 96 | + |
| 97 | +// We get our validation result |
| 98 | +
|
| 99 | +var validator = CreatePurchaseDessertRequestValidator(); |
| 100 | +var request = GetPurchaseDessertRequest(); |
| 101 | +var result = validator.Validate(request); |
| 102 | +``` |
| 103 | + |
| 104 | +This example creates a validator with four validation rules: |
| 105 | + |
| 106 | +* A not-null rule ensures that the request is not null |
| 107 | +* A not-null member rule ensures that the **DessertToPurchase** property is not null |
| 108 | +* A numeric range rule ensures that the **DesiredQuantity** property is at least 1 |
| 109 | +* A custom rule ensures that the buyer can afford their desserts |
| 110 | + |
| 111 | +Note the use of `.Configure()` in the numeric range rule. |
| 112 | +When building a validation manifest you may configure parameters in each individual rule via property setters on that rule. |
| 113 | + |
| 114 | +Additionally, note that most of the rules have dependencies. |
| 115 | +The way dependencies work is that a rule which carries a dependency will not be executed (it will be marked as *skipped due to dependency failure* in the results) if any of the rules it depends upon have failed. |
| 116 | +In the example above, there is no point executing any of the property rules if the request itself is `null`. |
| 117 | +There is also no way to get a meaningful result from the `CanAffordDessertRule` if either the dessert is `null` or if the desired quantity is less than one. |
| 118 | + |
| 119 | +Dependencies will automatically form chains; if a dependency rule is skipped due to a dependency failure of its own then any rules depending upon it will also be skipped. |
| 120 | + |
| 121 | +A validation result contains a list of every validation rule in the manifest and the outcome of every single rule. |
| 122 | +Rules which have an outcome of `Success` or `IntentionallySkipped` (used in advanced scenarios) should be considered OK to allow validation to pass. |
| 123 | +Any other outcome indicates a validation failure. |
| 124 | + |
| 125 | +## Build status |
7 | 126 | CI builds are configured via both Travis (for build & test on Linux/Mono) and AppVeyor (Windows/.NET). |
8 | 127 | Below are links to the most recent build statuses for these two CI platforms. |
9 | 128 |
|
|
0 commit comments