Skip to content

Allow for multiple naming conventions (camelCase vs kebab-case) #581

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 13 commits into from
Oct 17, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Services;
using JsonApiDotNetCoreExample.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace JsonApiDotNetCoreExample.Controllers
{
[Route("[controller]")]
[DisableRoutingConvention]
public class CamelCasedModelsController : JsonApiController<CamelCasedModel>
{
public CamelCasedModelsController(
Expand Down
6 changes: 3 additions & 3 deletions src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ResourceGraphBuilder : IResourceGraphBuilder

public ResourceGraphBuilder(IResourceNameFormatter formatter = null)
{
_resourceNameFormatter = formatter ?? new DefaultResourceNameFormatter();
_resourceNameFormatter = formatter ?? new KebabCaseFormatter();
}

/// <inheritdoc />
Expand All @@ -35,7 +35,7 @@ public IResourceGraph Build()
_entities.ForEach(SetResourceLinksOptions);

List<ControllerResourceMap> controllerContexts = new List<ControllerResourceMap>() { };
foreach(var cm in _controllerMapper)
foreach (var cm in _controllerMapper)
{
var model = cm.Key;
foreach (var controller in cm.Value)
Expand Down Expand Up @@ -180,7 +180,7 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
// Article → ArticleTag.Tag
hasManyThroughAttribute.RightProperty = throughProperties.SingleOrDefault(x => x.PropertyType == hasManyThroughAttribute.DependentType)
?? throw new JsonApiSetupException($"{hasManyThroughAttribute.ThroughType} does not contain a navigation property to type {hasManyThroughAttribute.DependentType}");

// ArticleTag.TagId
var rightIdPropertyName = JsonApiOptions.RelatedIdMapper.GetRelatedIdPropertyName(hasManyThroughAttribute.RightProperty.Name);
hasManyThroughAttribute.RightIdProperty = throughProperties.SingleOrDefault(x => x.Name == rightIdPropertyName)
Expand Down
7 changes: 0 additions & 7 deletions src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

namespace JsonApiDotNetCore.Configuration
{

/// <summary>
/// Global options
/// </summary>
Expand All @@ -29,12 +28,6 @@ public class JsonApiOptions : IJsonApiOptions
/// <inheritdoc/>
public Link RelationshipLinks { get; set; } = Link.All;


/// <summary>
/// Provides an interface for formatting resource names by convention
/// </summary>
public static IResourceNameFormatter ResourceNameFormatter { get; set; } = new DefaultResourceNameFormatter();

/// <summary>
/// Provides an interface for formatting relationship id properties given the navigation property name
/// </summary>
Expand Down
2 changes: 0 additions & 2 deletions src/JsonApiDotNetCore/Controllers/JsonApiController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

namespace JsonApiDotNetCore.Controllers
{


public class JsonApiController<T, TId> : BaseJsonApiController<T, TId> where T : class, IIdentifiable<TId>
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ public static IApplicationBuilder UseJsonApi(this IApplicationBuilder app, bool
app.UseMiddleware<CurrentRequestMiddleware>();

if (useMvc)
{
app.UseMvc();
}

using (var scope = app.ApplicationServices.CreateScope())
{
Expand Down
62 changes: 34 additions & 28 deletions src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@
using JsonApiDotNetCore.Serialization.Server.Builders;
using JsonApiDotNetCore.Serialization.Server;
using JsonApiDotNetCore.Serialization.Client;
using JsonApiDotNetCore.Controllers;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace JsonApiDotNetCore.Extensions
{
// ReSharper disable once InconsistentNaming
public static class IServiceCollectionExtensions
{
static private readonly Action<JsonApiOptions> _noopConfig = opt => { };
static private JsonApiOptions _options { get { return new JsonApiOptions(); } }
public static IServiceCollection AddJsonApi<TContext>(this IServiceCollection services,
IMvcCoreBuilder mvcBuilder = null)
where TContext : DbContext
Expand All @@ -48,25 +47,20 @@ public static IServiceCollection AddJsonApi<TContext>(this IServiceCollection se
/// <param name="configureAction"></param>
/// <returns></returns>
public static IServiceCollection AddJsonApi<TContext>(this IServiceCollection services,
Action<JsonApiOptions> configureAction,
Action<JsonApiOptions> configureOptions,
IMvcCoreBuilder mvcBuilder = null)
where TContext : DbContext
{
var options = _options;
var options = new JsonApiOptions();
// add basic Mvc functionality
mvcBuilder = mvcBuilder ?? services.AddMvcCore();
// set standard options
configureAction(options);

// configures JsonApiOptions;
configureOptions(options);
// ResourceGraphBuilder should not be exposed on JsonApiOptions.
// Instead, ResourceGraphBuilder should consume JsonApiOptions

// build the resource graph using ef core DbContext
options.BuildResourceGraph(builder => builder.AddDbContext<TContext>());

// add JsonApi fitlers and serializer
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options));

ConfigureMvc(services, mvcBuilder, options);
// register services
AddJsonApiInternals<TContext>(services, options);
return services;
Expand All @@ -83,13 +77,12 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services,
Action<JsonApiOptions> configureOptions,
IMvcCoreBuilder mvcBuilder = null)
{
var options = _options;
var options = new JsonApiOptions();
// add basic Mvc functionality
mvcBuilder = mvcBuilder ?? services.AddMvcCore();
// configures JsonApiOptions;
configureOptions(options);

// add JsonApi fitlers and serializer
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options));

ConfigureMvc(services, mvcBuilder, options);
// register services
AddJsonApiInternals(services, options);
return services;
Expand All @@ -107,22 +100,29 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services,
Action<ServiceDiscoveryFacade> autoDiscover,
IMvcCoreBuilder mvcBuilder = null)
{
var options = _options;
var options = new JsonApiOptions();
// add basic Mvc functionality
mvcBuilder = mvcBuilder ?? services.AddMvcCore();
// configures JsonApiOptions;
configureOptions(options);

// build the resource graph using auto discovery.
var facade = new ServiceDiscoveryFacade(services, options.ResourceGraphBuilder);
autoDiscover(facade);

// add JsonApi fitlers and serializer
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options));

ConfigureMvc(services, mvcBuilder, options);
// register services
AddJsonApiInternals(services, options);
return services;
}

private static void ConfigureMvc(IServiceCollection services, IMvcCoreBuilder mvcBuilder, JsonApiOptions options)
{
// add JsonApi filters and serializers
mvcBuilder.AddMvcOptions(opt => AddMvcOptions(opt, options));
// register services that allow user to override behaviour that is configured on startup, like routing conventions
AddStartupConfigurationServices(services, options);
var intermediateProvider = services.BuildServiceProvider();
mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, intermediateProvider.GetRequiredService<IJsonApiRoutingConvention>()));
}

private static void AddMvcOptions(MvcOptions options, JsonApiOptions config)
{
Expand All @@ -143,11 +143,20 @@ public static void AddJsonApiInternals<TContext>(
AddJsonApiInternals(services, jsonApiOptions);
}

/// <summary>
/// Adds services to the container that need to be retrieved with an intermediate provider during Startup.
/// </summary>
private static void AddStartupConfigurationServices(this IServiceCollection services, JsonApiOptions jsonApiOptions)
{
services.AddSingleton<IJsonApiOptions>(jsonApiOptions);
services.TryAddSingleton<IResourceNameFormatter>(new KebabCaseFormatter());
services.TryAddSingleton<IJsonApiRoutingConvention, DefaultRoutingConvention>();
}

public static void AddJsonApiInternals(
this IServiceCollection services,
JsonApiOptions jsonApiOptions)
{

var graph = jsonApiOptions.ResourceGraph ?? jsonApiOptions.ResourceGraphBuilder.Build();

if (graph.UsesDbContext == false)
Expand Down Expand Up @@ -183,14 +192,12 @@ public static void AddJsonApiInternals(
services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>));
services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>));

services.AddSingleton<IJsonApiOptions>(jsonApiOptions);
services.AddSingleton<ILinksConfiguration>(jsonApiOptions);
services.AddSingleton(graph);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IContextEntityProvider>(graph);
services.AddScoped<ICurrentRequest, CurrentRequest>();
services.AddScoped<IScopedServiceProvider, RequestScopedServiceProvider>();
services.AddScoped<JsonApiRouteHandler>();
services.AddScoped<IScopedServiceProvider, RequestScopedServiceProvider>();
services.AddScoped<IJsonApiWriter, JsonApiWriter>();
services.AddScoped<IJsonApiReader, JsonApiReader>();
services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
Expand Down Expand Up @@ -273,7 +280,6 @@ public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions js
{
options.InputFormatters.Insert(0, new JsonApiInputFormatter());
options.OutputFormatters.Insert(0, new JsonApiOutputFormatter());
options.Conventions.Insert(0, new DasherizedRoutingConvention(jsonApiOptions.Namespace));
}

/// <summary>
Expand Down
95 changes: 0 additions & 95 deletions src/JsonApiDotNetCore/Graph/IResourceNameFormatter.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using str = JsonApiDotNetCore.Extensions.StringExtensions;

namespace JsonApiDotNetCore.Graph
{
/// <summary>
/// Uses kebab-case as formatting options in the route and request/response body.
/// </summary>
/// <example>
/// <code>
/// _default.FormatResourceName(typeof(TodoItem)).Dump();
/// // > "todoItems"
/// </code>
/// </example>
/// <example>
/// Given the following property:
/// <code>
/// public string CompoundProperty { get; set; }
/// </code>
/// The public attribute will be formatted like so:
/// <code>
/// _default.FormatPropertyName(compoundProperty).Dump();
/// // > "compoundProperty"
/// </code>
/// </example>
/// <example>
/// <code>
/// _default.ApplyCasingConvention("TodoItems");
/// // > "todoItems"
///
/// _default.ApplyCasingConvention("TodoItem");
/// // > "todoItem"
/// </code>
/// </example>
public class CamelCaseFormatter: BaseResourceNameFormatter
{
/// <inheritdoc/>
public override string ApplyCasingConvention(string properName) => str.Camelize(properName);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Reflection;

namespace JsonApiDotNetCore.Graph
{
/// <summary>
/// Provides an interface for formatting resource names by convention
/// </summary>
public interface IResourceNameFormatter
{
/// <summary>
/// Get the publicly visible resource name from the internal type name
/// </summary>
string FormatResourceName(Type resourceType);

/// <summary>
/// Get the publicly visible name for the given property
/// </summary>
string FormatPropertyName(PropertyInfo property);

/// <summary>
/// Aoplies the desired casing convention to the internal string.
/// This is generally applied to the type name after pluralization.
/// </summary>
string ApplyCasingConvention(string properName);
}
}
Loading