-
Notifications
You must be signed in to change notification settings - Fork 3
Using Dryv in Asp.Net Core
Define a Ruleset for a model by adding a static field of type DryvRules
to a Class.
To Enable the Validation, add the DryvValidation
Attribute to the class. How, whenever this class is defined in a Model processed by the DryvValidationFilter, it will apply the Rules from OrderModel-Class
[DryvValidation]
public class OrderModel {
private static DryvRules Rules = DryvRules.For<OrderModel>();
public DateTimeOffset ShippingDate { get; set; }
}
You can also separate the validation rules from the model class. Just pass the type to DryvValidation
Attribute like in the following example:
internal static class OrderModelValidationRules {
public static DryvRules Rules = DryvRules.For<OrderModel>();
}
[DryvValidation(typeof(OrderModelValidationRules ))]
public class OrderModel {
public DateTimeOffset ShippingDate { get; set; }
}
Define a Ruleset for a Class with the generic DryvRules
Class from Dryv.Rules
Namespace. You'll need to provide the Modelclass the Rules apply to as a Typeparameter to DryvRules, so e.g. for OrderModel, use DryvRules<OrderModel>
.
A rule must return a DryvValidationResult
object. There is an implicit constructor conversion to convert strings or null to DryvValidationResult.
So you can return a string, which will result in DryvValidationResult.Error("Errormessage")
or you can return null, which will result in DryvValidationResult.Success
.
You can also create a DryvValidationResult using the constructor for more advanced usage. You can also send an object containing any data the client can evaluate and use. In the example below, we return a list of valid cities to the client which the client could then use to make suggestions.
public static DryvRules<Address> ValidationRules = DryvRules.For<Address>()
.Rule(m => m.City, m =>
!m.City.StartsWith("b", System.StringComparison.OrdinalIgnoreCase) ?
new DryvValidationResult("City does not start with letter 'b'", DryvResultType.Error,
new { cityList = new string[] { "Berlin", "Bern", "Bonn"} }
): null);
In Addition to DryvResultType.Error
there is also DryvResultType.Warning
. You can use the Warning for some soft validation approach and display it as a warning to the client. For Dryv, there is no real difference between Error and Warning. It must be handled by the client itself. The Dryvue Vue library can handle Warnings out of the box. It will expose the errors and warnings, so you can show the warning but let the user still submit the form if he wants to.
Validating with asynchronous Servicemethods works like written above. Only difference is that you'll need to return Task<DryvValidationResult>
.
If your Modelclass contains properties with nested objects, you can create validationrules on the upper top DryvRules ruleset, but you can also create another Ruleset for that nested Type. Dryv will automatically validate the nested objects in case it knows a validationrule for that particular type.
Rules for nested object in upper validation rules: As you can see, there is a nested Address Property and the Validationrules include rules for that Address, too.
[DryvValidation]
public class Person
{
private static DryvRules<Person> ValidationRules = DryvRules.For<Person>()
.Rule(m => m.FirstName, m => string.IsNullOrWhiteSpace(m.FirstName) ? "Please provide a firstname.": null)
.Rule(m => m.LastName, m => string.IsNullOrWhiteSpace(m.LastName) ? "Please provide a lastname." : null)
.Rule(m => m.Address, m => m == null ? "Address cannot be null": null)
.Rule(m => m.Address.City, m => string.IsNullOrWhiteSpace(m.Address.City) ? "Please provide a City": null);
public string FirstName { get; set; }
public string LastName { get; set; }
public Address Address { get; set; }
}
On the other hand, you can also define the Rule on the Address
Class. This will give better reusability when that Class is maybe used as nested property on multiple classes:
[DryvValidation]
public class Address
{
public static DryvRules<Address> ValidationRules = DryvRules.For<Address>()
.Rule(m => m.City, m => string.IsNullOrWhiteSpace(m.City) ? "Please provide a City" : null);
public string City { get; set; }
public string ZipCode { get; set; }
}
A Rule defines with .Rule
Method will be applied to both, the client and server validation. Dryv will try to translate the c# expression to javascript code and perform the validation in the browser. In case the expression is too complex or needs dependencies, Dryv will create a dynamic Controller at Application startup and will generate a javascript code which performs an ajax-request to that dynamic Controller.
In case you only want a certain Rule to be active on the Server, use the .ServerRule()
method of DryvRules Class. This can be handy to perform some more advanced validations when the user submits a form which would take too long in the browser.
In case you only want a certain Rule to be active in the Browser, use the .ClientRule()
method of DryvRules Class.
public static DryvRules<Address> ValidationRules = DryvRules.For<Address>()
.ClientRule(m => m.City, // perform a very basic nullcheck on the client.
m => string.IsNullOrWhiteSpace(m.City) ? "Please provide a City" : null)
.ServerRule<CityService>(m => m.City, // A more advanced Rule only active when validation on the Server
(m, svc) => !svc.IsValidCity(m.City) ? "City is not valid. Please provide a valid City" : null);
Dryv Rules can use Dependencies. Under the hood, Dryv uses HttpContext.RequestServices.GetService<IServiceType>()
to get the dependencies from the Dependency-injection framework, so make sure the dependencies you need are registered in your IOC-Container.
To use a Service in a Rule, use a method overload of Rule<TDependentService>
, ClientRule<TDependentService>
or ServerRule<TDependentService>
method of DryvRules
Class. Dryv can provide you many dependencies. Just specify multiple serviceTypes Rule<TDependentService1, TDependantSevice2>
like in the example below.
public static DryvRules<Address> ValidationRules = DryvRules.For<Address>()
.ServerRule<CityService, ZipCodeService>(m => m.City,
(m, cityService, zipService) => !cityService.IsValidCity(m.City) ? "City is not valid. Please provide a valid City"
: !zipService.IsValidZipcode(m.ZipCode) ? "Zipcode is not valid. Please provide a valid zipcode for your city" :null);
There are 3 ways to disable Rules
- Inside the rule itself by checking of model properties or dependent services. This is the most flexible one but also the most verbose option as the expression can grow a lot by performing the checks. Also that expression is evaluated every time a validation is performed. If you don't need to decide at validationtime, better use option 2 or 3
private static DryvRules Rules = DryvRules.For<HomeModel>()
.Rule(m => m.BillingAddress,
m => m.BillingEqualsShipping ? null
: m.BillingAddress == null ? "BillingAddress cannot be null" : null);
- Using
DisableRules
method ofDryvRules
class and provide an expression. The rule will be disabled when that expression yieldtrue
private static DryvRules Rules = DryvRules.For<HomeModel>()
.DisableRules(m => m.BillingAddress, m => m.BillingEqualsShipping);
- Using the RuleSwitch parameter at
Rule
,ClientRule
orServerRule
methods ofDryvRules
class
private static DryvRules Rules = DryvRules.For<HomeModel>()
.Rule<ShippingService>(m => m.BillingAddress,
(m, _) => m.BillingAddress == null ? "BillingAddress cannot be null" : null,
(shippingService) => shippingService.IsBillingAddressRequired());
Parameters will be integrated into the client generated rules. You can define values in a key-value structure.
The follwing snippet shows how to define 2 parameters for the DryvRuleset. Parameters defined using DryvRules.Parameter<> are available on both Server and Clientside.
Rules
.Parameter<System.DateTimeOffset>("minDateTime", () => System.DateTimeOffset.Now.AddDays(-30)) // minDateTime is 30 days ago from now.
.Parameter<System.DateTimeOffset>("maxDateTime", () => System.DateTimeOffset.Now) // MaxDateTime is current time
In addition to the above example, you can also provide parameters for the client as Dictionary<string, object> as attribute within the Taghelper. These parameters are clientside only, therefore not available inside DryvRules.Rule<> Methods.
<dryv-client-rules
name="order-form"
for="typeof(OrderModel)"
parameters="@(new Dictionary<string, object>{ ["maxDateTime"] = DateTimeOffset.Now })"
/>
You can get the previously defined parameters in other rules by injecting DryvParameters
into your validationrule. Use the Get Method to convert the parameter to the needed type.
Rules
.Rule<DryvParameters>(m => m.ShippingDate,
(m, p) => m.ShippingDate < p.Get<DateTimeOffset>("minDateTime") || m.ShippingDate > p.Get<DateTimeOffset>("maxDateTime")
? $"Date of delivery must be between {p.Get<DateTimeOffset>("minDateTime")} and {p.Get<DateTimeOffset>("maxDateTime")}"
: null)
Please note that the parameters are not stored on the server and also are not send by the browser by default.
In case you need to snapshot the parameters at time the rule is generated, you must send them to the server somehow and provide IDryParameterProvider
to DryvValidationFilter which grabs them from the request. You can send the parameters as HttpHeader or queryparameter for example.
/// Provider for DryvParameters
/// Creates the Parameters from Header Values send to the server.
/// Hint: This Class needs to be registered in your IOC Container
public class MyParametersProvider : IDryParameterProvider
{
public IReadOnlyDictionary<string, object> GetParameters(ActionExecutingContext context)
{
var paramsFromHeaders = context.HttpContext.Request
.Headers
.Where(h => h.Key.StartsWith("DryvParam"))
.ToDictionary(h => h.Key, h => h.Value.ToString() as object);
return new ReadOnlyDictionary<string, object>(paramsFromHeaders);
}
}
Pass the ParameterProvider to the DryvValidationFilter afterwards:
[DryvValidationFilter(ParametersProviderType = typeof(MyParametersProvider))]
public class OrderController : Controller
{
// ...
}
If needed, you can get all parameters from the window.$dryv.v
object.
const dryvRule= window.dryv.v["rulename"];
const minDateTime = dryvRule.parameters.minDateTime;