Skip to content

Commit eaf5e74

Browse files
committed
Activate and fix CA2000: Dispose objects before losing scope
1 parent 66fa5b8 commit eaf5e74

File tree

28 files changed

+308
-219
lines changed

28 files changed

+308
-219
lines changed

CodingGuidelines.ruleset

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@
3939
<Rule Id="CA1065" Action="Warning" />
4040
<Rule Id="CA1066" Action="Warning" />
4141
<Rule Id="CA1849" Action="Warning" />
42+
<Rule Id="CA2000" Action="Warning" />
4243
</Rules>
4344
</RuleSet>

benchmarks/Deserialization/DeserializationBenchmarkBase.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
namespace Benchmarks.Deserialization;
1313

14-
public abstract class DeserializationBenchmarkBase
14+
public abstract class DeserializationBenchmarkBase : IDisposable
1515
{
16+
private readonly ServiceContainer _serviceProvider = new();
17+
1618
protected JsonSerializerOptions SerializerReadOptions { get; }
1719
protected DocumentAdapter DocumentAdapter { get; }
1820

@@ -23,12 +25,11 @@ protected DeserializationBenchmarkBase()
2325
options.SerializerOptions.Converters.Add(new ResourceObjectConverter(resourceGraph));
2426
SerializerReadOptions = ((IJsonApiOptions)options).SerializerReadOptions;
2527

26-
var serviceContainer = new ServiceContainer();
27-
var resourceFactory = new ResourceFactory(serviceContainer);
28-
var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, serviceContainer);
28+
var resourceFactory = new ResourceFactory(_serviceProvider);
29+
var resourceDefinitionAccessor = new ResourceDefinitionAccessor(resourceGraph, _serviceProvider);
2930

30-
serviceContainer.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
31-
serviceContainer.AddService(typeof(IResourceDefinition<IncomingResource, int>), new JsonApiResourceDefinition<IncomingResource, int>(resourceGraph));
31+
_serviceProvider.AddService(typeof(IResourceDefinitionAccessor), resourceDefinitionAccessor);
32+
_serviceProvider.AddService(typeof(IResourceDefinition<IncomingResource, int>), new JsonApiResourceDefinition<IncomingResource, int>(resourceGraph));
3233

3334
// ReSharper disable once VirtualMemberCallInConstructor
3435
JsonApiRequest request = CreateJsonApiRequest(resourceGraph);
@@ -53,6 +54,22 @@ protected DeserializationBenchmarkBase()
5354

5455
protected abstract JsonApiRequest CreateJsonApiRequest(IResourceGraph resourceGraph);
5556

57+
public void Dispose()
58+
{
59+
Dispose(true);
60+
GC.SuppressFinalize(this);
61+
}
62+
63+
#pragma warning disable CA1063 // Implement IDisposable Correctly
64+
private void Dispose(bool disposing)
65+
#pragma warning restore CA1063 // Implement IDisposable Correctly
66+
{
67+
if (disposing)
68+
{
69+
_serviceProvider.Dispose();
70+
}
71+
}
72+
5673
[UsedImplicitly(ImplicitUseTargetFlags.Members)]
5774
public sealed class IncomingResource : Identifiable<int>
5875
{

benchmarks/QueryString/QueryStringParserBenchmarks.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ namespace Benchmarks.QueryString;
1414
[MarkdownExporter]
1515
[SimpleJob(3, 10, 20)]
1616
[MemoryDiagnoser]
17-
public class QueryStringParserBenchmarks
17+
public class QueryStringParserBenchmarks : IDisposable
1818
{
19+
private readonly ServiceContainer _serviceProvider = new();
1920
private readonly FakeRequestQueryStringAccessor _queryStringAccessor = new();
2021
private readonly QueryStringReader _queryStringReader;
2122

@@ -34,7 +35,7 @@ public QueryStringParserBenchmarks()
3435
IsCollection = true
3536
};
3637

37-
var resourceFactory = new ResourceFactory(new ServiceContainer());
38+
var resourceFactory = new ResourceFactory(_serviceProvider);
3839

3940
var includeParser = new IncludeParser(options);
4041
var includeReader = new IncludeQueryStringParameterReader(includeParser, request, resourceGraph);
@@ -92,4 +93,20 @@ public void ComplexQuery()
9293
_queryStringAccessor.SetQueryString(queryString);
9394
_queryStringReader.ReadAll(null);
9495
}
96+
97+
public void Dispose()
98+
{
99+
Dispose(true);
100+
GC.SuppressFinalize(this);
101+
}
102+
103+
#pragma warning disable CA1063 // Implement IDisposable Correctly
104+
private void Dispose(bool disposing)
105+
#pragma warning restore CA1063 // Implement IDisposable Correctly
106+
{
107+
if (disposing)
108+
{
109+
_serviceProvider.Dispose();
110+
}
111+
}
95112
}

