Skip to content

Commit a352f08

Browse files
committed
feat: decouples UsesDbContext from ResourceGraph and introduces decoupled application instantation
1 parent 85ee717 commit a352f08

File tree

15 files changed

+310
-369
lines changed

15 files changed

+310
-369
lines changed

src/Examples/JsonApiDotNetCoreExample/Startup.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
3232
services
3333
.AddSingleton<ILoggerFactory>(loggerFactory)
3434
.AddDbContext<AppDbContext>(options => options.UseNpgsql(GetDbConnectionString()), ServiceLifetime.Transient)
35-
.AddJsonApi(options => {
36-
options.Namespace = "api/v1";
37-
options.DefaultPageSize = 5;
38-
options.IncludeTotalRecordCount = true;
39-
options.EnableResourceHooks = true;
40-
options.LoaDatabaseValues = true;
41-
},
42-
discovery => discovery.AddCurrentAssembly());
35+
.AddJsonApi(
36+
options =>
37+
{
38+
options.Namespace = "api/v1";
39+
options.DefaultPageSize = 5;
40+
options.IncludeTotalRecordCount = true;
41+
options.EnableResourceHooks = true;
42+
options.LoaDatabaseValues = true;
43+
},
44+
discovery => discovery.AddCurrentAssembly());
4345

4446
return services.BuildServiceProvider();
4547
}

src/Examples/NoEntityFrameworkExample/Startup.cs

100755100644
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,11 @@ public virtual IServiceProvider ConfigureServices(IServiceCollection services)
3333
// Add framework services.
3434
var mvcBuilder = services.AddMvcCore();
3535

36-
services.AddJsonApi(options => {
37-
options.Namespace = "api/v1";
38-
options.BuildResourceGraph((builder) => {
39-
builder.AddResource<TodoItem>("custom-todo-items");
40-
});
41-
}, mvcBuilder);
36+
services.AddJsonApi(
37+
options => options.Namespace = "api/v1",
38+
resources: resources => resources.AddResource<TodoItem>("custom-todo-items"),
39+
mvcBuilder: mvcBuilder
40+
);
4241

4342
services.AddScoped<IResourceService<TodoItem>, TodoItemService>();
4443

src/JsonApiDotNetCore/Builders/IResourceGraphBuilder.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ public interface IResourceGraphBuilder
2626
/// </param>
2727
IResourceGraphBuilder AddResource<TResource>(string pluralizedTypeName = null) where TResource : class, IIdentifiable<int>;
2828

29-
IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null);
3029

