Skip to content

Using Dryv in Asp.Net Core

Alexander Winnen edited this page Sep 21, 2021 · 21 revisions

Defining a Ruleset

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

Rules inside the model Class

[DryvValidation]
public class OrderModel {
    private static DryvRules Rules = DryvRules.For<OrderModel>();

    public DateTimeOffset ShippingDate { get; set; }
}

Rules outside the model Class

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; }
}

Defining a Rule

Basics

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);

Hard and Soft Validation

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.

Async validation

Validating with asynchronous Servicemethods works like written above. Only difference is that you'll need to return Task<DryvValidationResult>.

Nested Rules

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; }
}

Server and Client Rules

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);

Dependencies

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);

Conditional Rules

There are 3 ways to disable Rules

  1. 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);
  1. Using DisableRules method of DryvRules class and provide an expression. The rule will be disabled when that expression yield true
private static DryvRules Rules = DryvRules.For<HomeModel>()
             .DisableRules(m => m.BillingAddress, m => m.BillingEqualsShipping);
  1. Using the RuleSwitch parameter at Rule, ClientRule or ServerRule methods of DryvRules 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());

Using Parameters

Parameters will be integrated into the client generated rules. You can define values in a key-value structure.

Defining parameters

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 })"
/>

Accessing parameters in C#

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 
{
	// ...
}

Accessing parameters in Javascript

If needed, you can get all parameters from the window.$dryv.v object.

const dryvRule= window.dryv.v["rulename"];
const minDateTime = dryvRule.parameters.minDateTime;