GraphQL Generics Unexpected Value Type Error - graphql

I can't figure out how to make the GraphQL types happy with my Generics plan. I am trying to have a response wrapper for all my query and mutation definitions.
Everything was working fine with my query definition using PaginationType and my service returning the Pagination model and all was good. I added the ResponseType wrapper and now I get the conflict.
Expected value of type "ABC.GraphQL.Framework.DTO.ResponseType1[ABC.GraphQL.Framework.Types.Object.PaginationType]\" for \"ResponseType\" but got: ABC.GraphQL.Framework.DTO.Response1[ABC.GraphQL.Framework.DTO.Pagination].",
FieldAsync<ResponseType<PaginationType>>(
"PaginationSearch",
"Returns paginated groups for specified search terms",
arguments: new QueryArguments(...),
resolve: async context => {
return await Service.GetPagination(...);
}
);
public class PaginationType : ObjectGraphType<Pagination>
{
public PaginationType()
{
Field(x => x.totalRecords, nullable: true).Description("Total Records");
....
}
}
public class ResponseType<T> : ObjectGraphType<Response<T>>
{
public ResponseType()
{
Name = "ResponseType";
Field(x => x.success, nullable: true).Description("Operation Success");
Field(x => x.message, nullable: true).Description("Operation Message");
Field(x => x.response, nullable: true, typeof(T)).Description("Operation Response");
}
}
Of course the plain backing models
public class Pagination
{
public int totalRecords { get; set; }
.....
}
public class Response<T>
{
public bool success { get; set; }
public string message { get; set; }
public T response { get; set; }
}
Now my service class returns the plain objects and this has been working to this point so not sure why adding the Response wrapper is now breaking it.
public async Task<Response<Pagination>> GetPagination(...)
{...}

I debugged into the GraphQL library and found it is by design. So I begun digging into to why and found this:
https://github.com/graphql-dotnet/graphql-dotnet/issues/2279

Related

Graphql-Mutation : Schema Exception

I am trying to implement GraphQL -mutation using HotChocolate. But there is some issue with the schema (https://localhost:1234/graphql?sdl) and I am getting the unhandled exception:
The schema builder was unable to identify the query type of the schema. Either specify which type is the query type or set the schema builder to non-strict validation mode.
SchemaException: For more details look at the Errors property. 1. The schema builder was unable to identify the query type of the schema. Either specify which type is the query type or set the schema builder to non-strict validation mode.
My mutation code:
using HotChocolate
public class Book
{
public int Id { get; set; }
[GraphQLNonNullType]
public string Title { get; set; }
public int Pages { get; set; }
public int Chapters { get; set; }
}
public class Mutation
{
public async Task<Book> Book(string title, int pages, string author, int chapters)
{
var book = new Book
{
Title = title,
Chapters = chapters,
Pages = pages,
};
return book;
}
}
I have added the following in the API startup.cs file
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLServer().AddMutationType<Mutation>();
}
You can try this:
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLServer()
.ConfigureSchema(sb => sb.ModifyOptions(opts => opts.StrictValidation = false))
.AddMutationType<Mutation>();
}
This seems to happen when you register only a mutation type. The error goes away if you also register a query type.
So you could add the following to your code
public class Query
{
public string HelloWorld()
{
return "Hello, from GraphQL!";
}
}
And register it in your services:
public void ConfigureServices(IServiceCollection services)
{
services.AddGraphQLServer()
.AddMutationType<Mutation>()
.AddQueryType<Query>();
}
Obviously this isnt an ideal solution, as it's possible that you intend fr your API to only have mutations (although that's probably unlikely). If, like me, your API will support both queries and mutations but you just started building the mutations, you could add this query in as a place holder for now.

swagger swashbuckle does not support nested class as action method parameter