3130
/// <summary>
3231
/// Add a json:api resource
@@ -57,14 +56,6 @@ public interface IResourceGraphBuilder
5756
/// that also implement <see cref="IIdentifiable"/>
5857
/// </summary>
5958
/// <typeparam name="T">The <see cref="DbContext"/> implementation type.</typeparam>
60-
IResourceGraphBuilder AddDbContext<T>() where T : DbContext;
61-
62-
/// <summary>
63-
/// Specify the <see cref="IResourceNameFormatter"/> used to format resource names.
64-
/// </summary>
65-
/// <param name="resourceNameFormatter">Formatter used to define exposed resource names by convention.</param>
66-
IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter);
67-
68-
59+
IResourceGraphBuilder AddDbContext<T>() where T : DbContext;
6960
}
7061
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using JsonApiDotNetCore.Configuration;
6+
using JsonApiDotNetCore.Data;
7+
using JsonApiDotNetCore.Formatters;
8+
using JsonApiDotNetCore.Graph;
9+
using JsonApiDotNetCore.Internal;
10+
using JsonApiDotNetCore.Internal.Generics;
11+
using JsonApiDotNetCore.Managers;
12+
using JsonApiDotNetCore.Managers.Contracts;
13+
using JsonApiDotNetCore.Middleware;
14+
using JsonApiDotNetCore.Models;
15+
using JsonApiDotNetCore.Serialization;
16+
using JsonApiDotNetCore.Hooks;
17+
using JsonApiDotNetCore.Services;
18+
using Microsoft.AspNetCore.Http;
19+
using Microsoft.AspNetCore.Mvc;
20+
using Microsoft.EntityFrameworkCore;
21+
using Microsoft.Extensions.DependencyInjection;
22+
using JsonApiDotNetCore.Internal.Contracts;
23+
using JsonApiDotNetCore.Query;
24+
using JsonApiDotNetCore.Serialization.Server.Builders;
25+
using JsonApiDotNetCore.Serialization.Server;
26+
using JsonApiDotNetCore.Serialization.Client;
27+
using Microsoft.Extensions.DependencyInjection.Extensions;
28+
using JsonApiDotNetCore.Builders;
29+
30+
namespace JsonApiDotNetCore.Builders
31+
{
32+
public class JsonApiApplicationBuilder
33+
{
34+
public readonly JsonApiOptions JsonApiOptions = new JsonApiOptions();
35+
private IResourceGraphBuilder _resourceGraphBuilder;
36+
private IServiceDiscoveryFacade _serviceDiscoveryFacade;
37+
private bool _usesDbContext;
38+
private readonly IServiceCollection _services;
39+
private readonly IMvcCoreBuilder _mvcBuilder;
40+
41+
public JsonApiApplicationBuilder(IServiceCollection services, IMvcCoreBuilder mvcBuilder)
42+
{
43+
_services = services;
44+
_mvcBuilder = mvcBuilder;
45+
}
46+
47+
48+
public void ConfigureJsonApiOptions(Action<JsonApiOptions> configureOptions) => configureOptions(JsonApiOptions);
49+
50+
public void ConfigureMvc()
51+
{
52+
RegisterJsonApiStartupServices();
53+
54+
var intermediateProvider = _services.BuildServiceProvider();
55+
_resourceGraphBuilder = intermediateProvider.GetRequiredService<IResourceGraphBuilder>();
56+
_serviceDiscoveryFacade = intermediateProvider.GetRequiredService<IServiceDiscoveryFacade>();
57+
var exceptionFilterProvider = intermediateProvider.GetRequiredService<IJsonApiExceptionFilterProvider>();
58+
var typeMatchFilterProvider = intermediateProvider.GetRequiredService<IJsonApiTypeMatchFilterProvider>();
59+
60+
_mvcBuilder.AddMvcOptions(mvcOptions =>
61+
{
62+
mvcOptions.Filters.Add(exceptionFilterProvider.Get());
63+
mvcOptions.Filters.Add(typeMatchFilterProvider.Get());
64+
mvcOptions.InputFormatters.Insert(0, new JsonApiInputFormatter());
65+
mvcOptions.OutputFormatters.Insert(0, new JsonApiOutputFormatter());
66+
});
67+
68+
var routingConvention = intermediateProvider.GetRequiredService<IJsonApiRoutingConvention>();
69+
_mvcBuilder.AddMvcOptions(opt => opt.Conventions.Insert(0, routingConvention));
70+
_services.AddSingleton(routingConvention); // <--- why is this needed?
71+
}
72+
73+
public void AutoDiscover(Action<IServiceDiscoveryFacade> autoDiscover)
74+
{
75+
autoDiscover(_serviceDiscoveryFacade);
76+
}
77+
78+
public void ConfigureResources(Action<IResourceGraphBuilder> resourceGraphBuilder)
79+
{
80+
resourceGraphBuilder(_resourceGraphBuilder);
81+
}
82+
83+
public void ConfigureResources<TContext>(Action<IResourceGraphBuilder> resourceGraphBuilder) where TContext : DbContext
84+
{
85+
_resourceGraphBuilder.AddDbContext<TContext>();
86+
_usesDbContext = true;
87+
_services.AddScoped<IDbContextResolver, DbContextResolver<TContext>>();
88+
resourceGraphBuilder?.Invoke(_resourceGraphBuilder);
89+
}
90+
91+
private void RegisterJsonApiStartupServices()
92+
{
93+
_services.AddSingleton<IJsonApiOptions>(JsonApiOptions);
94+
_services.TryAddSingleton<IResourceNameFormatter>(new KebabCaseFormatter());
95+
_services.TryAddSingleton<IJsonApiRoutingConvention, DefaultRoutingConvention>();
96+
_services.TryAddSingleton<IResourceGraphBuilder, ResourceGraphBuilder>();
97+
_services.TryAddSingleton<IServiceDiscoveryFacade>(sp => new ServiceDiscoveryFacade(_services, sp.GetRequiredService<IResourceGraphBuilder>()));
98+
_services.TryAddScoped<IJsonApiExceptionFilterProvider, JsonApiExceptionFilterProvider>();
99+
_services.TryAddScoped<IJsonApiTypeMatchFilterProvider, JsonApiTypeMatchFilterProvider>();
100+
}
101+
102+
public void ConfigureServices()
103+
{
104+
var graph = _resourceGraphBuilder.Build();
105+
106+
if (!_usesDbContext)
107+
{
108+
_services.AddScoped<DbContext>();
109+
_services.AddSingleton(new DbContextOptionsBuilder().Options);
110+
}
111+
112+
_services.AddScoped(typeof(IEntityRepository<>), typeof(DefaultEntityRepository<>));
113+
_services.AddScoped(typeof(IEntityRepository<,>), typeof(DefaultEntityRepository<,>));
114+
115+
_services.AddScoped(typeof(IEntityReadRepository<,>), typeof(DefaultEntityRepository<,>));
116+
_services.AddScoped(typeof(IEntityWriteRepository<,>), typeof(DefaultEntityRepository<,>));
117+
118+
_services.AddScoped(typeof(ICreateService<>), typeof(EntityResourceService<>));
119+
_services.AddScoped(typeof(ICreateService<,>), typeof(EntityResourceService<,>));
120+
121+
_services.AddScoped(typeof(IGetAllService<>), typeof(EntityResourceService<>));
122+
_services.AddScoped(typeof(IGetAllService<,>), typeof(EntityResourceService<,>));
123+
124+
_services.AddScoped(typeof(IGetByIdService<>), typeof(EntityResourceService<>));
125+
_services.AddScoped(typeof(IGetByIdService<,>), typeof(EntityResourceService<,>));
126+
127+
_services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<>));
128+
_services.AddScoped(typeof(IGetRelationshipService<,>), typeof(EntityResourceService<,>));
129+
130+
_services.AddScoped(typeof(IUpdateService<>), typeof(EntityResourceService<>));
131+
_services.AddScoped(typeof(IUpdateService<,>), typeof(EntityResourceService<,>));
132+
133+
_services.AddScoped(typeof(IDeleteService<>), typeof(EntityResourceService<>));
134+
_services.AddScoped(typeof(IDeleteService<,>), typeof(EntityResourceService<,>));
135+
136+
_services.AddScoped(typeof(IResourceService<>), typeof(EntityResourceService<>));
137+
_services.AddScoped(typeof(IResourceService<,>), typeof(EntityResourceService<,>));
138+
139+
_services.AddSingleton<ILinksConfiguration>(JsonApiOptions);
140+
_services.AddSingleton(graph);
141+
_services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
142+
_services.AddSingleton<IContextEntityProvider>(graph);
143+
_services.AddScoped<ICurrentRequest, CurrentRequest>();
144+
_services.AddScoped<IScopedServiceProvider, RequestScopedServiceProvider>();
145+
_services.AddScoped<IJsonApiWriter, JsonApiWriter>();
146+
_services.AddScoped<IJsonApiReader, JsonApiReader>();
147+
_services.AddScoped<IGenericProcessorFactory, GenericProcessorFactory>();
148+
_services.AddScoped(typeof(GenericProcessor<>));
149+
_services.AddScoped<IQueryParameterDiscovery, QueryParameterDiscovery>();
150+
_services.AddScoped<ITargetedFields, TargetedFields>();
151+
_services.AddScoped<IFieldsExplorer, FieldsExplorer>();
152+
_services.AddScoped<IResourceDefinitionProvider, ResourceDefinitionProvider>();
153+
_services.AddScoped<IFieldsToSerialize, FieldsToSerialize>();
154+
_services.AddScoped<IQueryParameterActionFilter, QueryParameterActionFilter>();
155+
156+
AddServerSerialization();
157+
AddQueryParameterServices();
158+
if (JsonApiOptions.EnableResourceHooks)
159+
AddResourceHooks();
160+
161+
_services.AddScoped<IInverseRelationships, InverseRelationships>();
162+
}
163+
164+
165+
private void AddQueryParameterServices()
166+
{
167+
_services.AddScoped<IIncludeService, IncludeService>();
168+
_services.AddScoped<IFilterService, FilterService>();
169+
_services.AddScoped<ISortService, SortService>();
170+
_services.AddScoped<ISparseFieldsService, SparseFieldsService>();
171+
_services.AddScoped<IPageService, PageService>();
172+
_services.AddScoped<IOmitDefaultService, OmitDefaultService>();
173+
_services.AddScoped<IOmitNullService, OmitNullService>();
174+
175+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IIncludeService>());
176+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IFilterService>());
177+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<ISortService>());
178+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<ISparseFieldsService>());
179+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IPageService>());
180+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IOmitDefaultService>());
181+
_services.AddScoped<IQueryParameterService>(sp => sp.GetService<IOmitNullService>());
182+
}
183+
184+
185+
private void AddResourceHooks()
186+
{
187+
_services.AddSingleton(typeof(IHooksDiscovery<>), typeof(HooksDiscovery<>));
188+
_services.AddScoped(typeof(IResourceHookContainer<>), typeof(ResourceDefinition<>));
189+
_services.AddTransient(typeof(IResourceHookExecutor), typeof(ResourceHookExecutor));
190+
_services.AddTransient<IHookExecutorHelper, HookExecutorHelper>();
191+
_services.AddTransient<ITraversalHelper, TraversalHelper>();
192+
}
193+
194+
private void AddServerSerialization()
195+
{
196+
_services.AddScoped<IIncludedResourceObjectBuilder, IncludedResourceObjectBuilder>();
197+
_services.AddScoped<IJsonApiDeserializer, RequestDeserializer>();
198+
_services.AddScoped<IResourceObjectBuilderSettingsProvider, ResourceObjectBuilderSettingsProvider>();
199+
_services.AddScoped<IJsonApiSerializerFactory, ResponseSerializerFactory>();
200+
_services.AddScoped<ILinkBuilder, LinkBuilder>();
201+
_services.AddScoped(typeof(IMetaBuilder<>), typeof(MetaBuilder<>));
202+
_services.AddScoped(typeof(ResponseSerializer<>));
203+
_services.AddScoped(sp => sp.GetRequiredService<IJsonApiSerializerFactory>().GetSerializer());
204+
_services.AddScoped<IResourceObjectBuilder, ResponseResourceObjectBuilder>();
205+
}
206+
}
207+
}

