Skip to content

Commit 062b208

Browse files
committed
optional attribute and relationship names
1 parent 24d7aa9 commit 062b208

File tree

12 files changed

+59
-38
lines changed

12 files changed

+59
-38
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
using GettingStarted.Models;
22
using JsonApiDotNetCore.Controllers;
33
using JsonApiDotNetCore.Services;
4-
using Microsoft.Extensions.Logging;
54

65
namespace GettingStarted
76
{
87
public class ArticlesController : JsonApiController<Article>
98
{
109
public ArticlesController(
1110
IJsonApiContext jsonApiContext,
12-
IResourceService<Article> resourceService,
13-
ILoggerFactory loggerFactory)
14-
: base(jsonApiContext, resourceService, loggerFactory)
11+
IResourceService<Article> resourceService)
12+
: base(jsonApiContext, resourceService)
1513
{ }
1614
}
1715
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
using GettingStarted.Models;
22
using JsonApiDotNetCore.Controllers;
33
using JsonApiDotNetCore.Services;
4-
using Microsoft.Extensions.Logging;
54

65
namespace GettingStarted
76
{
87
public class PeopleController : JsonApiController<Person>
98
{
109
public PeopleController(
1110
IJsonApiContext jsonApiContext,
12-
IResourceService<Person> resourceService,
13-
ILoggerFactory loggerFactory)
14-
: base(jsonApiContext, resourceService, loggerFactory)
11+
IResourceService<Person> resourceService)
12+
: base(jsonApiContext, resourceService)
1513
{ }
1614
}
1715
}

src/Examples/GettingStarted/Models/Article.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ namespace GettingStarted.Models
44
{
55
public class Article : Identifiable
66
{
7-
[Attr("title")]
7+
[Attr]
88
public string Title { get; set; }
99

10-
[HasOne("author")]
10+
[HasOne]
1111
public Person Author { get; set; }
1212
public int AuthorId { get; set; }
1313
}

src/Examples/GettingStarted/Models/Person.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ namespace GettingStarted.Models
55
{
66
public class Person : Identifiable
77
{
8-
[Attr("name")]
8+
[Attr]
99
public string Name { get; set; }
1010

11-
[HasMany("articles")]
11+
[HasMany]
1212
public List<Article> Articles { get; set; }
1313
}
1414
}

src/JsonApiDotNetCore/Builders/ContextGraphBuilder.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using JsonApiDotNetCore.Configuration;
56
using JsonApiDotNetCore.Extensions;
67
using JsonApiDotNetCore.Graph;
78
using JsonApiDotNetCore.Internal;
@@ -64,9 +65,8 @@ public class ContextGraphBuilder : IContextGraphBuilder
6465
{
6566
private List<ContextEntity> _entities = new List<ContextEntity>();
6667
private List<ValidationResult> _validationResults = new List<ValidationResult>();
67-
6868
private bool _usesDbContext;
69-
private IResourceNameFormatter _resourceNameFormatter = new DefaultResourceNameFormatter();
69+
private IResourceNameFormatter _resourceNameFormatter = JsonApiOptions.ResourceNameFormatter;
7070

7171
public Link DocumentLinks { get; set; } = Link.All;
7272

@@ -128,6 +128,7 @@ protected virtual List<AttrAttribute> GetAttributes(Type entityType)
128128
if (attribute == null)
129129
continue;
130130

131+
attribute.PublicAttributeName = attribute.PublicAttributeName ?? JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop);
131132
attribute.InternalAttributeName = prop.Name;
132133
attribute.PropertyInfo = prop;
133134

@@ -146,6 +147,8 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
146147
{
147148
var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute));
148149
if (attribute == null) continue;
150+
151+
attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? JsonApiOptions.ResourceNameFormatter.FormatPropertyName(prop);
149152
attribute.InternalRelationshipName = prop.Name;
150153
attribute.Type = GetRelationshipType(attribute, prop);
151154
attributes.Add(attribute);

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using JsonApiDotNetCore.Builders;
4+
using JsonApiDotNetCore.Graph;
45
using JsonApiDotNetCore.Internal;
56
using JsonApiDotNetCore.Models;
67
using JsonApiDotNetCore.Serialization;
@@ -16,6 +17,11 @@ namespace JsonApiDotNetCore.Configuration
1617
/// </summary>
1718
public class JsonApiOptions
1819
{
20+
/// <summary>
21+
/// Provides an interface for formatting resource names by convention
22+
/// </summary>
23+
public static IResourceNameFormatter ResourceNameFormatter { get; set; } = new DefaultResourceNameFormatter();
24+
1925
/// <summary>
2026
/// Whether or not stack traces should be serialized in Error objects
2127
/// </summary>

src/JsonApiDotNetCore/Graph/IResourceNameFormatter.cs

+22
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ public interface IResourceNameFormatter
1717
/// Get the publicly visible resource name from the internal type name
1818
/// </summary>
1919
string FormatResourceName(Type resourceType);
20+
21+
/// <summary>
22+
/// Get the publicly visible name for the given property
23+
/// </summary>
24+
string FormatPropertyName(PropertyInfo property);
2025
}
2126