I am using asp.net 5
I have two model class, which are nested, both of the inner class are named Command
public class EditModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
and
public class CreateModel
{
public class Command
{
public int Id { get; set; }
public string Info { get; set; }
}
}
In my Controller class has two methods
[HttpPost]
public IActionResult PutData(CreateModel.Command model)
{
return Ok();
}
[HttpPut]
public IActionResult PostData(EditModel.Command model)
{
return Ok();
}
Since for both Put and Post's query I am using nested class both name Command, Swagger will return the following error
An unhandled exception has occurred while executing the request.
Swashbuckle.AspNetCore.SwaggerGen.SwaggerGeneratorException: Conflicting method/path combination "PUT Test" for actions -
TestSwagger.Controllers.TestController.PutData
(TestSwagger),TestSwagger.Controllers.TestController.PostData
(TestSwagger). Actions require a unique method/path combination for
Swagger/OpenAPI 3.0. Use ConflictingActionsResolver as a workaround
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GenerateOperations(IEnumerable1 apiDescriptions, SchemaRepository schemaRepository) at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GeneratePaths(IEnumerable1
apiDescriptions, SchemaRepository schemaRepository)
at Swashbuckle.AspNetCore.SwaggerGen.SwaggerGenerator.GetSwagger(String
documentName, String host, String basePath)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext
httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext
context)
Swagger will work, if I change one of the Command model name to something different.
Yet, I believe this nested class model name is legit and should work with swagger also. If there a way to work around this. Thanks
By adding c.CustomSchemaIds(x => x.FullName);
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "TestSwagger", Version = "v1" });
c.CustomSchemaIds(x => x.FullName);
});
solved the schemaId conflict. Thanks to this question

Filtering schema out of NSwag swagger

I have an ASP.NET full framework application with API endpoints. I am using NSwag to generate a swagger document. This all works.
I need to generate a document for only a small subset of the endpoints. The paths are filtered, but the schema is not. How do I filter the schema objects to match the path filtering?
Example:
I have this filter
public class IncludeControllersInSwagger : IOperationProcessor
{
public Task<bool> ProcessAsync(OperationProcessorContext context)
{
return Task.FromResult(
context.ControllerType == typeof(ControllerA));
}
}
And this at startup:
settings.GeneratorSettings.OperationProcessors.Add(new IncludeControllersInSwagger());
The controllers are:
public class AResponse
{
public string Message { get; set; }
public bool Flag { get; set; }
}
public class BResponse
{
public string Message { get; set; }
public int Count { get; set; }
}
[Route("a")]
public class ControllerA : ApiController
{
[HttpGet]
public AResponse Get()
{
return new AResponse
{
Message = "Hello from A",
Flag = true
};
}
}
[Route("b")]
public class ControllerB : ApiController
{
[HttpGet]
public BResponse Get()
{
return new BResponse
{
Message = "Hello from B",
Count = 42
};
}
}
Then the generated swagger contains just one path:
"paths": {
"/a": {
"get": { .. etc
}
}
And that's all, this is correct.
But the schemas contains both:
"schemas": {
"AResponse": {
"type": "object",
"additionalProperties": false,
etc
},
"BResponse": {
"type": "object",
"additionalProperties": false,
etc
}
}
}
The BResponse type should not be there. How do you remove it?
This extra data makes the Schemas section extremely verbose and unsuitable for public documentation in the case where there are over 10 endpoints, and only 2 are exposed via a gateway and therefor documented in swagger.
There is a ISchemaProcessor but it does not return a Boolean like the IOperationProcessor.
Have you tried to add the operation filter as first element?
i.e. OperationProcessors.Insert(0, new IncludeControllersInSwagger())
I think this is important as it will filter out the operation before the dto schemas are generated and added to the document.
This is not an answer to your problem, as you already got an answer that seems to work. I do have a suggestion however. Instead of checking the type of controller in your processor, I would suggest to create a custom attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class IncludeInSwaggerAttribute : Attribute
{
}
Then change your processor to look for this attribute:
public class IncludeInSwaggerOperationProcessor : IOperationProcessor
{
public async Task<bool> ProcessAsync(OperationProcessorContext context)
{
return context.ControllerType.GetCustomAttributes<IncludeInSwaggerAttribute>().Any() ||
context.MethodInfo.GetCustomAttributes<IncludeInSwaggerAttribute>().Any();
}
}
This way, you can add the attribute to any new controller you want to include in swagger without having to change your processor. You can also add the attribute on a single action to only include that action, leaving the rest of the actions in the controller out of swagger.

Mediator Api call failing

