Skip to content

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

Merged
merged 53 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from 46 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
0800bb2
feat: use resource graph in routing convention rather than formatter
maurei Aug 26, 2020
0d97cba
fix: discovery tests
maurei Aug 26, 2020
10efa97
fix: null ref exception
maurei Aug 26, 2020
6d314aa
fix: client generated id tests
maurei Aug 26, 2020
22d8700
fix: self review 1
maurei Aug 26, 2020
0476228
chore: rename to pluralizedResourceName
maurei Aug 26, 2020
af01214
fix: whitespace
maurei Aug 26, 2020
c2484e8
fix: enumeration exception
maurei Aug 26, 2020
9ac7456
fix: remove whitespace
maurei Aug 26, 2020
787002a
fix: review
maurei Aug 27, 2020
dc0c75a
fix: review
maurei Aug 27, 2020
05f0ad9
test: resource definition discovery test
maurei Aug 27, 2020
28e76fa
docs: update routing related docs
maurei Aug 27, 2020
978e9dd
docs: rephrase
maurei Aug 27, 2020
9d90952
chore: review
maurei Aug 27, 2020
d91bf76
Update routing.md
maurei Aug 27, 2020
08ab1ac
Update routing.md
maurei Aug 27, 2020
6496a7f
chore: trigger build
maurei Aug 27, 2020
aa3c122
fix: reduce amount of intermediate service providers
maurei Aug 28, 2020
681d2cc
feat: configure MvcOptions in phase two, removal of intermediate serv…
maurei Aug 28, 2020
de23e9f
fix: tests
maurei Aug 28, 2020
0b788b5
feat: remove intermediate service providers, consistent middleware co…
maurei Aug 28, 2020
9a7ebe0
fix: remove unnecessary parameter usejsonapi
maurei Aug 31, 2020
6ff5883
fix: review round 31/08
maurei Aug 31, 2020
501e0b7
fix: merge fix attempt 1
maurei Aug 31, 2020
a0d4cfc
fix: docs
maurei Aug 31, 2020
60b9640
fix: merge I
maurei Sep 1, 2020
526b0c6
fix: merge II
maurei Sep 1, 2020
21ed990
fix: docs, publicName
maurei Sep 1, 2020
f519342
fix: whitespace
maurei Sep 1, 2020
c51eea3
chore: review
maurei Sep 2, 2020
234cc86
chore: review 2
maurei Sep 2, 2020
21dfc9b
docs: simplify middleware extensibility docs
maurei Sep 3, 2020
4262410
Changed filters and formatters to async
Sep 3, 2020
e9e24eb
docs: simpify routing docs
maurei Sep 3, 2020
e016d37
Merge branch 'fix/786' of https://github.com/json-api-dotnet/JsonApiD…
maurei Sep 3, 2020
5c05503
chore: improve diff readability
maurei Sep 3, 2020
685eef8
chore: diff readability, fixes
maurei Sep 3, 2020
9f6a4f6
fix: more diff readability
maurei Sep 3, 2020
6696492
fix: nullcheck
maurei Sep 3, 2020
540bc8a
docs: resource graph builder inaccuracy
maurei Sep 3, 2020
4268be1
fix: broken links markdown
maurei Sep 3, 2020
234e0ec
fix: broken markdown links
maurei Sep 3, 2020
39cfdde
fix: restore whitespace
maurei Sep 3, 2020
d60ced3
fix: diff
maurei Sep 3, 2020
1cbd61f
fix: whitespace
maurei Sep 3, 2020
7c65d38
chore: review
maurei Sep 9, 2020
e53fe67
fix: typo in docs
maurei Sep 9, 2020
57eb82e
chore: discovery tests fix
maurei Sep 9, 2020
cd1222a
docs: improvements
maurei Sep 9, 2020
0b623be
chore: review
maurei Sep 10, 2020
948b72a
chore: casing convention -> naming convention
maurei Sep 10, 2020
911924c
fix: update example in docs
maurei Sep 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion benchmarks/DependencyFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal static class DependencyFactory
{
public static IResourceGraph CreateResourceGraph(IJsonApiOptions options)
{
IResourceGraphBuilder builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
ResourceGraphBuilder builder = new ResourceGraphBuilder(options, NullLoggerFactory.Instance);
builder.Add<BenchmarkResource>(BenchmarkResourcePublicNames.Type);
return builder.Build();
}
Expand Down
45 changes: 38 additions & 7 deletions docs/usage/extensibility/middleware.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
# Middleware
Add the following to your Startup.ConfigureServices method. Replace AppDbContext with your DbContext.

```c3
services.AddJsonApi<AppDbContext>();
It is possible to execute your own middleware before or after `JsonApiMiddleware` by registering it accordingly.

```c#
/// In Startup.Configure
app.UseMiddleware<CustomPreMiddleware>();
app.UseJsonApi();
app.UseMiddleware<CustomPostMiddleware>();
```

It is also possible to replace any other JsonApiDotNetCore middleware component. The following example replaces the internal exception filter with a custom implementation
```c#
/// In Startup.ConfigureServices
services.AddService<IJsonApiGlobalExceptionFilter, CustomExceptionFilter>()
```

Add the middleware to the Startup.Configure method.
Alternatively, you can add additional middleware components by configuring `MvcOptions` directly.

## Configuring MvcOptions

JsonApiDotNetCore configures `MvcOptions` internally when calling `AddJsonApi()`. Additionaly, it is possible to perform a custom configuration of `MvcOptions`. To prevent the library from overwriting your configuration, it is recommended to configure it *after* the library is done configuring `MvcOptions`.

The following example demonstrates this by clearing all internal filters and registering a custom one.
```c#
/// In Startup.ConfigureServices
services.AddSingleton<CustomFilter>();
var builder = services.AddMvcCore();
services.AddJsonApi<AppDbContext>(mvcBuilder: builder);
// Ensure the configuration action is registered after the `AddJsonApiCall`.
builder.AddMvcOptions( mvcOptions =>
{
// Execute the MvcOptions configuration callback after the JsonApiDotNetCore callback as been executed.
_postConfigureMvcOptions?.Invoke(mvcOptions);
});

```c3
app.UseRouting();
/// In Startup.Configure
app.UseJsonApi();
app.UseEndpoints(endpoints => endpoints.MapControllers());
// Ensure the configuration callback is set after calling `UseJsonApi()`.
_postConfigureMvcOptions = mvcOptions =>
{
mvcOptions.Filters.Clear();
mvcOptions.Filters.Insert(0, app.ApplicationServices.GetService<CustomFilter>());
};
```
6 changes: 6 additions & 0 deletions docs/usage/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,14 @@ options.SerializerSettings.Converters.Add(new StringEnumConverter());
options.SerializerSettings.Formatting = Formatting.Indented;
```

The default naming convention as used in the routes and public resources names is also determined here, and can be changed (default is camel-case):
```c#
options.SerializerSettings.ContractResolver = new DefaultContractResolver { NamingStrategy = new KebabCaseNamingStrategy() };
```

Because we copy resource properties into an intermediate object before serialization, Newtonsoft.Json annotations on properties are ignored.


## Enable ModelState Validation

If you would like to use ASP.NET Core ModelState validation into your controllers when creating / updating resources, set `ValidateModelState = true`. By default, no model validation is performed.
Expand Down
61 changes: 37 additions & 24 deletions docs/usage/resource-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,30 @@ It is built at app startup and available as a singleton through Dependency Injec

There are three ways the resource graph can be created:

1. Auto-discovery
1. Manually specifying each resource
2. Specifying an entire DbContext
3. Manually specifying each resource
3. Auto-discovery

### Auto-Discovery
It is also possible to combine the three of them at once. Be aware that some configuration might overlap,
for example you could manually add a resource to the graph which is also auto-discovered. In such a scenario, the configuration
is prioritized by the order of the list above.

Auto-discovery refers to the process of reflecting on an assembly and
detecting all of the json:api resources and services.
### Manual Specification

The following command will build the resource graph using all `IIdentifiable`
implementations. It also injects resource definitions and service layer overrides which we will
cover in a later section. You can enable auto-discovery for the
current assembly by adding the following to your `Startup` class.
You can manually construct the graph.

```c#
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddJsonApi(
options => { /* ... */ },
discovery => discovery.AddCurrentAssembly());
services.AddJsonApi(resources: builder =>
{
builder.Add<Person>();
});
}
```

### Entity Framework Core DbContext
### Specifying an Entity Framework Core DbContext

If you are using Entity Framework Core as your ORM, you can add an entire `DbContext` with one line.

Expand All @@ -58,33 +57,47 @@ public void ConfigureServices(IServiceCollection services)
}
```

