Skip to content

Commit 26faa2d

Browse files
author
Bart Koelman
committed
Scan the EF Core model for entity types (removed the requirement to expose them as DbSet properties on DbContext). Removed error on duplicates, to enable first scanning EF Core models, then add all resource definitons etc. from the assembly.
1 parent 9239d61 commit 26faa2d

File tree

12 files changed

+70
-116
lines changed

12 files changed

+70
-116
lines changed

src/Examples/JsonApiDotNetCoreExample/Data/AppDbContext.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,17 @@ public sealed class AppDbContext : DbContext
1212
public DbSet<KebabCasedModel> KebabCasedModels { get; set; }
1313
public DbSet<Article> Articles { get; set; }
1414
public DbSet<Author> AuthorDifferentDbContextName { get; set; }
15-
public DbSet<NonJsonApiResource> NonJsonApiResources { get; set; }
1615
public DbSet<User> Users { get; set; }
17-
public DbSet<SuperUser> SuperUsers { get; set; }
1816
public DbSet<PersonRole> PersonRoles { get; set; }
1917
public DbSet<ArticleTag> ArticleTags { get; set; }
20-
public DbSet<IdentifiableArticleTag> IdentifiableArticleTags { get; set; }
2118
public DbSet<Tag> Tags { get; set; }
22-
public DbSet<ThrowingResource> ThrowingResources { get; set; }
2319

2420
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
2521

2622
protected override void OnModelCreating(ModelBuilder modelBuilder)
2723
{
24+
modelBuilder.Entity<ThrowingResource>();
25+
2826
modelBuilder.Entity<SuperUser>().HasBaseType<User>();
2927

3028
modelBuilder.Entity<TodoItem>()

src/Examples/JsonApiDotNetCoreExample/Startups/Startup.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public virtual void ConfigureServices(IServiceCollection services)
4040
.EnableSensitiveDataLogging()
4141
.UseNpgsql(GetDbConnectionString(), innerOptions => innerOptions.SetPostgresVersion(new Version(9,6)));
4242
}, ServiceLifetime.Transient)
43-
.AddJsonApi(ConfigureJsonApiOptions, discovery => discovery.AddCurrentAssembly());
43+
.AddJsonApi<AppDbContext>(ConfigureJsonApiOptions, discovery => discovery.AddCurrentAssembly());
4444

4545
// once all tests have been moved to WebApplicationFactory format we can get rid of this line below
4646
services.AddClientSerialization();

src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public interface IResourceGraphBuilder
3333
/// <summary>
3434
/// Add a Json:Api resource
3535
/// </summary>
36-
/// <param name="entityType">The resource model type</param>
36+
/// <param name="resourceType">The resource model type</param>
3737
/// <param name="idType">The resource model identifier type</param>
3838
/// <param name="pluralizedTypeName">
3939
/// The pluralized name that should be exposed by the API.
4040
/// If nothing is specified, the configured name formatter will be used.
4141
/// </param>
42-
IResourceGraphBuilder AddResource(Type entityType, Type idType, string pluralizedTypeName = null);
42+
IResourceGraphBuilder AddResource(Type resourceType, Type idType = null, string pluralizedTypeName = null);
4343
}
4444
}

src/JsonApiDotNetCore/Builders/JsonApiApplicationBuilder.cs

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using System;
22
using JsonApiDotNetCore.Configuration;
33
using JsonApiDotNetCore.Data;
4-
using JsonApiDotNetCore.Extensions.EntityFrameworkCore;
54
using JsonApiDotNetCore.Formatters;
65
using JsonApiDotNetCore.Graph;
76
using JsonApiDotNetCore.Internal;
@@ -34,7 +33,7 @@ internal sealed class JsonApiApplicationBuilder
3433
{
3534
private readonly JsonApiOptions _options = new JsonApiOptions();
3635
private IResourceGraphBuilder _resourceGraphBuilder;
37-
private bool _usesDbContext;
36+
private Type _dbContextType;
3837
private readonly IServiceCollection _services;
3938
private IServiceDiscoveryFacade _serviceDiscoveryFacade;
4039
private readonly IMvcCoreBuilder _mvcBuilder;
@@ -59,13 +58,17 @@ public void ConfigureJsonApiOptions(Action<JsonApiOptions> options)
5958
/// <see cref="IResourceGraphBuilder"/>, <see cref="IServiceDiscoveryFacade"/>, <see cref="IJsonApiExceptionFilterProvider"/>,
6059
/// <see cref="IJsonApiTypeMatchFilterProvider"/> and <see cref="IJsonApiRoutingConvention"/>.
6160
/// </summary>
62-
public void ConfigureMvc()
61+
public void ConfigureMvc(Type dbContextType)
6362
{
6463
RegisterJsonApiStartupServices();
6564

6665
var intermediateProvider = _services.BuildServiceProvider();
6766
_resourceGraphBuilder = intermediateProvider.GetRequiredService<IResourceGraphBuilder>();
6867
_serviceDiscoveryFacade = intermediateProvider.GetRequiredService<IServiceDiscoveryFacade>();
68+
_dbContextType = dbContextType;
69+
70+
AddResourceTypesFromDbContext(intermediateProvider);
71+
6972
var exceptionFilterProvider = intermediateProvider.GetRequiredService<IJsonApiExceptionFilterProvider>();
7073
var typeMatchFilterProvider = intermediateProvider.GetRequiredService<IJsonApiTypeMatchFilterProvider>();
7174
var routingConvention = intermediateProvider.GetRequiredService<IJsonApiRoutingConvention>();
@@ -89,6 +92,19 @@ public void ConfigureMvc()
8992
_services.AddSingleton<IControllerResourceMapping>(routingConvention);
9093
}
9194

95+
private void AddResourceTypesFromDbContext(ServiceProvider intermediateProvider)
96+
{
97+
if (_dbContextType != null)
98+
{
99+
var dbContext = (DbContext) intermediateProvider.GetRequiredService(_dbContextType);
100+
101+
foreach (var entityType in dbContext.Model.GetEntityTypes())
102+
{
103+
_resourceGraphBuilder.AddResource(entityType.ClrType);
104+
}
105+
}
106+
}
107+
92108
/// <summary>
93109
/// Executes auto-discovery of JADNC services.
94110
/// </summary>
@@ -97,16 +113,6 @@ public void AutoDiscover(Action<IServiceDiscoveryFacade> autoDiscover)
97113
autoDiscover?.Invoke(_serviceDiscoveryFacade);
98114
}
99115