src/JsonApiDotNetCore/Builders/ResourceGraphBuilder.cs

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,18 @@ namespace JsonApiDotNetCore.Builders
1717
{
1818
public class ResourceGraphBuilder : IResourceGraphBuilder
1919
{
20-
private List<ContextEntity> _entities = new List<ContextEntity>();
21-
private List<ValidationResult> _validationResults = new List<ValidationResult>();
22-
private Dictionary<Type, List<Type>> _controllerMapper = new Dictionary<Type, List<Type>>() { };
23-
private List<Type> _undefinedMapper = new List<Type>() { };
20+
private readonly List<ContextEntity> _entities = new List<ContextEntity>();
21+
private readonly List<ValidationResult> _validationResults = new List<ValidationResult>();
22+
private readonly Dictionary<Type, List<Type>> _controllerMapper = new Dictionary<Type, List<Type>>() { };
23+
private readonly List<Type> _undefinedMapper = new List<Type>() { };
2424
private bool _usesDbContext;
25-
private IResourceNameFormatter _resourceNameFormatter;
25+
private readonly IResourceNameFormatter _resourceNameFormatter = new KebabCaseFormatter();
2626

27-
public ResourceGraphBuilder(IResourceNameFormatter formatter = null)
27+
public ResourceGraphBuilder() { }
28+
29+
public ResourceGraphBuilder(IResourceNameFormatter formatter)
2830
{
29-
_resourceNameFormatter = formatter ?? new KebabCaseFormatter();
31+
_resourceNameFormatter = formatter;
3032
}
3133

3234
/// <inheritdoc />
@@ -263,30 +265,5 @@ private void AssertEntityIsNotAlreadyDefined(Type entityType)
263265
if (_entities.Any(e => e.EntityType == entityType))
264266
throw new InvalidOperationException($"Cannot add entity type {entityType} to context graph, there is already an entity of that type configured.");
265267
}
266-
267-
/// <inheritdoc />
268-
public IResourceGraphBuilder UseNameFormatter(IResourceNameFormatter resourceNameFormatter)
269-
{
270-
_resourceNameFormatter = resourceNameFormatter;
271-
return this;
272-
}
273-
274-
public IResourceGraphBuilder AddControllerPairing(Type controller, Type model = null)
275-
{
276-
if (model == null)
277-
{
278-
_undefinedMapper.Add(controller);
279-
return this;
280-
}
281-
if (_controllerMapper.Keys.Contains(model))
282-
{
283-
_controllerMapper[model].Add(controller);
284-
}
285-
else
286-
{
287-
_controllerMapper.Add(model, new List<Type>() { controller });
288-
}
289-
return this;
290-
}
291268
}
292269
}

