Skip to content

Commit d0a2c42

Browse files
committed
fixing auto-discovery
1 parent 491a6cb commit d0a2c42

File tree

8 files changed

+267
-72
lines changed

8 files changed

+267
-72
lines changed

src/Examples/JsonApiDotNetCoreExample/Startup.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,9 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
4141
options.IncludeTotalRecordCount = true;
4242
},
4343
mvcBuilder,
44-
discovery => discovery.AddCurrentAssemblyServices());
44+
discovery => discovery.AddCurrentAssembly());
4545

46-
var provider = services.BuildServiceProvider();
47-
return provider;
46+
return services.BuildServiceProvider();
4847
}
4948

5049
public virtual void Configure(

src/Examples/ReportsExample/Startup.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ public Startup(IHostingEnvironment env)
2525
public virtual void ConfigureServices(IServiceCollection services)
2626
{
2727
var mvcBuilder = services.AddMvcCore();
28-
services.AddJsonApi(
29-
opt => opt.Namespace = "api",
30-
mvcBuilder,
31-
discovery => discovery.AddCurrentAssemblyServices());
28+
services.AddJsonApi(
29+
opt => opt.Namespace = "api",
30+
mvcBuilder,
31+
discovery => discovery.AddCurrentAssembly());
3232
}
3333

3434
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)

src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using JsonApiDotNetCore.Builders;
35
using JsonApiDotNetCore.Configuration;
46
using JsonApiDotNetCore.Data;
@@ -7,6 +9,7 @@
79
using JsonApiDotNetCore.Internal;
810
using JsonApiDotNetCore.Internal.Generics;
911
using JsonApiDotNetCore.Middleware;
12+
using JsonApiDotNetCore.Models;
1013
using JsonApiDotNetCore.Serialization;
1114
using JsonApiDotNetCore.Services;
1215
using JsonApiDotNetCore.Services.Operations;
@@ -182,5 +185,56 @@ public static void SerializeAsJsonApi(this MvcOptions options, JsonApiOptions js
182185

183186
options.Conventions.Insert(0, new DasherizedRoutingConvention(jsonApiOptions.Namespace));
184187
}
188+
189+
/// <summary>
190+
/// </summary>
191+
public static IServiceCollection AddResourceService<T>(this IServiceCollection services)
192+
{
193+
var typeImplemenetsAnExpectedInterface = false;
194+
195+
var serviceImplementationType = typeof(T);
196+
197+
// it is _possible_ that a single concrete type could be used for multiple resources...
198+
var resourceDescriptors = GetResourceTypesFromServiceImplementation(serviceImplementationType);
199+
200+
foreach(var resourceDescriptor in resourceDescriptors)
201+
{
202+
foreach(var openGenericType in ServiceDiscoveryFacade.ServiceInterfaces)
203+
{
204+
var concreteGenericType = openGenericType.GetGenericArguments().Length == 1
205+
? openGenericType.MakeGenericType(resourceDescriptor.ResourceType)
206+
: openGenericType.MakeGenericType(resourceDescriptor.ResourceType, resourceDescriptor.IdType);
207+
208+
if(concreteGenericType.IsAssignableFrom(serviceImplementationType)) {
209+
services.AddScoped(serviceImplementationType, serviceImplementationType);
210+
typeImplemenetsAnExpectedInterface = true;
211+
}
212+
}
213+
}
214+
215+
if(typeImplemenetsAnExpectedInterface == false)
216+
throw new JsonApiSetupException($"{typeImplemenetsAnExpectedInterface} does not implement any of the expected JsonApiDotNetCore interfaces.");
217+
218+
return services;
219+
}
220+
221+
private static HashSet<ResourceDescriptor> GetResourceTypesFromServiceImplementation(Type type)
222+
{
223+
var resourceDecriptors = new HashSet<ResourceDescriptor>();
224+
var interfaces = type.GetInterfaces();
225+
foreach(var i in interfaces)
226+
{
227+
if(i.IsGenericType)
228+
{
229+
var firstGenericArgument = i.GetGenericTypeDefinition().GetGenericArguments().FirstOrDefault();
230+
if(TypeLocator.TryGetResourceDescriptor(firstGenericArgument, out var resourceDescriptor) == false)
231+
{
232+
resourceDecriptors.Add(resourceDescriptor);
233+
}
234+
}
235+
}
236+
237+
return resourceDecriptors;
238+
}
185239
}
186240
}