### Manual Specification
### Auto-discovery

You can also manually construct the graph.
Auto-discovery refers to the process of reflecting on an assembly and
detecting all of the json:api resources, resource definitions, resource services and repositories.

The following command will build the resource graph using all `IIdentifiable`
implementations. It also injects resource definitions and service layer overrides which we will
cover in a later section. You can enable auto-discovery for the
current assembly by adding the following to your `Startup` class.

```c#
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddJsonApi(resources: builder =>
{
builder.AddResource<Person>();
});
services.AddJsonApi(
options => { /* ... */ },
discovery => discovery.AddCurrentAssembly());
}
```

### Public Resource Type Name
### Public Resource Name

The public resource name is exposed through the `type` member in the json:api payload. This can be configured by the following approaches (in order of priority):

The public resource type name is determined by the following criteria (in order of priority):
1. The `publicName` parameter when manually adding a resource to the graph
```c#
services.AddJsonApi(resources: builder =>
{
builder.Add<Person>(publicName: "people");
});
```

1. The model is decorated with a `ResourceAttribute`
2. The model is decorated with a `ResourceAttribute`
```c#
[Resource("my-models")]
[Resource("myResources")]
public class MyModel : Identifiable { /* ... */ }
```

2. The configured naming convention (by default this is camel-case).
3. The configured naming convention (by default this is camel-case).
```c#
// this will be registered as "myModels"
public class MyModel : Identifiable { /* ... */ }
```
This convention can be changed by setting the `SerializerSettings.ContractResolver` property on `IJsonApiOptions`. See the [options documentation](./options.md#custom-serializer-settings).
70 changes: 34 additions & 36 deletions docs/usage/routing.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
# 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.

```http
GET /api/compoundModels HTTP/1.1
```

## Namespacing and Versioning URLs

You can add a namespace to all URLs by specifying it in ConfigureServices
You can add a namespace to all URLs by specifying it in ConfigureServices.

```c#
public void ConfigureServices(IServiceCollection services)
Expand All @@ -20,40 +12,46 @@ public void ConfigureServices(IServiceCollection services)
```
Which results in URLs like: https://yourdomain.com/api/v1/people

## Disable Convention
## Customizing Routes

The library will configure routes for all controllers in your project.

### Json:api endpoints

You can disable the default casing convention and specify your own template by using the `DisableRoutingConvention` attribute.
By default, for json:api controllers,
- routes are camel-cased. This is based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the json:api spec.
- the route of a controller will match the public name of the resource that is associated it. This means that routes can be customized by [configuring the public name of the associated resource](./resource-graph.md#public-resource-name).

```c#
[Route("[controller]")]
[DisableRoutingConvention]
public class CamelCasedModelsController : JsonApiController<CamelCasedModel>
{
public CamelCasedModelsController(
IJsonApiOptions options,
ILoggerFactory loggerFactory,
IResourceService<CamelCasedModel> resourceService)
: base(options, loggerFactory, resourceService)
{ }
}
public class MyResourceController : JsonApiController<MyApiResource> { /* .... */ }
```
The route for this example will be `/myApiResources`, which will match the type in the json:api payload: `{ "type": "myApiResources", ... }`.


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.
### Non-json:api endpoints

If a controller does not have an associated resource, the [configured naming strategy](./options.md#custom-serializer-settings) will be applied to the name of the controller.
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddJsonApi(resources: builder =>
builder.AddResource<TodoItem>("my-models")); // kebab-cased
}
public class MyResourceController : ControllerBase { /* .... */ }
```
The route for this example is `/myResources`, which can be changed by renaming the controller.

// controller definition
[Route("api/my-models"), DisableRoutingConvention]
public class MyModelsController : JsonApiController<TodoItem>
{
//...
}

## Disabling the Default Routing Convention

It is possible to by-pass the default routing convention for a controller by combining the `Route` and `DisableRoutingConvention`attributes. Any usage of `Route` without `DisableRoutingConvention` is ignored.
```c#
[Route("v1/camelCasedModels"), DisableRoutingConvention]
public class MyCustomResourceController : JsonApiController<CamelCasedModel> { /* ... */ }
```
This example exposes a versioned `CamelCasedModel` endpoint. To ensure guarantee valid link building, it is *highly recommended* to match your custom url with the public name of the associated resource.

See [this](~/usage/resource-graph.md#public-resource-type-name) for
more information on how the resource name is determined.
## Advanced Usage: Custom Routing Convention.

It is possible to use a [custom routing convention](add-link) by registering a custom `IJsonApiRoutingConvention` implementation. This is generally not recommended and for advanced usage only.
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IJsonApiConvention, CustomRoutingConvention>();
}
```
2 changes: 1 addition & 1 deletion src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public override void ConfigureServices(IServiceCollection services)
}, ServiceLifetime.Transient);