I'm trying to make a simple request using mediator and .net core. I'm getting an error that I am not understanding. All I'm trying to do is a simple call to get back a guid.
BaseController:
[Route("api/[controller]/[action]")]
[ApiController]
public class BaseController : Controller
{
private IMediator _mediator;
protected IMediator Mediator => _mediator ?? (_mediator = HttpContext.RequestServices.GetService<IMediator>());
}
Controller:
// GET: api/Customer/username/password
[HttpGet("{username}/{password}", Name = "Get")]
public async Task<ActionResult<CustomerViewModel>> Login(string username, string password)
{
return Ok(await Mediator.Send(new LoginCustomerQuery { Username = username,Password = password }));
}
Query:
public class LoginCustomerQuery : IRequest<CustomerViewModel>
{
public string Username { get; set; }
public string Password { get; set; }
}
View Model:
public class CustomerViewModel
{
public Guid ExternalId { get; set; }
}
Handler:
public async Task<CustomerViewModel> Handle(LoginCustomerQuery request, CancellationToken cancellationToken)
{
var entity = await _context.Customers
.Where(e =>
e.Username == request.Username
&& e.Password == Encypt.EncryptString(request.Password))
.FirstOrDefaultAsync(cancellationToken);
if (entity.Equals(null))
{
throw new NotFoundException(nameof(entity), request.Username);
}
return new CustomerViewModel
{
ExternalId = entity.ExternalId
};
}
This is the exception I am getting:
Please let me know what else you need to determine what could be the issue. Also, be kind I have been away from c# for a while.
Thanks for the info it was the missing DI. I added this
// Add MediatR
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(RequestPreProcessorBehavior<,>));
services.AddMediatR(typeof(LoginCustomerQueryHandler).GetTypeInfo().Assembly);
and we are good to go.

How to unit test child validators with When() condition with FluentValidation.TestHelper

The extension method .ShouldHaveChildValidator() in the FluentValidation.TestHelper namespace doesn't have an overload that takes the model. How do I then test that the child validators are set up correctly when using a When() clause like in the following example?
E.g.
public class ParentModel
{
public bool SomeCheckbox { get; set; }
public ChildModel SomeProperty { get; set; }
}
public class ParentModelValidator : AbstractValidator<ParentModel>
{
RuleFor(m => m.SomeProperty)
.SetValidator(new ChildModelValidator())
.When(m => m.SomeCheckbox);
}
I want to Assert that if SomeCheckbox is true, then the child validator is present, and if SomeCheckbox is false, then the child validator isn't present.
I have the following so far in the unit test:
ParentModelValidator validator = new ParentModelValidator();
validator.ShouldHaveChildValidator(
m => m.SomeProperty,
typeof(ChildModelValidator));
but that doesn't take into account the .When() condition.
I notice other methods in the FluentValidation.TestHelper namespace such as .ShouldHaveValidationErrorFor() have an overload that takes the model, so it's easy to test a simple property type with a When() clause by setting up a model that satisfies the precondition.
Any ideas?
Here's a snippet of how I achieve this:
public class ParentModelSimpleValidator : AbstractValidator<ParentModel>
{
public ParentModelSimpleValidator()
{
When(x => x.HasChild, () =>
RuleFor(x => x.Child)
.SetValidator(new ChildModelSimpleValidator()));
}
}
public class ChildModelSimpleValidator : AbstractValidator<ChildModel>
{
public ChildModelSimpleValidator()
{
RuleFor(x => x.ChildName)
.NotEmpty()
.WithMessage("Whatever");
}
}
Here's the relevant simplified models:
[Validator(typeof(ParentModelSimpleValidator))]
public class ParentModel
{
public bool HasChild { get { return Child != null; } }
public ChildModel Child { get; set; }
}
[Validator(typeof(ChildModelSimpleValidator))]
public class ChildModel
{
public string ChildName { get; set; }
public int? ChildAge { get; set; }
}
Here's a sample unit test:
[TestMethod]
public void ShouldValidateChildIfParentHasChild()
{
var validator = new ParentModelSimpleValidator();
var model = new ParentModel
{
ParentName = "AABBC",
Child = new ChildModel { ChildName = string.Empty }
};
validator.ShouldHaveErrorMessage(model, "Whatever");
}
very late to the game here, but I just started using FluentValidation and that was my solution
public class ParentValidator: AbstractValidator<ParentModel>
{
public ParentValidator()
{
// other rules here
// use == for bool?
When(model => model.SomeBoolProperty == false, () => RuleFor(model => model.ChildClass).SetValidator(new ChildClassValidator()));
}
}
public class ChildClassValidator: AbstractValidator<ChildClass>
{
public ChildClassValidator()
{
this
.RuleFor(model => model.SomeProperty).NotNull();
}
}
then the test is
[TestMethod]
public void ParentValidator_should_have_error_in_child_class_property_when_bool_is_false_on_parent()
{
// Arrange - API does not support typical unit test
var validator = new ParentValidator()
var foo = new ParentModel() { SomeBoolProperty = false };
foo.ChildClass.SomeProperty = null;
// Act
var result = validator.Validate(foo);
// Assert - using FluentAssertions
result.Errors.Should().Contain(err => err.PropertyName == "ChildClass.SomeProperty");
}

Resources