33using System . Linq ;
44using System . Reflection ;
55using JsonApiDotNetCore . Extensions ;
6+ using JsonApiDotNetCore . Graph ;
67using JsonApiDotNetCore . Internal ;
78using JsonApiDotNetCore . Models ;
89using Microsoft . EntityFrameworkCore ;
@@ -32,13 +33,27 @@ public interface IContextGraphBuilder
3233 /// <param name="pluralizedTypeName">The pluralized name that should be exposed by the API</param>
3334 IContextGraphBuilder AddResource < TResource , TId > ( string pluralizedTypeName ) where TResource : class , IIdentifiable < TId > ;
3435
36+ /// <summary>
37+ /// Add a json:api resource
38+ /// </summary>
39+ /// <param name="entityType">The resource model type</param>
40+ /// <param name="idType">The resource model identifier type</param>
41+ /// <param name="pluralizedTypeName">The pluralized name that should be exposed by the API</param>
42+ IContextGraphBuilder AddResource ( Type entityType , Type idType , string pluralizedTypeName ) ;
43+
3544 /// <summary>
3645 /// Add all the models that are part of the provided <see cref="DbContext" />
3746 /// that also implement <see cref="IIdentifiable"/>
3847 /// </summary>
3948 /// <typeparam name="T">The <see cref="DbContext"/> implementation type.</typeparam>
4049 IContextGraphBuilder AddDbContext < T > ( ) where T : DbContext ;
4150
51+ /// <summary>
52+ /// Specify the <see cref="IResourceNameFormatter"/> used to format resource names.
53+ /// </summary>
54+ /// <param name="resourceNameFormatter">Formatter used to define exposed resource names by convention.</param>
55+ IContextGraphBuilder UseNameFormatter ( IResourceNameFormatter resourceNameFormatter ) ;
56+
4257 /// <summary>
4358 /// Which links to include. Defaults to <see cref="Link.All"/>.
4459 /// </summary>
@@ -51,6 +66,8 @@ public class ContextGraphBuilder : IContextGraphBuilder
5166 private List < ValidationResult > _validationResults = new List < ValidationResult > ( ) ;
5267
5368 private bool _usesDbContext ;
69+ private IResourceNameFormatter _resourceNameFormatter = new DefaultResourceNameFormatter ( ) ;
70+
5471 public Link DocumentLinks { get ; set ; } = Link . All ;
5572
5673 public IContextGraph Build ( )
@@ -62,16 +79,20 @@ public IContextGraph Build()
6279 return graph ;
6380 }
6481
82+ /// <inheritdoc />
6583 public IContextGraphBuilder AddResource < TResource > ( string pluralizedTypeName ) where TResource : class , IIdentifiable < int >
6684 => AddResource < TResource , int > ( pluralizedTypeName ) ;
6785
86+ /// <inheritdoc />
6887 public IContextGraphBuilder AddResource < TResource , TId > ( string pluralizedTypeName ) where TResource : class , IIdentifiable < TId >
69- {
70- var entityType = typeof ( TResource ) ;
88+ => AddResource ( typeof ( TResource ) , typeof ( TId ) , pluralizedTypeName ) ;
7189
90+ /// <inheritdoc />
91+ public IContextGraphBuilder AddResource ( Type entityType , Type idType , string pluralizedTypeName )
92+ {
7293 AssertEntityIsNotAlreadyDefined ( entityType ) ;
7394
74- _entities . Add ( GetEntity ( pluralizedTypeName , entityType , typeof ( TId ) ) ) ;
95+ _entities . Add ( GetEntity ( pluralizedTypeName , entityType , idType ) ) ;
7596
7697 return this ;
7798 }
@@ -142,6 +163,7 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope
142163
143164 private Type GetResourceDefinitionType ( Type entityType ) => typeof ( ResourceDefinition < > ) . MakeGenericType ( entityType ) ;
144165
166+ /// <inheritdoc />
145167 public IContextGraphBuilder AddDbContext < T > ( ) where T : DbContext
146168 {
147169 _usesDbContext = true ;
@@ -164,30 +186,38 @@ public IContextGraphBuilder AddDbContext<T>() where T : DbContext
164186 var ( isJsonApiResource , idType ) = GetIdType ( entityType ) ;
165187
166188 if ( isJsonApiResource )
167- _entities . Add ( GetEntity ( GetResourceName ( property ) , entityType , idType ) ) ;
189+ _entities . Add ( GetEntity ( GetResourceNameFromDbSetProperty ( property , entityType ) , entityType , idType ) ) ;
168190 }
169191 }
170192
171193 return this ;
172194 }
173195
174- private string GetResourceName ( PropertyInfo property )
196+ private string GetResourceNameFromDbSetProperty ( PropertyInfo property , Type resourceType )
175197 {
176- var resourceAttribute = property . GetCustomAttribute ( typeof ( ResourceAttribute ) ) ;
177- if ( resourceAttribute == null )
178- return property . Name . Dasherize ( ) ;
179-
180- return ( ( ResourceAttribute ) resourceAttribute ) . ResourceName ;
198+ // this check is actually duplicated in the DefaultResourceNameFormatter
199+ // however, we perform it here so that we allow class attributes to be prioritized over
200+ // the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
201+ //
202+ // check the class definition first
203+ // [Resource("models"] public class Model : Identifiable { /* ... */ }
204+ if ( resourceType . GetCustomAttribute ( typeof ( ResourceAttribute ) ) is ResourceAttribute classResourceAttribute )
205+ return classResourceAttribute . ResourceName ;
206+
207+ // check the DbContext member next
208+ // [Resource("models")] public DbSet<Model> Models { get; set; }
209+ if ( property . GetCustomAttribute ( typeof ( ResourceAttribute ) ) is ResourceAttribute resourceAttribute )
210+ return resourceAttribute . ResourceName ;
211+
212+ // fallback to dsherized...this should actually check for a custom IResourceNameFormatter
213+ return _resourceNameFormatter . FormatResourceName ( resourceType ) ;
181214 }
182215
183216 private ( bool isJsonApiResource , Type idType ) GetIdType ( Type resourceType )
184217 {
185- var interfaces = resourceType . GetInterfaces ( ) ;
186- foreach ( var type in interfaces )
187- {
188- if ( type . GetTypeInfo ( ) . IsGenericType && type . GetGenericTypeDefinition ( ) == typeof ( IIdentifiable < > ) )
189- return ( true , type . GetGenericArguments ( ) [ 0 ] ) ;
190- }
218+ var possible = TypeLocator . GetIdType ( resourceType ) ;
219+ if ( possible . isJsonApiResource )
220+ return possible ;
191221
192222 _validationResults . Add ( new ValidationResult ( LogLevel . Warning , $ "{ resourceType } does not implement 'IIdentifiable<>'. ") ) ;
193223
@@ -199,5 +229,12 @@ private void AssertEntityIsNotAlreadyDefined(Type entityType)
199229 if ( _entities . Any ( e => e . EntityType == entityType ) )
200230 throw new InvalidOperationException ( $ "Cannot add entity type { entityType } to context graph, there is already an entity of that type configured.") ;
201231 }
232+
233+ /// <inheritdoc />
234+ public IContextGraphBuilder UseNameFormatter ( IResourceNameFormatter resourceNameFormatter )
235+ {
236+ _resourceNameFormatter = resourceNameFormatter ;
237+ return this ;
238+ }
202239 }
203240}
0 commit comments