Skip to content

Client-Side Validation

Sannr provides automatic generation of client-side validation rules from your server-side validation attributes, ensuring consistency between client and server validation logic.

Overview

The [GenerateClientValidators] attribute tells Sannr to generate JSON validation rules that can be consumed by client-side JavaScript/TypeScript validation libraries.

Basic Usage

1. Mark Your Model

csharp
using Sannr;

[GenerateClientValidators]
public class UserRegistrationForm
{
    [Required]
    public string? Username { get; set; }

    [Required, EmailAddress]
    public string? Email { get; set; }

    [Required, StringLength(100, MinimumLength = 8)]
    public string? Password { get; set; }

    [Range(13, 120)]
    public int Age { get; set; }

    [Url]
    public string? Website { get; set; }

    [Phone]
    public string? PhoneNumber { get; set; }
}

2. Access Generated Rules

After compilation, Sannr adds static properties to your partial class containing your validation rules in various formats:

csharp
// Accessing rules from your C# code (e.g., to pass to a frontend)
string jsonRules = UserRegistrationForm.ValidationRulesJson;
string tsRules = UserRegistrationForm.ValidationRulesTypeScript;
string jsRules = UserRegistrationForm.ValidationRulesJavaScript;

These properties are automatically added to any partial class that has validation attributes.

3. Use in Client-Side Code

You can expose these rules via an API endpoint:

csharp
app.MapGet("/api/rules/user-registration", () => 
    Results.Content(UserRegistrationForm.ValidationRulesJavaScript, "application/javascript"));

Then consume them in your JavaScript/TypeScript application:

javascript
// In your HTML/JS
const response = await fetch('/api/rules/user-registration');
const scriptContent = await response.text();
// The script contains: const userRegistrationFormValidators = { ... };

Attribute Mappings

Sannr AttributeJSON RuleExample
[Required]"required": true{ "required": true }
[EmailAddress]"email": true{ "email": true }
[StringLength(max, min)]"minLength": min, "maxLength": max{ "minLength": 8, "maxLength": 100 }
[Range(min, max)]"min": min, "max": max{ "min": 13, "max": 120 }
[Url]"url": true{ "url": true }
[Phone]"phone": true{ "phone": true }
[RequiredIf(other, val)]"requiredIf": { "otherProperty": "other", "targetValue": val }{ "requiredIf": { "otherProperty": "country", "targetValue": "USA" } }
[AllowedValues(vals)]"allowedValues": [vals]{ "allowedValues": ["USD", "EUR"] }

Advanced Configuration

Custom Namespace

csharp
[GenerateClientValidators(Namespace = "MyApp.Validation")]
public class ProductForm
{
    // ...
}

Validation Functions (Future)

csharp
[GenerateClientValidators(GenerateValidationFunctions = true)]
public class ContactForm
{
    // This will generate validation functions in addition to rules
}

Integration Examples

With jQuery Validation

javascript
$(document).ready(function() {
    const rules = JSON.parse(UserRegistrationFormValidators.ValidationRulesJson);

    $('#registrationForm').validate({
        rules: rules,
        messages: {
            username: "Please enter a username",
            email: "Please enter a valid email address"
        }
    });
});

With React Hook Form

typescript
import { useForm } from 'react-hook-form';
import { UserRegistrationFormValidators } from './generated-validators';

const validationRules = JSON.parse(UserRegistrationFormValidators.ValidationRulesJson);

function RegistrationForm() {
    const { register, handleSubmit, errors } = useForm({
        mode: 'onBlur',
        defaultValues: {},
        resolver: (data) => {
            // Apply validation rules
            const result = validateData(data, validationRules);
            return {
                values: result.isValid ? data : {},
                errors: result.errors
            };
        }
    });

    return (
        <form onSubmit={handleSubmit(onSubmit)}>
            <input {...register('username')} />
            {errors.username && <span>{errors.username.message}</span>}
            {/* ... other fields */}
        </form>
    );
}

With Vue.js + Vuelidate

javascript
import { validationMixin } from 'vuelidate';
import { UserRegistrationFormValidators } from './generated-validators';

const rules = JSON.parse(UserRegistrationFormValidators.ValidationRulesJson);

// Convert Sannr rules to Vuelidate format
const vuelidateRules = convertToVuelidate(rules);

export default {
    mixins: [validationMixin],
    validations: vuelidateRules,
    // ... component code
}

Best Practices

1. Single Source of Truth

Keep validation logic in one place - your C# models. Client-side validation becomes a natural extension of your server-side rules.

2. Progressive Enhancement

Use client-side validation for better UX, but always validate on the server. Client-side validation can be bypassed.

3. Consistent Error Messages

Consider localizing your validation error messages on the client side, or use the same message keys from your server-side validation.

4. Performance

Generated JSON is lightweight and can be cached. For large applications, consider lazy-loading validation rules for specific forms.

Limitations

  • Complex validation logic (custom validators, conditional validation) cannot be automatically translated to client-side rules
  • Model-level validation (IValidatableObject) is not supported on the client side
  • Some attributes may require client-side libraries (e.g., credit card validation)

Troubleshooting

Generated Code Not Found

Ensure your project references the Sannr.Gen analyzer:

xml
<ProjectReference Include="..\..\src\Sannr.Gen\Sannr.Gen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />

Rules Not Generating

  • Ensure the class has the [GenerateClientValidators] attribute
  • Check that properties have Sannr validation attributes
  • Rebuild the project to trigger source generation

JSON Parse Errors

The generated JSON is properly escaped. If you encounter parsing issues, check for special characters in regex patterns or custom error messages.

Released under the MIT License.