src/JsonApiDotNetCore/Graph/ResourceDescriptor.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ public ResourceDescriptor(Type resourceType, Type idType)
1212

1313
public Type ResourceType { get; set; }
1414
public Type IdType { get; set; }
15+
16+
internal static ResourceDescriptor Empty => new ResourceDescriptor(null, null);
1517
}
1618
}

src/JsonApiDotNetCore/Graph/ServiceDiscoveryFacade.cs

Lines changed: 74 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,47 @@
66
using Microsoft.EntityFrameworkCore;
77
using Microsoft.Extensions.DependencyInjection;
88
using System;
9+
using System.Collections.Generic;
910
using System.Linq;
1011
using System.Reflection;
1112

1213
namespace JsonApiDotNetCore.Graph
1314
{
1415
public class ServiceDiscoveryFacade
1516
{
17+
internal static HashSet<Type> ServiceInterfaces = new HashSet<Type> {
18+
typeof(IResourceService<>),
19+
typeof(IResourceService<,>),
20+
typeof(ICreateService<>),
21+
typeof(ICreateService<,>),
22+
typeof(IGetAllService<>),
23+
typeof(IGetAllService<,>),
24+
typeof(IGetByIdService<>),
25+
typeof(IGetByIdService<,>),
26+
typeof(IGetRelationshipService<>),
27+
typeof(IGetRelationshipService<,>),
28+
typeof(IUpdateService<>),
29+
typeof(IUpdateService<,>),
30+
typeof(IDeleteService<>),
31+
typeof(IDeleteService<,>)
32+
};
33+
34+
internal static HashSet<Type> RepositoryInterfaces = new HashSet<Type> {
35+
typeof(IEntityRepository<>),
36+
typeof(IEntityRepository<,>),
37+
typeof(IEntityWriteRepository<>),
38+
typeof(IEntityWriteRepository<,>),
39+
typeof(IEntityReadRepository<>),
40+
typeof(IEntityReadRepository<,>)
41+
};
42+
1643
private readonly IServiceCollection _services;
1744
private readonly IContextGraphBuilder _graphBuilder;
45+
private readonly List<ResourceDescriptor> _identifiables = new List<ResourceDescriptor>();
1846

19-
public ServiceDiscoveryFacade(IServiceCollection services, IContextGraphBuilder graphBuilder)
47+
public ServiceDiscoveryFacade(
48+
IServiceCollection services,
49+
IContextGraphBuilder graphBuilder)
2050
{
2151
_services = services;
2252
_graphBuilder = graphBuilder;
@@ -26,20 +56,25 @@ public ServiceDiscoveryFacade(IServiceCollection services, IContextGraphBuilder
2656
/// Add resources, services and repository implementations to the container.
2757
/// </summary>
2858
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
29-
public ServiceDiscoveryFacade AddCurrentAssemblyServices(IResourceNameFormatter resourceNameFormatter = null)
30-
=> AddAssemblyServices(Assembly.GetCallingAssembly(), resourceNameFormatter);
59+
public ServiceDiscoveryFacade AddCurrentAssembly(IResourceNameFormatter resourceNameFormatter = null)
60+
=> AddAssembly(Assembly.GetCallingAssembly(), resourceNameFormatter);
3161

3262
/// <summary>
3363
/// Add resources, services and repository implementations to the container.
3464
/// </summary>
3565
/// <param name="assembly">The assembly to search for resources in.</param>
3666
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
37-
public ServiceDiscoveryFacade AddAssemblyServices(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
67+
public ServiceDiscoveryFacade AddAssembly(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
3868
{
3969
AddDbContextResolvers(assembly);
40-
AddAssemblyResources(assembly, resourceNameFormatter);
41-
AddAssemblyServices(assembly);
42-
AddAssemblyRepositories(assembly);
70+
71+
var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly);
72+
foreach (var resourceDescriptor in resourceDescriptors)
73+
{
74+
AddResource(assembly, resourceDescriptor, resourceNameFormatter);
75+
AddServices(assembly, resourceDescriptor);
76+
AddRepositories(assembly, resourceDescriptor);
77+
}
4378

4479
return this;
4580
}
@@ -59,18 +94,21 @@ private void AddDbContextResolvers(Assembly assembly)
5994
/// </summary>
6095
/// <param name="assembly">The assembly to search for resources in.</param>
6196
/// <param name="resourceNameFormatter">The type name formatter used to get the string representation of resource names.</param>
62-
public ServiceDiscoveryFacade AddAssemblyResources(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
97+
public ServiceDiscoveryFacade AddResources(Assembly assembly, IResourceNameFormatter resourceNameFormatter = null)
6398
{
6499
var identifiables = TypeLocator.GetIdentifableTypes(assembly);
65100
foreach (var identifiable in identifiables)
66-
{
67-
RegisterResourceDefinition(assembly, identifiable);
68-
AddResourceToGraph(identifiable, resourceNameFormatter);
69-
}
101+
AddResource(assembly, identifiable, resourceNameFormatter);
70102

71103
return this;
72104
}
73105

106+
private void AddResource(Assembly assembly, ResourceDescriptor resourceDescriptor, IResourceNameFormatter resourceNameFormatter = null)
107+
{
108+
RegisterResourceDefinition(assembly, resourceDescriptor);
109+
AddResourceToGraph(resourceDescriptor, resourceNameFormatter);
110+
}
111+
74112
private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor identifiable)
75113
{
76114
try
@@ -83,9 +121,7 @@ private void RegisterResourceDefinition(Assembly assembly, ResourceDescriptor id
83121
}
84122
catch (InvalidOperationException e)
85123
{
86-
// TODO: need a better way to communicate failure since this is unlikely to occur during a web request
87-
throw new JsonApiException(500,
88-
$"Cannot define multiple ResourceDefinition<> implementations for '{identifiable.ResourceType}'", e);
124+
throw new JsonApiSetupException($"Cannot define multiple ResourceDefinition<> implementations for '{identifiable.ResourceType}'", e);
89125
}
90126
}
91127

@@ -105,61 +141,45 @@ private string FormatResourceName(Type resourceType, IResourceNameFormatter reso
105141
/// Add <see cref="IResourceService{T, TId}"/> implementations to container.
106142
/// </summary>
107143
/// <param name="assembly">The assembly to search for resources in.</param>
108-
public ServiceDiscoveryFacade AddAssemblyServices(Assembly assembly)
144+
public ServiceDiscoveryFacade AddServices(Assembly assembly)
109145
{
110-
RegisterServiceImplementations(assembly, typeof(IResourceService<>));
111-
RegisterServiceImplementations(assembly, typeof(IResourceService<,>));
112-
113-
RegisterServiceImplementations(assembly, typeof(ICreateService<>));
114-
RegisterServiceImplementations(assembly, typeof(ICreateService<,>));
115-
116-
RegisterServiceImplementations(assembly, typeof(IGetAllService<>));
117-
RegisterServiceImplementations(assembly, typeof(IGetAllService<,>));
118-
119-
RegisterServiceImplementations(assembly, typeof(IGetByIdService<>));
120-
RegisterServiceImplementations(assembly, typeof(IGetByIdService<,>));
121-
122-
RegisterServiceImplementations(assembly, typeof(IGetRelationshipService<>));
123-
RegisterServiceImplementations(assembly, typeof(IGetRelationshipService<,>));
124-
125-
RegisterServiceImplementations(assembly, typeof(IUpdateService<>));
126-
RegisterServiceImplementations(assembly, typeof(IUpdateService<,>));
127-
128-
RegisterServiceImplementations(assembly, typeof(IDeleteService<>));
129-
RegisterServiceImplementations(assembly, typeof(IDeleteService<,>));
146+
var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly);
147+
foreach (var resourceDescriptor in resourceDescriptors)
148+
AddServices(assembly, resourceDescriptor);
130149

131150
return this;
132151
}
133152

153+
private void AddServices(Assembly assembly, ResourceDescriptor resourceDescriptor)
154+
{
155+
foreach(var serviceInterface in ServiceInterfaces)
156+
RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor);
157+
}
158+
134159
/// <summary>
135160
/// Add <see cref="IEntityRepository{T, TId}"/> implementations to container.
136161
/// </summary>
137162
/// <param name="assembly">The assembly to search for resources in.</param>
138-
public ServiceDiscoveryFacade AddAssemblyRepositories(Assembly assembly)
163+
public ServiceDiscoveryFacade AddRepositories(Assembly assembly)
139164
{
140-
RegisterServiceImplementations(assembly, typeof(IEntityRepository<>));
141-
RegisterServiceImplementations(assembly, typeof(IEntityRepository<,>));
142-
143-
RegisterServiceImplementations(assembly, typeof(IEntityWriteRepository<>));
144-
RegisterServiceImplementations(assembly, typeof(IEntityWriteRepository<,>));
145-
146-
RegisterServiceImplementations(assembly, typeof(IEntityReadRepository<>));
147-
RegisterServiceImplementations(assembly, typeof(IEntityReadRepository<,>));
165+
var resourceDescriptors = TypeLocator.GetIdentifableTypes(assembly);
166+
foreach (var resourceDescriptor in resourceDescriptors)
167+
AddRepositories(assembly, resourceDescriptor);
148168

149169
return this;
150170
}
151171

152-
private ServiceDiscoveryFacade RegisterServiceImplementations(Assembly assembly, Type interfaceType)
172+
private void AddRepositories(Assembly assembly, ResourceDescriptor resourceDescriptor)
153173
{
154-
var identifiables = TypeLocator.GetIdentifableTypes(assembly);
155-
foreach (var identifiable in identifiables)
156-
{
157-
var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, identifiable.ResourceType, identifiable.IdType);
158-
if (service.implementation != null)
159-
_services.AddScoped(service.registrationInterface, service.implementation);
160-
}
174+
foreach(var serviceInterface in RepositoryInterfaces)
175+
RegisterServiceImplementations(assembly, serviceInterface, resourceDescriptor);
176+
}
161177

162-
return this;
178+
private void RegisterServiceImplementations(Assembly assembly, Type interfaceType, ResourceDescriptor resourceDescriptor)
179+
{
180+
var service = TypeLocator.GetGenericInterfaceImplementation(assembly, interfaceType, resourceDescriptor.ResourceType, resourceDescriptor.IdType);
181+
if (service.implementation != null)
182+
_services.AddScoped(service.registrationInterface, service.implementation);
163183
}
164184
}
165185
}

src/JsonApiDotNetCore/Graph/TypeLocator.cs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,24 +46,44 @@ private static Type[] GetAssemblyTypes(Assembly assembly)
4646
}
4747