src/Examples/JsonApiDotNetCoreExample/Controllers/NonJsonApiController.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ public IActionResult Get()
1616
[HttpPost]
1717
public async Task<IActionResult> PostAsync()
1818
{
19-
string name = await new StreamReader(Request.Body).ReadToEndAsync();
19+
using var reader = new StreamReader(Request.Body, leaveOpen: true);
20+
string name = await reader.ReadToEndAsync();
2021

2122
if (string.IsNullOrEmpty(name))
2223
{

src/JsonApiDotNetCore/Diagnostics/CascadingCodeTimer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.Runtime.InteropServices;
44
using System.Text;
55

6+
#pragma warning disable CA2000 // Dispose objects before losing scope
7+
68
namespace JsonApiDotNetCore.Diagnostics;
79

810
/// <summary>

src/JsonApiDotNetCore/Middleware/JsonApiMiddleware.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ public JsonApiMiddleware(RequestDelegate? next, IHttpContextAccessor httpContext
5353
_options = options;
5454
_logger = logger;
5555

56+
#pragma warning disable CA2000 // Dispose objects before losing scope
5657
var session = new AspNetCodeTimerSession(httpContextAccessor);
58+
#pragma warning restore CA2000 // Dispose objects before losing scope
5759
CodeTimingSessionManager.Capture(session);
5860
}
5961

test/DapperTests/IntegrationTests/DapperTestContext.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ public DapperTestContext()
5252

5353
private WebApplicationFactory<TodoItem> CreateFactory()
5454
{
55+
#pragma warning disable CA2000 // Dispose objects before losing scope
56+
// Justification: The child factory returned by WithWebHostBuilder() is owned by the parent factory, which disposes it.
5557
return new WebApplicationFactory<TodoItem>().WithWebHostBuilder(builder =>
58+
#pragma warning restore CA2000 // Dispose objects before losing scope
5659
{
5760
builder.UseSetting("ConnectionStrings:DapperExamplePostgreSql",
5861
$"Host=localhost;Database=DapperExample-{Guid.NewGuid():N};User ID=postgres;Password=postgres;Include Error Detail=true");

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicLoggingTests.cs

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,24 @@ public AtomicLoggingTests(IntegrationTestContext<TestableStartup<OperationsDbCon
1919

2020
testContext.UseController<OperationsController>();
2121

22-
var loggerFactory = new FakeLoggerFactory(LogLevel.Information);
23-
2422
testContext.ConfigureLogging(options =>
2523
{
26-
options.ClearProviders();
27-
options.AddProvider(loggerFactory);
24+
var loggerProvider = new CapturingLoggerProvider(LogLevel.Information);
25+
options.AddProvider(loggerProvider);
2826
options.SetMinimumLevel(LogLevel.Information);
29-
});
3027

31-
testContext.ConfigureServices(services =>
32-
{
33-
services.AddSingleton(loggerFactory);
34-
services.AddSingleton<IOperationsTransactionFactory, ThrowingOperationsTransactionFactory>();
28+
options.Services.AddSingleton(loggerProvider);
3529
});
30+
31+
testContext.ConfigureServices(services => services.AddSingleton<IOperationsTransactionFactory, ThrowingOperationsTransactionFactory>());
3632
}
3733

3834
[Fact]
3935
public async Task Logs_unhandled_exception_at_Error_level()
4036
{
4137
// Arrange
42-
var loggerFactory = _testContext.Factory.Services.GetRequiredService<FakeLoggerFactory>();
43-
loggerFactory.Logger.Clear();
38+
var loggerProvider = _testContext.Factory.Services.GetRequiredService<CapturingLoggerProvider>();
39+
loggerProvider.Clear();
4440

4541
var transactionFactory = (ThrowingOperationsTransactionFactory)_testContext.Factory.Services.GetRequiredService<IOperationsTransactionFactory>();
4642
transactionFactory.ThrowOnOperationStart = true;
@@ -80,19 +76,18 @@ public async Task Logs_unhandled_exception_at_Error_level()
8076
error.Source.ShouldNotBeNull();
8177
error.Source.Pointer.Should().Be("/atomic:operations[0]");
8278

83-
IReadOnlyList<FakeLogMessage> logMessages = loggerFactory.Logger.GetMessages();
84-
logMessages.ShouldNotBeEmpty();
79+
IReadOnlyList<LogMessage> logMessages = loggerProvider.GetMessages();
8580

86-
logMessages.Should().ContainSingle(message => message.LogLevel == LogLevel.Error &&
87-
message.Text.Contains("Simulated failure.", StringComparison.Ordinal));
81+
logMessages.Should().ContainSingle(message =>
82+
message.LogLevel == LogLevel.Error && message.Text.Contains("Simulated failure.", StringComparison.Ordinal));
8883
}
8984

9085
[Fact]
9186
public async Task Logs_invalid_request_body_error_at_Information_level()
9287
{
9388
// Arrange
94-
var loggerFactory = _testContext.Factory.Services.GetRequiredService<FakeLoggerFactory>();
95-
loggerFactory.Logger.Clear();
89+
var loggerProvider = _testContext.Factory.Services.GetRequiredService<CapturingLoggerProvider>();
90+
loggerProvider.Clear();
9691

9792
var transactionFactory = (ThrowingOperationsTransactionFactory)_testContext.Factory.Services.GetRequiredService<IOperationsTransactionFactory>();
9893
transactionFactory.ThrowOnOperationStart = false;
@@ -118,8 +113,7 @@ public async Task Logs_invalid_request_body_error_at_Information_level()
118113

119114
responseDocument.Errors.ShouldHaveCount(1);
120115

121-
IReadOnlyList<FakeLogMessage> logMessages = loggerFactory.Logger.GetMessages();
122-
logMessages.ShouldNotBeEmpty();
116+
IReadOnlyList<LogMessage> logMessages = loggerProvider.GetMessages();
123117

124118
logMessages.Should().ContainSingle(message => message.LogLevel == LogLevel.Information &&
125119
message.Text.Contains("Failed to deserialize request body", StringComparison.Ordinal));

test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Mixed/AtomicTraceLoggingTests.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,24 @@ public AtomicTraceLoggingTests(IntegrationTestContext<TestableStartup<Operations
1919

2020
testContext.UseController<OperationsController>();
2121

22-
var loggerFactory = new FakeLoggerFactory(LogLevel.Trace);
23-
2422
testContext.ConfigureLogging(options =>
2523
{
26-
options.ClearProviders();
27-
options.AddProvider(loggerFactory);
24+
var loggerProvider = new CapturingLoggerProvider((category, level) =>
25+
level >= LogLevel.Trace && category.StartsWith("JsonApiDotNetCore.", StringComparison.Ordinal));
26+
27+
options.AddProvider(loggerProvider);
2828
options.SetMinimumLevel(LogLevel.Trace);
29-
options.AddFilter((category, _) => category != null && category.StartsWith("JsonApiDotNetCore.", StringComparison.Ordinal));
30-
});
3129

32-
testContext.ConfigureServices(services => services.AddSingleton(loggerFactory));
30+
options.Services.AddSingleton(loggerProvider);
31+
});
3332
}
3433

3534
[Fact]
3635
public async Task Logs_execution_flow_at_Trace_level_on_operations_request()
3736
{
3837
// Arrange
39-
var loggerFactory = _testContext.Factory.Services.GetRequiredService<FakeLoggerFactory>();
40-
loggerFactory.Logger.Clear();
38+
var loggerProvider = _testContext.Factory.Services.GetRequiredService<CapturingLoggerProvider>();
39+
loggerProvider.Clear();
4140

4241
MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne();
4342
existingTrack.Lyric = _fakers.Lyric.GenerateOne();
@@ -116,7 +115,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext =>
116115

117116
responseDocument.Should().BeEmpty();
118117

119-
IReadOnlyList<string> logLines = loggerFactory.Logger.GetLines();
118+
IReadOnlyList<string> logLines = loggerProvider.GetLines();
120119

121120
logLines.Should().BeEquivalentTo(new[]
122121
{

test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/ApiControllerAttributeLogTests.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,33 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.CustomRoutes;
88

99
public sealed class ApiControllerAttributeLogTests : IntegrationTestContext<TestableStartup<CustomRouteDbContext>, CustomRouteDbContext>, IAsyncDisposable
1010
{
11-
private readonly FakeLoggerFactory _loggerFactory;
11+
private readonly CapturingLoggerProvider _loggerProvider;
1212

1313
public ApiControllerAttributeLogTests()
1414
{
1515
UseController<CiviliansController>();
1616

17-
_loggerFactory = new FakeLoggerFactory(LogLevel.Warning);
17+
_loggerProvider = new CapturingLoggerProvider(LogLevel.Warning);
1818

1919
ConfigureLogging(options =>
2020
{
21-
options.ClearProviders();
22-
options.AddProvider(_loggerFactory);
23-
});
21+
options.AddProvider(_loggerProvider);
2422

25-
ConfigureServices(services => services.AddSingleton(_loggerFactory));
23+
options.Services.AddSingleton(_loggerProvider);
24+
});
2625
}
2726

2827
[Fact]
2928
public void Logs_warning_at_startup_when_ApiControllerAttribute_found()
3029
{
3130
// Arrange
32-
_loggerFactory.Logger.Clear();
31+
_loggerProvider.Clear();
3332

3433
// Act
3534
_ = Factory;
3635

3736
// Assert
38-
IReadOnlyList<string> logLines = _loggerFactory.Logger.GetLines();
37+
IReadOnlyList<string> logLines = _loggerProvider.GetLines();
3938
logLines.ShouldHaveCount(1);
4039

4140
logLines[0].Should().Be(
@@ -44,7 +43,7 @@ public void Logs_warning_at_startup_when_ApiControllerAttribute_found()
4443

4544
public override Task DisposeAsync()
4645
{
47-
_loggerFactory.Dispose();
46+
_loggerProvider.Dispose();
4847
return base.DisposeAsync();
4948
}
5049

0 commit comments

Comments
 (0)