src/JsonApiDotNetCore/Configuration/IJsonApiOptions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public interface IJsonApiOptions : ILinksConfiguration, ISerializerOptions
2323
int DefaultPageSize { get; }
2424
bool ValidateModelState { get; }
2525
bool AllowClientGeneratedIds { get; }
26-
IResourceGraph ResourceGraph { get; set; }
2726
bool AllowCustomQueryParameters { get; set; }
2827
string Namespace { get; set; }
2928
}

src/JsonApiDotNetCore/Configuration/JsonApiOptions.cs

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
using System;
21
using System.Collections.Generic;
32
using JsonApiDotNetCore.Builders;
43
using JsonApiDotNetCore.Graph;
5-
using JsonApiDotNetCore.Internal.Contracts;
64
using JsonApiDotNetCore.Models;
75
using JsonApiDotNetCore.Models.Links;
8-
using Microsoft.EntityFrameworkCore;
96
using Newtonsoft.Json;
107

118
namespace JsonApiDotNetCore.Configuration
@@ -95,12 +92,6 @@ public class JsonApiOptions : IJsonApiOptions
9592
/// </example>
9693
public bool AllowClientGeneratedIds { get; set; }
9794

98-
/// <summary>
99-
/// The graph of all resources exposed by this application.
100-
/// </summary>
101-
[Obsolete("Use the standalone resourcegraph")]
102-
public IResourceGraph ResourceGraph { get; set; }
103-
10495
/// <summary>
10596
/// Whether or not to allow all custom query parameters.
10697
/// </summary>
@@ -139,24 +130,6 @@ public class JsonApiOptions : IJsonApiOptions
139130
NullValueHandling = NullValueHandling.Ignore
140131
};
141132

142-
public void BuildResourceGraph<TContext>(Action<IResourceGraphBuilder> builder) where TContext : DbContext
143-
{
144-
BuildResourceGraph(builder);
145-
146-
ResourceGraphBuilder.AddDbContext<TContext>();
147-
148-
ResourceGraph = ResourceGraphBuilder.Build();
149-
}
150-
151-
public void BuildResourceGraph(Action<IResourceGraphBuilder> builder)
152-
{
153-
if (builder == null) return;
154-
155-
builder(ResourceGraphBuilder);
156-
157-
ResourceGraph = ResourceGraphBuilder.Build();
158-
}
159-
160133
public void EnableExtension(JsonApiExtension extension)
161134
=> EnabledExtensions.Add(extension);
162135

0 commit comments

Comments
 (0)