diff --git a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs
index a9a74cf9a7..3b38531918 100644
--- a/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Internal;
using JsonApiDotNetCore.Internal.Contracts;
@@ -13,7 +14,6 @@ public interface IResourceGraphBuilder
/// Construct the
///
IResourceGraph Build();
-
///
/// Add a json:api resource
///
@@ -24,8 +24,6 @@ public interface IResourceGraphBuilder
/// See .
///
IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable;
-
-
///
/// Add a json:api resource
///
@@ -37,7 +35,6 @@ public interface IResourceGraphBuilder
/// See .
///
IResourceGraphBuilder AddResource(string pluralizedTypeName = null) where TResource : class, IIdentifiable;
-
///
/// Add a Json:Api resource
///
@@ -49,12 +46,5 @@ public interface IResourceGraphBuilder
/// See .
///
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);
-
- ///
- /// Add all the models that are part of the provided
- /// that also implement
- ///
- /// The implementation type.
- IResourceGraphBuilder AddDbContext() where T : DbContext;
}
}
diff --git a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
index 8c00ce8fc1..0d5bf08e12 100644
--- a/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs
@@ -20,6 +20,7 @@
using JsonApiDotNetCore.Serialization.Server.Builders;
using JsonApiDotNetCore.Serialization.Server;
using Microsoft.Extensions.DependencyInjection.Extensions;
+using JsonApiDotNetCore.Extensions;
namespace JsonApiDotNetCore.Builders
{
@@ -30,10 +31,10 @@ namespace JsonApiDotNetCore.Builders
public class JsonApiApplicationBuilder
{
public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions();
- private IResourceGraphBuilder _resourceGraphBuilder;
+ internal IResourceGraphBuilder _resourceGraphBuilder;
+ internal bool _usesDbContext;
+ internal readonly IServiceCollection _services;
private IServiceDiscoveryFacade _serviceDiscoveryFacade;
- private bool _usesDbContext;
- private readonly IServiceCollection _services;
private readonly IMvcCoreBuilder _mvcBuilder;
public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
@@ -42,11 +43,6 @@ public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mv
_mvcBuilder = mvcBuilder;
}
- internal void ConfigureLogging()
- {
- _services.AddLogging();
- }
-
///
/// Executes the action provided by the user to configure
///
@@ -98,18 +94,6 @@ public void ConfigureResources(Action resourceGraphBuilde
resourceGraphBuilder(_resourceGraphBuilder);
}
- ///
- /// Executes the action provided by the user to configure the resources using .
- /// Additionally, inspects the EF core database context for models that implement IIdentifiable.
- ///
- public void ConfigureResources(Action resourceGraphBuilder) where TContext : DbContext
- {
- _resourceGraphBuilder.AddDbContext();
- _usesDbContext = true;
- _services.AddScoped>();
- resourceGraphBuilder?.Invoke(_resourceGraphBuilder);
- }
-
///
/// Registers the remaining internals.
///
diff --git a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs
index e025d4f7de..05d4a6a8c5 100644
--- a/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs
+++ b/src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs
@@ -10,29 +10,28 @@
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
using JsonApiDotNetCore.Models.Links;
-using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace JsonApiDotNetCore.Builders
{
public class ResourceGraphBuilder : IResourceGraphBuilder
{
- private readonly List _entities = new List();
- private readonly List _validationResults = new List();
- private readonly IResourceNameFormatter _resourceNameFormatter = new KebabCaseFormatter();
+ private List _resources { get; set; } = new List();
+ private List _validationResults { get; set; } = new List();
+ private IResourceNameFormatter _formatter { get; set; } = new KebabCaseFormatter();
public ResourceGraphBuilder() { }
public ResourceGraphBuilder(IResourceNameFormatter formatter)
{
- _resourceNameFormatter = formatter;
+ _formatter = formatter;
}
///
public IResourceGraph Build()
{
- _entities.ForEach(SetResourceLinksOptions);
- var resourceGraph = new ResourceGraph(_entities, _validationResults);
+ _resources.ForEach(SetResourceLinksOptions);
+ var resourceGraph = new ResourceGraph(_resources, _validationResults);
return resourceGraph;
}
@@ -56,13 +55,19 @@ public IResourceGraphBuilder AddResource(string pluralizedTypeNa
=> AddResource(typeof(TResource), typeof(TId), pluralizedTypeName);
///
- public IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null)
+ public IResourceGraphBuilder AddResource(Type resourceType, Type idType = null, string pluralizedTypeName = null)
{
- AssertEntityIsNotAlreadyDefined(entityType);
-
- pluralizedTypeName = pluralizedTypeName ?? _resourceNameFormatter.FormatResourceName(entityType);
-
- _entities.Add(GetEntity(pluralizedTypeName, entityType, idType));
+ AssertEntityIsNotAlreadyDefined(resourceType);
+ if (resourceType.Implements())
+ {
+ pluralizedTypeName ??= _formatter.FormatResourceName(resourceType);
+ idType ??= TypeLocator.GetIdType(resourceType);
+ _resources.Add(GetEntity(pluralizedTypeName, resourceType, idType));
+ }
+ else
+ {
+ _validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
+ }
return this;
}
@@ -93,7 +98,7 @@ protected virtual List GetAttributes(Type entityType)
{
var idAttr = new AttrAttribute()
{
- PublicAttributeName = _resourceNameFormatter.FormatPropertyName(prop),
+ PublicAttributeName = _formatter.FormatPropertyName(prop),
PropertyInfo = prop,
InternalAttributeName = prop.Name
};
@@ -105,7 +110,7 @@ protected virtual List GetAttributes(Type entityType)
if (attribute == null)
continue;
- attribute.PublicAttributeName = attribute.PublicAttributeName ?? _resourceNameFormatter.FormatPropertyName(prop);
+ attribute.PublicAttributeName = attribute.PublicAttributeName ?? _formatter.FormatPropertyName(prop);
attribute.InternalAttributeName = prop.Name;
attribute.PropertyInfo = prop;
@@ -123,7 +128,7 @@ protected virtual List GetRelationships(Type entityType)
var attribute = (RelationshipAttribute)prop.GetCustomAttribute(typeof(RelationshipAttribute));
if (attribute == null) continue;
- attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _resourceNameFormatter.FormatPropertyName(prop);
+ attribute.PublicRelationshipName = attribute.PublicRelationshipName ?? _formatter.FormatPropertyName(prop);
attribute.InternalRelationshipName = prop.Name;
attribute.RightType = GetRelationshipType(attribute, prop);
attribute.LeftType = entityType;
@@ -178,63 +183,9 @@ protected virtual Type GetRelationshipType(RelationshipAttribute relation, Prope
private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);
- ///
- public IResourceGraphBuilder AddDbContext() where T : DbContext
- {
- var contextType = typeof(T);
- var contextProperties = contextType.GetProperties();
- foreach (var property in contextProperties)
- {
- var dbSetType = property.PropertyType;
- if (dbSetType.GetTypeInfo().IsGenericType
- && dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>))
- {
- var entityType = dbSetType.GetGenericArguments()[0];
- AssertEntityIsNotAlreadyDefined(entityType);
- var (isJsonApiResource, idType) = GetIdType(entityType);
- if (isJsonApiResource)
- _entities.Add(GetEntity(GetResourceNameFromDbSetProperty(property, entityType), entityType, idType));
- }
- }
-
- return this;
- }
-
- private string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType)
- {
- // this check is actually duplicated in the DefaultResourceNameFormatter
- // however, we perform it here so that we allow class attributes to be prioritized over
- // the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
- //
- // check the class definition first
- // [Resource("models"] public class Model : Identifiable { /* ... */ }
- if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute)
- return classResourceAttribute.ResourceName;
-
- // check the DbContext member next
- // [Resource("models")] public DbSet Models { get; set; }
- if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
- return resourceAttribute.ResourceName;
-
- // fallback to the established convention using the DbSet Property.Name
- // e.g DbSet FooBars { get; set; } => "foo-bars"
- return _resourceNameFormatter.FormatResourceName(resourceType);
- }
-
- private (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)
- {
- var possible = TypeLocator.GetIdType(resourceType);
- if (possible.isJsonApiResource)
- return possible;
-
- _validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
-
- return (false, null);
- }
-
private void AssertEntityIsNotAlreadyDefined(Type entityType)
{
- if (_entities.Any(e => e.ResourceType == entityType))
+ if (_resources.Any(e => e.ResourceType == entityType))
throw new InvalidOperationException($"Cannot add entity type {entityType} to context resourceGraph, there is already an entity of that type configured.");
}
}
diff --git a/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs b/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs
new file mode 100644
index 0000000000..af9272f48c
--- /dev/null
+++ b/src/JsonApiDotNetCore/Extensions/EntityFrameworkCoreExtension.cs
@@ -0,0 +1,111 @@
+using System;
+using System.Reflection;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Graph;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using JsonApiDotNetCore.Builders;
+using JsonApiDotNetCore.Models;
+using JsonApiDotNetCore.Data;
+
+namespace JsonApiDotNetCore.Extensions.EntityFrameworkCore
+{
+
+ ///
+ /// Extensions for configuring JsonApiDotNetCore with EF Core
+ ///
+ public static class IResourceGraphBuilderExtensions
+ {
+ ///
+ /// Add all the models that are part of the provided
+ /// that also implement
+ ///
+ /// The implementation type.
+ public static IResourceGraphBuilder AddDbContext(this IResourceGraphBuilder resourceGraphBuilder) where TDbContext : DbContext
+ {
+ var builder = (ResourceGraphBuilder)resourceGraphBuilder;
+ var contextType = typeof(TDbContext);
+ var contextProperties = contextType.GetProperties();
+ foreach (var property in contextProperties)
+ {
+ var dbSetType = property.PropertyType;
+ if (dbSetType.GetTypeInfo().IsGenericType
+ && dbSetType.GetGenericTypeDefinition() == typeof(DbSet<>))
+ {
+ var resourceType = dbSetType.GetGenericArguments()[0];
+ builder.AddResource(resourceType, pluralizedTypeName: GetResourceNameFromDbSetProperty(property, resourceType));
+ }
+ }
+ return resourceGraphBuilder;
+ }
+
+ private static string GetResourceNameFromDbSetProperty(PropertyInfo property, Type resourceType)
+ {
+ // this check is actually duplicated in the DefaultResourceNameFormatter
+ // however, we perform it here so that we allow class attributes to be prioritized over
+ // the DbSet attribute. Eventually, the DbSet attribute should be deprecated.
+ //
+ // check the class definition first
+ // [Resource("models"] public class Model : Identifiable { /* ... */ }
+ if (resourceType.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute classResourceAttribute)
+ return classResourceAttribute.ResourceName;
+
+ // check the DbContext member next
+ // [Resource("models")] public DbSet Models { get; set; }
+ if (property.GetCustomAttribute(typeof(ResourceAttribute)) is ResourceAttribute resourceAttribute)
+ return resourceAttribute.ResourceName;
+
+ return null;
+ }
+ }
+
+ ///
+ /// Extensions for configuring JsonApiDotNetCore with EF Core
+ ///
+ public static class IServiceCollectionExtensions
+ {
+ ///
+ /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static IServiceCollection AddJsonApi(this IServiceCollection services,
+ Action options = null,
+ Action discovery = null,
+ Action resources = null,
+ IMvcCoreBuilder mvcBuilder = null)
+ where TDbContext : DbContext
+ {
+ var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
+ if (options != null)
+ application.ConfigureJsonApiOptions(options);
+ application.ConfigureMvc();
+ if (discovery != null)
+ application.AutoDiscover(discovery);
+ application.ConfigureResources(resources);
+ application.ConfigureServices();
+ return services;
+ }
+ }
+
+ ///
+ /// Extensions for configuring JsonApiDotNetCore with EF Core
+ ///
+ public static class JsonApiApplicationBuildExtensions
+ {
+ ///
+ /// Executes the action provided by the user to configure the resources using .
+ /// Additionally, inspects the EF core database context for models that implement IIdentifiable.
+ ///
+ public static void ConfigureResources(this JsonApiApplicationBuilder builder, Action resourceGraphBuilder) where TContext : DbContext
+ {
+ builder._resourceGraphBuilder.AddDbContext();
+ builder._usesDbContext = true;
+ builder._services.AddScoped>();
+ resourceGraphBuilder?.Invoke(builder._resourceGraphBuilder);
+ }
+ }
+}
diff --git a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
index aab18dec6b..eb30283428 100644
--- a/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
+++ b/src/JsonApiDotNetCore/Extensions/IServiceCollectionExtensions.cs
@@ -18,33 +18,9 @@ namespace JsonApiDotNetCore.Extensions
// ReSharper disable once InconsistentNaming
public static class IServiceCollectionExtensions
{
- ///
- /// Enabling JsonApiDotNetCore using the EF Core DbContext to build the ResourceGraph.
- ///
- ///
- ///
- ///
- ///
- ///
- public static IServiceCollection AddJsonApi(this IServiceCollection services,
- Action options = null,
- Action resources = null,
- IMvcCoreBuilder mvcBuilder = null)
- where TEfCoreDbContext : DbContext
- {
- var application = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
- if (options != null)
- application.ConfigureJsonApiOptions(options);
- application.ConfigureLogging();
- application.ConfigureMvc();
- application.ConfigureResources(resources);
- application.ConfigureServices();
- return services;
- }
-
///
/// Enabling JsonApiDotNetCore using manual declaration to build the ResourceGraph.
- /// z
+ ///
///
///
///
diff --git a/src/JsonApiDotNetCore/Graph/TypeLocator.cs b/src/JsonApiDotNetCore/Graph/TypeLocator.cs
index 1e82e438c3..de5a2cd82f 100644
--- a/src/JsonApiDotNetCore/Graph/TypeLocator.cs
+++ b/src/JsonApiDotNetCore/Graph/TypeLocator.cs
@@ -1,3 +1,4 @@
+using JsonApiDotNetCore.Extensions;
using JsonApiDotNetCore.Models;
using System;
using System.Collections.Generic;
@@ -19,30 +20,10 @@ static class TypeLocator
/// Determine whether or not this is a json:api resource by checking if it implements .
/// Returns the status and the resultant id type, either `(true, Type)` OR `(false, null)`
///
- public static (bool isJsonApiResource, Type idType) GetIdType(Type resourceType)
+ public static Type GetIdType(Type resourceType)
{
- var identitifableType = GetIdentifiableIdType(resourceType);
- return (identitifableType != null)
- ? (true, identitifableType)
- : (false, null);
- }
-
- private static Type GetIdentifiableIdType(Type identifiableType)
- => GetIdentifiableInterface(identifiableType)?.GetGenericArguments()[0];
-
- private static Type GetIdentifiableInterface(Type type)
- => type.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>));
-
- // TODO: determine if this optimization is even helpful...
- private static Type[] GetAssemblyTypes(Assembly assembly)
- {
- if (_typeCache.TryGetValue(assembly, out var types) == false)
- {
- types = assembly.GetTypes();
- _typeCache[assembly] = types;
- }
-
- return types;
+ var identifiableInterface = resourceType.GetInterfaces().FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IIdentifiable<>));
+ return identifiableInterface?.GetGenericArguments()[0];
}
///
@@ -76,13 +57,11 @@ private static IEnumerable FindIdentifableTypes(Assembly ass
///
internal static bool TryGetResourceDescriptor(Type type, out ResourceDescriptor descriptor)
{
- var possible = GetIdType(type);
- if (possible.isJsonApiResource)
+ if (type.Implements())
{
- descriptor = new ResourceDescriptor(type, possible.idType);
+ descriptor = new ResourceDescriptor(type, GetIdType(type));
return true;
}
-
descriptor = ResourceDescriptor.Empty;
return false;
}
diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/EndToEndTest.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/EndToEndTest.cs
index 0f0d045bdc..ee5451c542 100644
--- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/EndToEndTest.cs
+++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/EndToEndTest.cs
@@ -52,6 +52,7 @@ public AppDbContext GetDbContext()
request.Content = new StringContent(content);
request.Content.Headers.ContentType = JsonApiContentType;
var response = await _client.SendAsync(request);
+
var body = await response.Content?.ReadAsStringAsync();
return (body, response);
}
diff --git a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs
index fae27307d2..058c8f05eb 100644
--- a/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs
+++ b/test/UnitTests/Builders/ContextGraphBuilder_Tests.cs
@@ -5,6 +5,7 @@
using Humanizer;
using JsonApiDotNetCore.Builders;
using JsonApiDotNetCore.Extensions;
+using JsonApiDotNetCore.Extensions.EntityFrameworkCore;
using JsonApiDotNetCore.Graph;
using JsonApiDotNetCore.Internal.Contracts;
using JsonApiDotNetCore.Models;
diff --git a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
index 20fa848b35..bd81705deb 100644
--- a/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
+++ b/test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs
@@ -19,6 +19,7 @@
using JsonApiDotNetCore.Managers.Contracts;
using JsonApiDotNetCore.Serialization.Server.Builders;
using JsonApiDotNetCore.Serialization.Server;
+using JsonApiDotNetCore.Extensions.EntityFrameworkCore;
namespace UnitTests.Extensions
{
@@ -29,7 +30,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services()
{
// Arrange
var services = new ServiceCollection();
-
+ services.AddLogging();
services.AddDbContext(options => options.UseInMemoryDatabase("UnitTestDb"), ServiceLifetime.Transient);
services.AddJsonApi();
diff --git a/test/UnitTests/Graph/TypeLocator_Tests.cs b/test/UnitTests/Graph/TypeLocator_Tests.cs
index 26381ea1fb..502cfa1139 100644
--- a/test/UnitTests/Graph/TypeLocator_Tests.cs
+++ b/test/UnitTests/Graph/TypeLocator_Tests.cs
@@ -59,14 +59,13 @@ public void GetIdType_Correctly_Identifies_JsonApiResource()
{
// Arrange
var type = typeof(Model);
- var exextedIdType = typeof(int);
+ var expectedIdType = typeof(int);
// Act
- var (isJsonApiResource, idType) = TypeLocator.GetIdType(type);
+ var idType = TypeLocator.GetIdType(type);
// Assert
- Assert.True(isJsonApiResource);
- Assert.Equal(exextedIdType, idType);
+ Assert.Equal(expectedIdType, idType);
}
[Fact]
@@ -74,14 +73,13 @@ public void GetIdType_Correctly_Identifies_NonJsonApiResource()
{
// Arrange
var type = typeof(DerivedType);
- Type exextedIdType = null;
+ Type expectedIdType = null;
// Act
- var (isJsonApiResource, idType) = TypeLocator.GetIdType(type);
+ var idType = TypeLocator.GetIdType(type);
// Assert
- Assert.False(isJsonApiResource);
- Assert.Equal(exextedIdType, idType);
+ Assert.Equal(expectedIdType, idType);
}
[Fact]
diff --git a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs b/test/UnitTests/Internal/ResourceGraphBuilder_Tests.cs
similarity index 96%
rename from test/UnitTests/Internal/ContextGraphBuilder_Tests.cs
rename to test/UnitTests/Internal/ResourceGraphBuilder_Tests.cs
index cf61c512d7..e765004ac5 100644
--- a/test/UnitTests/Internal/ContextGraphBuilder_Tests.cs
+++ b/test/UnitTests/Internal/ResourceGraphBuilder_Tests.cs
@@ -1,4 +1,5 @@
using JsonApiDotNetCore.Builders;
+using JsonApiDotNetCore.Extensions.EntityFrameworkCore;
using JsonApiDotNetCore.Internal;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;