services.AddJsonApi<AppDbContext>(ConfigureJsonApiOptions, discovery => discovery.AddCurrentAssembly());

// once all tests have been moved to WebApplicationFactory format we can get rid of this line below
services.AddClientSerialization();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using JsonApiDotNetCore.Middleware;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace JsonApiDotNetCore.Configuration
{
Expand All @@ -22,6 +23,23 @@ public static class ApplicationBuilderExtensions
public static void UseJsonApi(this IApplicationBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));

using var scope = builder.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope();
var inverseRelationshipResolver = scope.ServiceProvider.GetRequiredService<IInverseRelationships>();
inverseRelationshipResolver.Resolve();

var jsonApiApplicationBuilder = builder.ApplicationServices.GetRequiredService<IJsonApiApplicationBuilder>();
jsonApiApplicationBuilder.ConfigureMvcOptions = options =>
{
var inputFormatter = builder.ApplicationServices.GetRequiredService<IJsonApiInputFormatter>();
options.InputFormatters.Insert(0, inputFormatter);

var outputFormatter = builder.ApplicationServices.GetRequiredService<IJsonApiOutputFormatter>();
options.OutputFormatters.Insert(0, outputFormatter);

var routingConvention = builder.ApplicationServices.GetRequiredService<IJsonApiRoutingConvention>();
options.Conventions.Insert(0, routingConvention);
};

builder.UseMiddleware<JsonApiMiddleware>();
}
Expand Down
10 changes: 10 additions & 0 deletions src/JsonApiDotNetCore/Configuration/IJsonApiApplicationBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using Microsoft.AspNetCore.Mvc;

namespace JsonApiDotNetCore.Configuration
{
internal interface IJsonApiApplicationBuilder
{
public Action<MvcOptions> ConfigureMvcOptions { set; }
}
}
42 changes: 0 additions & 42 deletions src/JsonApiDotNetCore/Configuration/IResourceGraphBuilder.cs

This file was deleted.

20 changes: 0 additions & 20 deletions src/JsonApiDotNetCore/Configuration/IServiceDiscoveryFacade.cs

This file was deleted.

Loading