Creating Custom AttributeValidators

This tutorial will show you how to create custom AttributeValidators by implementing a complete example of a ForbiddenWordAttributeValidator.

AttributeValidators are great for validating objects based on if they have a specific attribute applied to them or not. This also allows you to take the arguments passed into the attribute into consideration when you validate your object. In our case we're going to be using the attribute to pass the forbidden strings into the validator.

If you're already familiar with creating other validator types, just look at the sample code and see what's different. All validator types have very similar code and the tutorials for each differ only slightly.

First we create the validator class and add the required boilerplate code. Alternatively, you can also create a new validator by right-clicking in the project folder and navigating to Odin Validator > Create Validator > Attribute Validator. This will create a new file for you and automatically add the boilerplate code. Odin tries to predict the correct code by looking at the name you chose for the validator, but you may have to change it a bit to make it work. You should now have something that looks similar to this code:

using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ForbiddenWordAttributeValidator))]

public class ForbiddenWordAttributeValidator : AttributeValidator<ForbiddenWordAttribute, string>
{
    protected override void Validate(ValidationResult result)
    {
    }
}
We also need to create the attribute we want to use.
using System;

public class ForbiddenWordAttribute : Attribute
{
    public string[] ForbiddenWords;

    public ForbiddenWordAttribute(params string[] forbiddenWords) => ForbiddenWords = forbiddenWords;
}
Let's go through it and explain what we're doing.
using Sirenix.OdinInspector.Editor.Validation;

The AttributeValidator type is in the Sirenix.OdinInspector.Editor.Validation namespace, so we need to include it.

[assembly: RegisterValidator(typeof(ForbiddenWordAttributeValidator))]

We register the validator with the RegisterValidator attribute. There is another attribute that can be used to register a validator called RegisterValidationRule. The rest of the code is identical no matter which one you use, but registering a validator as a validation rule can give you additional functionality. To learn more about it, check out the following tutorial. Validators vs Validation Rules

Do not forget to register the validator, or it will not be picked up by the validation system.

public class ForbiddenWordAttributeValidator : AttributeValidator<ForbiddenWordAttribute, string>
{
    protected override void Validate(ValidationResult result)
    {
    }
}

We create the Validator class and inherit from AttributeValidator<T1, T2>. Since we want to validate all objects that have the ForbiddenWord attribute applied to them, we need to pass ForbiddenWordAttribute as a type parameter. We also want to add another type parameter to constrain it to only string values in this case, since our attribute wouldn't make sense on an integer or another type. You can also see that we're overriding the Validate method. This method is the bread and butter of any validator and is responsible for executing the validation logic and setting the appropriate validation result.

Let's implement the actuall validation logic now.

using Sirenix.OdinInspector.Editor.Validation;

[assembly: RegisterValidator(typeof(ForbiddenWordAttributeValidator))]

public class ForbiddenWordAttributeValidator : AttributeValidator<ForbiddenWordAttribute, string>
{
    protected override void Validate(ValidationResult result)
    {
        foreach (var forbiddenWord in this.Attribute.ForbiddenWords)
        {
            if (this.Value == forbiddenWord)
            {
                result.AddWarning($"'{this.Value}' is a forbidden word, please change it!");
                break;
            }
        }
    }
}

We're looping over all forbidden words that where passed into the attribute and check if the this.Value is equal to one of the forbidden words. If we found a forbidden word, we add a warning to the validation results. this.Value refers to the currently validated value, in our case that's the string.

Here's an example of the attribute at work.

using UnityEngine;

public class SomeMonoBehaviour : MonoBehaviour
{
    [ForbiddenWord("Equilibrium", "Phosphorous", "Serendipity")]
    public string SomeString;
}

The result object is your main way of interacting with the validation system. Whenever your validator finds an issue you can add a warning / error to it using

  • .AddWarning()
  • .AddError()

Alternatively, you can also use the .Add() method, which takes a ValidatorSeverity argument. This is useful for validation rules (see Validators vs Validation Rules) as it allows you to configure the severity of this validator inside of the editor.

You can also add Buttons, Fixes, Metadata, and Context Menu Items by calling these methods after you have added warning / error.

  • .WithFix() (see Creating Custom Fixes)
  • .WithButton()
  • .WithMetaData()
  • .WithContextClick()
  • .WithSceneGUI()
  • .WithSelectionObject()
  • .EnableRichText()

If you want to draw something in the scene as the result of a warning / error you can do so by using the .WithSceneGUI() method.
All of these method calls can be chained since they use a builder pattern.

That's pretty much all you need to create a custom AttribtueValidator.
The validation logic can of course be a lot more sophisticated, but the general workflow is always the same.

Validators vs Validation Rules

You already know how to create custom validators and are interested in the difference between Validators and Validation Rules? Then this tutorial is right for you.

Validators vs Validation Rules

Custom Fixes

You want to add a button to your validator that executes a custom fix? Then this tutorial is right for you.

Creating Custom Fixes

Other Validator Types

Are you interested in the other validator types? Check out this overview to see which one is right for your use case.

Validator Types Overview