2227
public class DefaultResourceNameFormatter : IResourceNameFormatter
@@ -47,5 +52,22 @@ public string FormatResourceName(Type type)
4752
throw new InvalidOperationException($"Cannot define multiple {nameof(ResourceAttribute)}s on type '{type}'.", e);
4853
}
4954
}
55+
56+
/// <summary>
57+
/// Uses the internal PropertyInfo to determine the external resource name.
58+
/// By default the name will be formatted to kebab-case.
59+
/// </summary>
60+
/// <example>
61+
/// Given the following property:
62+
/// <code>
63+
/// public string CompoundProperty { get; set; }
64+
/// </code>
65+
/// The public attribute will be formatted like so:
66+
/// <code>
67+
/// _default.FormatPropertyName(compoundProperty).Dump();
68+
/// // > "compound-property"
69+
/// </code>
70+
/// </example>
71+
public string FormatPropertyName(PropertyInfo property) => str.Dasherize(property.Name);
5072
}
5173
}

src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs

+12-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using JsonApiDotNetCore.Builders;
2+
using JsonApiDotNetCore.Configuration;
23
using JsonApiDotNetCore.Data;
34
using JsonApiDotNetCore.Internal;
45
using JsonApiDotNetCore.Models;
@@ -55,23 +56,20 @@ public ServiceDiscoveryFacade(
5556
/// <summary>
5657
/// Add resources, services and repository implementations to the container.
5758
/// </summary>
58-
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
59-
public ServiceDiscoveryFacade AddCurrentAssembly(IResourceNameFormatter resourceNameFormatter = null)
60-
=> AddAssembly(Assembly.GetCallingAssembly(), resourceNameFormatter);
59+
public ServiceDiscoveryFacade AddCurrentAssembly() => AddAssembly(Assembly.GetCallingAssembly());
6160

6261
/// <summary>
6362
/// Add resources, services and repository implementations to the container.
6463
/// </summary>
6564
/// <param name="assembly">The assembly to search for resources in.</param>
66-
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
67-
public ServiceDiscoveryFacade AddAssembly(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
65+
public ServiceDiscoveryFacade AddAssembly(Assembly assembly)
6866
{
6967
AddDbContextResolvers(assembly);
7068

7169
var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly);
7270
foreach (var resourceDescriptor in resourceDescriptors)
7371
{
74-
AddResource(assembly, resourceDescriptor, resourceNameFormatter);
72+
AddResource(assembly, resourceDescriptor);
7573
AddServices(assembly, resourceDescriptor);
7674
AddRepositories(assembly, resourceDescriptor);
7775
}
@@ -93,20 +91,19 @@ private void AddDbContextResolvers(Assembly assembly)
9391
/// Adds resources to the graph and registers <see cref="ResourceDefinition{T}"/> types on the container.
9492
/// </summary>
9593
/// <param name="assembly">The assembly to search for resources in.</param>
96-
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
97-
public ServiceDiscoveryFacade AddResources(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
94+
public ServiceDiscoveryFacade AddResources(Assembly assembly)
9895
{
9996
var identifiables = TypeLocator.GetIdentifableTypes(assembly);
10097
foreach (var identifiable in identifiables)
101-
AddResource(assembly, identifiable, resourceNameFormatter);
98+
AddResource(assembly, identifiable);
10299

103100
return this;
104101
}
105102

106-
private void AddResource(Assembly assembly, ResourceDescriptor resourceDescriptor, IResourceNameFormatter resourceNameFormatter = null)
103+
private void AddResource(Assembly assembly, ResourceDescriptor resourceDescriptor)
107104
{
108105
RegisterResourceDefinition(assembly, resourceDescriptor);
109-
AddResourceToGraph(resourceDescriptor, resourceNameFormatter);
106+
AddResourceToGraph(resourceDescriptor);
110107
}
111108

112109
private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor identifiable)
@@ -125,17 +122,14 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id
125122
}
126123
}
127124

