Skip to content

Commit 3707110

Browse files
authored
Move EF core related building to extension methods (#616)
* feat: remove inability to use request serializer with unknown type at runtime * fix: typo in test setup * chore: spacing * fix: add IResourceQueryService and IResourceCmdService to DI container in application builder * chore: update comments IRequestSerializer * chore: moved ef core related building to extension methods * chore: spacing, rename file * refactor: remove need for internal modifier * refactor: minor refactor id type retrieval
1 parent 382acce commit 3707110

File tree

11 files changed

+156
-163
lines changed

11 files changed

+156
-163
lines changed

src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using JsonApiDotNetCore.Graph;
34
using JsonApiDotNetCore.Internal;
45
using JsonApiDotNetCore.Internal.Contracts;
@@ -13,7 +14,6 @@ public interface IResourceGraphBuilder
1314
/// Construct the <see cref="ResourceGraph"/>
1415
/// </summary>
1516
IResourceGraph Build();
16-
1717
/// <summary>
1818
/// Add a json:api resource
1919
/// </summary>
@@ -24,8 +24,6 @@ public interface IResourceGraphBuilder
2424
/// See <see cref="IResourceNameFormatter" />.
2525
/// </param>
2626
IResourceGraphBuilder AddResource<TResource>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<int>;
27-
28-
2927
/// <summary>
3028
/// Add a json:api resource
3129
/// </summary>
@@ -37,7 +35,6 @@ public interface IResourceGraphBuilder
3735
/// See <see cref="IResourceNameFormatter" />.
3836
/// </param>
3937
IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<TId>;
40-
4138
/// <summary>
4239
/// Add a Json:Api resource
4340
/// </summary>
@@ -49,12 +46,5 @@ public interface IResourceGraphBuilder
4946
/// See <see cref="IResourceNameFormatter" />.
5047
/// </param>
5148
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);
52-
53-
/// <summary>
54-
/// Add all the models that are part of the provided <see cref="DbContext" />
55-
/// that also implement <see cref="IIdentifiable"/>
56-
/// </summary>
57-
/// <typeparam name="T">The <see cref="DbContext"/> implementation type.</typeparam>
58-
IResourceGraphBuilder AddDbContext<T>() where T : DbContext;
5949
}
6050
}

src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs

+4-20
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using JsonApiDotNetCore.Serialization.Server.Builders;
2121
using JsonApiDotNetCore.Serialization.Server;
2222
using Microsoft.Extensions.DependencyInjection.Extensions;
23+
using JsonApiDotNetCore.Extensions;
2324

