-
-
Notifications
You must be signed in to change notification settings - Fork 158
Routing does not respect custom pluralized resource name #805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 30 commits
0800bb2
0d97cba
10efa97
6d314aa
22d8700
0476228
af01214
c2484e8
9ac7456
787002a
dc0c75a
05f0ad9
28e76fa
978e9dd
9d90952
d91bf76
08ab1ac
6496a7f
aa3c122
681d2cc
de23e9f
0b788b5
9a7ebe0
6ff5883
501e0b7
a0d4cfc
60b9640
526b0c6
21ed990
f519342
c51eea3
234cc86
21dfc9b
4262410
e9e24eb
e016d37
5c05503
685eef8
9f6a4f6
6696492
540bc8a
4268be1
234e0ec
39cfdde
d60ced3
1cbd61f
7c65d38
e53fe67
57eb82e
cd1222a
0b623be
948b72a
911924c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,84 @@ | ||
# Middleware | ||
Add the following to your Startup.ConfigureServices method. Replace AppDbContext with your DbContext. | ||
|
||
```c3 | ||
The following is the default configuration of JsonApiDotNetCore: | ||
1. Call one of the `AddJsonApi( ... )` overloads in the ` Startup.ConfigureServices` method. In this example uses a `DbContext` to build the resource graph | ||
|
||
```c# | ||
services.AddJsonApi<AppDbContext>(); | ||
``` | ||
|
||
Add the middleware to the Startup.Configure method. | ||
2. In the Startup.Configure method, configure your application to use routing, to add the JsonApiMiddleware and to configure endpoint routing. | ||
|
||
```c3 | ||
```c# | ||
app.UseRouting(); | ||
app.UseJsonApi(); | ||
app.UseEndpoints(endpoints => endpoints.MapControllers()); | ||
``` | ||
|
||
The following middleware components, in respective order, are registered: | ||
|
||
Filters: | ||
- `IJsonApiExceptionFilter` | ||
- `IJsonApiTypeMatchFilter` | ||
- `IQueryStringActionFilter` | ||
- `IConvertEmptyActionResultFilter` | ||
|
||
Formatters: | ||
- `IJsonApiInputFormatter` | ||
- `IJsonApiOutputFormatter` | ||
|
||
Routing convention: | ||
- `IJsonApiRoutingConvention` | ||
|
||
Middleware: | ||
- `JsonApiMiddleware` | ||
|
||
All of these components (except for `JsonApiMiddleware`) can be customized by registering your own implementation of these services. For example: | ||
|
||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```c# | ||
services.AddSingleton<IJsonApiExceptionFilter, MyCustomExceptionFilter>(); | ||
``` | ||
|
||
It is also possible to directly access the .NET Core `MvcOptions` object and have full controll over which components are registered. | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Configuring MvcOptions | ||
|
||
JsonApiDotNetCore internally configures `MvcOptions` when calling `AddJsonApi( ... )`. However, it is still possible to register a custom configuration callback. To achieve this it is recommended to register this callback *after* the `AddJsonApi( ... )` call. It is also possible to do it earlier, but your configuration might be overridden by the JsonApiDotNetCore configuration. | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The following example replaces all internally registered filters by retrieving a custom filter from the DI container. | ||
```c# | ||
public class Startup | ||
{ | ||
private Action<MvcOptions> _postConfigureMvcOptions; | ||
|
||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
... | ||
|
||
var builder = services.AddMvcCore(); | ||
services.AddJsonApi<AppDbContext>( ... , mvcBuilder: builder); | ||
mvcCoreBuilder.AddMvcOptions(x => | ||
{ | ||
// execute the mvc configuration callback after the JsonApiDotNetCore callback as been executed. | ||
_postConfigureMvcOptions?.Invoke(x); | ||
}); | ||
|
||
... | ||
} | ||
|
||
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment) | ||
{ | ||
|
||
... | ||
|
||
// Using a callback, we can defer to later (when service collection has become available). | ||
_postConfigureMvcOptions = mvcOptions => | ||
{ | ||
mvcOptions.Filters.Clear(); | ||
mvcOptions.Filters.Insert(0, app.ApplicationServices.GetService<CustomFilter>()); | ||
}; | ||
|
||
... | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to provide code samples that actually work. Novice developers copy/paste this and are then clueless. Also rename 'x' to 'setupAction' (which matches the method signature), use capital "Execute" in comment, rename There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I disagree. The goal of these examples is not to provide the developer with a minimally working example that works off-the-shelve. That results overly verbose examples while the code of interest is actually a very minimal set of lines. (Microsoft does the former in their documentation and I have always found their examples extremely unreadable, I don't want to go down that road). The goal of these examples is to point the developer to the relevant bits of code, under the assumption that the developer knows how to call Agree on the following and will change accordingly:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking about this further, if you agree on just focussing on the few lines of interest rather than having a verbose example, I could change the example to not be syntactically incorrect by changing in into: // In Startup.Configure
var builder = services.AddMvcCore();
services.AddJsonApi<AppDbContext>(mvcBuilder: builder);
mvcCoreBuilder.AddMvcOptions( mvcOptions =>
{
// Execute the MvcOptions configuration callback after the JsonApiDotNetCore callback as been executed.
_postConfigureMvcOptions?.Invoke(mvcOptions);
});
// In Startup.Configure
_postConfigureMvcOptions = mvcOptions =>
{
mvcOptions.Filters.Clear();
mvcOptions.Filters.Insert(0, app.ApplicationServices.GetService<CustomFilter>());
}; Let me know if you think this is an improvement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I'm not saying that. Providing a code sample that actually works does not mean providing an entire sample application. It means to ensure it has no obvious compile errors or references that do not exist because you missed something during copy/paste. Your new sample is more or less what I had in mind. But even that one contains compile errors: you declare To comment on your sample, I think it is fine like that, except that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And it looks like the first comment needs to be ConfigureServices instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should rename "CustomFilter" to something more concrete, for example: CustomAsyncQueryStringActionFilter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An empty line after There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Modified There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to verify when exactly the callback gets executed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated example accordingly |
||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,59 +1,69 @@ | ||
# Routing | ||
|
||
By default the library will configure routes for each controller. | ||
Based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the json:api spec, routes are camel-cased. | ||
The library will configure routes for each controller. By default, based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the json:api spec, routes are camel-cased. | ||
|
||
```http | ||
GET /api/compoundModels HTTP/1.1 | ||
``` | ||
|
||
## Namespacing and Versioning URLs | ||
There are two ways the library will try to create a route for a controller: | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
1. **By inspecting the controller for an associated resource**. The library will try to first use the public resource name of the resource associated to a controller. This means that the value of the `type` member of the json:api document for a resource will be equal to the route. | ||
Note that this implies that it is possible to configure a route configuring the exposed resource name. See [this section](~/usage/resource-graph.md#public-resource-name) on how this can be achieved. Example: | ||
```c# | ||
// controller | ||
public class MyResourceController : JsonApiController<MyApiResource> { /* .... */ } // note that the route is NOT "myResources", but "myApiResources" | ||
|
||
You can add a namespace to all URLs by specifying it in ConfigureServices | ||
// request | ||
GET /myApiResources HTTP/1.1 | ||
|
||
// response | ||
HTTP/1.1 200 OK | ||
Content-Type: application/vnd.api+json | ||
|
||
```c# | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddJsonApi<AppDbContext>( | ||
options => options.Namespace = "api/v1"); | ||
"data": [{ | ||
"type": "myApiResources", | ||
"id": "1", | ||
"attributes": { ... } | ||
}] | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
``` | ||
Which results in URLs like: https://yourdomain.com/api/v1/people | ||
|
||
## Disable Convention | ||
2. **By using the name of the controller**. If no associated resource was detected for a controller, the library will construct a route from the name of the controller by using the configured naming strategy (*camelCase* by default, see [this section](~/usage/resource-graph.md#public-resource-name) on how to configure this). This is in alignment with the default .NET Core MVC routing approach. | ||
In the following example the controller is not associated to a resource by the library because it does not inherit from `BaseJsonApiController<T>`. | ||
```c# | ||
// controller | ||
public class MyResourceController : ControllerBase { /* .... */ } | ||
|
||
You can disable the default casing convention and specify your own template by using the `DisableRoutingConvention` attribute. | ||
// request | ||
GET /myResources HTTP/1.1 | ||
``` | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## Customized the Routing Convention | ||
It is possible to fully customize routing behaviour by registering a `IJsonApiRoutingConvention` implementation. | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
||
```c# | ||
[Route("[controller]")] | ||
[DisableRoutingConvention] | ||
public class CamelCasedModelsController : JsonApiController<CamelCasedModel> | ||
// Startup.cs | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
public CamelCasedModelsController( | ||
IJsonApiOptions options, | ||
ILoggerFactory loggerFactory, | ||
IResourceService<CamelCasedModel> resourceService) | ||
: base(options, loggerFactory, resourceService) | ||
{ } | ||
services.AddSingleton<IJsonApiConvention, CustomRoutingConvention>(); | ||
} | ||
``` | ||
|
||
It is important to note that your routes must still end with the model name in the same format as the resource name. This is so that we can build accurate resource links in the json:api document. For example, if you define a resource as MyModels, the controller route must match. | ||
## Namespacing and Versioning URLs | ||
You can add a namespace to all URLs by specifying it in ConfigureServices | ||
|
||
```c# | ||
public void ConfigureServices(IServiceCollection services) | ||
{ | ||
services.AddJsonApi(resources: builder => | ||
builder.AddResource<TodoItem>("my-models")); // kebab-cased | ||
} | ||
|
||
// controller definition | ||
[Route("api/my-models"), DisableRoutingConvention] | ||
public class MyModelsController : JsonApiController<TodoItem> | ||
{ | ||
//... | ||
services.AddJsonApi<AppDbContext>( | ||
options => options.Namespace = "api/v1"); | ||
} | ||
``` | ||
Which results in URLs like: https://yourdomain.com/api/v1/people | ||
|
||
See [this](~/usage/resource-graph.md#public-resource-type-name) for | ||
more information on how the resource name is determined. | ||
## Disabling the Default Routing Convention | ||
It is possible to completely bypass the default routing convention for a particular controller and specify a custom routing template by using the `DisableRoutingConvention` attribute. | ||
In the following example, the `CamelCasedModel` resource can be accessed on `/my-custom-route`. | ||
|
||
```c# | ||
[Route("my-custom-route"), DisableRoutingConvention] | ||
public class MyCustomResourceController : JsonApiController<CamelCasedModel> { /* ... */ } | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
using System.Runtime.CompilerServices; | ||
[assembly:InternalsVisibleTo("UnitTests")] | ||
[assembly:InternalsVisibleTo("DiscoveryTests")] | ||
[assembly:InternalsVisibleTo("JsonApiDotNetCoreExampleTests")] | ||
[assembly:InternalsVisibleTo("NoEntityFrameworkTests")] | ||
[assembly:InternalsVisibleTo("Benchmarks")] | ||
[assembly:InternalsVisibleTo("ResourceEntitySeparationExampleTests")] | ||
bart-degreed marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.