128-
private void AddResourceToGraph(ResourceDescriptor identifiable, IResourceNameFormatter resourceNameFormatter = null)
125+
private void AddResourceToGraph(ResourceDescriptor identifiable)
129126
{
130-
var resourceName = FormatResourceName(identifiable.ResourceType, resourceNameFormatter);
127+
var resourceName = FormatResourceName(identifiable.ResourceType);
131128
_graphBuilder.AddResource(identifiable.ResourceType, identifiable.IdType, resourceName);
132129
}
133130

134-
private string FormatResourceName(Type resourceType, IResourceNameFormatter resourceNameFormatter)
135-
{
136-
resourceNameFormatter = resourceNameFormatter ?? new DefaultResourceNameFormatter();
137-
return resourceNameFormatter.FormatResourceName(resourceType);
138-
}
131+
private string FormatResourceName(Type resourceType)
132+
=> JsonApiOptions.ResourceNameFormatter.FormatResourceName(resourceType);
139133

140134
/// <summary>
141135
/// Add <see cref="IResourceService{T, TId}"/> implementations to container.

src/JsonApiDotNetCore/Models/AttrAttribute.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ public class AttrAttribute : Attribute
2626
/// </code>
2727
///
2828
/// </example>
29-
public AttrAttribute(string publicName, bool isImmutable = false, bool isFilterable = true, bool isSortable = true)
29+
public AttrAttribute(string publicName = null, bool isImmutable = false, bool isFilterable = true, bool isSortable = true)
3030
{
3131
PublicAttributeName = publicName;
3232
IsImmutable = isImmutable;
3333
IsFilterable = isFilterable;
3434
IsSortable = isSortable;
3535
}
3636

37-
public AttrAttribute(string publicName, string internalName, bool isImmutable = false)
37+
internal AttrAttribute(string publicName, string internalName, bool isImmutable = false)
3838
{
3939
PublicAttributeName = publicName;
4040
InternalAttributeName = internalName;
@@ -44,7 +44,7 @@ public AttrAttribute(string publicName, string internalName, bool isImmutable =
4444
/// <summary>
4545
/// How this attribute is exposed through the API
4646
/// </summary>
47-
public string PublicAttributeName { get; }
47+
public string PublicAttributeName { get; internal set;}
4848

4949
/// <summary>
5050
/// The internal property name this attribute belongs to.

src/JsonApiDotNetCore/Models/HasManyAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public class HasManyAttribute : RelationshipAttribute
2323
/// </code>
2424
///
2525
/// </example>
26-
public HasManyAttribute(string publicName, Link documentLinks = Link.All, bool canInclude = true)
26+
public HasManyAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true)
2727
: base(publicName, documentLinks, canInclude)
2828
{ }
2929

src/JsonApiDotNetCore/Models/HasOneAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class HasOneAttribute : RelationshipAttribute
2626
/// </code>
2727
///
2828
/// </example>
29-
public HasOneAttribute(string publicName, Link documentLinks = Link.All, bool canInclude = true, string withForeignKey = null)
29+
public HasOneAttribute(string publicName = null, Link documentLinks = Link.All, bool canInclude = true, string withForeignKey = null)
3030
: base(publicName, documentLinks, canInclude)
3131
{
3232
_explicitIdentifiablePropertyName = withForeignKey;

src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
1111
CanInclude = canInclude;
1212
}
1313

14-
public string PublicRelationshipName { get; }
14+
public string PublicRelationshipName { get; internal set; }
1515
public string InternalRelationshipName { get; internal set; }
1616

1717
/// <summary>

0 commit comments

Comments
 (0)