2425
namespace JsonApiDotNetCore.Builders
2526
{
@@ -30,10 +31,10 @@ namespace JsonApiDotNetCore.Builders
3031
public class JsonApiApplicationBuilder
3132
{
3233
public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions();
33-
private IResourceGraphBuilder _resourceGraphBuilder;
34+
internal IResourceGraphBuilder _resourceGraphBuilder;
35+
internal bool _usesDbContext;
36+
internal readonly IServiceCollection _services;
3437
private IServiceDiscoveryFacade _serviceDiscoveryFacade;
35-
private bool _usesDbContext;
36-
private readonly IServiceCollection _services;
3738
private readonly IMvcCoreBuilder _mvcBuilder;
3839

3940
public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
@@ -42,11 +43,6 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
4243
_mvcBuilder = mvcBuilder;
4344
}
4445

45-
internal void ConfigureLogging()
46-
{
47-
_services.AddLogging();
48-
}
49-
5046
/// <summary>
5147
/// Executes the action provided by the user to configure <see cref="JsonApiOptions"/>
5248
/// </summary>
@@ -98,18 +94,6 @@ public void ConfigureResources(Action<IResourceGraphBuilder> resourceGraphBuilde
9894
resourceGraphBuilder(_resourceGraphBuilder);
9995
}
10096

101-
/// <summary>
102-
/// Executes the action provided by the user to configure the resources using <see cref="IResourceGraphBuilder"/>.
103-
/// Additionally, inspects the EF core database context for models that implement IIdentifiable.
104-
/// </summary>
105-
public void ConfigureResources<TContext>(Action<IResourceGraphBuilder> resourceGraphBuilder) where TContext : DbContext
106-
{
107-
_resourceGraphBuilder.AddDbContext<TContext>();
108-
_usesDbContext = true;
109-
_services.AddScoped<IDbContextResolver, DbContextResolver<TContext>>();
110-
resourceGraphBuilder?.Invoke(_resourceGraphBuilder);
111-
}
112-
11397
/// <summary>
11498
/// Registers the remaining internals.
11599
/// </summary>

src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs

+22-71
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,28 @@
1010
using JsonApiDotNetCore.Internal.Contracts;
1111
using JsonApiDotNetCore.Models;
1212
using JsonApiDotNetCore.Models.Links;
13-
using Microsoft.EntityFrameworkCore;
1413
using Microsoft.Extensions.Logging;
1514

1615
namespace JsonApiDotNetCore.Builders
1716
{
1817
public class ResourceGraphBuilder : IResourceGraphBuilder
1918
{
20-
private readonly List<ResourceContext> _entities = new List<ResourceContext>();
21-
private readonly List<ValidationResult> _validationResults = new List<ValidationResult>();
22-
private readonly IResourceNameFormatter _resourceNameFormatter = new KebabCaseFormatter();
19+
private List<ResourceContext> _resources { get; set; } = new List<ResourceContext>();
20+
private List<ValidationResult> _validationResults { get; set; } = new List<ValidationResult>();
21+
private IResourceNameFormatter _formatter { get; set; } = new KebabCaseFormatter();
2322

2423
public ResourceGraphBuilder() { }
2524

2625
public ResourceGraphBuilder(IResourceNameFormatter formatter)
2726
{
28-
_resourceNameFormatter = formatter;
27+
_formatter = formatter;
2928
}
3029

3130
/// <inheritdoc />
3231
public IResourceGraph Build()
3332
{
34-
_entities.ForEach(SetResourceLinksOptions);
35-
var resourceGraph = new ResourceGraph(_entities, _validationResults);
33+
_resources.ForEach(SetResourceLinksOptions);
34+
var resourceGraph = new ResourceGraph(_resources, _validationResults);
3635
return resourceGraph;
3736
}
3837

@@ -56,13 +55,19 @@ public IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeNa
5655
=> AddResource(typeof(TResource), typeof(TId), pluralizedTypeName);
5756

5857
/// <inheritdoc />
59-
public IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null)
58+
public IResourceGraphBuilder AddResource(Type resourceType, Type idType = null, string pluralizedTypeName = null)
6059
{
61-
AssertEntityIsNotAlreadyDefined(entityType);
62-
63-
pluralizedTypeName = pluralizedTypeName ?? _resourceNameFormatter.FormatResourceName(entityType);
64-
65-
_entities.Add(GetEntity(pluralizedTypeName, entityType, idType));
60+
AssertEntityIsNotAlreadyDefined(resourceType);
61+
if (resourceType.Implements<IIdentifiable>())
62+
{
63+
pluralizedTypeName ??= _formatter.FormatResourceName(resourceType);
64+
idType ??= TypeLocator.GetIdType(resourceType);
65+
_resources.Add(GetEntity(pluralizedTypeName, resourceType, idType));
66+
}
67+
else
68+
{
69+
_validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
70+
}
6671

6772
return this;
6873
}
@@ -93,7 +98,7 @@ protected virtual List<AttrAttribute> GetAttributes(Type entityType)
9398
{
9499
var idAttr = new AttrAttribute()
95100
{
96-
PublicAttributeName = _resourceNameFormatter.FormatPropertyName(prop),
101+
PublicAttributeName = _formatter.FormatPropertyName(prop),
97102
PropertyInfo = prop,
98103
InternalAttributeName = prop.Name
99104
};
@@ -105,7 +110,7 @@ protected virtual List<AttrAttribute> GetAttributes(Type entityType)
105110
if (attribute == null)
106111
continue;
107112

108-
attribute.PublicAttributeName = attribute.PublicAttributeName ?? _resourceNameFormatter.FormatPropertyName(prop);
113+
attribute.PublicAttributeName = attribute.PublicAttributeName ?? _formatter.FormatPropertyName(prop);
109114
attribute.InternalAttributeName = prop.Name;
110115
attribute.PropertyInfo = prop;
111116

@@ -123,7 +128,7 @@ protected virtual List<RelationshipAttribute> GetRelationships(Type entityType)
123128
var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute));
124129
if (attribute == null) continue;
125130

126-
attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _resourceNameFormatter.FormatPropertyName(prop);
131+
attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _formatter.FormatPropertyName(prop);
127132
attribute.InternalRelationshipName = prop.Name;
128133
attribute.RightType = GetRelationshipType(attribute, prop);
129134
attribute.LeftType = entityType;
@@ -178,63 +183,9 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope
178183

179184
private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);
180185