100-
public void ConfigureResourcesFromDbContext<TDbContext>(Action<IResourceGraphBuilder> resources)
101-
where TDbContext : DbContext
102-
{
103-
_resourceGraphBuilder.AddDbContext<TDbContext>();
104-
_usesDbContext = true;
105-
_services.AddScoped<IDbContextResolver, DbContextResolver<TDbContext>>();
106-
107-
ConfigureResources(resources);
108-
}
109-
110116
/// <summary>
111117
/// Executes the action provided by the user to configure the resources using <see cref="IResourceGraphBuilder"/>
112118
/// </summary>
@@ -122,7 +128,12 @@ public void ConfigureServices()
122128
{
123129
var resourceGraph = _resourceGraphBuilder.Build();
124130

125-
if (!_usesDbContext)
131+
if (_dbContextType != null)
132+
{
133+
var contextResolverType = typeof(DbContextResolver<>).MakeGenericType(_dbContextType);
134+
_services.AddScoped(typeof(IDbContextResolver), contextResolverType);
135+
}
136+
else
126137
{
127138
_services.AddScoped<DbContext>();
128139
_services.AddSingleton(new DbContextOptionsBuilder().Options);

src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,25 @@ public IResourceGraphBuilder AddResource<TResource, TId>(string pluralizedTypeNa
5656
/// <inheritdoc />
5757
public IResourceGraphBuilder AddResource(Type resourceType, Type idType = null, string pluralizedTypeName = null)
5858
{
59-
AssertEntityIsNotAlreadyDefined(resourceType);
60-
if (resourceType.Implements<IIdentifiable>())
59+
if (_resources.All(e => e.ResourceType != resourceType))
6160
{
62-
pluralizedTypeName ??= FormatResourceName(resourceType);
63-
idType ??= TypeLocator.GetIdType(resourceType);
64-
_resources.Add(GetEntity(pluralizedTypeName, resourceType, idType));
65-
}
66-
else
67-
{
68-
_validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement 'IIdentifiable<>'. "));
61+
if (resourceType.Implements<IIdentifiable>())
62+
{
63+
pluralizedTypeName ??= FormatResourceName(resourceType);
64+
idType ??= TypeLocator.GetIdType(resourceType);
65+
var resourceContext = CreateResourceContext(pluralizedTypeName, resourceType, idType);
66+
_resources.Add(resourceContext);
67+
}
68+
else
69+
{
70+
_validationResults.Add(new ValidationResult(LogLevel.Warning, $"{resourceType} does not implement '{nameof(IIdentifiable)}'. "));
71+
}
6972
}
7073

7174
return this;
7275
}
7376

74-
private ResourceContext GetEntity(string pluralizedTypeName, Type entityType, Type idType) => new ResourceContext
77+
private ResourceContext CreateResourceContext(string pluralizedTypeName, Type entityType, Type idType) => new ResourceContext
7578
{
7679
ResourceName = pluralizedTypeName,
7780
ResourceType = entityType,
@@ -213,12 +216,6 @@ private static Type TypeOrElementType(Type type)
213216

214217
private Type GetResourceDefinitionType(Type entityType) => typeof(ResourceDefinition<>).MakeGenericType(entityType);
215218

216-
private void AssertEntityIsNotAlreadyDefined(Type entityType)
217-
{
218-
if (_resources.Any(e => e.ResourceType == entityType))
219-
throw new InvalidOperationException($"Cannot add entity type {entityType} to context resourceGraph, there is already an entity of that type configured.");
220-
}
221-
222219
private string FormatResourceName(Type resourceType)
223220
{
224221
var formatter = new ResourceNameFormatter(_options);

src/JsonApiDotNetCore/Extensions/ResourceGraphBuilderExtensions.cs

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/JsonApiDotNetCore/Extensions/ServiceCollectionExtensions.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ public static IServiceCollection AddJsonApi(this IServiceCollection services,
2525
Action<IResourceGraphBuilder> resources = null,
2626
IMvcCoreBuilder mvcBuilder = null)
2727
{
28-
var applicationBuilder = SetupApplicationBuilder(services, options, discovery, mvcBuilder);
29-
30-
applicationBuilder.ConfigureResources(resources);
31-
32-
applicationBuilder.ConfigureServices();
28+
SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, null);
3329
return services;
3430
}
3531

@@ -43,24 +39,21 @@ public static IServiceCollection AddJsonApi<TDbContext>(this IServiceCollection
4339
IMvcCoreBuilder mvcBuilder = null)
4440
where TDbContext : DbContext
4541
{
46-
var applicationBuilder = SetupApplicationBuilder(services, options, discovery, mvcBuilder);
47-
48-
applicationBuilder.ConfigureResourcesFromDbContext<TDbContext>(resources);
49-
50-
applicationBuilder.ConfigureServices();
42+
SetupApplicationBuilder(services, options, discovery, resources, mvcBuilder, typeof(TDbContext));
5143
return services;
5244
}
5345

54-
private static JsonApiApplicationBuilder SetupApplicationBuilder(IServiceCollection services, Action<JsonApiOptions> options, Action<IServiceDiscoveryFacade> discovery,
55-
IMvcCoreBuilder mvcBuilder)
46+
private static void SetupApplicationBuilder(IServiceCollection services, Action<JsonApiOptions> options,
47+
Action<IServiceDiscoveryFacade> discovery,
48+
Action<IResourceGraphBuilder> resources, IMvcCoreBuilder mvcBuilder, Type dbContextType)
5649
{
5750
var applicationBuilder = new JsonApiApplicationBuilder(services, mvcBuilder ?? services.AddMvcCore());
5851

5952
applicationBuilder.ConfigureJsonApiOptions(options);
60-
applicationBuilder.ConfigureMvc();
53+
applicationBuilder.ConfigureMvc(dbContextType);
6154
applicationBuilder.AutoDiscover(discovery);
62-
63-
return applicationBuilder;
55+
applicationBuilder.ConfigureResources(resources);
56+
applicationBuilder.ConfigureServices();
6457
}
6558

6659
/// <summary>

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/CreatingDataTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public async Task CreateResource_ModelWithEntityFrameworkInheritance_IsCreated()
4343
// Assert
4444
AssertEqualStatusCode(HttpStatusCode.Created, response);
4545
var createdSuperUser = _deserializer.DeserializeSingle<SuperUser>(body).Data;
46-
var first = _dbContext.SuperUsers.FirstOrDefault(e => e.Id.Equals(createdSuperUser.Id));
46+
var first = _dbContext.Set<SuperUser>().FirstOrDefault(e => e.Id.Equals(createdSuperUser.Id));
4747
Assert.NotNull(first);
4848
}
4949

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingDataTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public async Task PatchResource_ModelWithEntityFrameworkInheritance_IsPatched()
4949
var dbContext = PrepareTest<Startup>();
5050
var serializer = GetSerializer<SuperUser>(e => new { e.SecurityLevel, e.Username, e.Password });
5151
var superUser = new SuperUser { SecurityLevel = 1337, Username = "Super", Password = "User", LastPasswordChange = DateTime.Now.AddMinutes(-15) };
52-
dbContext.SuperUsers.Add(superUser);
52+
dbContext.Set<SuperUser>().Add(superUser);
5353
dbContext.SaveChanges();
5454
var su = new SuperUser { Id = superUser.Id, SecurityLevel = 2674, Username = "Power", Password = "secret" };
5555
var content = serializer.Serialize(su);

test/UnitTests/Builders/ContextGraphBuilder_Tests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Linq;
34
using JsonApiDotNetCore;
@@ -20,13 +21,20 @@ private sealed class DbResource : Identifiable { }
2021
private class TestContext : DbContext
2122
{
2223
public DbSet<DbResource> DbResources { get; set; }
24+
25+
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
26+
{
27+
optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
28+
}
2329
}
2430

2531
[Fact]
2632
public void Can_Build_ResourceGraph_Using_Builder()
2733
{
2834
// Arrange
2935
var services = new ServiceCollection();
36+
services.AddDbContext<TestContext>();
37+
3038
services.AddJsonApi<TestContext>(resources: builder => builder.AddResource<NonDbResource>("nonDbResources"));
3139

3240
// Act

0 commit comments

Comments
 (0)