4848
/// <summary>
49-
/// Get all implementations of <see cref="IIdentifiable"/>. in the assembly
49+
/// Get all implementations of <see cref="IIdentifiable"/> in the assembly
5050
/// </summary>
51-
public static List<ResourceDescriptor> GetIdentifableTypes(Assembly assembly)
51+
public static IEnumerable<ResourceDescriptor> GetIdentifableTypes(Assembly assembly)
52+
=> (_identifiableTypeCache.TryGetValue(assembly, out var descriptors) == false)
53+
? FindIdentifableTypes(assembly)
54+
: _identifiableTypeCache[assembly];
55+
56+
private static IEnumerable<ResourceDescriptor> FindIdentifableTypes(Assembly assembly)
5257
{
53-
if (_identifiableTypeCache.TryGetValue(assembly, out var descriptors) == false)
54-
{
55-
descriptors = new List<ResourceDescriptor>();
56-
_identifiableTypeCache[assembly] = descriptors;
58+
var descriptors = new List<ResourceDescriptor>();
59+
_identifiableTypeCache[assembly] = descriptors;
5760

58-
foreach (var type in assembly.GetTypes())
61+
foreach (var type in assembly.GetTypes())
62+
{
63+
if (TryGetResourceDescriptor(type, out var descriptor))
5964
{
60-
var possible = GetIdType(type);
61-
if (possible.isJsonApiResource)
62-
descriptors.Add(new ResourceDescriptor(type, possible.idType));
65+
descriptors.Add(descriptor);
66+
yield return descriptor;
6367
}
6468
}
69+
}
6570

66-
return descriptors;
71+
/// <summary>
72+
/// Attempts to get a descriptor of the resource type.
73+
/// </summary>
74+
/// <returns>
75+
/// True if the type is a valid json:api type (must implement <see cref="IIdentifiable"/>), false otherwise.
76+
/// </returns>
77+
internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor descriptor)
78+
{
79+
var possible = GetIdType(type);
80+
if (possible.isJsonApiResource) {
81+
descriptor = new ResourceDescriptor(type, possible.idType);
82+
return true;
83+
}
84+
85+
descriptor = ResourceDescriptor.Empty;
86+
return false;
6787
}
6888

6989
/// <summary>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System;
2+
3+
namespace JsonApiDotNetCore.Internal
4+
{
5+
public class JsonApiSetupException : Exception
6+
{
7+
public JsonApiSetupException(string message)
8+
: base(message) { }
9+
10+
public JsonApiSetupException(string message, Exception innerException)
11+
: base(message, innerException) { }
12+
}
13+
}

0 commit comments

Comments
 (0)