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 all 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
38 changes: 32 additions & 6 deletions docs/usage/extensibility/middleware.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,40 @@
# Middleware
Add the following to your Startup.ConfigureServices method. Replace AppDbContext with your DbContext.

```c3
services.AddJsonApi<AppDbContext>();
It is possible to replace JsonApiDotNetCore middleware components by configuring the IoC container and by configuring `MvcOptions`.

## Configuring the IoC container

The following example replaces the internal exception filter with a custom implementation.
```c#
/// In Startup.ConfigureServices
services.AddService<IAsyncJsonApiExceptionFilter, CustomAsyncExceptionFilter>()
```

Add the middleware to the Startup.Configure method.
## Configuring `MvcOptions`

The following example replaces all internal filters with a custom filter.
```c#
/// In Startup.ConfigureServices
services.AddSingleton<CustomAsyncQueryStringActionFilter>();

var builder = services.AddMvcCore();
services.AddJsonApi<AppDbContext>(mvcBuilder: builder);

```c3
app.UseRouting();
// Ensure this call is placed after the AddJsonApi call.
builder.AddMvcOptions(mvcOptions =>
{
_postConfigureMvcOptions?.Invoke(mvcOptions);
});

/// In Startup.Configure
app.UseJsonApi();

// Ensure this call is placed before the UseEndpoints call.
_postConfigureMvcOptions = mvcOptions =>
{
mvcOptions.Filters.Clear();
mvcOptions.Filters.Insert(0, app.ApplicationServices.GetService<CustomAsyncQueryStringActionFilter>());
};

app.UseEndpoints(endpoints => endpoints.MapControllers());
```
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
38 changes: 26 additions & 12 deletions docs/usage/resource-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ There are three ways the resource graph can be created:
2. Specifying an entire DbContext
3. Manually specifying each resource

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

### Auto-discovery

Auto-discovery refers to the process of reflecting on an assembly and
detecting all of the json:api resources and services.
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
Expand All @@ -34,9 +38,9 @@ public void ConfigureServices(IServiceCollection services)
}
```

### 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.
If you are using Entity Framework Core as your ORM, you can add all the models of a `DbContext` to the resource graph.

```c#
// Startup.cs
Expand All @@ -46,7 +50,7 @@ public void ConfigureServices(IServiceCollection services)
}
```

Be aware that the previous command does not inject resource definitions and service layer overrides. You can combine it with auto-discovery to register them.
Be aware that this does not register resource definitions, resource services and repositories. You can combine it with auto-discovery to achieve this.

```c#
// Startup.cs
Expand All @@ -60,31 +64,41 @@ public void ConfigureServices(IServiceCollection services)

### Manual Specification

You can also manually construct the graph.
You can manually construct the graph.

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

### 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 { /* ... */ }
```

The default naming convention can be changed in [options](./options.md#custom-serializer-settings).
9 changes: 2 additions & 7 deletions docs/usage/resources/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,8 @@ public class Person : Identifiable

There are two ways the public attribute name is determined:

1. By convention, specified in @JsonApiDotNetCore.Configuration.JsonApiOptions#JsonApiDotNetCore_Configuration_JsonApiOptions_SerializerSettings
```c#
options.SerializerSettings.ContractResolver = new DefaultContractResolver
{
NamingStrategy = new KebabCaseNamingStrategy() // default: CamelCaseNamingStrategy
};
```
1. Using the configured [naming convention](./options.md#custom-serializer-settings).


2. Individually using the attribute's constructor
```c#
Expand Down
71 changes: 35 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,47 @@ public void ConfigureServices(IServiceCollection services)
```
Which results in URLs like: https://yourdomain.com/api/v1/people

## Disable Convention
## Default Routing Convention

You can disable the default casing convention and specify your own template by using the `DisableRoutingConvention` attribute.
The library will configure routes for all controllers in your project. By default, routes are camel-cased. This is based on the [recommendations](https://jsonapi.org/recommendations/) outlined in the json:api spec.

```c#
[Route("[controller]")]
[DisableRoutingConvention]
public class CamelCasedModelsController : JsonApiController<CamelCasedModel>
{
public CamelCasedModelsController(
IJsonApiOptions options,
ILoggerFactory loggerFactory,
IResourceService<CamelCasedModel> resourceService)
: base(options, loggerFactory, resourceService)
{ }
}
public class OrderLine : Identifiable { }

public class OrderLineController : JsonApiController<OrderLine> { /* .... */ }
```

```http
GET /orderLines HTTP/1.1
```

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.
The public name of the resource ([which can be customized](./resource-graph.md#public-resource-name)) is used for the route, instead of the controller name.

### Non-json:api controllers

If a controller does not inherit from `JsonApiController<TResource>`, the [configured naming convention](./options.md#custom-serializer-settings) is 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 OrderLineController : ControllerBase { /* .... */ }
```
```http
GET /orderLines HTTP/1.1
```

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

It is possible to bypass the default routing convention for a controller.
```c#
[Route("v1/custom/route/orderLines"), DisableRoutingConvention]
public class OrderLineController : JsonApiController<OrderLine> { /* ... */ }
```
It is required to match your custom url with the public name of the associated resource.

## Advanced Usage: Custom Routing Convention

See [this](~/usage/resource-graph.md#public-resource-type-name) for
more information on how the resource name is determined.
It is possible to replace the built-in routing convention with a [custom routing convention]](https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/application-model?view=aspnetcore-3.1#sample-custom-routing-convention) by registering an implementation of `IJsonApiRoutingConvention`.
```c#
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IJsonApiRoutingConvention, 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; }
}
}
2 changes: 1 addition & 1 deletion src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public interface IJsonApiOptions
/// Specifies the settings that are used by the <see cref="JsonSerializer"/>.
/// Note that at some places a few settings are ignored, to ensure json:api spec compliance.
/// <example>
/// The next example changes the casing convention to kebab casing.
/// The next example changes the naming convention to kebab casing.
/// <code><![CDATA[
/// options.SerializerSettings.ContractResolver = new DefaultContractResolver
/// {
Expand Down
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