Skip to content

Commit f90d023

Browse files
committed
Merge branch 'master' into openapi
2 parents c48dea3 + 5e56950 commit f90d023

File tree

66 files changed

+255
-231
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+255
-231
lines changed

Diff for: .config/dotnet-tools.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
]
1616
},
1717
"dotnet-reportgenerator-globaltool": {
18-
"version": "5.1.25",
18+
"version": "5.1.26",
1919
"commands": [
2020
"reportgenerator"
2121
]

Diff for: Directory.Build.props

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<Project>
2-
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
2+
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
33
<NoWarn>$(NoWarn);AV2210</NoWarn>
44
</PropertyGroup>
55

6-
<PropertyGroup Condition="'$(Configuration)'=='Release'">
6+
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
77
<NoWarn>$(NoWarn);1591</NoWarn>
88
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
99
<GenerateDocumentationFile>true</GenerateDocumentationFile>

Diff for: docs/usage/common-pitfalls.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,13 @@ Neither sounds very compelling. If stored procedures is what you need, you're be
8787
Although recommended by Microsoft for hard-written controllers, the opinionated behavior of [`[ApiController]`](https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-7.0#apicontroller-attribute) violates the JSON:API specification.
8888
Despite JsonApiDotNetCore trying its best to deal with it, the experience won't be as good as leaving it out.
8989

90-
#### Replace injectable services *after* calling `AddJsonApi()`
91-
Registering your own services in the IoC container afterwards increases the chances that your replacements will take effect.
92-
Also, register with `services.AddResourceDefinition/AddResourceService/AddResourceRepository()` instead of `services.AddScoped()`.
90+
#### Register/override injectable services
91+
Register your JSON:API resource services, resource definitions and repositories with `services.AddResourceService/AddResourceDefinition/AddResourceRepository()` instead of `services.AddScoped()`.
9392
When using [Auto-discovery](~/usage/resource-graph.md#auto-discovery), you don't need to register these at all.
9493

94+
> [!NOTE]
95+
> In older versions of JsonApiDotNetCore, registering your own services in the IoC container *afterwards* increased the chances that your replacements would take effect.
96+
9597
#### Never use the Entity Framework Core In-Memory Database Provider
9698
When using this provider, many invalid mappings go unnoticed, leading to strange errors or wrong behavior. A real SQL engine fails to create the schema when mappings are invalid.
9799
If you're in need of a quick setup, use [SQLite](https://www.sqlite.org/). After adding its [NuGet package](https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.Sqlite), it's as simple as:

Diff for: src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ private string GetConnectionString()
4444
throw GetErrorForInvalidTenant(tenantName);
4545
}
4646

47-
string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres";
48-
return connectionString.Replace("###", postgresPassword);
47+
return connectionString;
4948
}
5049

5150
private string? GetTenantName()

Diff for: src/Examples/DatabasePerTenantExample/appsettings.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"ConnectionStrings": {
3-
"Default": "Host=localhost;Database=DefaultTenantDb;User ID=postgres;Password=###;Include Error Detail=true",
4-
"AdventureWorks": "Host=localhost;Database=AdventureWorks;User ID=postgres;Password=###;Include Error Detail=true",
5-
"Contoso": "Host=localhost;Database=Contoso;User ID=postgres;Password=###;Include Error Detail=true"
3+
"Default": "Host=localhost;Database=DefaultTenantDb;User ID=postgres;Password=postgres;Include Error Detail=true",
4+
"AdventureWorks": "Host=localhost;Database=AdventureWorks;User ID=postgres;Password=postgres;Include Error Detail=true",
5+
"Contoso": "Host=localhost;Database=Contoso;User ID=postgres;Password=postgres;Include Error Detail=true"
66
},
77
"Logging": {
88
"LogLevel": {

Diff for: src/Examples/JsonApiDotNetCoreExample/Program.cs

+1-7
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ static void ConfigureServices(WebApplicationBuilder builder)
5050

5151
builder.Services.AddDbContext<AppDbContext>(options =>
5252
{
53-
string? connectionString = GetConnectionString(builder.Configuration);
53+
string? connectionString = builder.Configuration.GetConnectionString("Default");
5454
options.UseNpgsql(connectionString);
5555

5656
SetDbContextDebugOptions(options);
@@ -81,12 +81,6 @@ static void ConfigureServices(WebApplicationBuilder builder)
8181
}
8282
}
8383

84-
static string? GetConnectionString(IConfiguration configuration)
85-
{
86-
string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres";
87-
return configuration.GetConnectionString("Default")?.Replace("###", postgresPassword);
88-
}
89-
9084
[Conditional("DEBUG")]
9185
static void SetDbContextDebugOptions(DbContextOptionsBuilder options)
9286
{

Diff for: src/Examples/JsonApiDotNetCoreExample/appsettings.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"ConnectionStrings": {
3-
"Default": "Host=localhost;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=###;Include Error Detail=true"
3+
"Default": "Host=localhost;Database=JsonApiDotNetCoreExample;User ID=postgres;Password=postgres;Include Error Detail=true"
44
},
55
"Logging": {
66
"LogLevel": {

Diff for: src/Examples/MultiDbContextExample/Program.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
SetDbContextDebugOptions(options);
2323
});
2424

25+
builder.Services.AddResourceRepository<DbContextARepository<ResourceA>>();
26+
builder.Services.AddResourceRepository<DbContextBRepository<ResourceB>>();
27+
2528
builder.Services.AddJsonApi(options =>
2629
{
2730
options.Namespace = "api";
@@ -39,9 +42,6 @@
3942
typeof(DbContextB)
4043
});
4144

42-
builder.Services.AddResourceRepository<DbContextARepository<ResourceA>>();
43-
builder.Services.AddResourceRepository<DbContextBRepository<ResourceB>>();
44-
4545
WebApplication app = builder.Build();
4646

4747
// Configure the HTTP request pipeline.

Diff for: src/Examples/NoEntityFrameworkExample/Program.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
// Add services to the container.
77

8+
builder.Services.AddScoped<IInverseNavigationResolver, InMemoryInverseNavigationResolver>();
9+
810
builder.Services.AddJsonApi(options =>
911
{
1012
options.Namespace = "api";
@@ -18,8 +20,6 @@
1820
#endif
1921
}, discovery => discovery.AddCurrentAssembly());
2022

21-
builder.Services.AddScoped<IInverseNavigationResolver, InMemoryInverseNavigationResolver>();
22-
2323
WebApplication app = builder.Build();
2424

2525
// Configure the HTTP request pipeline.

Diff for: src/JsonApiDotNetCore/Configuration/JsonApiApplicationBuilder.cs

+66-66
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public void ConfigureResourceGraph(ICollection<Type> dbContextTypes, Action<Reso
8989

9090
_options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
9191

92-
_services.AddSingleton(resourceGraph);
92+
_services.TryAddSingleton(resourceGraph);
9393
}
9494

9595
/// <summary>
@@ -109,7 +109,7 @@ public void ConfigureMvc()
109109
if (_options.ValidateModelState)
110110
{
111111
_mvcBuilder.AddDataAnnotations();
112-
_services.AddSingleton<IModelMetadataProvider, JsonApiModelMetadataProvider>();
112+
_services.Replace(new ServiceDescriptor(typeof(IModelMetadataProvider), typeof(JsonApiModelMetadataProvider), ServiceLifetime.Singleton));
113113
}
114114
}
115115

@@ -130,19 +130,19 @@ public void ConfigureServiceContainer(ICollection<Type> dbContextTypes)
130130

131131
if (dbContextTypes.Any())
132132
{
133-
_services.AddScoped(typeof(DbContextResolver<>));
133+
_services.TryAddScoped(typeof(DbContextResolver<>));
134134

135135
foreach (Type dbContextType in dbContextTypes)
136136
{
137137
Type dbContextResolverClosedType = typeof(DbContextResolver<>).MakeGenericType(dbContextType);
138-
_services.AddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
138+
_services.TryAddScoped(typeof(IDbContextResolver), dbContextResolverClosedType);
139139
}
140140

141-
_services.AddScoped<IOperationsTransactionFactory, EntityFrameworkCoreTransactionFactory>();
141+
_services.TryAddScoped<IOperationsTransactionFactory, EntityFrameworkCoreTransactionFactory>();
142142
}
143143
else
144144
{
145-
_services.AddScoped<IOperationsTransactionFactory, MissingTransactionFactory>();
145+
_services.TryAddScoped<IOperationsTransactionFactory, MissingTransactionFactory>();
146146
}
147147

148148
AddResourceLayer();
@@ -153,46 +153,46 @@ public void ConfigureServiceContainer(ICollection<Type> dbContextTypes)
153153
AddQueryStringLayer();
154154
AddOperationsLayer();
155155

156-
_services.AddScoped(typeof(IResourceChangeTracker<>), typeof(ResourceChangeTracker<>));
157-
_services.AddScoped<IPaginationContext, PaginationContext>();
158-
_services.AddScoped<IEvaluatedIncludeCache, EvaluatedIncludeCache>();
159-
_services.AddScoped<ISparseFieldSetCache, SparseFieldSetCache>();
160-
_services.AddScoped<IQueryLayerComposer, QueryLayerComposer>();
161-
_services.AddScoped<IInverseNavigationResolver, InverseNavigationResolver>();
156+
_services.TryAddScoped(typeof(IResourceChangeTracker<>), typeof(ResourceChangeTracker<>));
157+
_services.TryAddScoped<IPaginationContext, PaginationContext>();
158+
_services.TryAddScoped<IEvaluatedIncludeCache, EvaluatedIncludeCache>();
159+
_services.TryAddScoped<ISparseFieldSetCache, SparseFieldSetCache>();
160+
_services.TryAddScoped<IQueryLayerComposer, QueryLayerComposer>();
161+
_services.TryAddScoped<IInverseNavigationResolver, InverseNavigationResolver>();
162162
}
163163

164164
private void AddMiddlewareLayer()
165165
{
166-
_services.AddSingleton<IJsonApiOptions>(_options);
167-
_services.AddSingleton<IJsonApiApplicationBuilder>(this);
168-
_services.AddSingleton<IExceptionHandler, ExceptionHandler>();
169-
_services.AddScoped<IAsyncJsonApiExceptionFilter, AsyncJsonApiExceptionFilter>();
170-
_services.AddScoped<IAsyncQueryStringActionFilter, AsyncQueryStringActionFilter>();
171-
_services.AddScoped<IAsyncConvertEmptyActionResultFilter, AsyncConvertEmptyActionResultFilter>();
172-
_services.AddSingleton<IJsonApiInputFormatter, JsonApiInputFormatter>();
173-
_services.AddSingleton<IJsonApiOutputFormatter, JsonApiOutputFormatter>();
174-
_services.AddSingleton<IJsonApiRoutingConvention, JsonApiRoutingConvention>();
175-
_services.AddSingleton<IControllerResourceMapping>(sp => sp.GetRequiredService<IJsonApiRoutingConvention>());
176-
_services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
177-
_services.AddScoped<IJsonApiRequest, JsonApiRequest>();
178-
_services.AddScoped<IJsonApiWriter, JsonApiWriter>();
179-
_services.AddScoped<IJsonApiReader, JsonApiReader>();
180-
_services.AddScoped<ITargetedFields, TargetedFields>();
166+
_services.TryAddSingleton<IJsonApiOptions>(_options);
167+
_services.TryAddSingleton<IJsonApiApplicationBuilder>(this);
168+
_services.TryAddSingleton<IExceptionHandler, ExceptionHandler>();
169+
_services.TryAddScoped<IAsyncJsonApiExceptionFilter, AsyncJsonApiExceptionFilter>();
170+
_services.TryAddScoped<IAsyncQueryStringActionFilter, AsyncQueryStringActionFilter>();
171+
_services.TryAddScoped<IAsyncConvertEmptyActionResultFilter, AsyncConvertEmptyActionResultFilter>();
172+
_services.TryAddSingleton<IJsonApiInputFormatter, JsonApiInputFormatter>();
173+
_services.TryAddSingleton<IJsonApiOutputFormatter, JsonApiOutputFormatter>();
174+
_services.TryAddSingleton<IJsonApiRoutingConvention, JsonApiRoutingConvention>();
175+
_services.TryAddSingleton<IControllerResourceMapping>(provider => provider.GetRequiredService<IJsonApiRoutingConvention>());
176+
_services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
177+
_services.TryAddScoped<IJsonApiRequest, JsonApiRequest>();
178+
_services.TryAddScoped<IJsonApiWriter, JsonApiWriter>();
179+
_services.TryAddScoped<IJsonApiReader, JsonApiReader>();
180+
_services.TryAddScoped<ITargetedFields, TargetedFields>();
181181
}
182182

183183
private void AddResourceLayer()
184184
{
185185
RegisterImplementationForInterfaces(ServiceDiscoveryFacade.ResourceDefinitionUnboundInterfaces, typeof(JsonApiResourceDefinition<,>));
186186

187-
_services.AddScoped<IResourceDefinitionAccessor, ResourceDefinitionAccessor>();
188-
_services.AddScoped<IResourceFactory, ResourceFactory>();
187+
_services.TryAddScoped<IResourceDefinitionAccessor, ResourceDefinitionAccessor>();
188+
_services.TryAddScoped<IResourceFactory, ResourceFactory>();
189189
}
190190

191191
private void AddRepositoryLayer()
192192
{
193193
RegisterImplementationForInterfaces(ServiceDiscoveryFacade.RepositoryUnboundInterfaces, typeof(EntityFrameworkCoreRepository<,>));
194194

195-
_services.AddScoped<IResourceRepositoryAccessor, ResourceRepositoryAccessor>();
195+
_services.TryAddScoped<IResourceRepositoryAccessor, ResourceRepositoryAccessor>();
196196

197197
_services.TryAddTransient<IQueryableBuilder, QueryableBuilder>();
198198
_services.TryAddTransient<IIncludeClauseBuilder, IncludeClauseBuilder>();
@@ -225,12 +225,12 @@ private void AddQueryStringLayer()
225225
_services.TryAddTransient<ISparseFieldSetParser, SparseFieldSetParser>();
226226
_services.TryAddTransient<IPaginationParser, PaginationParser>();
227227

228-
_services.AddScoped<IIncludeQueryStringParameterReader, IncludeQueryStringParameterReader>();
229-
_services.AddScoped<IFilterQueryStringParameterReader, FilterQueryStringParameterReader>();
230-
_services.AddScoped<ISortQueryStringParameterReader, SortQueryStringParameterReader>();
231-
_services.AddScoped<ISparseFieldSetQueryStringParameterReader, SparseFieldSetQueryStringParameterReader>();
232-
_services.AddScoped<IPaginationQueryStringParameterReader, PaginationQueryStringParameterReader>();
233-
_services.AddScoped<IResourceDefinitionQueryableParameterReader, ResourceDefinitionQueryableParameterReader>();
228+
_services.TryAddScoped<IIncludeQueryStringParameterReader, IncludeQueryStringParameterReader>();
229+
_services.TryAddScoped<IFilterQueryStringParameterReader, FilterQueryStringParameterReader>();
230+
_services.TryAddScoped<ISortQueryStringParameterReader, SortQueryStringParameterReader>();
231+
_services.TryAddScoped<ISparseFieldSetQueryStringParameterReader, SparseFieldSetQueryStringParameterReader>();
232+
_services.TryAddScoped<IPaginationQueryStringParameterReader, PaginationQueryStringParameterReader>();
233+
_services.TryAddScoped<IResourceDefinitionQueryableParameterReader, ResourceDefinitionQueryableParameterReader>();
234234

235235
RegisterDependentService<IQueryStringParameterReader, IIncludeQueryStringParameterReader>();
236236
RegisterDependentService<IQueryStringParameterReader, IFilterQueryStringParameterReader>();
@@ -246,50 +246,50 @@ private void AddQueryStringLayer()
246246
RegisterDependentService<IQueryConstraintProvider, IPaginationQueryStringParameterReader>();
247247
RegisterDependentService<IQueryConstraintProvider, IResourceDefinitionQueryableParameterReader>();
248248

249-
_services.AddScoped<IQueryStringReader, QueryStringReader>();
250-
_services.AddSingleton<IRequestQueryStringAccessor, RequestQueryStringAccessor>();
249+
_services.TryAddScoped<IQueryStringReader, QueryStringReader>();
250+
_services.TryAddSingleton<IRequestQueryStringAccessor, RequestQueryStringAccessor>();
251251
}
252252

253253
private void RegisterDependentService<TCollectionElement, TElementToAdd>()
254254
where TCollectionElement : class
255255
where TElementToAdd : TCollectionElement
256256
{
257-
_services.AddScoped<TCollectionElement>(serviceProvider => serviceProvider.GetRequiredService<TElementToAdd>());
257+
_services.AddScoped<TCollectionElement>(provider => provider.GetRequiredService<TElementToAdd>());
258258
}
259259

260260
private void AddSerializationLayer()
261261
{
262-
_services.AddScoped<IResourceIdentifierObjectAdapter, ResourceIdentifierObjectAdapter>();
263-
_services.AddScoped<IRelationshipDataAdapter, RelationshipDataAdapter>();
264-
_services.AddScoped<IResourceObjectAdapter, ResourceObjectAdapter>();
265-
_services.AddScoped<IResourceDataAdapter, ResourceDataAdapter>();
266-
_services.AddScoped<IAtomicReferenceAdapter, AtomicReferenceAdapter>();
267-
_services.AddScoped<IResourceDataInOperationsRequestAdapter, ResourceDataInOperationsRequestAdapter>();
268-
_services.AddScoped<IAtomicOperationObjectAdapter, AtomicOperationObjectAdapter>();
269-
_services.AddScoped<IDocumentInResourceOrRelationshipRequestAdapter, DocumentInResourceOrRelationshipRequestAdapter>();
270-
_services.AddScoped<IDocumentInOperationsRequestAdapter, DocumentInOperationsRequestAdapter>();
271-
_services.AddScoped<IDocumentAdapter, DocumentAdapter>();
272-
273-
_services.AddScoped<ILinkBuilder, LinkBuilder>();
274-
_services.AddScoped<IResponseMeta, EmptyResponseMeta>();
275-
_services.AddScoped<IMetaBuilder, MetaBuilder>();
276-
_services.AddSingleton<IFingerprintGenerator, FingerprintGenerator>();
277-
_services.AddSingleton<IETagGenerator, ETagGenerator>();
278-
_services.AddScoped<IResponseModelAdapter, ResponseModelAdapter>();
262+
_services.TryAddScoped<IResourceIdentifierObjectAdapter, ResourceIdentifierObjectAdapter>();
263+
_services.TryAddScoped<IRelationshipDataAdapter, RelationshipDataAdapter>();
264+
_services.TryAddScoped<IResourceObjectAdapter, ResourceObjectAdapter>();
265+
_services.TryAddScoped<IResourceDataAdapter, ResourceDataAdapter>();
266+
_services.TryAddScoped<IAtomicReferenceAdapter, AtomicReferenceAdapter>();
267+
_services.TryAddScoped<IResourceDataInOperationsRequestAdapter, ResourceDataInOperationsRequestAdapter>();
268+
_services.TryAddScoped<IAtomicOperationObjectAdapter, AtomicOperationObjectAdapter>();
269+
_services.TryAddScoped<IDocumentInResourceOrRelationshipRequestAdapter, DocumentInResourceOrRelationshipRequestAdapter>();
270+
_services.TryAddScoped<IDocumentInOperationsRequestAdapter, DocumentInOperationsRequestAdapter>();
271+
_services.TryAddScoped<IDocumentAdapter, DocumentAdapter>();
272+
273+
_services.TryAddScoped<ILinkBuilder, LinkBuilder>();
274+
_services.TryAddScoped<IResponseMeta, EmptyResponseMeta>();
275+
_services.TryAddScoped<IMetaBuilder, MetaBuilder>();
276+
_services.TryAddSingleton<IFingerprintGenerator, FingerprintGenerator>();
277+
_services.TryAddSingleton<IETagGenerator, ETagGenerator>();
278+
_services.TryAddScoped<IResponseModelAdapter, ResponseModelAdapter>();
279279
}
280280

281281
private void AddOperationsLayer()
282282
{
283-
_services.AddScoped(typeof(ICreateProcessor<,>), typeof(CreateProcessor<,>));
284-
_services.AddScoped(typeof(IUpdateProcessor<,>), typeof(UpdateProcessor<,>));
285-
_services.AddScoped(typeof(IDeleteProcessor<,>), typeof(DeleteProcessor<,>));
286-
_services.AddScoped(typeof(IAddToRelationshipProcessor<,>), typeof(AddToRelationshipProcessor<,>));
287-
_services.AddScoped(typeof(ISetRelationshipProcessor<,>), typeof(SetRelationshipProcessor<,>));
288-
_services.AddScoped(typeof(IRemoveFromRelationshipProcessor<,>), typeof(RemoveFromRelationshipProcessor<,>));
289-
290-
_services.AddScoped<IOperationsProcessor, OperationsProcessor>();
291-
_services.AddScoped<IOperationProcessorAccessor, OperationProcessorAccessor>();
292-
_services.AddScoped<ILocalIdTracker, LocalIdTracker>();
283+
_services.TryAddScoped(typeof(ICreateProcessor<,>), typeof(CreateProcessor<,>));
284+
_services.TryAddScoped(typeof(IUpdateProcessor<,>), typeof(UpdateProcessor<,>));
285+
_services.TryAddScoped(typeof(IDeleteProcessor<,>), typeof(DeleteProcessor<,>));
286+
_services.TryAddScoped(typeof(IAddToRelationshipProcessor<,>), typeof(AddToRelationshipProcessor<,>));
287+
_services.TryAddScoped(typeof(ISetRelationshipProcessor<,>), typeof(SetRelationshipProcessor<,>));
288+
_services.TryAddScoped(typeof(IRemoveFromRelationshipProcessor<,>), typeof(RemoveFromRelationshipProcessor<,>));
289+
290+
_services.TryAddScoped<IOperationsProcessor, OperationsProcessor>();
291+
_services.TryAddScoped<IOperationProcessorAccessor, OperationProcessorAccessor>();
292+
_services.TryAddScoped<ILocalIdTracker, LocalIdTracker>();
293293
}
294294

295295
public void Dispose()

0 commit comments

Comments
 (0)