Closed
Description
DESCRIPTION
In a simple project that uses JsonApiDotNetCore.OpenApi.Swashbuckle, when I create a single resource and do something like:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// BEGIN FOOTGUN
builder.Services.AddOpenApiForJsonApi();
builder.Services.AddJsonApi(opts => {
opts.Namespace = "api";
}, discovery: discovery => discovery.AddCurrentAssembly());
// END FOOTGUN
// ...
var app = builder.Build();
app.UseJsonApi();
I repeatedly get the error described in #1179:
JsonApiDotNetCore.Errors.InvalidConfigurationException: Multiple controllers found for resource type 'myResources': 'MyApp.Api.Library.MyResource.MyResourceController' and 'MyApp.Api.Library.MyResource.MyResourceController'
- This happens whether I use the
[Resource]
attribute or define an explicit controller. - It happens whether I use automatic discovery or manually register the resource.
Any of the following changes stops the error from occurring:
- Removing the call to
app.UseJsonApi();
- Removing
JsonApiDotNetCore.OpenApi.Swashbuckle
from the project - Putting the
AddOpenApiForJsonApi()
call afterAddJsonApi()
STEPS TO REPRODUCE
- Use both JsonApiDotNetCore and JsonApiDotNetCore.OpenApi.Swashbuckle in a project.
- Call
Services.AddOpenApiForJsonApi()
beforeServices.AddJsonApi()
. - Start the project.
EXPECTED BEHAVIOR
I would expect the package to handle this gracefully or surface an error telling me that I need to call those methods in a specific order.
ACTUAL BEHAVIOR
I see an error for which the only matching issue in Github tells me it's a problem with source generation in .NET 6.
VERSIONS USED
- JsonApiDotNetCore version: 5.7.1
- .NET Core version: 8.0.410
- Entity Framework Core version: N/A
- Database provider: N/A
Activity
bkoelman commentedon May 29, 2025
Thanks for reporting this. It is true that OpenAPI must be registered afterwards. Would you like to create a PR that throws an exception with a helpful message?
isaaclyman commentedon May 29, 2025
I would like to, but unfortunately don't have the time. If my schedule opens up in a couple months I might be able to take a stab at it.
bkoelman commentedon Jun 2, 2025
No worries, I can take care of it.
I'm curious about your experience with our OpenAPI support. I'd appreciate any feedback; anything you'd like to share? Are you interested only in a documentation website, or also in generating typed client libraries?
isaaclyman commentedon Jun 2, 2025
For the moment it's just for documentation. It works pretty well, though I have noticed that if I create a controller that only supports certain methods (e.g. injects a getAll service and leaves the others null), all the other methods still show up in Swagger even though they're not allowed. (There are workarounds, of course.)
I've also had a difficult time figuring out how to generate dual documentation for both endpoints generated/owned by this library and our own custom endpoints. I imagine there's a way, if I invest more time into it, though it looks like the code does some service replacement so I'm not sure if that makes things more difficult.
In general, though, I'm happy there's a well-maintained library with so much extensibility. For our use case we mostly care about conformance to the JSON:API spec from an external client perspective, and the application in question won't be doing any direct database querying, so it's valuable to be able to swap out functionality at whatever level is most convenient.
One thing I'd like to see in the documentation (and may come back to add later on, once I've got a handle on it) is some rules of thumb for which level of extensibility to plug into: BaseJsonApiController, JsonApiController, Service, Repository, or Resource Definition. Not all the boundaries between those things are clear to me yet.
Anyway that's what comes to mind after diving in for a few days. Thanks for your work building and maintaining this and I hope to be able to give some time back to the library later on.
bkoelman commentedon Jun 2, 2025
I'm aware of that limitation, but I'm not sure what we can do to improve that, other than document better. Because the endpoints are exposed (they just throw when called), scanning for action methods will detect them. We can't know what their implementation does. See the comments at:
JsonApiDotNetCore/src/JsonApiDotNetCore.OpenApi.Swashbuckle/OpenApiEndpointConvention.cs
Lines 85 to 100 in 86cee8b
This also answers your question on whether to choose
JsonApiController
orBaseJsonApiController
.BaseJsonApiController
contains only logic, without exposing any endpoints. Deriving from it gives flexibility to specify which endpoints to expose (using[HttpGet]
and similar attributes). Alternatively, switch to auto-generated controllers, because then we know what's being exposed to get it right.Yes, we do lots of replacements to compensate for the fact that our controller action method signatures don't match the JSON:API request/response structure. Basically, there are two use cases:
Which of the two are you referring to?
Endpoint expansion happens in JsonApiActionDescriptorCollectionProvider. This turns a route such as
/article/{id}/{relationshipName}
into/article/{id}/author
and/article/{id}/revisions
.This is documented a bit at https://www.jsonapi.net/usage/faq.html#whats-the-best-place-to-put-my-custom-businessvalidation-logic and the intro text at https://www.jsonapi.net/usage/extensibility/services.html. The essence is that resource definitions are resource-oriented, whereas controllers, resource services and repositories are pipeline-oriented. So it depends on your needs as to where is best to plug in. For example, to handle "any filter anywhere on an article" use a resource definition. To handle "an article on a primary endpoint," use the pipeline extensibility. The latter doesn't kick in when an article appears in a secondary endpoint or in an
include
at another endpoint.Thanks for your kind words. Your feedback is greatly appreciated.
isaaclyman commentedon Jun 3, 2025
Thanks, this is really helpful commentary.
To answer your question about additional endpoints in Swagger, my use case is option 2 (non-JSON:API controllers). I'm probably making some kind of configuration mistake on my end, but they simply don't show up.
bkoelman commentedon Jun 3, 2025
I was wrong about this. They are currently blocked to avoid downstream crashes. This is tracked at #1066.