1
\$\begingroup\$

This is my implementation of an .NET ValidationAttribute to validate a string against a JSON schema. The JSON schema file is stored within the application as an embedded resource and is referenced by passing its fully qualified name in the constructor.

A couple of things I'm not 100% about:

  1. Returning ValidationResult as opposed to throwing exceptions in certain circumstances - couldn't quite decide what the best approach was.
  2. How I'm composing the error messages

Can anybody find fault with this implementation?

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
public sealed class JsonSchemaAttribute : ValidationAttribute
{
    private const string _errorMessage = "Failed to validate field {0} against JSON schema {1}.";
    private readonly string _schemaName;

    public string SchemaName { get { return _schemaName; } }

    public JsonSchemaAttribute(string schemaName) : base(_errorMessage)
    {
        if (string.IsNullOrWhiteSpace(schemaName))
            throw new ArgumentNullException(schemaName);

        _schemaName = schemaName;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        if (value == null)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName, "The value of the property cannot be null."));

        var jsonString = value as string;

        if (jsonString == null)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName, $"Type {value.GetType()} is not supported."));

        var jObject = JObject.Parse(jsonString);

        if (jObject == null)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName, "Failed to parse JSON string."));

        var schema = GetJsonSchema(_schemaName);

        if (schema == null)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName, "Failed to parse JSON schema."));

        IList<string> validationErrors = new List<string>();
        var result = jObject.IsValid(schema, out validationErrors);

        if (!result)
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName, string.Join(" ", validationErrors)));

        return ValidationResult.Success;
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _schemaName);
    }

    private string FormatErrorMessage(string name, string reason)
    {
        return $"{FormatErrorMessage(name)} {reason}";
    }

    private JSchema GetJsonSchema(string name)
    {
        string result;

        using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name))
        {
            if (stream == null)
                return null;

            using (var reader = new StreamReader(stream))
            {
                if (reader == null)
                    return null;

                result = reader.ReadToEnd();
            }
        }

        return JSchema.Parse(result);
    }
}
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.