GithubHelp home page GithubHelp logo

fiakkasa / dataannotatedmodelvalidations Goto Github PK

View Code? Open in Web Editor NEW
26.0 2.0 4.0 74 KB

Data Annotated Model Middleware for HotChocolate

License: MIT License

C# 100.00%
hotchocolate graphql validation aspnetcore csharp dotnet

dataannotatedmodelvalidations's Introduction

DataAnnotatedModelValidations

NuGet Version NuGet Downloads License: MIT

Data Annotated Model Validation Middleware for HotChocolate.

The purpose of this Middleware is to provide the same behavior like a ASP.Net controllers where all models would be validated according to the specified Data Annotations or the IValidatableObject implementation; in essence it's always on.

In addition individual method arguments can be validated using annotations from System.ComponentModel.Annotations.

Nuget

Note

โš ๏ธ For HC 13.5 and up please use version 4.1 and up.

Usage

Locate your GraphQL Server registration and append .AddDataAnnotationsValidator()

ex.

// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    // ...
    services
        .AddGraphQLServer()
        .AddDataAnnotationsValidator()
        .AddQueryType<Query>();
    // ...
}

Excluding a model from being validated

To exclude a certain method argument from being validated just add the IgnoreModelValidation attribute.

ex.

public class Sample
{
    [Required]
    [MinLength(3)]
    [EmailAddress]
    public string? Email { get; set; }
}

public class Query
{
    public string? GetTextIgnoreValidation([IgnoreModelValidation][MinLength(5)] string? text) => text;

    public Sample? GetSampleIgnoreValidation([IgnoreModelValidation] Sample? sample) => sample;
}

Models

In C# one may define models in a multitude of ways.

To help alleviate potential issues consider the following:

Standard Classes

public class Sample
{
    [Required]
    [MinLength(3)]
    [EmailAddress]
    public string? Email { get; set; }
}

Records with declared properties

public record Sample
{
    [Required]
    [MinLength(3)]
    [EmailAddress]
    public string? Email { get; set; }
}

Records with auto synthesized properties

The auto-property is initialized to the value of the corresponding primary constructor parameter. Attributes can be applied to the synthesized auto-property and its backing field by using property: or field: targets for attributes syntactically applied to the corresponding record parameter.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records

So per the documentation and following the guideline, a solution to the challenge mentioned would be adding the property: prefix to the validation attribute.

public record Sample(
    [property:Required]
    [property:MinLength(3)]
    [property:EmailAddress]
    string? Email
);

Notes

When implementing the IValidatableObject interface HotChocolate considers the Validate as a resolver; to avoid getting schema errors said method needs to be ignored.

ex.

public class Sample : IValidatableObject
{
    [Required]
    [MinLength(3)]
    [EmailAddress]
    public string? Email { get; set; }

    [GraphQLIgnore]
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) =>
        Email?.StartsWith("no-property-name") == true
            ? new[] { new ValidationResult("no-property-name") }
            : Enumerable.Empty<ValidationResult>();
}

Numerical sequences

If you need to support numerical sequences in your validation results, consider adding the names as FieldName:[index].

The field name will be transformed in the error path as fieldName,_index_.

{
  "errors": [
    {
      "message": "The field Count must be between 1 and 10.",
      "path": ["sample", "obj", "children", "_2_", "count"],
      "extensions": {
        "code": "DAMV-400",
        "field": "sample",
        "type": "Mutation",
        "specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
      }
    }
  ]
}

If you choose to omit the : character and add the names as FieldName[index] the field name and the index will be represented as one entry, fieldName_index_.

{
  "errors": [
    {
      "message": "The field Count must be between 1 and 10.",
      "path": ["sample", "obj", "children_2_", "count"],
      "extensions": {
        "code": "DAMV-400",
        "field": "sample",
        "type": "Mutation",
        "specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
      }
    }
  ]
}

Multiple members

If multiple member names are added they will be treated as distinct error messages.

Ex. validation error '"Some validation error!"' was assigned to properties hello and world:

{
  "errors": [
    {
      "message": "Some validation error!",
      "path": ["sample", "obj", "hello"],
      "extensions": {
        "code": "DAMV-400",
        "field": "sample",
        "type": "Query",
        "specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
      }
    },
    {
      "message": "Some validation error!",
      "path": ["sample", "obj", "world"],
      "extensions": {
        "code": "DAMV-400",
        "field": "sample",
        "type": "Query",
        "specifiedBy": "http://spec.graphql.org/June2018/#sec-Values-of-Correct-Type"
      }
    }
  ],
  "data": {
    "sample": null
  }
}

Property nesting

If there is a need to express a nested relationship of a property and it's parent, consider adding the names separated by a colon as ParentName:FieldName:[index]

Similar Projects

dataannotatedmodelvalidations's People

Contributors

arjentfe avatar fiakkasa avatar socolin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

dataannotatedmodelvalidations's Issues

Type error when using HotChocolate 13.5 preview

I'm getting this error when using this package 4.0.2 with HotChocolate 13.5

      ResolverError: Unexpected Execution Error
      System.TypeLoadException: Could not load type 'HotChocolate.Execution.PathFactory' from assembly 'HotChocolate.Abstractions, Version=13.5.0.0, Culture=neutral, PublicKeyToken=null'.
         at DataAnnotatedModelValidations.ValidatorMiddleware.GenerateArgumentPath(String name, String memberName, List`1 contextPath, Nullable`1 valueValidation)    
         at DataAnnotatedModelValidations.ValidatorMiddleware.ReportError(IMiddlewareContext context, IInputField argument, List`1 contextPathList, Nullable`1 valueValidation, String message, String memberName)
         at DataAnnotatedModelValidations.ValidatorMiddleware.<>c__DisplayClass6_0.<ReportErrorFactory>b__0(IInputField argument)
         at System.Linq.Parallel.ForAllOperator`1.ForAllEnumerator`1.MoveNext(TInput& currentElement, Int32& currentKey)
         at System.Linq.Parallel.ForAllSpoolingTask`2.SpoolingWork()
         at System.Linq.Parallel.SpoolingTaskBase.Work()
         at System.Linq.Parallel.QueryTask.BaseWork(Object unused)
         at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
      --- End of stack trace from previous location ---
         at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)
         at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)

the reason for this is because PathFactory was removed in 13.5 here. It looks like the replacement is to use Path.Root.Append.

I'd be happy to implement the fix, but I'm not sure how to make it backwards compatible with older versions of HotChocolate and this new version that they're working on. Maybe we implement classes for both and pick an implementation based on which version of HotChocolate is loaded at runtime?

Why drop net6.0 already?

I'm curious why you opted to block dotnet 6 from future updates already, barely a week after 8 came out? The latest version doesn't really seem to add anything net7+ specific that I see, so would still be fully compatible. Even the upcoming Hot Chocolate 14 still targets net6.0.

Validation on root class ok, but child class doesn't work

When I have the likes of this, it works as expected.
public class MyClass { [MaxLength(20)] public string FirstName { get; set; }

But when I have child class, the validation on the child class seems to be ignored

   public class ChildCLass
    {
        [MaxLength(20)]
        public string ChildFirstName { get; set; }
    }

    public class MyClass
    {
        [MaxLength(20)]
        public string FirstName { get; set; }

       public ChildCLass Child { get; set; }
    }

Error on optional field

Hi, great job, it works perfectly but I have this error when I use the annotation on optional field.

[MaxLength(10)]
public Optional Test { get; init; }

"extensions": {
"message": "The field of type HotChocolate.Optional1[System.String] must be a string, array or ICollection type.", "stackTrace": " at System.ComponentModel.DataAnnotations.MaxLengthAttribute.IsValid(Object value)\r\n at System.ComponentModel.DataAnnotations.ValidationAttribute.IsValid(Object value, ValidationContext validationContext)\r\n at System.ComponentModel.DataAnnotations.ValidationAttribute.GetValidationResult(Object value, ValidationContext validationContext)\r\n at System.ComponentModel.DataAnnotations.Validator.TryValidate(Object value, ValidationContext validationContext, ValidationAttribute attribute, ValidationError& validationError)\r\n at System.ComponentModel.DataAnnotations.Validator.GetValidationErrors(Object value, ValidationContext validationContext, IEnumerable1 attributes, Boolean breakOnFirstError)\r\n at System.ComponentModel.DataAnnotations.Validator.GetObjectPropertyValidationErrors(Object instance, ValidationContext validationContext, Boolean validateAllProperties, Boolean breakOnFirstError)\r\n at System.ComponentModel.DataAnnotations.Validator.GetObjectValidationErrors(Object instance, ValidationContext validationContext, Boolean validateAllProperties, Boolean breakOnFirstError)\r\n at System.ComponentModel.DataAnnotations.Validator.TryValidateObject(Object instance, ValidationContext validationContext, ICollection1 validationResults, Boolean validateAllProperties)\r\n at DataAnnotatedModelValidations.ValidatorMiddleware.ValidateItem(IReadOnlyDictionary2 context, Object item, IServiceProvider serviceProvider, List1 validationResults)\r\n at DataAnnotatedModelValidations.ValidatorMiddleware.<>c__DisplayClass3_0.<ReportErrorFactory>b__0(IInputField argument)\r\n at System.Linq.Parallel.ForAllOperator1.ForAllEnumerator1.MoveNext(TInput& currentElement, Int32& currentKey)\r\n at System.Linq.Parallel.ForAllSpoolingTask2.SpoolingWork()\r\n at System.Linq.Parallel.SpoolingTaskBase.Work()\r\n at System.Linq.Parallel.QueryTask.BaseWork(Object unused)\r\n at System.Linq.Parallel.QueryTask.<>c.<.cctor>b__10_0(Object o)\r\n at System.Threading.Tasks.Task.InnerInvoke()\r\n at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)\r\n at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\r\n--- End of stack trace from previous location ---\r\n at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\r\n at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)"
}

Will you be upgrading to support version 12 of HotChocolate?

Title.

Currently, I'm getting this error with HC v12:

REQUEST:

mutation ForgottenPassword {
  forgottenPassword(input: {
    email: "[email protected]"
  }) {
    success
    reason
  }
}

RESPONSE:

{
  "errors": [
    {
      "message": "Unexpected Execution Error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "forgottenPassword"
      ],
      "extensions": {
        "message": "Method not found: '!!0 HotChocolate.Resolvers.IResolverContext.ArgumentValue(HotChocolate.NameString)'.",
        "stackTrace": "   at DataAnnotatedModelValidations.ValidatorMiddleware.<>c__DisplayClass3_0.<ReportErrorFactory>b__0(IInputField argument)\r\n   at System.Linq.Parallel.ForAllOperator`1.ForAllEnumerator`1.MoveNext(TInput& currentElement, Int32& currentKey)\r\n   at System.Linq.Parallel.ForAllSpoolingTask`2.SpoolingWork()\r\n   at System.Linq.Parallel.SpoolingTaskBase.Work()\r\n   at System.Linq.Parallel.QueryTask.BaseWork(Object unused)\r\n   at System.Linq.Parallel.QueryTask.<>c.<.cctor>b__10_0(Object o)\r\n   at System.Threading.Tasks.Task.InnerInvoke()\r\n   at System.Threading.Tasks.Task.<>c.<.cctor>b__277_0(Object obj)\r\n   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\r\n--- End of stack trace from previous location ---\r\n   at System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(Thread threadPoolThread, ExecutionContext executionContext, ContextCallback callback, Object state)\r\n   at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot, Thread threadPoolThread)"
      }
    }
  ],
  "data": {
    "forgottenPassword": null
  }
}```

Modify class property with Annotation

Hi,

Firstly thanks for everything awesome work. Im implemented custom validation attribute for modify property with using data annotation. Custom validation running but not changed property value. Do you have any suggestion?

Validation for record doesn't work.

          Validation for record also doesn't work.
public async Task<UpdateClientFullNamePayload> UpdateClientFullNameAsync(UpdateClientFullNameInput input)
{
    var client = await _dbContext.Clients.FindAsync(input.Id);
    if (client != null)
    {
        client.FullName = input.FullName;
        await _dbContext.SaveChangesAsync();
    }
    return new UpdateClientFullNamePayload(client);
}
public record UpdateClientFullNameInput([ID(nameof(Client))] int Id, [MaxLength(128)] string FullName);
public record UpdateClientFullNamePayload(Client Client);

Originally posted by @LeXaMeN in #11 (comment)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.