181-
/// <inheritdoc />
182-
public IResourceGraphBuilder AddDbContext<T>() where T : DbContext
183-
{
184-
var contextType = typeof(T);
185-
var contextProperties = contextType.GetProperties();
186-
foreach (var property in contextProperties)
187-
{
188-
var dbSetType = property.PropertyType;
189-
if (dbSetType.GetTypeInfo().IsGenericType
190-
&& dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>))
191-
{
192-
var entityType = dbSetType.GetGenericArguments()[0];
193-
AssertEntityIsNotAlreadyDefined(entityType);
194-
var (isJsonApiResource, idType) = GetIdType(entityType);
195-
if (isJsonApiResource)
196-
_entities.Add(GetEntity(GetResourceNameFromDbSetProperty(property, entityType), entityType, idType));
197-
}
198-
}
199-
200-
return this;
201-
}
202-
203-
private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType)
204-
{
205-
// this check is actually duplicated in the DefaultResourceNameFormatter
206-
// however, we perform it here so that we allow class attributes to be prioritized over
207-
// the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
208-
//
209-
// check the class definition first
210-
// [Resource("models"] public class Model : Identifiable { /* ... */ }
211-
if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute)
212-
return classResourceAttribute.ResourceName;
213-
214-
// check the DbContext member next
215-
// [Resource("models")] public DbSet<Model> Models { get; set; }
216-
if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
217-
return resourceAttribute.ResourceName;
218-
219-
// fallback to the established convention using the DbSet Property.Name
220-
// e.g DbSet<FooBar> FooBars { get; set; } => "foo-bars"
221-
return _resourceNameFormatter.FormatResourceName(resourceType);
222-
}
223-
224-
private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)
225-
{
226-
var possible = TypeLocator.GetIdType(resourceType);
227-
if (possible.isJsonApiResource)
228-
return possible;
229-
230-
_validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
231-
232-
return (false, null);
233-
}
234-
235186
private void AssertEntityIsNotAlreadyDefined(Type entityType)
236187
{
237-
if (_entities.Any(e => e.ResourceType == entityType))
188+
if (_resources.Any(e => e.ResourceType == entityType))
238189
throw new InvalidOperationException($"Cannot add entity type {entityType} to context resourceGraph, there is already an entity of that type configured.");
239190
}
240191
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
using System;
2+
using System.Reflection;
3+
using JsonApiDotNetCore.Configuration;
4+
using JsonApiDotNetCore.Graph;
5+
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.Extensions.DependencyInjection;
7+
using JsonApiDotNetCore.Builders;
8+
using JsonApiDotNetCore.Models;
9+
using JsonApiDotNetCore.Data;
10+
11+
namespace JsonApiDotNetCore.Extensions.EntityFrameworkCore
12+
{
13+
14+
/// <summary>
15+
/// Extensions for configuring JsonApiDotNetCore with EF Core
16+
/// </summary>
17+
public static class IResourceGraphBuilderExtensions
18+
{
19+
/// <summary>
20+
/// Add all the models that are part of the provided <see cref="DbContext" />
21+
/// that also implement <see cref="IIdentifiable"/>
22+
/// </summary>
23+
/// <typeparam name="TDbContext">The <see cref="DbContext"/> implementation type.</typeparam>
24+
public static IResourceGraphBuilder AddDbContext<TDbContext>(this IResourceGraphBuilder resourceGraphBuilder) where TDbContext : DbContext
25+
{
26+
var builder = (ResourceGraphBuilder)resourceGraphBuilder;
27+
var contextType = typeof(TDbContext);
28+
var contextProperties = contextType.GetProperties();
29+
foreach (var property in contextProperties)
30+
{
31+
var dbSetType = property.PropertyType;
32+
if (dbSetType.GetTypeInfo().IsGenericType
33+
&& dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>))
34+
{
35+
var resourceType = dbSetType.GetGenericArguments()[0];
36+
builder.AddResource(resourceType, pluralizedTypeName: GetResourceNameFromDbSetProperty(property, resourceType));
37+
}
38+
}
39+
return resourceGraphBuilder;
40+
}
41+
42+
private static string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType)
43+
{
44+
// this check is actually duplicated in the DefaultResourceNameFormatter
45+
// however, we perform it here so that we allow class attributes to be prioritized over
46+
// the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
47+
//
48+
// check the class definition first
49+
// [Resource("models"] public class Model : Identifiable { /* ... */ }
50+
if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute)
51+
return classResourceAttribute.ResourceName;
52+
53+
// check the DbContext member next
54+
// [Resource("models")] public DbSet<Model> Models { get; set; }
55+
if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
56+
return resourceAttribute.ResourceName;
57+
58+
return null;
59+
}
60+
}
61+
62+
/// <summary>
63+
/// Extensions for configuring JsonApiDotNetCore with EF Core
64+
/// </summary>
65+
public static class IServiceCollectionExtensions
66+
{
67+
/// <summary>
68+
/// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph.
69+
/// </summary>
70+
/// <typeparam name="TContext"></typeparam>
71+
/// <param name="services"></param>
72+
/// <param name="options"></param>
73+
/// <param name="resources"></param>
74+
/// <returns></returns>
75+
public static IServiceCollection AddJsonApi<TDbContext>(this IServiceCollection services,
76+
Action<JsonApiOptions> options = null,
77+
Action<IServiceDiscoveryFacade> discovery = null,
78+
Action<IResourceGraphBuilder> resources = null,
79+
IMvcCoreBuilder mvcBuilder = null)
80+
where TDbContext : DbContext
81+
{
82+
var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
83+
if (options != null)
84+
application.ConfigureJsonApiOptions(options);
85+
application.ConfigureMvc();
86+
if (discovery != null)
87+
application.AutoDiscover(discovery);
88+
application.ConfigureResources<TDbContext>(resources);
89+
application.ConfigureServices();
90+
return services;
91+
}
92+
}
93+
94+
/// <summary>
95+
/// Extensions for configuring JsonApiDotNetCore with EF Core
96+
/// </summary>
97+
public static class JsonApiApplicationBuildExtensions
98+
{
99+
/// <summary>
100+
/// Executes the action provided by the user to configure the resources using <see cref="IResourceGraphBuilder"/>.
101+
/// Additionally, inspects the EF core database context for models that implement IIdentifiable.
102+
/// </summary>
103+
public static void ConfigureResources<TContext>(this JsonApiApplicationBuilder builder, Action<IResourceGraphBuilder> resourceGraphBuilder) where TContext : DbContext
104+
{
105+
builder._resourceGraphBuilder.AddDbContext<TContext>();
106+
builder._usesDbContext = true;
107+
builder._services.AddScoped<IDbContextResolver, DbContextResolver<TContext>>();
108+
resourceGraphBuilder?.Invoke(builder._resourceGraphBuilder);
109+
}
110+
}
111+
}

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

+1-25
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,9 @@ namespace JsonApiDotNetCore.Extensions
1818
// ReSharper disable once InconsistentNaming
1919
public static class IServiceCollectionExtensions
2020
{
21-
/// <summary>
22-
/// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph.
23-
/// </summary>
24-
/// <typeparam name="TContext"></typeparam>
25-
/// <param name="services"></param>
26-
/// <param name="options"></param>
27-
/// <param name="resources"></param>
28-
/// <returns></returns>
29-
public static IServiceCollection AddJsonApi<TEfCoreDbContext>(this IServiceCollection services,
30-
Action<JsonApiOptions> options = null,
31-
Action<IResourceGraphBuilder> resources = null,
32-
IMvcCoreBuilder mvcBuilder = null)
33-
where TEfCoreDbContext : DbContext
34-
{
35-
var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
36-
if (options != null)
37-
application.ConfigureJsonApiOptions(options);
38-
application.ConfigureLogging();
39-
application.ConfigureMvc();
40-
application.ConfigureResources<TEfCoreDbContext>(resources);
41-
application.ConfigureServices();
42-
return services;
43-
}
44-
4521
/// <summary>
4622
/// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph.
47-
/// </summary>z
23+
/// </summary>
4824
/// <param name="services"></param>
4925
/// <param name="options"></param>
5026
/// <param name="resources"></param>

0 commit comments

Comments
 (0)